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