    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;
    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");
    325
    326            pageClassLoaderContextManager.clear();
    327
    328            PlasticProxyFactory proxyFactory = createPlasticProxyFactory(parent);
    329            rootPageClassloaderContext = new PageClassLoaderContext(
    330                    "root", null, Collections.emptySet(), proxyFactory, pageClassLoaderContextManager::get);
    331        }
    332        else
    333        {
    334            logger.info("Restarting page pool");
    335        }
    336
    337        classToInstantiator.clear();
    338        classToModel.clear();
    339    }
    340
    341    private PlasticProxyFactory createPlasticProxyFactory(final ClassLoader parentClassloader)
    342    {
    343        PlasticManagerBuilder builder = PlasticManager.withClassLoader(parentClassloader)
    344                .delegate(this)
    345                .packages(controlledPackageNames);
    346        if (!productionMode)
    347        {
    348            builder.enable(TransformationOption.FIELD_WRITEBEHIND);
    349        }
    350        PlasticManager plasticManager = builder.create();
    351        plasticManager.addPlasticClassListener(this);
    352        PlasticProxyFactory proxyFactory = new PlasticProxyFactoryImpl(plasticManager, logger);
    353        return proxyFactory;
    354    }
    355
    356    public Instantiator getInstantiator(final String className)
    357    {
    358        return classToInstantiator.computeIfAbsent(className, this::createInstantiatorForClass);
    359    }
    360
    361    private static final ThreadLocal<Set<String>> OPEN_INSTANTIATORS =
    362            ThreadLocal.withInitial(HashSet::new);
    363
    364    private Instantiator createInstantiatorForClass(final String className)
    365    {
    366        return tracker.invoke(String.format("Creating instantiator for component class %s", className),
    367                new Invokable<Instantiator>()
    368                {
    369                    public Instantiator invoke()
    370                    {
    371
    372                        // Force the creation of the class (and the transformation of the class). This will first
    373                        // trigger transformations of any base classes.
    374
    375                        OPEN_INSTANTIATORS.get().add(className);
    376
    377                        componentDependencyRegistry.disableInvalidations();
    378                        PageClassLoaderContext context;
    379                        try
    380                        {
    381                            context = pageClassLoaderContextManager.get(className);
    382                        }
    383                        finally
    384                        {
    385                            componentDependencyRegistry.enableInvalidations();
    386                        }
    387
    388                        // Make sure the dependencies have been processed in case
    389                        // there was some invalidation going on and they're not there.
    390
    391                        // TODO: maybe we need superclasses here too?
    392                        final Set<String> dependencies = componentDependencyRegistry.getDependencies(className, DependencyType.USAGE);
    393                        for (String dependency : dependencies)
    394                        {
    395                            if (!OPEN_INSTANTIATORS.get().contains(dependency))
    396                            {
    397                                createInstantiatorForClass(dependency);
    398                            }
    399                        }
    400
    401                        ClassInstantiator<Component> plasticInstantiator = context.getPlasticManager().getClassInstantiator(className);
    402                        final ComponentModel model = classToModel.get(className);
    403
    404                        OPEN_INSTANTIATORS.get().remove(className);
    405
    406                        return new Instantiator()
    407                        {
    408                            public Component newInstance(InternalComponentResources resources)
    409                            {
    410                                return plasticInstantiator.with(ComponentResources.class, resources)
    411                                        .with(InternalComponentResources.class, resources).newInstance();
    412                            }
    413
    414                            public ComponentModel getModel()
    415                            {
    416                                return model;
    417                            }
    418
    419                            @Override
    420                            public String toString()
    421                            {
    422                                return String.format("[Instantiator[%s:%s]", className, context);
    423                            }
    424                        };
    425                    }
    426                });
    427    }
    428
    429    public boolean exists(String className)
    430    {
    431        return parent.getResource(PlasticInternalUtils.toClassPath(className)) != null;
    432    }
    433
    434    public PlasticProxyFactory getProxyFactory()
    435    {
    436        if (plasticProxyFactoryProxy == null)
    437        {
    438            plasticProxyFactoryProxy = new PlasticProxyFactoryProxy();
    439        }
    440        return plasticProxyFactoryProxy;
    441    }
    442
    443    public void transform(final PlasticClass plasticClass)
    444    {
    445        tracker.run(String.format("Running component class transformations on %s", plasticClass.getClassName()),
    446                new Runnable()
    447                {
    448                    public void run()
    449                    {
    450                        String className = plasticClass.getClassName();
    451                        String parentClassName = plasticClass.getSuperClassName();
    452
    453                        // The parent model may not exist, if the super class is not in a controlled package.
    454
    455                        ComponentModel parentModel = classToModel.get(parentClassName);
    456
    457                        final boolean isRoot = parentModel == null;
    458
    459                        if (isRoot
    460                                && !(parentClassName.equals("java.lang.Object") || parentClassName
    461                                .equals("groovy.lang.GroovyObjectSupport")))
    462                        {
    463                            String suggestedPackageName = buildSuggestedPackageName(className);
    464
    465                            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));
    466                        }
    467
    468                        // Tapestry 5.2 was more sensitive that the parent class have a public no-args constructor.
    469                        // Plastic
    470                        // doesn't care, and we don't have the tools to dig that information out.
    471
    472                        Logger logger = loggerSource.getLogger(className);
    473
    474                        Resource baseResource = new ClasspathResource(parent, PlasticInternalUtils
    475                                .toClassPath(className));
    476
    477                        changeTracker.add(baseResource.toURL(), new ClassName(className));
    478
    479                        if (isRoot)
    480                        {
    481                            implementComponentInterface(plasticClass);
    482                        }
    483
    484                        boolean isPage = resolver.isPage(className);
    485
    486                        boolean superClassImplementsPageLifecycle = plasticClass.isInterfaceImplemented(PageLifecycleListener.class);
    487
    488                        String libraryName = resolver.getLibraryNameForClass(className);
    489
    490                        final MutableComponentModel model = new MutableComponentModelImpl(className, logger, baseResource,
    491                                parentModel, isPage, libraryName);
    492
    493                        TransformationSupportImpl transformationSupport = new TransformationSupportImpl(plasticClass, isRoot, model);
    494
    495                        transformerChain.transform(plasticClass, transformationSupport, model);
    496
    497                        transformationSupport.commit();
    498
    499                        if (!superClassImplementsPageLifecycle && plasticClass.isInterfaceImplemented(PageLifecycleListener.class))
    500                        {
    501                            plasticClass.onConstruct(REGISTER_AS_PAGE_LIFECYCLE_LISTENER);
    502                        }
    503
    504                        classToModel.put(className, model);
    505                    }
    506                });
    507    }
    508
    509    private void implementComponentInterface(PlasticClass plasticClass)
    510    {
    511        plasticClass.introduceInterface(Component.class);
    512
    513        final PlasticField resourcesField = plasticClass.introduceField(InternalComponentResources.class,
    514                "internalComponentResources").injectFromInstanceContext();
    515
    516        plasticClass.introduceMethod(GET_COMPONENT_RESOURCES, new InstructionBuilderCallback()
    517        {
    518            public void doBuild(InstructionBuilder builder)
    519            {
    520                builder.loadThis().getField(resourcesField).returnResult();
    521            }
    522        });
    523    }
    524
    525    public <T> ClassInstantiator<T> configureInstantiator(String className, ClassInstantiator<T> instantiator)
    526    {
    527        return instantiator;
    528    }
    529
    530    private String buildSuggestedPackageName(String className)
    531    {
    532        for (String subpackage : InternalConstants.SUBPACKAGES)
    533        {
    534            String term = "." + subpackage + ".";
    535
    536            int pos = className.indexOf(term);
    537
    538            // Keep the leading '.' in the subpackage name and tack on "base".
    539
    540            if (pos > 0)
    541                return className.substring(0, pos + 1) + InternalConstants.BASE_SUBPACKAGE;
    542        }
    543
    544        // Is this even reachable? className should always be in a controlled package and so
    545        // some subpackage above should have matched.
    546
    547        return null;
    548    }
    549
    550    public void classWillLoad(PlasticClassEvent event)
    551    {
    552        Logger logger = loggerSource.getLogger("tapestry.transformer." + event.getPrimaryClassName());
    553
    554        if (logger.isDebugEnabled())
    555            logger.debug(event.getDissasembledBytecode());
    556    }
    557
    558    private class TransformationSupportImpl implements TransformationSupport
    559    {
    560        private final PlasticClass plasticClass;
    561
    562        private final boolean root;
    563
    564        private final MutableComponentModel model;
    565
    566        private final List<MethodAdvice> eventHandlerAdvice = CollectionFactory.newList();
    567
    568        public TransformationSupportImpl(PlasticClass plasticClass, boolean root, MutableComponentModel model)
    569        {
    570            this.plasticClass = plasticClass;
    571            this.root = root;
    572            this.model = model;
    573        }
    574
    575        /**
    576         * Commits any stored changes to the PlasticClass; this is used to defer adding advice to the dispatch method.
    577         */
    578        public void commit()
    579        {
    580            if (!eventHandlerAdvice.isEmpty())
    581            {
    582                PlasticMethod dispatchMethod = plasticClass.introduceMethod(TransformConstants.DISPATCH_COMPONENT_EVENT_DESCRIPTION);
    583                for (MethodAdvice advice : eventHandlerAdvice)
    584                {
    585                    dispatchMethod.addAdvice(advice);
    586                }
    587            }
    588        }
    589
    590        public Class toClass(String typeName)
    591        {
    592            try
    593            {
    594                final PageClassLoaderContext context = pageClassLoaderContextManager.get(typeName);
    595                return PlasticInternalUtils.toClass(context.getPlasticManager().getClassLoader(), typeName);
    596            } catch (ClassNotFoundException ex)
    597            {
    598                throw new RuntimeException(String.format(
    599                        "Unable to convert type '%s' to a Class: %s", typeName,
    600                        ExceptionUtils.toMessage(ex)), ex);
    601            }
    602        }
    603
    604        public boolean isRootTransformation()
    605        {
    606            return root;
    607        }
    608
    609        public void addEventHandler(final String eventType, final int minContextValues, final String operationDescription, final ComponentEventHandler handler)
    610        {
    611            assert InternalUtils.isNonBlank(eventType);
    612            assert minContextValues >= 0;
    613            assert handler != null;
    614
    615            model.addEventHandler(eventType);
    616
    617            MethodAdvice advice = new EventMethodAdvice(tracker, eventType, minContextValues, operationDescription, handler);
    618
    619            // The advice is added at the very end, after the logic provided by the OnEventWorker
    620
    621            eventHandlerAdvice.add(advice);
    622        }
    623    }
    624
    625    private static class EventMethodAdvice implements MethodAdvice
    626    {
    627        final OperationTracker tracker;
    628        final String eventType;
    629        final int minContextValues;
    630        final String operationDescription;
    631        final ComponentEventHandler handler;
    632
    633        public EventMethodAdvice(OperationTracker tracker, String eventType, int minContextValues, String operationDescription, ComponentEventHandler handler)
    634        {
    635            this.tracker = tracker;
    636            this.eventType = eventType;
    637            this.minContextValues = minContextValues;
    638            this.operationDescription = operationDescription;
    639            this.handler = handler;
    640        }
    641
    642        public void advise(final MethodInvocation invocation)
    643        {
    644            final ComponentEvent event = (ComponentEvent) invocation.getParameter(0);
    645
    646            boolean matches = !event.isAborted() && event.matches(eventType, "", minContextValues);
    647
    648            if (matches)
    649            {
    650                tracker.run(operationDescription, new Runnable()
    651                {
    652                    public void run()
    653                    {
    654                        Component instance = (Component) invocation.getInstance();
    655
    656                        handler.handleEvent(instance, event);
    657                    }
    658                });
    659            }
    660
    661            // Order of operations is key here. This logic takes precedence; base class event dispatch and event handler methods
    662            // in the class come AFTER.
    663
    664            invocation.proceed();
    665
    666            if (matches)
    667            {
    668                invocation.setReturnValue(true);
    669            }
    670        }
    671    }
    672
    673    private static class ClassName implements ClassNameHolder
    674    {
    675        private String className;
    676
    677        public ClassName(String className)
    678        {
    679            super();
    680            this.className = className;
    681        }
    682
    683        @Override
    684        public String getClassName()
    685        {
    686            return className;
    687        }
    688
    689        @Override
    690        public int hashCode() {
    691            return Objects.hash(className);
    692        }
    693
    694        @Override
    695        public boolean equals(Object obj)
    696        {
    697            if (this == obj) {
    698                return true;
    699            }
    700            if (!(obj instanceof ClassName)) {
    701                return false;
    702            }
    703            ClassName other = (ClassName) obj;
    704            return Objects.equals(className, other.className);
    705        }
    706
    707        @Override
    708        public String toString()
    709        {
    710            return className;
    711        }
    712
    713    }
    714
    715    private class PlasticProxyFactoryProxy implements PlasticProxyFactory
    716    {
    717
    718        @Override
    719        public void addPlasticClassListener(PlasticClassListener listener)
    720        {
    721            throw new UnsupportedOperationException();
    722        }
    723
    724        @Override
    725        public void removePlasticClassListener(PlasticClassListener listener)
    726        {
    727            throw new UnsupportedOperationException();
    728        }
    729
    730        @Override
    731        public ClassLoader getClassLoader() {
    732            return rootPageClassloaderContext.getProxyFactory().getClassLoader();
    733        }
    734
    735        @Override
    736        public <T> ClassInstantiator<T> createProxy(Class<T> interfaceType, PlasticClassTransformer callback)
    737        {
    738            return getProxyFactory(interfaceType.getName()).createProxy(interfaceType, callback);
    739        }
    740
    741        @Override
    742        public <T> ClassInstantiator<T> createProxy(Class<T> interfaceType,
    743                Class<? extends T> implementationType,
    744                PlasticClassTransformer callback,
    745                boolean introduceInterface)
    746        {
    747            throw new UnsupportedOperationException();
    748        }
    749
    750        @Override
    751        public <T> ClassInstantiator<T> createProxy(Class<T> interfaceType, Class<? extends T> implementationType, PlasticClassTransformer callback)
    752        {
    753            throw new UnsupportedOperationException();
    754        }
    755
    756        @Override
    757        public <T> PlasticClassTransformation<T> createProxyTransformation(Class<T> interfaceType)
    758        {
    759            throw new UnsupportedOperationException();
    760        }
    761
    762        @Override
    763        public <T> PlasticClassTransformation<T> createProxyTransformation(Class<T> interfaceType, Class<? extends T> implementationType)
    764        {
    765            throw new UnsupportedOperationException();
    766        }
    767
    768        @Override
    769        public <T> T createProxy(Class<T> interfaceType, ObjectCreator<T> creator, String description)
    770        {
    771            throw new UnsupportedOperationException();
    772        }
    773
    774        @Override
    775        public <T> T createProxy(Class<T> interfaceType, Class<? extends T> implementationType, ObjectCreator<T> creator, String description)
    776        {
    777            throw new UnsupportedOperationException();
    778        }
    779
    780        @Override
    781        public Location getMethodLocation(Method method) {
    782            return getProxyFactory(method.getDeclaringClass().getName()).getMethodLocation(method);
    783        }
    784
    785        @Override
    786        public Location getConstructorLocation(Constructor constructor)
    787        {
    788            return getProxyFactory(constructor.getDeclaringClass().getName()).getConstructorLocation(constructor);
    789        }
    790
    791        @Override
    792        public void clearCache() {
    793            throw new UnsupportedOperationException();
    794        }
    795
    796        @Override
    797        public PlasticManager getPlasticManager() {
    798            return rootPageClassloaderContext.getProxyFactory().getPlasticManager();
    799        }
    800
    801        @Override
    802        public PlasticProxyFactory getProxyFactory(String className)
    803        {
    804            PageClassLoaderContext context = rootPageClassloaderContext.findByClassName(className);
    805            if (context == null)
    806            {
    807                context = pageClassLoaderContextManager.get(className);
    808            }
    809            return context.getProxyFactory();
    810        }
    811
    812    }
    813
    814}