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
015package org.apache.tapestry5.internal.transform;
016
017import org.apache.tapestry5.EventConstants;
018import org.apache.tapestry5.Link;
019import org.apache.tapestry5.ValueEncoder;
020import org.apache.tapestry5.annotations.ActivationRequestParameter;
021import org.apache.tapestry5.internal.services.ComponentClassCache;
022import org.apache.tapestry5.ioc.util.IdAllocator;
023import org.apache.tapestry5.model.MutableComponentModel;
024import org.apache.tapestry5.plastic.FieldHandle;
025import org.apache.tapestry5.plastic.PlasticClass;
026import org.apache.tapestry5.plastic.PlasticField;
027import org.apache.tapestry5.runtime.Component;
028import org.apache.tapestry5.runtime.ComponentEvent;
029import org.apache.tapestry5.services.ComponentEventHandler;
030import org.apache.tapestry5.services.Request;
031import org.apache.tapestry5.services.URLEncoder;
032import org.apache.tapestry5.services.ValueEncoderSource;
033import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
034import 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")
045public 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}