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