    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
    015package org.apache.tapestry5.internal.transform;
    016
    017import org.apache.tapestry5.func.F;
    018import org.apache.tapestry5.func.Flow;
    019import org.apache.tapestry5.func.Predicate;
    020import org.apache.tapestry5.func.Worker;
    021import org.apache.tapestry5.model.MutableComponentModel;
    022import org.apache.tapestry5.plastic.*;
    023import org.apache.tapestry5.runtime.PageLifecycleListener;
    024import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
    025import org.apache.tapestry5.services.transform.TransformationSupport;
    026
    027import 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 */
    033public 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}