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 }