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