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}