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