Package org.apache.tapestry5.internal.transform

Source Code of org.apache.tapestry5.internal.transform.ParameterWorker$ParameterState

// Copyright 2006, 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package org.apache.tapestry5.internal.transform;

import java.util.List;

import org.apache.tapestry5.Binding;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.func.Predicate;
import org.apache.tapestry5.internal.InternalComponentResources;
import org.apache.tapestry5.internal.bindings.LiteralBinding;
import org.apache.tapestry5.internal.services.ComponentClassCache;
import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.ioc.internal.util.TapestryException;
import org.apache.tapestry5.ioc.services.PerThreadValue;
import org.apache.tapestry5.ioc.services.PerthreadManager;
import org.apache.tapestry5.ioc.services.TypeCoercer;
import org.apache.tapestry5.model.MutableComponentModel;
import org.apache.tapestry5.services.BindingSource;
import org.apache.tapestry5.services.ClassTransformation;
import org.apache.tapestry5.services.ComponentClassTransformWorker;
import org.apache.tapestry5.services.ComponentDefaultProvider;
import org.apache.tapestry5.services.ComponentMethodAdvice;
import org.apache.tapestry5.services.ComponentMethodInvocation;
import org.apache.tapestry5.services.ComponentValueProvider;
import org.apache.tapestry5.services.FieldAccess;
import org.apache.tapestry5.services.MethodAccess;
import org.apache.tapestry5.services.MethodInvocationResult;
import org.apache.tapestry5.services.TransformConstants;
import org.apache.tapestry5.services.TransformField;
import org.apache.tapestry5.services.TransformMethod;
import org.apache.tapestry5.services.TransformMethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Responsible for identifying parameters via the {@link org.apache.tapestry5.annotations.Parameter} annotation on
* component fields. This is one of the most complex of the transformations.
*/
public class ParameterWorker implements ComponentClassTransformWorker
{
    private final Logger logger = LoggerFactory.getLogger(ParameterWorker.class);

    private final class InvokeResetOnParameterConduit implements ComponentMethodAdvice
    {
        private final FieldAccess conduitAccess;

        private InvokeResetOnParameterConduit(FieldAccess conduitAccess)
        {
            this.conduitAccess = conduitAccess;
        }

        public void advise(ComponentMethodInvocation invocation)
        {
            getConduit(invocation, conduitAccess).reset();

            invocation.proceed();
        }
    }

    /**
     * Contains the per-thread state about a parameter, as stored (using
     * a unique key) in the {@link PerthreadManager}. Externalizing such state
     * is part of Tapestry 5.2's pool-less pages.
     */
    private final class ParameterState
    {
        boolean cached;

        Object value;

        void reset(Object defaultValue)
        {
            cached = false;
            value = defaultValue;
        }
    }

    private final class InvokeParameterDefaultMethod implements ComponentMethodAdvice
    {
        private final FieldAccess conduitAccess;

        private final MethodAccess defaultMethodAccess;

        private InvokeParameterDefaultMethod(FieldAccess conduitAccess, MethodAccess defaultMethodAccess)
        {
            this.conduitAccess = conduitAccess;
            this.defaultMethodAccess = defaultMethodAccess;
        }

        public void advise(ComponentMethodInvocation invocation)
        {
            logger.debug(String.format("%s invoking default method %s", invocation.getComponentResources()
                    .getCompleteId(), defaultMethodAccess));

            MethodInvocationResult result = defaultMethodAccess.invoke(invocation.getInstance());

            result.rethrow();

            getConduit(invocation, conduitAccess).setDefault(result.getReturnValue());

            invocation.proceed();
        }
    }

    private final class InvokeLoadOnParmeterConduit implements ComponentMethodAdvice
    {
        private final FieldAccess conduitAccess;

        private InvokeLoadOnParmeterConduit(FieldAccess conduitAccess)
        {
            this.conduitAccess = conduitAccess;
        }

        public void advise(ComponentMethodInvocation invocation)
        {
            getConduit(invocation, conduitAccess).load();

            invocation.proceed();
        }
    }

    private final ComponentClassCache classCache;

    private final BindingSource bindingSource;

    private final ComponentDefaultProvider defaultProvider;

    private final TypeCoercer typeCoercer;

    private final PerthreadManager perThreadManager;

    public ParameterWorker(ComponentClassCache classCache, BindingSource bindingSource,
            ComponentDefaultProvider defaultProvider, TypeCoercer typeCoercer, PerthreadManager perThreadManager)
    {
        this.classCache = classCache;
        this.bindingSource = bindingSource;
        this.defaultProvider = defaultProvider;
        this.typeCoercer = typeCoercer;
        this.perThreadManager = perThreadManager;
    }

    public void transform(ClassTransformation transformation, MutableComponentModel model)
    {
        transformFields(transformation, model, true);
        transformFields(transformation, model, false);
    }

    private void transformFields(ClassTransformation transformation, MutableComponentModel model, boolean principal)
    {
        for (TransformField field : matchParameterFields(transformation, principal))
        {
            convertFieldIntoParameter(transformation, model, field);
        }
    }

    private List<TransformField> matchParameterFields(ClassTransformation transformation, final boolean principal)
    {
        Predicate<TransformField> predicate = new Predicate<TransformField>()
        {
            public boolean accept(TransformField field)
            {
                Parameter annotation = field.getAnnotation(Parameter.class);

                return annotation != null && annotation.principal() == principal;
            }
        };

        return transformation.matchFields(predicate);
    }

    private void convertFieldIntoParameter(ClassTransformation transformation, MutableComponentModel model,
            TransformField field)
    {
        Parameter annotation = field.getAnnotation(Parameter.class);

        String fieldType = field.getType();

        String parameterName = getParameterName(field.getName(), annotation.name());

        field.claim(annotation);

        model.addParameter(parameterName, annotation.required(), annotation.allowNull(), annotation.defaultPrefix(),
                annotation.cache());

        ComponentValueProvider<ParameterConduit> provider = createParameterConduitProvider(parameterName, fieldType,
                annotation);

        TransformField conduitField = transformation.addIndirectInjectedField(ParameterConduit.class, parameterName
                + "$conduit", provider);

        FieldAccess conduitAccess = conduitField.getAccess();

        addCodeForParameterDefaultMethod(transformation, parameterName, conduitAccess);

        field.replaceAccess(conduitField);

        invokeLoadOnParameterConduitAtPageLoad(transformation, conduitAccess);

        invokeResetOnParameterConduitAtPostRenderCleanup(transformation, conduitAccess);
    }

    private void invokeResetOnParameterConduitAtPostRenderCleanup(ClassTransformation transformation,
            final FieldAccess conduitAccess)
    {
        ComponentMethodAdvice advice = new InvokeResetOnParameterConduit(conduitAccess);

        addMethodAdvice(transformation, TransformConstants.POST_RENDER_CLEANUP_SIGNATURE, advice);
    }

    private void addMethodAdvice(ClassTransformation transformation, TransformMethodSignature methodSignature,
            ComponentMethodAdvice advice)
    {
        transformation.getOrCreateMethod(methodSignature).addAdvice(advice);
    }

    private void invokeLoadOnParameterConduitAtPageLoad(ClassTransformation transformation, FieldAccess conduitAccess)
    {
        ComponentMethodAdvice pageLoadAdvice = new InvokeLoadOnParmeterConduit(conduitAccess);

        addPageLoadAdvice(transformation, pageLoadAdvice);
    }

    @SuppressWarnings("all")
    private ComponentValueProvider<ParameterConduit> createParameterConduitProvider(final String parameterName,
            final String fieldTypeName, final Parameter annotation)
    {
        return new ComponentValueProvider<ParameterConduit>()
        {
            public ParameterConduit get(ComponentResources resources)
            {
                final InternalComponentResources icr = (InternalComponentResources) resources;

                final Class fieldType = classCache.forName(fieldTypeName);

                final PerThreadValue<ParameterState> stateValue = perThreadManager.createValue();

                // Rely on some code generation in the component to set the default binding from
                // the field, or from a default method.

                return new ParameterConduit()
                {
                    // Default value for parameter, computed *once* at
                    // page load time.

                    private Object defaultValue = classCache.defaultValueForType(fieldTypeName);

                    private Binding parameterBinding;

                    boolean loaded = false;

                    private boolean invariant = false;

                    {
                        // Inform the ComponentResources about the parameter conduit, so it can be
                        // shared with mixins.

                        icr.setParameterConduit(parameterName, this);
                    }

                    private ParameterState getState()
                    {
                        ParameterState state = stateValue.get();

                        if (state == null)
                        {
                            state = new ParameterState();
                            state.value = defaultValue;
                            stateValue.set(state);
                        }

                        return state;
                    }

                    private boolean isLoaded()
                    {
                        return loaded;
                    }

                    public void set(Object newValue)
                    {
                        ParameterState state = getState();

                        // Assignments before the page is loaded ultimately exist to set the
                        // default value for the field. Often this is from the (original)
                        // constructor method, which is converted to a real method as part of the transformation.

                        if (!loaded)
                        {
                            state.value = newValue;
                            defaultValue = newValue;
                            return;
                        }

                        // This will catch read-only or unbound parameters.

                        writeToBinding(newValue);

                        state.value = newValue;

                        // If caching is enabled for the parameter (the typical case) and the
                        // component is currently rendering, then the result
                        // can be cached in this ParameterConduit (until the component finishes
                        // rendering).

                        state.cached = annotation.cache() && icr.isRendering();
                    }

                    private Object readFromBinding()
                    {
                        Object result = null;

                        try
                        {
                            Object boundValue = parameterBinding.get();

                            result = typeCoercer.coerce(boundValue, fieldType);
                        }
                        catch (RuntimeException ex)
                        {
                            throw new TapestryException(String.format(
                                    "Failure reading parameter '%s' of component %s: %s", parameterName,
                                    icr.getCompleteId(), InternalUtils.toMessage(ex)), parameterBinding, ex);
                        }

                        if (result != null || annotation.allowNull())
                            return result;

                        throw new TapestryException(
                                String.format(
                                        "Parameter '%s' of component %s is bound to null. This parameter is not allowed to be null.",
                                        parameterName, icr.getCompleteId()), parameterBinding, null);
                    }

                    private void writeToBinding(Object newValue)
                    {
                        // An unbound parameter acts like a simple field
                        // with no side effects.

                        if (parameterBinding == null)
                            return;

                        try
                        {
                            Object coerced = typeCoercer.coerce(newValue, parameterBinding.getBindingType());

                            parameterBinding.set(coerced);
                        }
                        catch (RuntimeException ex)
                        {
                            throw new TapestryException(String.format(
                                    "Failure writing parameter '%s' of component %s: %s", parameterName,
                                    icr.getCompleteId(), InternalUtils.toMessage(ex)), icr, ex);
                        }
                    }

                    public void reset()
                    {
                        if (!invariant)
                        {
                            getState().reset(defaultValue);
                        }
                    }

                    public void load()
                    {
                        logger.debug(String.format("%s loading parameter %s", icr.getCompleteId(), parameterName));

                        // If it's bound at this point, that's because of an explicit binding
                        // in the template or @Component annotation.

                        if (!icr.isBound(parameterName))
                        {
                            logger.debug(String.format("%s parameter %s not yet bound", icr.getCompleteId(),
                                    parameterName));

                            // Otherwise, construct a default binding, or use one provided from
                            // the component.

                            Binding binding = getDefaultBindingForParameter();

                            logger.debug(String.format("%s parameter %s bound to default %s", icr.getCompleteId(),
                                    parameterName, binding));

                            if (binding != null)
                                icr.bindParameter(parameterName, binding);
                        }

                        parameterBinding = icr.getBinding(parameterName);

                        loaded = true;

                        invariant = parameterBinding != null && parameterBinding.isInvariant();

                        getState().value = defaultValue;
                    }

                    public boolean isBound()
                    {
                        return parameterBinding != null;
                    }

                    public Object get()
                    {
                        if (!isLoaded()) { return defaultValue; }

                        ParameterState state = getState();

                        if (state.cached || !isBound()) { return state.value; }

                        // Read the parameter's binding and cast it to the
                        // field's type.

                        Object result = readFromBinding();

                        // If the value is invariant, we can cache it until at least the end of the request (before
                        // 5.2, it would be cached forever in the pooled instance).
                        // Otherwise, we
                        // we may want to cache it for the remainder of the component render (if the
                        // component is currently rendering).

                        if (invariant || (annotation.cache() && icr.isRendering()))
                        {
                            state.value = result;
                            state.cached = true;
                        }

                        return result;
                    }

                    private Binding getDefaultBindingForParameter()
                    {
                        if (InternalUtils.isNonBlank(annotation.value()))
                            return bindingSource.newBinding("default " + parameterName, icr,
                                    annotation.defaultPrefix(), annotation.value());

                        if (annotation.autoconnect())
                            return defaultProvider.defaultBinding(parameterName, icr);

                        // Return (if not null) the binding from the setDefault() method which is
                        // set via a default method on the component, or from the field's initial
                        // value.

                        return parameterBinding;
                    }

                    public void setDefault(Object value)
                    {
                        if (value == null)
                            return;

                        if (value instanceof Binding)
                        {
                            parameterBinding = (Binding) value;
                            return;
                        }

                        parameterBinding = new LiteralBinding(null, "default " + parameterName, value);
                    }
                };
            }
        };
    }

    private ParameterConduit getConduit(ComponentMethodInvocation invocation, FieldAccess access)
    {
        return (ParameterConduit) access.read(invocation.getInstance());
    }

    private void addCodeForParameterDefaultMethod(ClassTransformation transformation, final String parameterName,
            final FieldAccess conduitAccess)
    {
        final String methodName = "default" + parameterName;

        Predicate<TransformMethod> predicate = new Predicate<TransformMethod>()
        {
            public boolean accept(TransformMethod method)
            {
                return method.getSignature().getParameterTypes().length == 0
                        && method.getName().equalsIgnoreCase(methodName);
            }
        };

        List<TransformMethod> matches = transformation.matchMethods(predicate);

        // This will match exactly 0 or 1 (unless the user does something really silly)
        // methods, and if it matches, we know the name of the method.

        if (matches.isEmpty())
            return;

        TransformMethod defaultMethod = matches.get(0);

        captureDefaultValueFromDefaultMethod(transformation, defaultMethod, conduitAccess);
    }

    private void captureDefaultValueFromDefaultMethod(ClassTransformation transformation,
            TransformMethod defaultMethod, final FieldAccess conduitAccess)
    {
        final MethodAccess access = defaultMethod.getAccess();

        ComponentMethodAdvice advice = new InvokeParameterDefaultMethod(conduitAccess, access);

        addPageLoadAdvice(transformation, advice);
    }

    private void addPageLoadAdvice(ClassTransformation transformation, ComponentMethodAdvice advice)
    {
        addMethodAdvice(transformation, TransformConstants.CONTAINING_PAGE_DID_LOAD_SIGNATURE, advice);
    }

    private static String getParameterName(String fieldName, String annotatedName)
    {
        if (InternalUtils.isNonBlank(annotatedName))
            return annotatedName;

        return InternalUtils.stripMemberName(fieldName);
    }
}
TOP

Related Classes of org.apache.tapestry5.internal.transform.ParameterWorker$ParameterState

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.