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.ValueEncoder; 017import org.apache.tapestry5.annotations.ActivationRequestParameter; 018import org.apache.tapestry5.commons.internal.util.TapestryException; 019import org.apache.tapestry5.http.Link; 020import org.apache.tapestry5.http.services.Request; 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.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}