001: // Copyright 2006, 2007 The Apache Software Foundation
002: //
003: // Licensed under the Apache License, Version 2.0 (the "License");
004: // you may not use this file except in compliance with the License.
005: // You may obtain a copy of the License at
006: //
007: // http://www.apache.org/licenses/LICENSE-2.0
008: //
009: // Unless required by applicable law or agreed to in writing, software
010: // distributed under the License is distributed on an "AS IS" BASIS,
011: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: // See the License for the specific language governing permissions and
013: // limitations under the License.
014:
015: package org.apache.tapestry.ioc.internal;
016:
017: import static org.apache.tapestry.ioc.internal.ConfigurationType.MAPPED;
018: import static org.apache.tapestry.ioc.internal.ConfigurationType.ORDERED;
019: import static org.apache.tapestry.ioc.internal.ConfigurationType.UNORDERED;
020: import static org.apache.tapestry.ioc.internal.IOCMessages.buildMethodConflict;
021: import static org.apache.tapestry.ioc.internal.IOCMessages.buildMethodWrongReturnType;
022: import static org.apache.tapestry.ioc.internal.IOCMessages.decoratorMethodWrongReturnType;
023: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newCaseInsensitiveMap;
024: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newMap;
025: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newSet;
026:
027: import java.lang.reflect.InvocationTargetException;
028: import java.lang.reflect.Method;
029: import java.lang.reflect.Modifier;
030: import java.util.Arrays;
031: import java.util.Comparator;
032: import java.util.Map;
033: import java.util.Set;
034:
035: import org.apache.commons.logging.Log;
036: import org.apache.tapestry.ioc.Configuration;
037: import org.apache.tapestry.ioc.IOCConstants;
038: import org.apache.tapestry.ioc.MappedConfiguration;
039: import org.apache.tapestry.ioc.ObjectCreator;
040: import org.apache.tapestry.ioc.OrderedConfiguration;
041: import org.apache.tapestry.ioc.ServiceBinder;
042: import org.apache.tapestry.ioc.ServiceBuilderResources;
043: import org.apache.tapestry.ioc.annotations.EagerLoad;
044: import org.apache.tapestry.ioc.annotations.Scope;
045: import org.apache.tapestry.ioc.annotations.Match;
046: import org.apache.tapestry.ioc.annotations.Order;
047: import org.apache.tapestry.ioc.def.ContributionDef;
048: import org.apache.tapestry.ioc.def.DecoratorDef;
049: import org.apache.tapestry.ioc.def.ModuleDef;
050: import org.apache.tapestry.ioc.def.ServiceDef;
051: import org.apache.tapestry.ioc.internal.util.InternalUtils;
052: import org.apache.tapestry.ioc.services.ClassFactory;
053:
054: /**
055: * Starting from the Class for a module builder, identifies all the services (service builder
056: * methods), decorators (service decorator methods) and (not yet implemented) contributions (service
057: * contributor methods).
058: */
059: public class DefaultModuleDefImpl implements ModuleDef,
060: ServiceDefAccumulator {
061: /** The prefix used to identify service builder methods. */
062: private static final String BUILD_METHOD_NAME_PREFIX = "build";
063:
064: /** The prefix used to identify service decorator methods. */
065: private static final String DECORATE_METHOD_NAME_PREFIX = "decorate";
066:
067: /** The prefix used to identify service contribution methods. */
068: private static final String CONTRIBUTE_METHOD_NAME_PREFIX = "contribute";
069:
070: private final Class _builderClass;
071:
072: private final Log _log;
073:
074: private final ClassFactory _classFactory;
075:
076: /** Keyed on service id. */
077: private final Map<String, ServiceDef> _serviceDefs = newCaseInsensitiveMap();
078:
079: /** Keyed on decorator id. */
080: private final Map<String, DecoratorDef> _decoratorDefs = newCaseInsensitiveMap();
081:
082: private final Set<ContributionDef> _contributionDefs = newSet();
083:
084: private final static Map<Class, ConfigurationType> PARAMETER_TYPE_TO_CONFIGURATION_TYPE = newMap();
085:
086: static {
087: PARAMETER_TYPE_TO_CONFIGURATION_TYPE.put(Configuration.class,
088: UNORDERED);
089: PARAMETER_TYPE_TO_CONFIGURATION_TYPE.put(
090: OrderedConfiguration.class, ORDERED);
091: PARAMETER_TYPE_TO_CONFIGURATION_TYPE.put(
092: MappedConfiguration.class, MAPPED);
093: }
094:
095: /**
096: * @param builderClass
097: * the class that is responsible for building services, etc.
098: * @param log
099: * @param classFactory
100: * TODO
101: */
102: public DefaultModuleDefImpl(Class builderClass, Log log,
103: ClassFactory classFactory) {
104: _builderClass = builderClass;
105: _log = log;
106: _classFactory = classFactory;
107:
108: grind();
109: bind();
110: }
111:
112: /** Identified the module builder class and a list of service ids within the module. */
113: @Override
114: public String toString() {
115: return String.format("ModuleDef[%s %s]", _builderClass
116: .getName(), InternalUtils.joinSorted(_serviceDefs
117: .keySet()));
118: }
119:
120: public Class getBuilderClass() {
121: return _builderClass;
122: }
123:
124: public Set<String> getServiceIds() {
125: return _serviceDefs.keySet();
126: }
127:
128: public ServiceDef getServiceDef(String serviceId) {
129: return _serviceDefs.get(serviceId);
130: }
131:
132: private void grind() {
133: Method[] methods = _builderClass.getMethods();
134:
135: Comparator<Method> c = new Comparator<Method>() {
136: // By name, ascending, then by parameter count, descending.
137:
138: public int compare(Method o1, Method o2) {
139: int result = o1.getName().compareTo(o2.getName());
140:
141: if (result == 0)
142: result = o2.getParameterTypes().length
143: - o1.getParameterTypes().length;
144:
145: return result;
146: }
147:
148: };
149:
150: Arrays.sort(methods, c);
151:
152: for (Method m : methods) {
153: String name = m.getName();
154:
155: if (name.startsWith(BUILD_METHOD_NAME_PREFIX)) {
156: addServiceDef(m);
157: continue;
158: }
159:
160: if (name.startsWith(DECORATE_METHOD_NAME_PREFIX)) {
161: addDecoratorDef(m);
162: continue;
163: }
164:
165: if (name.startsWith(CONTRIBUTE_METHOD_NAME_PREFIX)) {
166: addContributionDef(m);
167: continue;
168: }
169:
170: }
171: }
172:
173: private void addContributionDef(Method method) {
174: String serviceId = stripMethodPrefix(method,
175: CONTRIBUTE_METHOD_NAME_PREFIX);
176:
177: Class returnType = method.getReturnType();
178: if (!returnType.equals(void.class))
179: _log.warn(IOCMessages.contributionWrongReturnType(method));
180:
181: ConfigurationType type = null;
182:
183: for (Class parameterType : method.getParameterTypes()) {
184: ConfigurationType this Parameter = PARAMETER_TYPE_TO_CONFIGURATION_TYPE
185: .get(parameterType);
186:
187: if (this Parameter != null) {
188: if (type != null) {
189: _log.warn(IOCMessages
190: .tooManyContributionParameters(method));
191: return;
192: }
193:
194: type = this Parameter;
195: }
196: }
197:
198: if (type == null) {
199: _log.warn(IOCMessages.noContributionParameter(method));
200: return;
201: }
202:
203: ContributionDef def = new ContributionDefImpl(serviceId,
204: method, _classFactory);
205:
206: _contributionDefs.add(def);
207: }
208:
209: private void addDecoratorDef(Method method) {
210: // TODO: methods just named "decorate"
211:
212: String decoratorId = stripMethodPrefix(method,
213: DECORATE_METHOD_NAME_PREFIX);
214:
215: // TODO: Check for duplicates
216:
217: Class returnType = method.getReturnType();
218:
219: if (returnType.isPrimitive() || returnType.isArray()) {
220: _log.warn(decoratorMethodWrongReturnType(method), null);
221: return;
222: }
223:
224: if (!methodContainsObjectParameter(method)) {
225: _log.warn(IOCMessages
226: .decoratorMethodNeedsDelegateParameter(method),
227: null);
228: return;
229: }
230:
231: // TODO: Check that at least one parameter is type java.lang.Object,
232: // since that's how the delegate is passed in.
233:
234: Order orderAnnotation = method.getAnnotation(Order.class);
235: Match match = method.getAnnotation(Match.class);
236:
237: String[] constraints = orderAnnotation != null ? orderAnnotation
238: .value()
239: : null;
240:
241: // TODO: Validate constraints here?
242:
243: String[] patterns = match == null ? new String[] { decoratorId }
244: : match.value();
245:
246: DecoratorDef def = new DecoratorDefImpl(decoratorId, method,
247: patterns, constraints, _classFactory);
248:
249: _decoratorDefs.put(decoratorId, def);
250: }
251:
252: private boolean methodContainsObjectParameter(Method method) {
253: for (Class parameterType : method.getParameterTypes()) {
254: // TODO: But what if the type Object parameter has an injection?
255: // We should skip it and look for a different parameter.
256:
257: if (parameterType.equals(Object.class))
258: return true;
259: }
260:
261: return false;
262: }
263:
264: private String stripMethodPrefix(Method method, String prefix) {
265: return method.getName().substring(prefix.length());
266: }
267:
268: /** Invoked for public methods that have the proper prefix. */
269: private void addServiceDef(final Method method) {
270: String serviceId = stripMethodPrefix(method,
271: BUILD_METHOD_NAME_PREFIX);
272:
273: // If the method name was just "build()", then work from the return type.
274:
275: if (serviceId.equals(""))
276: serviceId = method.getReturnType().getSimpleName();
277:
278: // Any number of parameters is fine, we'll adapt. Eventually we have to check
279: // that we can satisfy the parameters requested. Thrown exceptions of the method
280: // will be caught and wrapped, so we don't need to check those. But we do need a proper
281: // return type.
282:
283: Class returnType = method.getReturnType();
284:
285: if (returnType.isPrimitive() || returnType.isArray()) {
286: _log.warn(buildMethodWrongReturnType(method), null);
287: return;
288: }
289:
290: String scope = extractServiceScope(method);
291: boolean eagerLoad = method.isAnnotationPresent(EagerLoad.class);
292:
293: ObjectCreatorSource source = new ObjectCreatorSource() {
294: public ObjectCreator constructCreator(
295: ServiceBuilderResources resources) {
296: return new ServiceBuilderMethodInvoker(resources,
297: getDescription(), method);
298: }
299:
300: public String getDescription() {
301: return InternalUtils.asString(method, _classFactory);
302: }
303: };
304:
305: ServiceDefImpl serviceDef = new ServiceDefImpl(returnType,
306: serviceId, scope, eagerLoad, source);
307:
308: addServiceDef(serviceDef);
309: }
310:
311: public void addServiceDef(ServiceDef serviceDef) {
312: String serviceId = serviceDef.getServiceId();
313:
314: ServiceDef existing = _serviceDefs.get(serviceId);
315:
316: if (existing != null) {
317: _log.warn(buildMethodConflict(serviceDef.toString(),
318: existing.toString()), null);
319: return;
320: }
321:
322: _serviceDefs.put(serviceId, serviceDef);
323: }
324:
325: private String extractServiceScope(Method method) {
326: Scope scope = method.getAnnotation(Scope.class);
327:
328: return scope != null ? scope.value()
329: : IOCConstants.DEFAULT_SCOPE;
330: }
331:
332: public Set<DecoratorDef> getDecoratorDefs() {
333: return newSet(_decoratorDefs.values());
334: }
335:
336: public Set<ContributionDef> getContributionDefs() {
337: return _contributionDefs;
338: }
339:
340: public String getLogName() {
341: return _builderClass.getName();
342: }
343:
344: /** See if the build class defined a bind method and invoke it. */
345: private void bind() {
346: Throwable failure = null;
347: Method bindMethod = null;
348:
349: try {
350: bindMethod = _builderClass.getMethod("bind",
351: ServiceBinder.class);
352:
353: if (!Modifier.isStatic(bindMethod.getModifiers())) {
354: _log.error(IOCMessages
355: .bindMethodMustBeStatic(InternalUtils.asString(
356: bindMethod, _classFactory)));
357:
358: return;
359: }
360:
361: ServiceBinderImpl binder = new ServiceBinderImpl(this ,
362: _classFactory);
363:
364: bindMethod.invoke(null, binder);
365:
366: binder.finish();
367:
368: return;
369: } catch (NoSuchMethodException ex) {
370: // No problem! Many modules will not have such a method.
371:
372: return;
373: } catch (IllegalArgumentException ex) {
374: failure = ex;
375: } catch (IllegalAccessException ex) {
376: failure = ex;
377: } catch (InvocationTargetException ex) {
378: failure = ex.getTargetException();
379: }
380:
381: String methodId = InternalUtils.asString(bindMethod,
382: _classFactory);
383:
384: throw new RuntimeException(IOCMessages.errorInBindMethod(
385: methodId, failure), failure);
386: }
387: }
|