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    }