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