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