blob: 6253cb7770990525e407d3dbac233b22fa36659f [file] [log] [blame]
// Copyright 2007, 2008 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.tapestry5.corelib.components;
import org.apache.tapestry5.*;
import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.beaneditor.BeanModel;
import org.apache.tapestry5.beaneditor.PropertyModel;
import org.apache.tapestry5.ioc.Messages;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.internal.util.TapestryException;
import org.apache.tapestry5.services.*;
import java.lang.annotation.Annotation;
import java.util.Locale;
/**
* Used to edit a single property of a bean. This is used primarily by {@link BeanEditForm}. Unlike BeanEditForm, the
* object to be edited must already exist and the {@linkplain BeanModel model} must be passed in explicitly.
*/
public class PropertyEditor
{
/**
* Configures and stores a {@link PropertyEditContext} into the {@link Environment}.
*/
static class SetupEnvironment implements ComponentAction<PropertyEditor>
{
private static final long serialVersionUID = 5337049721509981997L;
private final String property;
public SetupEnvironment(String property)
{
this.property = property;
}
public void execute(PropertyEditor component)
{
component.setupEnvironment(property);
}
}
static class CleanupEnvironment implements ComponentAction<PropertyEditor>
{
private static final long serialVersionUID = 7878694042753046523L;
public void execute(PropertyEditor component)
{
component.cleanupEnvironment();
}
}
/**
* The object to be edited by the BeanEditor. This will be read when the component renders and updated when the form
* for the component is submitted. Typically, the container will listen for a "prepare" event, in order to ensure
* that a non-null value is ready to be read or updated.
*/
@Parameter(required = true)
private Object object;
/**
* Where to search for local overrides of property editing blocks as block parameters. This is normally the
* containing component of the PropertyEditor, but when the component is used within a BeanEditor, it will be the
* BeanEditForm's block parameters that will be searched.
*/
@Parameter(value = "componentResources")
private ComponentResources overrides;
/**
* Identifies the property to be edited by the editor.
*/
@Parameter(required = true)
private String property;
/**
* The model that identifies the parameters to be edited, their order, and every other aspect.
*/
@Parameter(required = true)
private BeanModel model;
@Inject
private FieldValidatorDefaultSource fieldValidatorDefaultSource;
@Inject
private Environment environment;
@Inject
private BeanBlockSource beanBlockSource;
@Inject
private Messages messages;
@Inject
private Locale locale;
@Inject
private ComponentResources resources;
@Environmental
private FormSupport formSupport;
private PropertyModel propertyModel;
@Inject
private TranslatorSource translatorSource;
/**
* Creates a {@link org.apache.tapestry5.services.PropertyEditContext} and pushes it onto the {@link
* org.apache.tapestry5.services.Environment} stack.
*/
void setupEnvironment(final String propertyName)
{
propertyModel = model.get(propertyName);
PropertyEditContext context = new PropertyEditContext()
{
public Messages getContainerMessages()
{
return overrides.getContainerMessages();
}
public String getLabel()
{
return propertyModel.getLabel();
}
public String getPropertyId()
{
return propertyModel.getId();
}
public Class getPropertyType()
{
return propertyModel.getPropertyType();
}
public Object getPropertyValue()
{
return propertyModel.getConduit().get(object);
}
public Translator getTranslator()
{
return translatorSource.getByType(propertyModel.getPropertyType());
}
public FieldValidator getValidator(Field field)
{
return fieldValidatorDefaultSource.createDefaultValidator(field, propertyName,
overrides.getContainerMessages(), locale,
propertyModel.getPropertyType(),
propertyModel.getConduit());
}
public void setPropertyValue(Object value)
{
propertyModel.getConduit().set(object, value);
}
public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
{
return propertyModel.getAnnotation(annotationClass);
}
};
environment.push(PropertyEditContext.class, context);
}
/**
* Called at the end of the form render (or at the end of the form submission) to clean up the {@link Environment}
* stack.
*/
void cleanupEnvironment()
{
environment.pop(PropertyEditContext.class);
}
/**
* Record into the Form what's needed to process the property.
*/
void setupRender()
{
// Sets up the PropertyEditContext for the duration of the render of this component
// (which will include the duration of the editor block).
formSupport.storeAndExecute(this, new SetupEnvironment(property));
}
/**
* Records into the Form the cleanup logic for the property.
*/
void cleanupRender()
{
// Removes the PropertyEditContext after this component (including the editor block)
// has rendered.
formSupport.storeAndExecute(this, new CleanupEnvironment());
}
/**
* Returns a Block for rendering the property. The Block will be able to access the {@link PropertyEditContext} via
* the {@link Environmental} annotation.
*/
Block beginRender()
{
Block override = overrides.getBlockParameter(propertyModel.getId());
if (override != null)
{
return override;
}
String dataType = propertyModel.getDataType();
try
{
return beanBlockSource.getEditBlock(dataType);
}
catch (RuntimeException ex)
{
String message = messages.format("block-error", propertyModel.getPropertyName(), dataType, object, ex);
throw new TapestryException(message, resources.getLocation(), ex);
}
}
/**
* Returns false, to prevent the rendering of the body of the component. PropertyEditor should not have a body.
*/
boolean beforeRenderBody()
{
return false;
}
/**
* Used for testing.
*/
void inject(ComponentResources resources, ComponentResources overrides, PropertyModel propertyModel,
BeanBlockSource beanBlockSource, Messages messages, Object object)
{
this.resources = resources;
this.overrides = overrides;
this.propertyModel = propertyModel;
this.beanBlockSource = beanBlockSource;
this.messages = messages;
this.object = object;
}
}