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
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.plastic.*;
026import org.slf4j.Logger;
027
028import java.io.ObjectStreamException;
029import java.io.Serializable;
030import java.lang.reflect.Constructor;
031import java.lang.reflect.InvocationTargetException;
032import java.lang.reflect.Method;
033import java.lang.reflect.Modifier;
034import java.util.*;
035
036import static java.lang.String.format;
037
038@SuppressWarnings("all")
039public 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}