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