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