001    // Copyright 2007, 2010, 2011 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    // http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry5.internal.transform;
016    
017    import org.apache.tapestry5.func.F;
018    import org.apache.tapestry5.func.Flow;
019    import org.apache.tapestry5.func.Predicate;
020    import org.apache.tapestry5.func.Worker;
021    import org.apache.tapestry5.model.MutableComponentModel;
022    import org.apache.tapestry5.plastic.*;
023    import org.apache.tapestry5.runtime.PageLifecycleListener;
024    import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
025    import org.apache.tapestry5.services.transform.TransformationSupport;
026    
027    import java.lang.annotation.Annotation;
028    
029    /**
030     * Similar to {@link org.apache.tapestry5.internal.transform.RenderPhaseMethodWorker} but applies to annotations/methods
031     * related to the overall page lifecycle. Page lifecycle methods are always void and take no parameters.
032     */
033    public class PageLifecycleAnnotationWorker implements ComponentClassTransformWorker2
034    {
035        private final Class<? extends Annotation> methodAnnotationClass;
036    
037        private final MethodDescription lifecycleMethodDescription;
038    
039        private final String methodAlias;
040    
041        private final Predicate<PlasticMethod> MATCHER = new Predicate<PlasticMethod>()
042        {
043            public boolean accept(PlasticMethod method)
044            {
045                return method.getDescription().methodName.equalsIgnoreCase(methodAlias)
046                        || method.hasAnnotation(methodAnnotationClass);
047            }
048        };
049    
050        private final Worker<PlasticMethod> VALIDATE = new Worker<PlasticMethod>()
051        {
052            public void work(PlasticMethod method)
053            {
054                if (!method.isVoid())
055                    throw new RuntimeException(String.format("Method %s is a lifecycle method and should return void.", method
056                            .getMethodIdentifier()));
057    
058                if (!method.getParameters().isEmpty())
059                    throw new RuntimeException(String.format("Method %s is a lifecycle method and should take no parameters.",
060                            method.getMethodIdentifier()));
061    
062            }
063        };
064    
065        public PageLifecycleAnnotationWorker(Class<? extends Annotation> methodAnnotationClass,
066                                             MethodDescription lifecycleMethodDescription, String methodAlias)
067        {
068            this.methodAnnotationClass = methodAnnotationClass;
069            this.lifecycleMethodDescription = lifecycleMethodDescription;
070            this.methodAlias = methodAlias;
071        }
072    
073        public void transform(PlasticClass plasticClass, TransformationSupport support, MutableComponentModel model)
074        {
075            Flow<PlasticMethod> methods = matchLifecycleMethods(plasticClass);
076    
077            if (methods.isEmpty())
078            {
079                return;
080            }
081    
082            plasticClass.introduceInterface(PageLifecycleListener.class);
083    
084            for (PlasticMethod method : methods)
085            {
086                invokeMethodWithinLifecycle(plasticClass, method);
087            }
088        }
089    
090    
091        private void invokeMethodWithinLifecycle(PlasticClass plasticClass, PlasticMethod method)
092        {
093            MethodHandle handle = method.getHandle();
094    
095            plasticClass.introduceMethod(lifecycleMethodDescription).addAdvice(createAdvice(handle));
096        }
097    
098        private MethodAdvice createAdvice(final MethodHandle handle)
099        {
100            return new MethodAdvice()
101            {
102                public void advise(MethodInvocation invocation)
103                {
104                    invocation.proceed();
105    
106                    handle.invoke(invocation.getInstance()).rethrow();
107                }
108            };
109        }
110    
111        private Flow<PlasticMethod> matchLifecycleMethods(PlasticClass plasticClass)
112        {
113            return F.flow(plasticClass.getMethods()).filter(MATCHER).each(VALIDATE);
114        }
115    }