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