001 // Copyright 2007, 2008, 2009, 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.corelib.components;
016
017 import org.apache.tapestry5.*;
018 import org.apache.tapestry5.annotations.Environmental;
019 import org.apache.tapestry5.annotations.Parameter;
020 import org.apache.tapestry5.beaneditor.BeanModel;
021 import org.apache.tapestry5.beaneditor.PropertyModel;
022 import org.apache.tapestry5.internal.BeanValidationContext;
023 import org.apache.tapestry5.ioc.Messages;
024 import org.apache.tapestry5.ioc.annotations.Inject;
025 import org.apache.tapestry5.ioc.internal.util.TapestryException;
026 import org.apache.tapestry5.services.*;
027
028 import java.lang.annotation.Annotation;
029 import java.util.Locale;
030
031 /**
032 * Used to edit a single property of a bean. This is used primarily by {@link BeanEditForm}. Unlike BeanEditForm, the
033 * object to be edited must already exist and the {@linkplain BeanModel model} must be passed in explicitly.
034 *
035 * @tapestrydoc
036 */
037 public class PropertyEditor
038 {
039 /**
040 * Configures and stores a {@link PropertyEditContext} into the {@link Environment}.
041 */
042 static class SetupEnvironment implements ComponentAction<PropertyEditor>
043 {
044 private static final long serialVersionUID = 5337049721509981997L;
045
046 private final String property;
047
048 public SetupEnvironment(String property)
049 {
050 this.property = property;
051 }
052
053 public void execute(PropertyEditor component)
054 {
055 component.setupEnvironment(property);
056 }
057
058 @Override
059 public String toString()
060 {
061 return String.format("PropertyEditor.SetupEnvironment[%s]", property);
062 }
063 }
064
065 static class CleanupEnvironment implements ComponentAction<PropertyEditor>
066 {
067 private static final long serialVersionUID = 7878694042753046523L;
068
069 public void execute(PropertyEditor component)
070 {
071 component.cleanupEnvironment();
072 }
073
074 @Override
075 public String toString()
076 {
077 return "PropertyEditor.CleanupEnvironment";
078 }
079 }
080
081 private static final ComponentAction CLEANUP_ENVIRONMENT = new CleanupEnvironment();
082
083 /**
084 * The object to be edited by the BeanEditor. This will be read when the component renders and updated when the form
085 * for the component is submitted. Typically, the container will listen for a "prepare" event, in order to ensure
086 * that a non-null value is ready to be read or updated.
087 */
088 @Parameter(required = true, allowNull = false)
089 private Object object;
090
091 /**
092 * Where to search for local overrides of property editing blocks as block parameters. This is normally the
093 * containing component of the PropertyEditor, but when the component is used within a BeanEditor, it will be the
094 * BeanEditor's block parameters that will be searched.
095 */
096 @Parameter(value = "this", allowNull = false)
097 private PropertyOverrides overrides;
098
099 /**
100 * Identifies the property to be edited by the editor.
101 */
102 @Parameter(required = true)
103 private String property;
104
105 /**
106 * The model that identifies the parameters to be edited, their order, and every other aspect.
107 */
108 @Parameter(required = true, allowNull = false)
109 private BeanModel model;
110
111 @Inject
112 private FieldValidatorDefaultSource fieldValidatorDefaultSource;
113
114 @Inject
115 private Environment environment;
116
117 /**
118 * Source for property editor blocks. This defaults to the default implementation of {@link
119 * org.apache.tapestry5.services.BeanBlockSource}.
120 */
121 @Parameter(required = true, allowNull = false)
122 private BeanBlockSource beanBlockSource;
123
124 @Inject
125 @Core
126 private BeanBlockSource defaultBeanBlockSource;
127
128 @Inject
129 private Messages messages;
130
131 @Inject
132 private Locale locale;
133
134 @Inject
135 private ComponentResources resources;
136
137 @Inject
138 private FieldTranslatorSource fieldTranslatorSource;
139
140 @Environmental
141 private FormSupport formSupport;
142
143 private PropertyModel propertyModel;
144
145 BeanBlockSource defaultBeanBlockSource()
146 {
147 return defaultBeanBlockSource;
148 }
149
150 /**
151 * Creates a {@link org.apache.tapestry5.services.PropertyEditContext} and pushes it onto the {@link
152 * org.apache.tapestry5.services.Environment} stack.
153 */
154 void setupEnvironment(final String propertyName)
155 {
156 propertyModel = model.get(propertyName);
157
158 PropertyEditContext context = new PropertyEditContext()
159 {
160 public Messages getContainerMessages()
161 {
162 return overrides.getOverrideMessages();
163 }
164
165 public String getLabel()
166 {
167 return propertyModel.getLabel();
168 }
169
170 public String getPropertyId()
171 {
172 return propertyModel.getId();
173 }
174
175 public Class getPropertyType()
176 {
177 return propertyModel.getPropertyType();
178 }
179
180 public Object getPropertyValue()
181 {
182 return propertyModel.getConduit().get(object);
183 }
184
185 public FieldTranslator getTranslator(Field field)
186 {
187 return fieldTranslatorSource.createDefaultTranslator(field, propertyName,
188 overrides.getOverrideMessages(), locale,
189 propertyModel.getPropertyType(),
190 propertyModel.getConduit());
191 }
192
193 public FieldValidator getValidator(Field field)
194 {
195 return fieldValidatorDefaultSource.createDefaultValidator(field, propertyName,
196 overrides.getOverrideMessages(), locale,
197 propertyModel.getPropertyType(),
198 propertyModel.getConduit());
199 }
200
201 public void setPropertyValue(Object value)
202 {
203 propertyModel.getConduit().set(object, value);
204 }
205
206 public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
207 {
208 return propertyModel.getAnnotation(annotationClass);
209 }
210 };
211
212 environment.push(PropertyEditContext.class, context);
213
214 BeanValidationContext beanValidationContext = environment.peek(BeanValidationContext.class);
215
216 if(beanValidationContext != null)
217 {
218 beanValidationContext.setCurrentProperty(propertyName);
219 }
220 }
221
222 /**
223 * Called at the end of the form render (or at the end of the form submission) to clean up the {@link Environment}
224 * stack.
225 */
226 void cleanupEnvironment()
227 {
228 environment.pop(PropertyEditContext.class);
229 }
230
231 /**
232 * Record into the Form what's needed to process the property.
233 */
234 void setupRender()
235 {
236 // Sets up the PropertyEditContext for the duration of the render of this component
237 // (which will include the duration of the editor block).
238
239 formSupport.storeAndExecute(this, new SetupEnvironment(property));
240 }
241
242 /**
243 * Records into the Form the cleanup logic for the property.
244 */
245 void cleanupRender()
246 {
247 // Removes the PropertyEditContext after this component (including the editor block)
248 // has rendered.
249
250 formSupport.storeAndExecute(this, CLEANUP_ENVIRONMENT);
251 }
252
253 /**
254 * Returns a Block for rendering the property. The Block will be able to access the {@link PropertyEditContext} via
255 * the {@link Environmental} annotation.
256 */
257 Block beginRender()
258 {
259
260 Block override = overrides.getOverrideBlock(propertyModel.getId());
261
262 if (override != null)
263 {
264 return override;
265 }
266
267 String dataType = propertyModel.getDataType();
268
269 if (dataType == null)
270 throw new RuntimeException(
271 String.format("The data type for property '%s' of %s is null.", propertyModel.getPropertyName(),
272 object));
273
274 try
275 {
276
277 return beanBlockSource.getEditBlock(dataType);
278 }
279 catch (RuntimeException ex)
280 {
281 String message = messages.format("block-error", propertyModel.getPropertyName(), dataType, object, ex);
282
283 throw new TapestryException(message, resources.getLocation(), ex);
284 }
285
286 }
287
288 /**
289 * Returns false, to prevent the rendering of the body of the component. PropertyEditor should not have a body.
290 */
291 boolean beforeRenderBody()
292 {
293 return false;
294 }
295
296 /**
297 * Used for testing.
298 */
299 void inject(ComponentResources resources, PropertyOverrides overrides, PropertyModel propertyModel,
300 BeanBlockSource beanBlockSource, Messages messages, Object object)
301 {
302 this.resources = resources;
303 this.overrides = overrides;
304 this.propertyModel = propertyModel;
305 this.beanBlockSource = beanBlockSource;
306 this.messages = messages;
307 this.object = object;
308 }
309 }