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