    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.transform;
    014
    015import org.apache.tapestry5.EventConstants;
    016import org.apache.tapestry5.Link;
    017import org.apache.tapestry5.ValueEncoder;
    018import org.apache.tapestry5.annotations.ActivationRequestParameter;
    019import org.apache.tapestry5.internal.services.ComponentClassCache;
    020import org.apache.tapestry5.ioc.internal.util.TapestryException;
    021import org.apache.tapestry5.ioc.util.IdAllocator;
    022import org.apache.tapestry5.model.MutableComponentModel;
    023import org.apache.tapestry5.plastic.FieldHandle;
    024import org.apache.tapestry5.plastic.PlasticClass;
    025import org.apache.tapestry5.plastic.PlasticField;
    026import org.apache.tapestry5.runtime.Component;
    027import org.apache.tapestry5.runtime.ComponentEvent;
    028import org.apache.tapestry5.services.ComponentEventHandler;
    029import org.apache.tapestry5.services.Request;
    030import org.apache.tapestry5.services.URLEncoder;
    031import org.apache.tapestry5.services.ValueEncoderSource;
    032import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
    033import org.apache.tapestry5.services.transform.TransformationSupport;
    034
    035/**
    036 * Hooks the activate event handler on the component (presumably, a page) to
    037 * extract query parameters, and hooks the link decoration events to extract values
    038 * and add them to the {@link Link}.
    039 *
    040 * @see ActivationRequestParameter
    041 * @since 5.2.0
    042 */
    043@SuppressWarnings("all")
    044public class ActivationRequestParameterWorker implements ComponentClassTransformWorker2
    045{
    046    private final Request request;
    047
    048    private final ComponentClassCache classCache;
    049
    050    private final ValueEncoderSource valueEncoderSource;
    051
    052    private final URLEncoder urlEncoder;
    053
    054    public ActivationRequestParameterWorker(Request request, ComponentClassCache classCache,
    055                                            ValueEncoderSource valueEncoderSource, URLEncoder urlEncoder)
    056    {
    057        this.request = request;
    058        this.classCache = classCache;
    059        this.valueEncoderSource = valueEncoderSource;
    060        this.urlEncoder = urlEncoder;
    061    }
    062
    063    public void transform(PlasticClass plasticClass, TransformationSupport support, MutableComponentModel model)
    064    {
    065        for (PlasticField field : plasticClass.getFieldsWithAnnotation(ActivationRequestParameter.class))
    066        {
    067            mapFieldToQueryParameter(field, support);
    068        }
    069    }
    070
    071    private void mapFieldToQueryParameter(PlasticField field, TransformationSupport support)
    072    {
    073        ActivationRequestParameter annotation = field.getAnnotation(ActivationRequestParameter.class);
    074
    075        String parameterName = getParameterName(field, annotation);
    076
    077        // Assumption: the field type is not one that's loaded by the component class loader, so it's safe
    078        // to convert to a hard type during class transformation.
    079
    080        Class fieldType = classCache.forName(field.getTypeName());
    081
    082        ValueEncoder encoder = valueEncoderSource.getValueEncoder(fieldType);
    083
    084        FieldHandle handle = field.getHandle();
    085
    086        String fieldName = String.format("%s.%s", field.getPlasticClass().getClassName(), field.getName());
    087
    088        setValueFromInitializeEventHandler(support, fieldName, annotation.required(), handle, parameterName, encoder, urlEncoder);
    089
    090        decorateLinks(support, fieldName, handle, parameterName, encoder, urlEncoder);
    091
    092        preallocateName(support, parameterName);
    093    }
    094
    095
    096    private static void preallocateName(TransformationSupport support, final String parameterName)
    097    {
    098        ComponentEventHandler handler = new ComponentEventHandler()
    099        {
    100            public void handleEvent(Component instance, ComponentEvent event)
    101            {
    102                IdAllocator idAllocator = event.getEventContext().get(IdAllocator.class, 0);
    103
    104                idAllocator.allocateId(parameterName);
    105            }
    106        };
    107
    108        support.addEventHandler(EventConstants.PREALLOCATE_FORM_CONTROL_NAMES, 1,
    109                "ActivationRequestParameterWorker preallocate form control name '" + parameterName + "' event handler",
    110                handler);
    111    }
    112
    113    @SuppressWarnings("all")
    114    private void setValueFromInitializeEventHandler(final TransformationSupport support, final String fieldName, final boolean required, final FieldHandle handle,
    115                                                    final String parameterName, final ValueEncoder encoder, final URLEncoder urlEncoder)
    116    {
    117        ComponentEventHandler handler = new ComponentEventHandler()
    118        {
    119            public void handleEvent(Component instance, ComponentEvent event)
    120            {
    121                String clientValue = request.getParameter(parameterName);
    122
    123                if (clientValue == null)
    124                {
    125                    if (required)
    126                    {
    127                        throw new TapestryException(String.format("Activation request parameter field %s is marked as required, but query parameter '%s' is null.",
    128                                fieldName,
    129                                parameterName), null);
    130                    }
    131
    132                    return;
    133                }
    134
    135                // TAP5-1768: unescape encoded value
    136                clientValue = urlEncoder.decode(clientValue);
    137
    138                Object value = encoder.toValue(clientValue);
    139
    140                handle.set(instance, value);
    141            }
    142        };
    143
    144        support.addEventHandler(EventConstants.ACTIVATE, 0,
    145                String.format("Restoring field %s from query parameter '%s'", fieldName, parameterName), handler);
    146
    147    }
    148
    149    @SuppressWarnings("all")
    150    private static void decorateLinks(TransformationSupport support, String fieldName, final FieldHandle handle,
    151                                      final String parameterName, final ValueEncoder encoder, final URLEncoder urlEncoder)
    152    {
    153        ComponentEventHandler handler = new ComponentEventHandler()
    154        {
    155            public void handleEvent(Component instance, ComponentEvent event)
    156            {
    157                Object value = handle.get(instance);
    158
    159                if (value == null)
    160                {
    161                    return;
    162                }
    163
    164                Link link = event.getEventContext().get(Link.class, 0);
    165
    166                String clientValue = encoder.toClient(value);
    167
    168                // TAP5-1768: escape special characters
    169                clientValue = urlEncoder.encode(clientValue);
    170
    171                link.addParameter(parameterName, clientValue);
    172            }
    173        };
    174
    175        support.addEventHandler(EventConstants.DECORATE_COMPONENT_EVENT_LINK, 0,
    176                String.format("ActivationRequestParameterWorker decorate component event link event handler for field %s as query parameter '%s'", fieldName, parameterName), handler);
    177
    178        support.addEventHandler(EventConstants.DECORATE_PAGE_RENDER_LINK, 0, String.format(
    179                "ActivationRequestParameterWorker decorate page render link event handler for field %s as query parameter '%s'", fieldName, parameterName), handler);
    180    }
    181
    182    private String getParameterName(PlasticField field, ActivationRequestParameter annotation)
    183    {
    184        if (annotation.value().equals(""))
    185            return field.getName();
    186
    187        return annotation.value();
    188    }
    189
    190}