001 // Copyright 2007, 2008, 2010, 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.BindingConstants; 018 import org.apache.tapestry5.ComponentAction; 019 import org.apache.tapestry5.ComponentResources; 020 import org.apache.tapestry5.PropertyOverrides; 021 import org.apache.tapestry5.annotations.Environmental; 022 import org.apache.tapestry5.annotations.Parameter; 023 import org.apache.tapestry5.annotations.Property; 024 import org.apache.tapestry5.annotations.SupportsInformalParameters; 025 import org.apache.tapestry5.beaneditor.BeanModel; 026 import org.apache.tapestry5.internal.BeanEditContextImpl; 027 import org.apache.tapestry5.internal.BeanValidationContext; 028 import org.apache.tapestry5.internal.BeanValidationContextImpl; 029 import org.apache.tapestry5.internal.beaneditor.BeanModelUtils; 030 import org.apache.tapestry5.ioc.annotations.Inject; 031 import org.apache.tapestry5.ioc.internal.util.TapestryException; 032 import org.apache.tapestry5.plastic.PlasticUtils; 033 import org.apache.tapestry5.services.BeanEditContext; 034 import org.apache.tapestry5.services.BeanModelSource; 035 import org.apache.tapestry5.services.Environment; 036 import org.apache.tapestry5.services.FormSupport; 037 038 import java.lang.annotation.Annotation; 039 040 /** 041 * A component that generates a user interface for editing the properties of a bean. This is the central component of 042 * the {@link BeanEditForm}, and utilizes a {@link PropertyEditor} for much of its functionality. This component places 043 * a {@link BeanEditContext} into the environment. 044 * 045 * @tapestrydoc 046 */ 047 @SupportsInformalParameters 048 public class BeanEditor 049 { 050 public static class Prepare implements ComponentAction<BeanEditor> 051 { 052 private static final long serialVersionUID = 6273600092955522585L; 053 054 public void execute(BeanEditor component) 055 { 056 component.doPrepare(); 057 } 058 059 @Override 060 public String toString() 061 { 062 return "BeanEditor.Prepare"; 063 } 064 } 065 066 static class CleanupEnvironment implements ComponentAction<BeanEditor> 067 { 068 private static final long serialVersionUID = 6867226962459227016L; 069 070 public void execute(BeanEditor component) 071 { 072 component.cleanupEnvironment(); 073 } 074 075 @Override 076 public String toString() 077 { 078 return "BeanEditor.CleanupEnvironment"; 079 } 080 } 081 082 private static final ComponentAction<BeanEditor> CLEANUP_ENVIRONMENT = new CleanupEnvironment(); 083 084 /** 085 * The object to be edited by the BeanEditor. This will be read when the component renders and updated when the form 086 * for the component is submitted. Typically, the container will listen for a "prepare" event, in order to ensure 087 * that a non-null value is ready to be read or updated. 088 */ 089 @Parameter(autoconnect = true) 090 private Object object; 091 092 /** 093 * A comma-separated list of property names to be retained from the 094 * {@link org.apache.tapestry5.beaneditor.BeanModel} (only used 095 * when a default model is created automatically). 096 * Only these properties will be retained, and the properties will also be reordered. The names are 097 * case-insensitive. 098 */ 099 @Parameter(defaultPrefix = BindingConstants.LITERAL) 100 private String include; 101 102 /** 103 * A comma-separated list of property names to be removed from the {@link org.apache.tapestry5.beaneditor.BeanModel} 104 * (only used 105 * when a default model is created automatically). 106 * The names are case-insensitive. 107 */ 108 @Parameter(defaultPrefix = BindingConstants.LITERAL) 109 private String exclude; 110 111 /** 112 * A comma-separated list of property names indicating the order in which the properties should be presented. The 113 * names are case insensitive. Any properties not indicated in the list will be appended to the end of the display 114 * orde. Only used 115 * when a default model is created automatically. 116 */ 117 @Parameter(defaultPrefix = BindingConstants.LITERAL) 118 private String reorder; 119 120 /** 121 * A comma-separated list of property names to be added to the {@link org.apache.tapestry5.beaneditor.BeanModel} 122 * (only used 123 * when a default model is created automatically). 124 */ 125 @Parameter(defaultPrefix = BindingConstants.LITERAL) 126 private String add; 127 128 /** 129 * The model that identifies the parameters to be edited, their order, and every other aspect. If not specified, a 130 * default bean model will be created from the type of the object bound to the object parameter. The add, include, 131 * exclude and reorder 132 * parameters are <em>only</em> applied to a default model, not an explicitly provided one. 133 */ 134 @Parameter 135 @Property(write = false) 136 private BeanModel model; 137 138 /** 139 * Where to search for local overrides of property editing blocks as block parameters. Further, the container of the 140 * overrides is used as the source for overridden validation messages. This is normally the BeanEditor component 141 * itself, but when the component is used within a BeanEditForm, it will be the BeanEditForm's resources that will 142 * be searched. 143 */ 144 @Parameter(value = "this", allowNull = false) 145 @Property(write = false) 146 private PropertyOverrides overrides; 147 148 @Inject 149 private BeanModelSource modelSource; 150 151 @Inject 152 private ComponentResources resources; 153 154 @Inject 155 private Environment environment; 156 157 @Environmental 158 private FormSupport formSupport; 159 160 // Value that change with each change to the current property: 161 162 @Property 163 private String propertyName; 164 165 /** 166 * To support nested BeanEditors, we need to cache the object value inside {@link #doPrepare()}. See TAPESTRY-2460. 167 */ 168 private Object cachedObject; 169 170 // Needed for testing as well 171 public Object getObject() 172 { 173 return cachedObject; 174 } 175 176 void setupRender() 177 { 178 formSupport.storeAndExecute(this, new Prepare()); 179 } 180 181 void cleanupRender() 182 { 183 formSupport.storeAndExecute(this, CLEANUP_ENVIRONMENT); 184 } 185 186 /** 187 * Used to initialize the model if necessary, to instantiate the object being edited if necessary, and to push the 188 * BeanEditContext into the environment. 189 */ 190 void doPrepare() 191 { 192 if (model == null) 193 { 194 Class type = resources.getBoundType("object"); 195 model = modelSource.createEditModel(type, overrides.getOverrideMessages()); 196 197 BeanModelUtils.modify(model, add, include, exclude, reorder); 198 } 199 200 // The only problem here is that if the bound property is backed by a persistent field, it 201 // is assigned (and stored to the session, and propagated around the cluster) first, 202 // before values are assigned. 203 204 if (object == null) 205 { 206 try 207 { 208 object = model.newInstance(); 209 } 210 catch (Exception ex) 211 { 212 String message = String.format("Exception instantiating instance of %s (for component '%s'): %s", 213 PlasticUtils.toTypeName(model.getBeanType()), resources.getCompleteId(), ex); 214 throw new TapestryException(message, resources.getLocation(), ex); 215 } 216 } 217 218 BeanEditContext context = new BeanEditContextImpl(model.getBeanType()); 219 220 cachedObject = object; 221 222 environment.push(BeanEditContext.class, context); 223 // TAP5-2101: Always provide a new BeanValidationContext 224 environment.push(BeanValidationContext.class, new BeanValidationContextImpl(object)); 225 } 226 227 void cleanupEnvironment() 228 { 229 environment.pop(BeanEditContext.class); 230 environment.pop(BeanValidationContext.class); 231 } 232 233 // For testing 234 void inject(ComponentResources resources, PropertyOverrides overrides, BeanModelSource source, 235 Environment environment) 236 { 237 this.resources = resources; 238 this.overrides = overrides; 239 this.environment = environment; 240 modelSource = source; 241 } 242 }