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