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