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    }