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