    001// Licensed under the Apache License, Version 2.0 (the "License");
    002// you may not use this file except in compliance with the License.
    003// You may obtain a copy of the License at
    004//
    005// http://www.apache.org/licenses/LICENSE-2.0
    006//
    007// Unless required by applicable law or agreed to in writing, software
    008// distributed under the License is distributed on an "AS IS" BASIS,
    009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    010// See the License for the specific language governing permissions and
    011// limitations under the License.
    012
    013package org.apache.tapestry5.internal.services;
    014
    015import java.lang.reflect.Constructor;
    016import java.lang.reflect.Method;
    017import java.util.Collections;
    018import java.util.HashSet;
    019import java.util.Iterator;
    020import java.util.List;
    021import java.util.Map;
    022import java.util.Map.Entry;
    023import java.util.Objects;
    024import java.util.Set;
    025import java.util.stream.Collectors;
    026
    027import org.apache.tapestry5.ComponentResources;
    028import org.apache.tapestry5.SymbolConstants;
    029import org.apache.tapestry5.beanmodel.services.PlasticProxyFactoryImpl;
    030import org.apache.tapestry5.commons.Location;
    031import org.apache.tapestry5.commons.ObjectCreator;
    032import org.apache.tapestry5.commons.Resource;
    033import org.apache.tapestry5.commons.services.PlasticProxyFactory;
    034import org.apache.tapestry5.commons.util.CollectionFactory;
    035import org.apache.tapestry5.commons.util.ExceptionUtils;
    036import org.apache.tapestry5.http.TapestryHttpSymbolConstants;
    037import org.apache.tapestry5.internal.InternalComponentResources;
    038import org.apache.tapestry5.internal.InternalConstants;
    039import org.apache.tapestry5.internal.model.MutableComponentModelImpl;
    040import org.apache.tapestry5.internal.plastic.PlasticInternalUtils;
    041import org.apache.tapestry5.internal.services.ComponentDependencyRegistry.DependencyType;
    042import org.apache.tapestry5.ioc.Invokable;
    043import org.apache.tapestry5.ioc.LoggerSource;
    044import org.apache.tapestry5.ioc.OperationTracker;
    045import org.apache.tapestry5.ioc.annotations.PostInjection;
    046import org.apache.tapestry5.ioc.annotations.Primary;
    047import org.apache.tapestry5.ioc.annotations.Symbol;
    048import org.apache.tapestry5.ioc.internal.util.ClasspathResource;
    049import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    050import org.apache.tapestry5.ioc.internal.util.URLChangeTracker;
    051import org.apache.tapestry5.ioc.services.Builtin;
    052import org.apache.tapestry5.ioc.services.ClasspathURLConverter;
    053import org.apache.tapestry5.ioc.services.UpdateListener;
    054import org.apache.tapestry5.ioc.services.UpdateListenerHub;
    055import org.apache.tapestry5.model.ComponentModel;
    056import org.apache.tapestry5.model.MutableComponentModel;
    057import org.apache.tapestry5.plastic.ClassInstantiator;
    058import org.apache.tapestry5.plastic.ConstructorCallback;
    059import org.apache.tapestry5.plastic.InstanceContext;
    060import org.apache.tapestry5.plastic.InstructionBuilder;
    061import org.apache.tapestry5.plastic.InstructionBuilderCallback;
    062import org.apache.tapestry5.plastic.MethodAdvice;
    063import org.apache.tapestry5.plastic.MethodDescription;
    064import org.apache.tapestry5.plastic.MethodInvocation;
    065import org.apache.tapestry5.plastic.PlasticClass;
    066import org.apache.tapestry5.plastic.PlasticClassEvent;
    067import org.apache.tapestry5.plastic.PlasticClassListener;
    068import org.apache.tapestry5.plastic.PlasticClassTransformation;
    069import org.apache.tapestry5.plastic.PlasticClassTransformer;
    070import org.apache.tapestry5.plastic.PlasticField;
    071import org.apache.tapestry5.plastic.PlasticManager;
    072import org.apache.tapestry5.plastic.PlasticManager.PlasticManagerBuilder;
    073import org.apache.tapestry5.plastic.PlasticManagerDelegate;
    074import org.apache.tapestry5.plastic.PlasticMethod;
    075import org.apache.tapestry5.plastic.PlasticUtils;
    076import org.apache.tapestry5.plastic.TransformationOption;
    077import org.apache.tapestry5.runtime.Component;
    078import org.apache.tapestry5.runtime.ComponentEvent;
    079import org.apache.tapestry5.runtime.ComponentResourcesAware;
    080import org.apache.tapestry5.runtime.PageLifecycleListener;
    081import org.apache.tapestry5.services.ComponentClassResolver;
    082import org.apache.tapestry5.services.ComponentEventHandler;
    083import org.apache.tapestry5.services.TransformConstants;
    084import org.apache.tapestry5.services.pageload.PageClassLoaderContext;
    085import org.apache.tapestry5.services.pageload.PageClassLoaderContextManager;
    086import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
    087import org.apache.tapestry5.services.transform.ControlledPackageType;
    088import org.apache.tapestry5.services.transform.TransformationSupport;
    089import org.slf4j.Logger;
    090
    091/**
    092 * A wrapper around a {@link PlasticManager} that allows certain classes to be modified as they are loaded.
    093 */
    094public final class ComponentInstantiatorSourceImpl implements ComponentInstantiatorSource, UpdateListener,
    095        Runnable, PlasticManagerDelegate, PlasticClassListener
    096{
    097    private final Set<String> controlledPackageNames = CollectionFactory.newSet();
    098
    099    private final URLChangeTracker<ClassName> changeTracker;
    100
    101    private final ClassLoader parent;
    102
    103    private final ComponentClassTransformWorker2 transformerChain;
    104
    105    private final LoggerSource loggerSource;
    106
    107    private final Logger logger;
    108
    109    private final OperationTracker tracker;
    110
    111    private final InternalComponentInvalidationEventHub invalidationHub;
    112
    113    private final boolean productionMode;
    114
    115    private final boolean multipleClassLoaders;
    116
    117    private final ComponentClassResolver resolver;
    118
    119    private final PageClassLoaderContextManager pageClassLoaderContextManager;
    120
    121    private PageClassLoaderContext rootPageClassloaderContext;
    122
    123    private PlasticProxyFactoryProxy plasticProxyFactoryProxy;
    124
    125    private ComponentDependencyRegistry componentDependencyRegistry;
    126
    127    private static final ThreadLocal<String> CURRENT_PAGE = ThreadLocal.withInitial(() -> null);
    128
    129    /**
    130     * Map from class name to Instantiator.
    131     */
    132    private final Map<String, Instantiator> classToInstantiator = CollectionFactory.newConcurrentMap();
    133
    134    private final Map<String, ComponentModel> classToModel = CollectionFactory.newMap();
    135
    136    private final MethodDescription GET_COMPONENT_RESOURCES = PlasticUtils.getMethodDescription(
    137            ComponentResourcesAware.class, "getComponentResources");
    138
    139    private final ConstructorCallback REGISTER_AS_PAGE_LIFECYCLE_LISTENER = new ConstructorCallback()
    140    {
    141        public void onConstruct(Object instance, InstanceContext context)
    142        {
    143            InternalComponentResources resources = context.get(InternalComponentResources.class);
    144
    145            resources.addPageLifecycleListener((PageLifecycleListener) instance);
    146        }
    147    };
    148
    149    public ComponentInstantiatorSourceImpl(Logger logger,
    150
    151                                           LoggerSource loggerSource,
    152
    153                                           @Builtin
    154                                           PlasticProxyFactory proxyFactory,
    155
    156                                           @Primary
    157                                           ComponentClassTransformWorker2 transformerChain,
    158
    159                                           ClasspathURLConverter classpathURLConverter,
    160
    161                                           OperationTracker tracker,
    162
    163                                           Map<String, ControlledPackageType> configuration,
    164
    165                                           @Symbol(TapestryHttpSymbolConstants.PRODUCTION_MODE)
    166                                           boolean productionMode,
    167
    168                                           @Symbol(SymbolConstants.MULTIPLE_CLASSLOADERS)
    169                                           boolean multipleClassLoaders,
    170
    171                                           ComponentClassResolver resolver,
    172
    173                                           InternalComponentInvalidationEventHub invalidationHub,
    174
    175                                           PageClassLoaderContextManager pageClassLoaderContextManager,
    176
    177                                           ComponentDependencyRegistry componentDependencyRegistry
    178            )
    179    {
    180        this.parent = proxyFactory.getClassLoader();
    181        this.transformerChain = transformerChain;
    182        this.logger = logger;
    183        this.loggerSource = loggerSource;
    184        this.changeTracker = new URLChangeTracker<ClassName>(classpathURLConverter);
    185        this.tracker = tracker;
    186        this.invalidationHub = invalidationHub;
    187        this.productionMode = productionMode;
    188        this.multipleClassLoaders = multipleClassLoaders && !productionMode;
    189        this.resolver = resolver;
    190        this.pageClassLoaderContextManager = pageClassLoaderContextManager;
    191        this.componentDependencyRegistry = componentDependencyRegistry;
    192
    193        // For now, we just need the keys of the configuration. When there are more types of controlled
    194        // packages, we'll need to do more.
    195
    196        controlledPackageNames.addAll(configuration.keySet());
    197
    198        initializeService();
    199
    200        pageClassLoaderContextManager.initialize(
    201                rootPageClassloaderContext,
    202                ComponentInstantiatorSourceImpl.this::createPlasticProxyFactory);
    203
    204    }
    205
    206    @PostInjection
    207    public void listenForUpdates(UpdateListenerHub hub)
    208    {
    209        invalidationHub.addInvalidationCallback(this::invalidate);
    210        hub.addUpdateListener(this);
    211    }
    212
    213    public synchronized void checkForUpdates()
    214    {
    215        final Set<ClassName> changedResources = changeTracker.getChangedResourcesInfo();
    216        if (!changedResources.isEmpty())
    217        {
    218
    219            final List<String> classNames = changedResources.stream().map(ClassName::getClassName).collect(Collectors.toList());
    220
    221            if (logger.isInfoEnabled())
    222            {
    223                logger.info("Component class(es) changed: {}", String.join(", ", classNames));
    224            }
    225
    226            if (multipleClassLoaders)
    227            {
    228
    229                final Set<String> classesToInvalidate = new HashSet<>();
    230
    231                for (String className : classNames)
    232                {
    233                    final PageClassLoaderContext context = rootPageClassloaderContext.findByClassName(className);
    234                    if (context != rootPageClassloaderContext && context != null)
    235                    {
    236                        classesToInvalidate.addAll(pageClassLoaderContextManager.invalidate(context));
    237                    }
    238                }
    239
    240                classNames.clear();
    241                classNames.addAll(classesToInvalidate);
    242
    243                invalidate(classNames);
    244
    245                invalidationHub.fireInvalidationEvent(classNames);
    246            }
    247            else
    248            {
    249                invalidationHub.classInControlledPackageHasChanged();
    250            }
    251
    252        }
    253    }
    254
    255    private List<String> invalidate(final List<String> classNames) {
    256
    257        if (classNames.isEmpty())
    258        {
    259            clearCaches();
    260        }
    261        else
    262        {
    263
    264            final String currentPage = CURRENT_PAGE.get();
    265
    266            final Iterator<Entry<String, Instantiator>> classToInstantiatorIterator = classToInstantiator.entrySet().iterator();
    267            while (classToInstantiatorIterator.hasNext())
    268            {
    269                final String className = classToInstantiatorIterator.next().getKey();
    270                if (!className.equals(currentPage) && classNames.contains(className))
    271                {
    272                    classToInstantiatorIterator.remove();
    273                }
    274            }
    275
    276            final Iterator<Entry<String, ComponentModel>> classToModelIterator = classToModel.entrySet().iterator();
    277            while (classToModelIterator.hasNext())
    278            {
    279                final String className = classToModelIterator.next().getKey();
    280                if (!className.equals(currentPage) && classNames.contains(className))
    281                {
    282                    classToModelIterator.remove();
    283                }
    284            }
    285
    286        }
    287
    288        return Collections.emptyList();
    289    }
    290
    291    public void forceComponentInvalidation()
    292    {
    293        clearCaches();
    294        invalidationHub.classInControlledPackageHasChanged();
    295    }
    296
    297    private void clearCaches()
    298    {
    299        classToInstantiator.clear();
    300        pageClassLoaderContextManager.clear();
    301    }
    302
    303    public void run()
    304    {
    305        changeTracker.clear();
    306        classToInstantiator.clear();
    307        classToModel.clear();
    308        pageClassLoaderContextManager.clear();
    309        initializeService();
    310    }
    311
    312    /**
    313     * Invoked at object creation, or when there are updates to class files (i.e., invalidation), to create a new set of
    314     * Javassist class pools and loaders.
    315     * Since TAP5-2742, this method is only called once.
    316     */
    317    private void initializeService()
    318    {
    319
    320        pageClassLoaderContextManager.clear();
    321
    322        if (rootPageClassloaderContext == null)
    323        {
    324            logger.info("Initializing page pool. Production mode " + (productionMode ? "enabled" : "disabled") +
    325                    ". Multiple classloaders " + (!productionMode && multipleClassLoaders ? "enabled" : "disabled") + ".");
    326
    327            pageClassLoaderContextManager.clear();
    328
    329            PlasticProxyFactory proxyFactory = createPlasticProxyFactory(parent);
    330            rootPageClassloaderContext = new PageClassLoaderContext(
    331                    "root", null, Collections.emptySet(), proxyFactory, pageClassLoaderContextManager::get);
    332        }
    333        else
    334        {
    335            logger.info("Restarting page pool");
    336        }
    337
    338        classToInstantiator.clear();
    339        classToModel.clear();
    340    }
    341
    342    private PlasticProxyFactory createPlasticProxyFactory(final ClassLoader parentClassloader)
    343    {
    344        PlasticManagerBuilder builder = PlasticManager.withClassLoader(parentClassloader)
    345                .delegate(this)
    346                .packages(controlledPackageNames);
    347        if (!productionMode)
    348        {
    349            builder.enable(TransformationOption.FIELD_WRITEBEHIND);
    350        }
    351        PlasticManager plasticManager = builder.create();
    352        plasticManager.addPlasticClassListener(this);
    353        PlasticProxyFactory proxyFactory = new PlasticProxyFactoryImpl(plasticManager, logger);
    354        return proxyFactory;
    355    }
    356
    357    public Instantiator getInstantiator(final String className)
    358    {
    359        Instantiator instantiator;
    360        if (multipleClassLoaders)
    361        {
    362            instantiator = classToInstantiator.get(className);
    363
    364            if (instantiator == null)
    365            {
    366                instantiator = createInstantiatorForClass(className);
    367                classToInstantiator.put(className, instantiator);
    368            }
    369
    370        }
    371        else
    372        {
    373            instantiator = classToInstantiator.computeIfAbsent(className, this::createInstantiatorForClass);
    374        }
    375        return instantiator;
    376    }
    377
    378    private static final ThreadLocal<Set<String>> OPEN_INSTANTIATORS =
    379            ThreadLocal.withInitial(HashSet::new);
    380
    381    private Instantiator createInstantiatorForClass(final String className)
    382    {
    383        return tracker.invoke(String.format("Creating instantiator for component class %s", className),
    384                new Invokable<Instantiator>()
    385                {
    386                    public Instantiator invoke()
    387                    {
    388
    389                        // Force the creation of the class (and the transformation of the class). This will first
    390                        // trigger transformations of any base classes.
    391
    392                        OPEN_INSTANTIATORS.get().add(className);
    393
    394                        componentDependencyRegistry.disableInvalidations();
    395
    396                        // Make sure the dependencies have been processed in case
    397                        // there was some invalidation going on and they're not there.
    398
    399                        if (multipleClassLoaders)
    400                        {
    401
    402                            final Set<String> dependencies = new HashSet<>();
    403                            dependencies.addAll(
    404                                    componentDependencyRegistry.getDependencies(className, DependencyType.USAGE));
    405                            dependencies.addAll(
    406                                    componentDependencyRegistry.getDependencies(className, DependencyType.SUPERCLASS));
    407                            for (String dependency : dependencies)
    408                            {
    409                                if (!OPEN_INSTANTIATORS.get().contains(dependency))
    410                                {
    411                                    if (multipleClassLoaders)
    412                                    {
    413                                        getInstantiator(dependency);
    414                                    }
    415                                    else
    416                                    {
    417                                        createInstantiatorForClass(dependency);
    418                                    }
    419                                }
    420                            }
    421                        }
    422
    423                        PageClassLoaderContext context;
    424                        try
    425                        {
    426                            context = pageClassLoaderContextManager.get(className);
    427                        }
    428                        finally
    429                        {
    430                            componentDependencyRegistry.enableInvalidations();
    431                        }
    432
    433                        ClassInstantiator<Component> plasticInstantiator;
    434                        try
    435                        {
    436                            plasticInstantiator = context.getPlasticManager().getClassInstantiator(className);
    437                            if (multipleClassLoaders)
    438                            {
    439                                context.getPlasticManager().getClassLoader().loadClass(className);
    440                            }
    441                        } catch (Exception e) {
    442//                            System.out.println(pageClassLoaderContextManager.getRoot().toRecursiveString());
    443                            throw new RuntimeException(e);
    444                        }
    445                        final ComponentModel model = classToModel.get(className);
    446
    447                        OPEN_INSTANTIATORS.get().remove(className);
    448
    449                        return new Instantiator()
    450                        {
    451                            public Component newInstance(InternalComponentResources resources)
    452                            {
    453                                return plasticInstantiator.with(ComponentResources.class, resources)
    454                                        .with(InternalComponentResources.class, resources).newInstance();
    455                            }
    456
    457                            public ComponentModel getModel()
    458                            {
    459                                return model;
    460                            }
    461
    462                            @Override
    463                            public String toString()
    464                            {
    465                                return String.format("[Instantiator[%s:%s]", className, context);
    466                            }
    467                        };
    468                    }
    469                });
    470    }
    471
    472    public boolean exists(String className)
    473    {
    474        return parent.getResource(PlasticInternalUtils.toClassPath(className)) != null;
    475    }
    476
    477    public PlasticProxyFactory getProxyFactory()
    478    {
    479        if (plasticProxyFactoryProxy == null)
    480        {
    481            plasticProxyFactoryProxy = new PlasticProxyFactoryProxy();
    482        }
    483        return plasticProxyFactoryProxy;
    484    }
    485
    486    public void transform(final PlasticClass plasticClass)
    487    {
    488        tracker.run(String.format("Running component class transformations on %s", plasticClass.getClassName()),
    489                new Runnable()
    490                {
    491                    public void run()
    492                    {
    493                        String className = plasticClass.getClassName();
    494                        String parentClassName = plasticClass.getSuperClassName();
    495
    496                        // The parent model may not exist, if the super class is not in a controlled package.
    497
    498                        ComponentModel parentModel = classToModel.get(parentClassName);
    499
    500                        final boolean isRoot = parentModel == null;
    501
    502                        if (isRoot
    503                                && !(parentClassName.equals("java.lang.Object") || parentClassName
    504                                .equals("groovy.lang.GroovyObjectSupport")))
    505                        {
    506                            String suggestedPackageName = buildSuggestedPackageName(className);
    507
    508                            throw new RuntimeException(String.format("Base class %s (super class of %s) is not in a controlled package and is therefore not valid. You should try moving the class to package %s.", parentClassName, className, suggestedPackageName));
    509                        }
    510
    511                        // Tapestry 5.2 was more sensitive that the parent class have a public no-args constructor.
    512                        // Plastic
    513                        // doesn't care, and we don't have the tools to dig that information out.
    514
    515                        Logger logger = loggerSource.getLogger(className);
    516
    517                        Resource baseResource = new ClasspathResource(parent, PlasticInternalUtils
    518                                .toClassPath(className));
    519
    520                        changeTracker.add(baseResource.toURL(), new ClassName(className));
    521
    522                        if (isRoot)
    523                        {
    524                            implementComponentInterface(plasticClass);
    525                        }
    526
    527                        boolean isPage = resolver.isPage(className);
    528
    529                        boolean superClassImplementsPageLifecycle = plasticClass.isInterfaceImplemented(PageLifecycleListener.class);
    530
    531                        String libraryName = resolver.getLibraryNameForClass(className);
    532
    533                        final MutableComponentModel model = new MutableComponentModelImpl(className, logger, baseResource,
    534                                parentModel, isPage, libraryName);
    535
    536                        TransformationSupportImpl transformationSupport = new TransformationSupportImpl(plasticClass, isRoot, model);
    537
    538                        transformerChain.transform(plasticClass, transformationSupport, model);
    539
    540                        transformationSupport.commit();
    541
    542                        if (!superClassImplementsPageLifecycle && plasticClass.isInterfaceImplemented(PageLifecycleListener.class))
    543                        {
    544                            plasticClass.onConstruct(REGISTER_AS_PAGE_LIFECYCLE_LISTENER);
    545                        }
    546
    547                        classToModel.put(className, model);
    548                    }
    549                });
    550    }
    551
    552    private void implementComponentInterface(PlasticClass plasticClass)
    553    {
    554        plasticClass.introduceInterface(Component.class);
    555
    556        final PlasticField resourcesField = plasticClass.introduceField(InternalComponentResources.class,
    557                "internalComponentResources").injectFromInstanceContext();
    558
    559        plasticClass.introduceMethod(GET_COMPONENT_RESOURCES, new InstructionBuilderCallback()
    560        {
    561            public void doBuild(InstructionBuilder builder)
    562            {
    563                builder.loadThis().getField(resourcesField).returnResult();
    564            }
    565        });
    566    }
    567
    568    public <T> ClassInstantiator<T> configureInstantiator(String className, ClassInstantiator<T> instantiator)
    569    {
    570        return instantiator;
    571    }
    572
    573    private String buildSuggestedPackageName(String className)
    574    {
    575        for (String subpackage : InternalConstants.SUBPACKAGES)
    576        {
    577            String term = "." + subpackage + ".";
    578
    579            int pos = className.indexOf(term);
    580
    581            // Keep the leading '.' in the subpackage name and tack on "base".
    582
    583            if (pos > 0)
    584                return className.substring(0, pos + 1) + InternalConstants.BASE_SUBPACKAGE;
    585        }
    586
    587        // Is this even reachable? className should always be in a controlled package and so
    588        // some subpackage above should have matched.
    589
    590        return null;
    591    }
    592
    593    public void classWillLoad(PlasticClassEvent event)
    594    {
    595        Logger logger = loggerSource.getLogger("tapestry.transformer." + event.getPrimaryClassName());
    596
    597        if (logger.isDebugEnabled())
    598            logger.debug(event.getDissasembledBytecode());
    599    }
    600
    601    private class TransformationSupportImpl implements TransformationSupport
    602    {
    603        private final PlasticClass plasticClass;
    604
    605        private final boolean root;
    606
    607        private final MutableComponentModel model;
    608
    609        private final List<MethodAdvice> eventHandlerAdvice = CollectionFactory.newList();
    610
    611        public TransformationSupportImpl(PlasticClass plasticClass, boolean root, MutableComponentModel model)
    612        {
    613            this.plasticClass = plasticClass;
    614            this.root = root;
    615            this.model = model;
    616        }
    617
    618        /**
    619         * Commits any stored changes to the PlasticClass; this is used to defer adding advice to the dispatch method.
    620         */
    621        public void commit()
    622        {
    623            if (!eventHandlerAdvice.isEmpty())
    624            {
    625                PlasticMethod dispatchMethod = plasticClass.introduceMethod(TransformConstants.DISPATCH_COMPONENT_EVENT_DESCRIPTION);
    626                for (MethodAdvice advice : eventHandlerAdvice)
    627                {
    628                    dispatchMethod.addAdvice(advice);
    629                }
    630            }
    631        }
    632
    633        public Class toClass(String typeName)
    634        {
    635            try
    636            {
    637                final PageClassLoaderContext context = pageClassLoaderContextManager.get(typeName);
    638                return PlasticInternalUtils.toClass(context.getPlasticManager().getClassLoader(), typeName);
    639            } catch (ClassNotFoundException ex)
    640            {
    641                throw new RuntimeException(String.format(
    642                        "Unable to convert type '%s' to a Class: %s", typeName,
    643                        ExceptionUtils.toMessage(ex)), ex);
    644            }
    645        }
    646
    647        public boolean isRootTransformation()
    648        {
    649            return root;
    650        }
    651
    652        public void addEventHandler(final String eventType, final int minContextValues, final String operationDescription, final ComponentEventHandler handler)
    653        {
    654            assert InternalUtils.isNonBlank(eventType);
    655            assert minContextValues >= 0;
    656            assert handler != null;
    657
    658            model.addEventHandler(eventType);
    659
    660            MethodAdvice advice = new EventMethodAdvice(tracker, eventType, minContextValues, operationDescription, handler);
    661
    662            // The advice is added at the very end, after the logic provided by the OnEventWorker
    663
    664            eventHandlerAdvice.add(advice);
    665        }
    666    }
    667
    668    private static class EventMethodAdvice implements MethodAdvice
    669    {
    670        final OperationTracker tracker;
    671        final String eventType;
    672        final int minContextValues;
    673        final String operationDescription;
    674        final ComponentEventHandler handler;
    675
    676        public EventMethodAdvice(OperationTracker tracker, String eventType, int minContextValues, String operationDescription, ComponentEventHandler handler)
    677        {
    678            this.tracker = tracker;
    679            this.eventType = eventType;
    680            this.minContextValues = minContextValues;
    681            this.operationDescription = operationDescription;
    682            this.handler = handler;
    683        }
    684
    685        public void advise(final MethodInvocation invocation)
    686        {
    687            final ComponentEvent event = (ComponentEvent) invocation.getParameter(0);
    688
    689            boolean matches = !event.isAborted() && event.matches(eventType, "", minContextValues);
    690
    691            if (matches)
    692            {
    693                tracker.run(operationDescription, new Runnable()
    694                {
    695                    public void run()
    696                    {
    697                        Component instance = (Component) invocation.getInstance();
    698
    699                        handler.handleEvent(instance, event);
    700                    }
    701                });
    702            }
    703
    704            // Order of operations is key here. This logic takes precedence; base class event dispatch and event handler methods
    705            // in the class come AFTER.
    706
    707            invocation.proceed();
    708
    709            if (matches)
    710            {
    711                invocation.setReturnValue(true);
    712            }
    713        }
    714    }
    715
    716    private static class ClassName implements ClassNameHolder
    717    {
    718        private String className;
    719
    720        public ClassName(String className)
    721        {
    722            super();
    723            this.className = className;
    724        }
    725
    726        @Override
    727        public String getClassName()
    728        {
    729            return className;
    730        }
    731
    732        @Override
    733        public int hashCode() {
    734            return Objects.hash(className);
    735        }
    736
    737        @Override
    738        public boolean equals(Object obj)
    739        {
    740            if (this == obj) {
    741                return true;
    742            }
    743            if (!(obj instanceof ClassName)) {
    744                return false;
    745            }
    746            ClassName other = (ClassName) obj;
    747            return Objects.equals(className, other.className);
    748        }
    749
    750        @Override
    751        public String toString()
    752        {
    753            return className;
    754        }
    755
    756    }
    757
    758    private class PlasticProxyFactoryProxy implements PlasticProxyFactory
    759    {
    760
    761        @Override
    762        public void addPlasticClassListener(PlasticClassListener listener)
    763        {
    764            throw new UnsupportedOperationException();
    765        }
    766
    767        @Override
    768        public void removePlasticClassListener(PlasticClassListener listener)
    769        {
    770            throw new UnsupportedOperationException();
    771        }
    772
    773        @Override
    774        public ClassLoader getClassLoader() {
    775            return rootPageClassloaderContext.getProxyFactory().getClassLoader();
    776        }
    777
    778        @Override
    779        public <T> ClassInstantiator<T> createProxy(Class<T> interfaceType, PlasticClassTransformer callback)
    780        {
    781            return getProxyFactory(interfaceType.getName()).createProxy(interfaceType, callback);
    782        }
    783
    784        @Override
    785        public <T> ClassInstantiator<T> createProxy(Class<T> interfaceType,
    786                Class<? extends T> implementationType,
    787                PlasticClassTransformer callback,
    788                boolean introduceInterface)
    789        {
    790            throw new UnsupportedOperationException();
    791        }
    792
    793        @Override
    794        public <T> ClassInstantiator<T> createProxy(Class<T> interfaceType, Class<? extends T> implementationType, PlasticClassTransformer callback)
    795        {
    796            throw new UnsupportedOperationException();
    797        }
    798
    799        @Override
    800        public <T> PlasticClassTransformation<T> createProxyTransformation(Class<T> interfaceType)
    801        {
    802            throw new UnsupportedOperationException();
    803        }
    804
    805        @Override
    806        public <T> PlasticClassTransformation<T> createProxyTransformation(Class<T> interfaceType, Class<? extends T> implementationType)
    807        {
    808            throw new UnsupportedOperationException();
    809        }
    810
    811        @Override
    812        public <T> T createProxy(Class<T> interfaceType, ObjectCreator<T> creator, String description)
    813        {
    814            throw new UnsupportedOperationException();
    815        }
    816
    817        @Override
    818        public <T> T createProxy(Class<T> interfaceType, Class<? extends T> implementationType, ObjectCreator<T> creator, String description)
    819        {
    820            throw new UnsupportedOperationException();
    821        }
    822
    823        @Override
    824        public Location getMethodLocation(Method method) {
    825            return getProxyFactory(method.getDeclaringClass().getName()).getMethodLocation(method);
    826        }
    827
    828        @Override
    829        public Location getConstructorLocation(Constructor constructor)
    830        {
    831            return getProxyFactory(constructor.getDeclaringClass().getName()).getConstructorLocation(constructor);
    832        }
    833
    834        @Override
    835        public void clearCache() {
    836            throw new UnsupportedOperationException();
    837        }
    838
    839        @Override
    840        public PlasticManager getPlasticManager() {
    841            return rootPageClassloaderContext.getProxyFactory().getPlasticManager();
    842        }
    843
    844        @Override
    845        public PlasticProxyFactory getProxyFactory(String className)
    846        {
    847            PageClassLoaderContext context = rootPageClassloaderContext.findByClassName(className);
    848            if (context == null)
    849            {
    850                context = pageClassLoaderContextManager.get(className);
    851            }
    852            return context.getProxyFactory();
    853        }
    854
    855    }
    856
    857}