001    // Copyright 2006, 2007, 2008, 2009, 2010, 2011 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.tapestry5.ioc.internal;
016    
017    import org.apache.tapestry5.ioc.*;
018    import org.apache.tapestry5.ioc.annotations.Local;
019    import org.apache.tapestry5.ioc.def.*;
020    import org.apache.tapestry5.ioc.internal.services.JustInTimeObjectCreator;
021    import org.apache.tapestry5.ioc.internal.util.*;
022    import org.apache.tapestry5.ioc.services.AspectDecorator;
023    import org.apache.tapestry5.ioc.services.PlasticProxyFactory;
024    import org.apache.tapestry5.ioc.services.Status;
025    import org.apache.tapestry5.plastic.*;
026    import org.slf4j.Logger;
027    
028    import java.io.ObjectStreamException;
029    import java.io.Serializable;
030    import java.lang.reflect.Constructor;
031    import java.lang.reflect.InvocationTargetException;
032    import java.lang.reflect.Method;
033    import java.lang.reflect.Modifier;
034    import java.util.*;
035    
036    import static java.lang.String.format;
037    
038    @SuppressWarnings("all")
039    public class ModuleImpl implements Module
040    {
041        private final InternalRegistry registry;
042    
043        private final ServiceActivityTracker tracker;
044    
045        private final ModuleDef2 moduleDef;
046    
047        private final PlasticProxyFactory proxyFactory;
048    
049        private final Logger logger;
050    
051        /**
052         * Lazily instantiated. Access is guarded by BARRIER.
053         */
054        private Object moduleInstance;
055    
056        // Set to true when invoking the module constructor. Used to
057        // detect endless loops caused by irresponsible dependencies in
058        // the constructor.
059        private boolean insideConstructor;
060    
061        /**
062         * Keyed on fully qualified service id; values are instantiated services (proxies). Guarded by BARRIER.
063         */
064        private final Map<String, Object> services = CollectionFactory.newCaseInsensitiveMap();
065    
066        private final Map<String, ServiceDef3> serviceDefs = CollectionFactory.newCaseInsensitiveMap();
067    
068        /**
069         * The barrier is shared by all modules, which means that creation of *any* service for any module is single
070         * threaded.
071         */
072        private final static ConcurrentBarrier BARRIER = new ConcurrentBarrier();
073    
074        /**
075         * "Magic" method related to Externalizable that allows the Proxy object to replace itself with the token.
076         */
077        private static final MethodDescription WRITE_REPLACE = new MethodDescription(Modifier.PRIVATE, "java.lang.Object",
078                "writeReplace", null, null, new String[]
079                {ObjectStreamException.class.getName()});
080    
081        public ModuleImpl(InternalRegistry registry, ServiceActivityTracker tracker, ModuleDef moduleDef,
082                          PlasticProxyFactory proxyFactory, Logger logger)
083        {
084            this.registry = registry;
085            this.tracker = tracker;
086            this.proxyFactory = proxyFactory;
087            this.moduleDef = InternalUtils.toModuleDef2(moduleDef);
088            this.logger = logger;
089    
090            for (String id : moduleDef.getServiceIds())
091            {
092                ServiceDef sd = moduleDef.getServiceDef(id);
093    
094                ServiceDef3 sd3 = InternalUtils.toServiceDef3(sd);
095    
096                serviceDefs.put(id, sd3);
097            }
098        }
099    
100        public <T> T getService(String serviceId, Class<T> serviceInterface)
101        {
102            assert InternalUtils.isNonBlank(serviceId);
103            assert serviceInterface != null;
104            ServiceDef3 def = getServiceDef(serviceId);
105    
106            // RegistryImpl should already have checked that the service exists.
107            assert def != null;
108    
109            Object service = findOrCreate(def, null);
110    
111            try
112            {
113                return serviceInterface.cast(service);
114            } catch (ClassCastException ex)
115            {
116                // This may be overkill: I don't know how this could happen
117                // given that the return type of the method determines
118                // the service interface.
119    
120                throw new RuntimeException(IOCMessages.serviceWrongInterface(serviceId, def.getServiceInterface(),
121                        serviceInterface));
122            }
123        }
124    
125        public Set<DecoratorDef> findMatchingDecoratorDefs(ServiceDef serviceDef)
126        {
127            Set<DecoratorDef> result = CollectionFactory.newSet();
128    
129            for (DecoratorDef def : moduleDef.getDecoratorDefs())
130            {
131                if (def.matches(serviceDef) || markerMatched(serviceDef, InternalUtils.toDecoratorDef2(def)))
132                    result.add(def);
133            }
134    
135            return result;
136        }
137    
138        public Set<AdvisorDef> findMatchingServiceAdvisors(ServiceDef serviceDef)
139        {
140            Set<AdvisorDef> result = CollectionFactory.newSet();
141    
142            for (AdvisorDef def : moduleDef.getAdvisorDefs())
143            {
144                if (def.matches(serviceDef) || markerMatched(serviceDef, InternalUtils.toAdvisorDef2(def)))
145                    result.add(def);
146            }
147    
148            return result;
149        }
150    
151        @SuppressWarnings("unchecked")
152        public Collection<String> findServiceIdsForInterface(Class serviceInterface)
153        {
154            assert serviceInterface != null;
155            Collection<String> result = CollectionFactory.newList();
156    
157            for (ServiceDef2 def : serviceDefs.values())
158            {
159                if (serviceInterface.isAssignableFrom(def.getServiceInterface()))
160                    result.add(def.getServiceId());
161            }
162    
163            return result;
164        }
165    
166        /**
167         * Locates the service proxy for a particular service (from the service definition).
168         *
169         * @param def              defines the service
170         * @param eagerLoadProxies collection into which proxies for eager loaded services are added (or null)
171         * @return the service proxy
172         */
173        private Object findOrCreate(final ServiceDef3 def, final Collection<EagerLoadServiceProxy> eagerLoadProxies)
174        {
175            final String key = def.getServiceId();
176    
177            final Invokable create = new Invokable()
178            {
179                public Object invoke()
180                {
181                    // In a race condition, two threads may try to create the same service simulatenously.
182                    // The second will block until after the first creates the service.
183    
184                    Object result = services.get(key);
185    
186                    // Normally, result is null, unless some other thread slipped in and created the service
187                    // proxy.
188    
189                    if (result == null)
190                    {
191                        result = create(def, eagerLoadProxies);
192    
193                        services.put(key, result);
194                    }
195    
196                    return result;
197                }
198            };
199    
200            Invokable find = new Invokable()
201            {
202                public Object invoke()
203                {
204                    Object result = services.get(key);
205    
206                    if (result == null)
207                        result = BARRIER.withWrite(create);
208    
209                    return result;
210                }
211            };
212    
213            return BARRIER.withRead(find);
214        }
215    
216        public void collectEagerLoadServices(final Collection<EagerLoadServiceProxy> proxies)
217        {
218            Runnable work = new Runnable()
219            {
220                public void run()
221                {
222                    for (ServiceDef3 def : serviceDefs.values())
223                    {
224                        if (def.isEagerLoad())
225                            findOrCreate(def, proxies);
226                    }
227                }
228            };
229    
230            registry.run("Eager loading services", work);
231        }
232    
233        /**
234         * Creates the service and updates the cache of created services.
235         *
236         * @param eagerLoadProxies a list into which any eager loaded proxies should be added
237         */
238        private Object create(final ServiceDef3 def, final Collection<EagerLoadServiceProxy> eagerLoadProxies)
239        {
240            final String serviceId = def.getServiceId();
241    
242            final Logger logger = registry.getServiceLogger(serviceId);
243    
244            final Class serviceInterface = def.getServiceInterface();
245    
246            String description = String.format("Creating %s service %s",
247                    serviceInterface.isInterface() ? "proxy for" : "non-proxied instance of",
248                    serviceId);
249    
250            if (logger.isDebugEnabled())
251                logger.debug(description);
252    
253            final Module module = this;
254    
255            Invokable operation = new Invokable()
256            {
257                public Object invoke()
258                {
259                    try
260                    {
261                        ServiceBuilderResources resources = new ServiceResourcesImpl(registry, module, def, proxyFactory,
262                                logger);
263    
264                        // Build up a stack of operations that will be needed to realize the service
265                        // (by the proxy, at a later date).
266    
267                        ObjectCreator creator = def.createServiceCreator(resources);
268    
269    
270                        // For non-proxyable services, we immediately create the service implementation
271                        // and return it. There's no interface to proxy, which throws out the possibility of
272                        // deferred instantiation, service lifecycles, and decorators.
273    
274                        ServiceLifecycle2 lifecycle = registry.getServiceLifecycle(def.getServiceScope());
275    
276                        if (!serviceInterface.isInterface())
277                        {
278                            if (lifecycle.requiresProxy())
279                                throw new IllegalArgumentException(
280                                        String.format(
281                                                "Service scope '%s' requires a proxy, but the service does not have a service interface (necessary to create a proxy). Provide a service interface or select a different service scope.",
282                                                def.getServiceScope()));
283    
284                            return creator.createObject();
285                        }
286    
287                        creator = new OperationTrackingObjectCreator(registry, String.format("Instantiating service %s implementation via %s", serviceId, creator), creator);
288    
289                        creator = new LifecycleWrappedServiceCreator(lifecycle, resources, creator);
290    
291                        // Marked services (or services inside marked modules) are not decorated.
292                        // TapestryIOCModule prevents decoration of its services. Note that all decorators will decorate
293                        // around the aspect interceptor, which wraps around the core service implementation.
294    
295                        boolean allowDecoration = !def.isPreventDecoration();
296    
297                        if (allowDecoration)
298                        {
299                            creator = new AdvisorStackBuilder(def, creator, getAspectDecorator(), registry);
300                            creator = new InterceptorStackBuilder(def, creator, registry);
301                        }
302    
303                        // Add a wrapper that checks for recursion.
304    
305                        creator = new RecursiveServiceCreationCheckWrapper(def, creator, logger);
306    
307                        creator = new OperationTrackingObjectCreator(registry, "Realizing service " + serviceId, creator);
308    
309                        JustInTimeObjectCreator delegate = new JustInTimeObjectCreator(tracker, creator, serviceId);
310    
311                        Object proxy = createProxy(resources, delegate);
312    
313                        registry.addRegistryShutdownListener(delegate);
314    
315                        // Occasionally eager load service A may invoke service B from its service builder method; if
316                        // service B is eager loaded, we'll hit this method but eagerLoadProxies will be null. That's OK
317                        // ... service B is being realized anyway.
318    
319                        if (def.isEagerLoad() && eagerLoadProxies != null)
320                            eagerLoadProxies.add(delegate);
321    
322                        tracker.setStatus(serviceId, Status.VIRTUAL);
323    
324                        return proxy;
325                    } catch (Exception ex)
326                    {
327                        throw new RuntimeException(IOCMessages.errorBuildingService(serviceId, def, ex), ex);
328                    }
329                }
330            };
331    
332            return registry.invoke(description, operation);
333        }
334    
335        private AspectDecorator getAspectDecorator()
336        {
337            return registry.invoke("Obtaining AspectDecorator service", new Invokable<AspectDecorator>()
338            {
339                public AspectDecorator invoke()
340                {
341                    return registry.getService(AspectDecorator.class);
342                }
343            });
344        }
345    
346        private final Runnable instantiateModule = new Runnable()
347        {
348            public void run()
349            {
350                moduleInstance = registry.invoke("Constructing module class " + moduleDef.getBuilderClass().getName(),
351                        new Invokable()
352                        {
353                            public Object invoke()
354                            {
355                                return instantiateModuleInstance();
356                            }
357                        });
358            }
359        };
360    
361        private final Invokable provideModuleInstance = new Invokable<Object>()
362        {
363            public Object invoke()
364            {
365                if (moduleInstance == null)
366                    BARRIER.withWrite(instantiateModule);
367    
368                return moduleInstance;
369            }
370        };
371    
372        public Object getModuleBuilder()
373        {
374            return BARRIER.withRead(provideModuleInstance);
375        }
376    
377        private Object instantiateModuleInstance()
378        {
379            Class moduleClass = moduleDef.getBuilderClass();
380    
381            Constructor[] constructors = moduleClass.getConstructors();
382    
383            if (constructors.length == 0)
384                throw new RuntimeException(IOCMessages.noPublicConstructors(moduleClass));
385    
386            if (constructors.length > 1)
387            {
388                // Sort the constructors ascending by number of parameters (descending); this is really
389                // just to allow the test suite to work properly across different JVMs (which will
390                // often order the constructors differently).
391    
392                Comparator<Constructor> comparator = new Comparator<Constructor>()
393                {
394                    public int compare(Constructor c1, Constructor c2)
395                    {
396                        return c2.getParameterTypes().length - c1.getParameterTypes().length;
397                    }
398                };
399    
400                Arrays.sort(constructors, comparator);
401    
402                logger.warn(IOCMessages.tooManyPublicConstructors(moduleClass, constructors[0]));
403            }
404    
405            Constructor constructor = constructors[0];
406    
407            if (insideConstructor)
408                throw new RuntimeException(IOCMessages.recursiveModuleConstructor(moduleClass, constructor));
409    
410            ObjectLocator locator = new ObjectLocatorImpl(registry, this);
411            Map<Class, Object> resourcesMap = CollectionFactory.newMap();
412    
413            resourcesMap.put(Logger.class, logger);
414            resourcesMap.put(ObjectLocator.class, locator);
415            resourcesMap.put(OperationTracker.class, registry);
416    
417            InjectionResources resources = new MapInjectionResources(resourcesMap);
418    
419            Throwable fail = null;
420    
421            try
422            {
423                insideConstructor = true;
424    
425                ObjectCreator[] parameterValues = InternalUtils.calculateParameters(locator, resources,
426                        constructor.getParameterTypes(), constructor.getGenericParameterTypes(),
427                        constructor.getParameterAnnotations(), registry);
428    
429                Object[] realized = InternalUtils.realizeObjects(parameterValues);
430    
431                Object result = constructor.newInstance(realized);
432    
433                InternalUtils.injectIntoFields(result, locator, resources, registry);
434    
435                return result;
436            } catch (InvocationTargetException ex)
437            {
438                fail = ex.getTargetException();
439            } catch (Exception ex)
440            {
441                fail = ex;
442            } finally
443            {
444                insideConstructor = false;
445            }
446    
447            throw new RuntimeException(IOCMessages.instantiateBuilderError(moduleClass, fail), fail);
448        }
449    
450        private Object createProxy(ServiceResources resources, ObjectCreator creator)
451        {
452            String serviceId = resources.getServiceId();
453            Class serviceInterface = resources.getServiceInterface();
454    
455            String toString = format("<Proxy for %s(%s)>", serviceId, serviceInterface.getName());
456    
457            ServiceProxyToken token = SerializationSupport.createToken(serviceId);
458    
459            return createProxyInstance(creator, token, serviceInterface, resources.getImplementationClass(), toString);
460        }
461    
462        private Object createProxyInstance(final ObjectCreator creator, final ServiceProxyToken token,
463                                           final Class serviceInterface, final Class serviceImplementation, final String description)
464        {
465            ClassInstantiator instantiator = proxyFactory.createProxy(serviceInterface, new PlasticClassTransformer()
466            {
467                public void transform(final PlasticClass plasticClass)
468                {
469                    plasticClass.introduceInterface(Serializable.class);
470    
471                    final PlasticField creatorField = plasticClass.introduceField(ObjectCreator.class, "creator").inject(
472                            creator);
473    
474                    final PlasticField tokenField = plasticClass.introduceField(ServiceProxyToken.class, "token").inject(
475                            token);
476    
477                    PlasticMethod delegateMethod = plasticClass.introducePrivateMethod(serviceInterface.getName(),
478                            "delegate", null, null);
479    
480                    // If not concerned with efficiency, this might be done with method advice instead.
481                    delegateMethod.changeImplementation(new InstructionBuilderCallback()
482                    {
483                        public void doBuild(InstructionBuilder builder)
484                        {
485                            builder.loadThis().getField(plasticClass.getClassName(), creatorField.getName(),
486                                    ObjectCreator.class);
487                            builder.invoke(ObjectCreator.class, Object.class, "createObject").checkcast(serviceInterface)
488                                    .returnResult();
489                        }
490                    });
491    
492                    for (Method m : serviceInterface.getMethods())
493                    {
494                        plasticClass.introduceMethod(m).delegateTo(delegateMethod);
495                    }
496    
497                    plasticClass.introduceMethod(WRITE_REPLACE).changeImplementation(new InstructionBuilderCallback()
498                    {
499                        public void doBuild(InstructionBuilder builder)
500                        {
501                            builder.loadThis()
502                                    .getField(plasticClass.getClassName(), tokenField.getName(), ServiceProxyToken.class)
503                                    .returnResult();
504                        }
505                    });
506    
507                    plasticClass.addToString(description);
508                }
509            });
510    
511            return instantiator.newInstance();
512        }
513    
514        @SuppressWarnings("all")
515        public Set<ContributionDef2> getContributorDefsForService(ServiceDef serviceDef)
516        {
517            Set<ContributionDef2> result = CollectionFactory.newSet();
518    
519            for (ContributionDef next : moduleDef.getContributionDefs())
520            {
521                ContributionDef2 def = InternalUtils.toContributionDef2(next);
522    
523                if (serviceDef.getServiceId().equalsIgnoreCase(def.getServiceId()))
524                {
525                    result.add(def);
526                } else
527                {
528                    if (markerMatched(serviceDef, def))
529                    {
530                        result.add(def);
531                    }
532                }
533            }
534    
535            return result;
536        }
537    
538        private boolean markerMatched(ServiceDef serviceDef, Markable markable)
539        {
540            final Class markableInterface = markable.getServiceInterface();
541    
542            if (markableInterface == null || !markableInterface.isAssignableFrom(serviceDef.getServiceInterface()))
543                return false;
544    
545            Set<Class> contributionMarkers = CollectionFactory.newSet(markable.getMarkers());
546    
547            if (contributionMarkers.contains(Local.class))
548            {
549                // If @Local is present, filter out services that aren't in the same module.
550                // Don't consider @Local to be a marker annotation
551                // for the later match, however.
552    
553                if (!isLocalServiceDef(serviceDef))
554                    return false;
555    
556                contributionMarkers.remove(Local.class);
557            }
558    
559            // Filter out any stray annotations that aren't used by some
560            // service, in any module, as a marker annotation.
561    
562            contributionMarkers.retainAll(registry.getMarkerAnnotations());
563    
564            //@Advise and @Decorate default to Object.class service interface.
565            //If @Match is present, no marker annotations are needed.
566            //In such a case an empty contribution marker list  should be ignored.
567            if (markableInterface == Object.class && contributionMarkers.isEmpty())
568                return false;
569    
570            return serviceDef.getMarkers().containsAll(contributionMarkers);
571        }
572    
573        private boolean isLocalServiceDef(ServiceDef serviceDef)
574        {
575            return serviceDefs.containsKey(serviceDef.getServiceId());
576        }
577    
578        public ServiceDef3 getServiceDef(String serviceId)
579        {
580            return serviceDefs.get(serviceId);
581        }
582    
583        public String getLoggerName()
584        {
585            return moduleDef.getLoggerName();
586        }
587    
588        @Override
589        public String toString()
590        {
591            return String.format("ModuleImpl[%s]", moduleDef.getLoggerName());
592        }
593    }