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 java.lang.String.format;
018: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newCaseInsensitiveMap;
019: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
020: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newMap;
021: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newSet;
022: import static org.apache.tapestry.ioc.internal.util.Defense.notBlank;
023: import static org.apache.tapestry.ioc.internal.util.Defense.notNull;
024:
025: import java.lang.reflect.Constructor;
026: import java.lang.reflect.InvocationTargetException;
027: import java.lang.reflect.Modifier;
028: import java.util.Arrays;
029: import java.util.Collection;
030: import java.util.Comparator;
031: import java.util.List;
032: import java.util.Map;
033: import java.util.Set;
034:
035: import org.apache.commons.logging.Log;
036: import org.apache.tapestry.ioc.ObjectCreator;
037: import org.apache.tapestry.ioc.ObjectLocator;
038: import org.apache.tapestry.ioc.ServiceBuilderResources;
039: import org.apache.tapestry.ioc.ServiceDecorator;
040: import org.apache.tapestry.ioc.ServiceResources;
041: import org.apache.tapestry.ioc.def.ContributionDef;
042: import org.apache.tapestry.ioc.def.DecoratorDef;
043: import org.apache.tapestry.ioc.def.ModuleDef;
044: import org.apache.tapestry.ioc.def.ServiceDef;
045: import org.apache.tapestry.ioc.internal.services.JustInTimeObjectCreator;
046: import org.apache.tapestry.ioc.internal.util.InternalUtils;
047: import org.apache.tapestry.ioc.services.ClassFab;
048: import org.apache.tapestry.ioc.services.ClassFactory;
049: import org.apache.tapestry.ioc.services.MethodSignature;
050: import org.apache.tapestry.ioc.services.TapestryIOCModule;
051:
052: public class ModuleImpl implements Module {
053: private final InternalRegistry _registry;
054:
055: private final ModuleDef _moduleDef;
056:
057: private final ClassFactory _classFactory;
058:
059: private final Log _log;
060:
061: // Guarded by MUTEX
062: private Object _moduleBuilder;
063:
064: /**
065: * A single mutex, shared by all modules, that serializes creation of services across all
066: * threads. This is a bit draconian, but appears to be necessary. Fortunately, service creation
067: * is a very tiny part of any individual service's lifecycle.
068: */
069: private static final Object MUTEX = new Object();
070:
071: // Set to true when invoking the module constructor. Used to
072: // detect endless loops caused by irresponsible dependencies in
073: // the constructor. Guarded by MUTEX.
074: private boolean _insideConstructor;
075:
076: public ModuleImpl(InternalRegistry registry, ModuleDef moduleDef,
077: ClassFactory classFactory, Log log) {
078: _registry = registry;
079: _moduleDef = moduleDef;
080: _classFactory = classFactory;
081: _log = log;
082: }
083:
084: /** Keyed on fully qualified service id; values are instantiated services (proxies). */
085: private final Map<String, Object> _services = newCaseInsensitiveMap();
086:
087: public <T> T getService(String serviceId, Class<T> serviceInterface) {
088: notBlank(serviceId, "serviceId");
089: notNull(serviceInterface, "serviceInterface");
090: // module may be null.
091:
092: ServiceDef def = _moduleDef.getServiceDef(serviceId);
093:
094: // RegistryImpl should already have checked that the service exists.
095: assert def != null;
096:
097: Object service = findOrCreate(def, null);
098:
099: try {
100: return serviceInterface.cast(service);
101: } catch (ClassCastException ex) {
102: // This may be overkill: I don't know how this could happen
103: // given that the return type of the method determines
104: // the service interface.
105:
106: throw new RuntimeException(IOCMessages
107: .serviceWrongInterface(serviceId, def
108: .getServiceInterface(), serviceInterface));
109: }
110: }
111:
112: public Set<DecoratorDef> findMatchingDecoratorDefs(
113: ServiceDef serviceDef) {
114: Set<DecoratorDef> result = newSet();
115:
116: for (DecoratorDef def : _moduleDef.getDecoratorDefs()) {
117: if (def.matches(serviceDef))
118: result.add(def);
119: }
120:
121: return result;
122: }
123:
124: public List<ServiceDecorator> findDecoratorsForService(
125: String serviceId) {
126: ServiceDef sd = _moduleDef.getServiceDef(serviceId);
127:
128: return _registry.findDecoratorsForService(sd);
129: }
130:
131: @SuppressWarnings("unchecked")
132: public Collection<String> findServiceIdsForInterface(
133: Class serviceInterface) {
134: notNull(serviceInterface, "serviceInterface");
135:
136: Collection<String> result = newList();
137:
138: for (String id : _moduleDef.getServiceIds()) {
139: ServiceDef def = _moduleDef.getServiceDef(id);
140:
141: if (serviceInterface.isAssignableFrom(def
142: .getServiceInterface()))
143: result.add(id);
144: }
145:
146: return result;
147: }
148:
149: /**
150: * Locates the service proxy for a particular service (from the service definition).
151: * <p>
152: * Access is synchronized via {@link #MUTEX}.
153: *
154: * @param def
155: * defines the service
156: * @param eagerLoadProxies
157: * TODO
158: * @return the service proxy
159: */
160: private Object findOrCreate(ServiceDef def,
161: List<EagerLoadServiceProxy> eagerLoadProxies) {
162: synchronized (MUTEX) {
163: String key = def.getServiceId();
164:
165: Object result = _services.get(key);
166:
167: if (result == null) {
168: result = create(def, eagerLoadProxies);
169: _services.put(key, result);
170: }
171:
172: return result;
173: }
174: }
175:
176: public void eagerLoadServices() {
177: List<EagerLoadServiceProxy> proxies = newList();
178:
179: synchronized (MUTEX) {
180: for (String serviceId : _moduleDef.getServiceIds()) {
181: ServiceDef def = _moduleDef.getServiceDef(serviceId);
182:
183: if (def.isEagerLoad())
184: findOrCreate(def, proxies);
185: }
186:
187: for (EagerLoadServiceProxy proxy : proxies)
188: proxy.eagerLoadService();
189: }
190: }
191:
192: /**
193: * Creates the service and updates the cache of created services. Access is synchronized via
194: * {@link #MUTEX}.
195: *
196: * @param eagerLoadProxies
197: * a list into which any eager loaded proxies should be added
198: */
199: private Object create(ServiceDef def,
200: List<EagerLoadServiceProxy> eagerLoadProxies) {
201: String serviceId = def.getServiceId();
202:
203: Log log = _registry.logForService(serviceId);
204:
205: if (log.isDebugEnabled())
206: log.debug(IOCMessages.creatingService(serviceId));
207:
208: try {
209: ServiceBuilderResources resources = new ServiceResourcesImpl(
210: _registry, this , def, _classFactory, log);
211:
212: // Build up a stack of operations that will be needed to realize the service
213: // (by the proxy, at a later date).
214:
215: ObjectCreator creator = def.createServiceCreator(resources);
216:
217: Class serviceInterface = def.getServiceInterface();
218:
219: // For non-proxyable services, we immediately create the service implementation
220: // and return it. There's no interface to proxy, which throws out the possibility of
221: // deferred instantiation, service lifecycles, and decorators.
222:
223: if (!serviceInterface.isInterface())
224: return creator.createObject();
225:
226: creator = new LifecycleWrappedServiceCreator(_registry, def
227: .getServiceScope(), resources, creator);
228:
229: // Don't allow the core IOC services services to be decorated.
230:
231: if (!TapestryIOCModule.class.equals(_moduleDef
232: .getBuilderClass()))
233: creator = new InterceptorStackBuilder(this , serviceId,
234: creator);
235:
236: // Add a wrapper that checks for recursion.
237:
238: creator = new RecursiveServiceCreationCheckWrapper(def,
239: creator, log);
240:
241: JustInTimeObjectCreator delegate = new JustInTimeObjectCreator(
242: creator, serviceId);
243:
244: Object proxy = createProxy(resources, delegate);
245:
246: _registry.addRegistryShutdownListener(delegate);
247:
248: // Occasionally service A may invoke service B from its service builder method; if
249: // service B
250: // is eager loaded, we'll hit this method but eagerLoadProxies will be null. That's OK
251: // ... service B
252: // is being realized anyway.
253:
254: if (def.isEagerLoad() && eagerLoadProxies != null)
255: eagerLoadProxies.add(delegate);
256:
257: return proxy;
258: } catch (Exception ex) {
259: throw new RuntimeException(IOCMessages
260: .errorBuildingService(serviceId, def, ex), ex);
261: }
262: }
263:
264: public Object getModuleBuilder() {
265: synchronized (MUTEX) {
266: if (_moduleBuilder == null)
267: _moduleBuilder = instantiateModuleBuilder();
268:
269: return _moduleBuilder;
270: }
271: }
272:
273: /** Access synchronized by MUTEX. */
274: private Object instantiateModuleBuilder() {
275: Class builderClass = _moduleDef.getBuilderClass();
276:
277: Constructor[] constructors = builderClass.getConstructors();
278:
279: if (constructors.length == 0)
280: throw new RuntimeException(IOCMessages
281: .noPublicConstructors(builderClass));
282:
283: if (constructors.length > 1) {
284: // Sort the constructors ascending by number of parameters (descending); this is really
285: // just to allow the test suite to work properly across different JVMs (which will
286: // often order the constructors differently).
287:
288: Comparator<Constructor> comparator = new Comparator<Constructor>() {
289: public int compare(Constructor c1, Constructor c2) {
290: return c2.getParameterTypes().length
291: - c1.getParameterTypes().length;
292: }
293: };
294:
295: Arrays.sort(constructors, comparator);
296:
297: _log.warn(IOCMessages.tooManyPublicConstructors(
298: builderClass, constructors[0]));
299: }
300:
301: Constructor constructor = constructors[0];
302:
303: if (_insideConstructor)
304: throw new RuntimeException(IOCMessages
305: .recursiveModuleConstructor(builderClass,
306: constructor));
307:
308: ObjectLocator locator = new ObjectLocatorImpl(_registry, this );
309: Map<Class, Object> parameterDefaults = newMap();
310:
311: parameterDefaults.put(Log.class, _log);
312: parameterDefaults.put(ObjectLocator.class, locator);
313:
314: Throwable fail = null;
315:
316: try {
317: _insideConstructor = true;
318:
319: Object[] parameterValues = InternalUtils
320: .calculateParameters(locator, parameterDefaults,
321: constructor.getParameterTypes(),
322: constructor.getParameterAnnotations());
323:
324: return constructor.newInstance(parameterValues);
325: } catch (InvocationTargetException ex) {
326: fail = ex.getTargetException();
327: } catch (Exception ex) {
328: fail = ex;
329: } finally {
330: _insideConstructor = false;
331: }
332:
333: throw new RuntimeException(IOCMessages.instantiateBuilderError(
334: builderClass, fail), fail);
335: }
336:
337: private Object createProxy(ServiceResources resources,
338: ObjectCreator creator) {
339: String serviceId = resources.getServiceId();
340: Class serviceInterface = resources.getServiceInterface();
341:
342: String toString = format("<Proxy for %s(%s)>", serviceId,
343: serviceInterface.getName());
344:
345: return createProxyInstance(creator, serviceId,
346: serviceInterface, toString);
347: }
348:
349: private Object createProxyInstance(ObjectCreator creator,
350: String serviceId, Class serviceInterface, String description) {
351: Class proxyClass = createProxyClass(serviceId,
352: serviceInterface, description);
353:
354: try {
355: return proxyClass.getConstructors()[0].newInstance(creator);
356: } catch (Exception ex) {
357: // This should never happen, so we won't go to a lot of trouble
358: // reporting it.
359: throw new RuntimeException(ex.getMessage(), ex);
360: }
361: }
362:
363: private Class createProxyClass(String serviceId,
364: Class serviceInterface, String proxyDescription) {
365: ClassFab cf = _registry.newClass(serviceInterface);
366:
367: cf.addField("_creator", Modifier.PRIVATE | Modifier.FINAL,
368: ObjectCreator.class);
369:
370: cf.addConstructor(new Class[] { ObjectCreator.class }, null,
371: "_creator = $1;");
372:
373: addDelegateGetter(cf, serviceInterface, serviceId);
374:
375: cf.proxyMethodsToDelegate(serviceInterface, "_delegate()",
376: proxyDescription);
377:
378: return cf.createClass();
379: }
380:
381: private void addDelegateGetter(ClassFab cf, Class serviceInterface,
382: String serviceId) {
383: String body = format("return (%s) _creator.createObject();",
384: serviceInterface.getName());
385:
386: MethodSignature sig = new MethodSignature(serviceInterface,
387: "_delegate", null, null);
388:
389: cf.addMethod(Modifier.PRIVATE, sig, body);
390: }
391:
392: public Set<ContributionDef> getContributorDefsForService(
393: String serviceId) {
394: Set<ContributionDef> result = newSet();
395:
396: for (ContributionDef def : _moduleDef.getContributionDefs()) {
397: if (def.getServiceId().equals(serviceId))
398: result.add(def);
399: }
400:
401: return result;
402: }
403:
404: public ServiceDef getServiceDef(String serviceId) {
405: return _moduleDef.getServiceDef(serviceId);
406: }
407:
408: public String getLogName() {
409: return _moduleDef.getLogName();
410: }
411:
412: }
|