    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
    015    package org.apache.tapestry5.ioc.internal;
    016
    017    import org.apache.tapestry5.ioc.*;
    018    import org.apache.tapestry5.ioc.annotations.Local;
    019    import org.apache.tapestry5.ioc.def.*;
    020    import org.apache.tapestry5.ioc.internal.services.JustInTimeObjectCreator;
    021    import org.apache.tapestry5.ioc.internal.util.*;
    022    import org.apache.tapestry5.ioc.services.AspectDecorator;
    023    import org.apache.tapestry5.ioc.services.PlasticProxyFactory;
    024    import org.apache.tapestry5.ioc.services.Status;
    025    import org.apache.tapestry5.plastic.*;
    026    import org.slf4j.Logger;
    027
    028    import java.io.ObjectStreamException;
    029    import java.io.Serializable;
    030    import java.lang.reflect.Constructor;
    031    import java.lang.reflect.InvocationTargetException;
    032    import java.lang.reflect.Method;
    033    import java.lang.reflect.Modifier;
    034    import java.util.*;
    035
    036    import static java.lang.String.format;
    037
    038    @SuppressWarnings("all")
    039    public 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    }