    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    }