package org.tynamo.components; import java.lang.annotation.Annotation; import java.util.Locale; import java.util.Map.Entry; import org.apache.tapestry5.Block; import org.apache.tapestry5.ComponentAction; import org.apache.tapestry5.ComponentResources; import org.apache.tapestry5.Field; import org.apache.tapestry5.FieldTranslator; import org.apache.tapestry5.FieldValidator; 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.internal.BeanValidationContext; import org.apache.tapestry5.ioc.Messages; import org.apache.tapestry5.ioc.annotations.Inject; import org.apache.tapestry5.ioc.annotations.Primary; import org.apache.tapestry5.ioc.internal.util.TapestryException; import org.apache.tapestry5.ioc.services.ClassPropertyAdapter; import org.apache.tapestry5.ioc.services.PropertyAccess; import org.apache.tapestry5.ioc.services.PropertyAdapter; import org.apache.tapestry5.services.BeanModelSource; import org.apache.tapestry5.services.DataTypeAnalyzer; import org.apache.tapestry5.services.Environment; import org.apache.tapestry5.services.FieldTranslatorSource; import org.apache.tapestry5.services.FieldValidatorDefaultSource; import org.apache.tapestry5.services.FormSupport; import org.tynamo.descriptor.TynamoPropertyDescriptor; import org.tynamo.search.SearchFilterOperator; import org.tynamo.search.SearchFilterPredicate; import org.tynamo.services.PropertySearchFilterContext; import org.tynamo.services.SearchFilterBlockSource; public class PropertySearchFilter { /** * Configures and stores a {@link PropertySearchFilterContext} into the {@link Environment}. */ static class SetupEnvironment implements ComponentAction<PropertySearchFilter> { private static final long serialVersionUID = 1L; private final String property; public SetupEnvironment(String property) { this.property = property; } public void execute(PropertySearchFilter component) { component.setupEnvironment(property); } @Override public String toString() { return String.format("PropertySearchFilter.SetupEnvironment[%s]", property); } } static class CleanupEnvironment implements ComponentAction<PropertySearchFilter> { private static final long serialVersionUID = 1L; public void execute(PropertySearchFilter component) { component.cleanupEnvironment(); } @Override public String toString() { return "PropertySearchFilter.CleanupEnvironment"; } } @Parameter(required = true, allowNull = false) private Entry<TynamoPropertyDescriptor, SearchFilterPredicate> entry; @Inject private SearchFilterBlockSource searchFilterBlockSource; @Inject private Messages messages; @Inject private Locale locale; @Inject private ComponentResources resources; @Inject private FieldTranslatorSource fieldTranslatorSource; @Inject private FieldValidatorDefaultSource fieldValidatorDefaultSource; @Inject private Environment environment; @Inject private PropertyAccess propertyAccess; @Inject @Primary DataTypeAnalyzer dataTypeAnalyzer; @Inject private BeanModelSource beanModelSource; @Environmental private FormSupport formSupport; private static final ComponentAction CLEANUP_ENVIRONMENT = new CleanupEnvironment(); private BeanModel model; private PropertyModel propertyModel; private String resolveDataType(TynamoPropertyDescriptor descriptor) { // we are going backwards - we have the propertydescriptor but DatatypeAnalyzer interface uses property adapters ClassPropertyAdapter classAdapter = propertyAccess.getAdapter(descriptor.getBeanType()); PropertyAdapter adapter = classAdapter.getPropertyAdapter(descriptor.getName()); return dataTypeAnalyzer.identifyDataType(adapter); } /** * Returns a Block for rendering the property. The Block will be able to access the {@link PropertySearchFilterContext} via the * {@link Environmental} annotation. */ Block beginRender() { // TODO See PropertyEditor.beginRender() if we need instance specific overrides. We'd have to change from using // just property descriptors to using bean model. Not sure if it makes sense for search String dataType = resolveDataType(entry.getKey()); if (dataType == null) throw new RuntimeException(String.format("The data type for property '%s' of type %s is null.", entry.getKey() .getName(), entry.getKey().getBeanType())); try { return searchFilterBlockSource.toBlock(dataType); } catch (RuntimeException ex) { // TODO check if it's ok to pass null object String message = messages.format("core-block-error", entry.getKey().getName(), dataType, null, ex); throw new TapestryException(message, resources.getLocation(), ex); } } /** * Returns false, to prevent the rendering of the body of the component. PropertySeachFilter should not have a body. */ boolean beforeRenderBody() { return false; } /** * 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(entry.getKey().getName())); } /** * 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, CLEANUP_ENVIRONMENT); } /** * Creates a {@link org.apache.tapestry5.services.PropertySearchFilterContext} and pushes it onto the * {@link org.apache.tapestry5.services.Environment} stack. */ void setupEnvironment(final String propertyName) { model = beanModelSource.createEditModel(entry.getKey().getBeanType(), resources.getContainerMessages()); propertyModel = model.get(propertyName); PropertySearchFilterContext context = new PropertySearchFilterContext() { public Messages getContainerMessages() { return messages; } public String getLabel() { return propertyModel.getLabel(); } public boolean isEnabled() { return entry.getValue().isEnabled(); } public void setEnabled(boolean value) { entry.getValue().setEnabled(value); } public String getPropertyId() { return propertyModel.getId(); } public Class getPropertyType() { return propertyModel.getPropertyType(); } public Object getLowValue() { // return propertyModel.getConduit().get(object); return entry.getValue().getLowValue(); } public Object getHighValue() { return entry.getValue().getHighValue(); } public FieldTranslator getTranslator(Field field) { // return fieldTranslatorSource.createDefaultTranslator(field, propertyName, overrides.getOverrideMessages(), // locale, propertyModel.getPropertyType(), propertyModel.getConduit()); return fieldTranslatorSource.createDefaultTranslator(field, propertyName, messages, locale, propertyModel.getPropertyType(), propertyModel.getConduit()); } public FieldValidator getValidator(Field field) { // return fieldValidatorDefaultSource.createDefaultValidator(field, propertyName, overrides.getOverrideMessages(), // locale, propertyModel.getPropertyType(), propertyModel.getConduit()); return fieldValidatorDefaultSource.createDefaultValidator(field, propertyName, messages, locale, propertyModel.getPropertyType(), propertyModel.getConduit()); } public void setLowValue(Object value) { entry.getValue().setLowValue(value); } public void setHighValue(Object value) { entry.getValue().setHighValue(value); } public String getOperatorId() { return propertyModel.getId() + "_searchoperator"; } public SearchFilterOperator getOperatorValue() { return entry.getValue().getOperator(); } public void setOperatorValue(SearchFilterOperator value) { entry.getValue().setOperator(value); } public <T extends Annotation> T getAnnotation(Class<T> annotationClass) { return propertyModel.getAnnotation(annotationClass); } }; environment.push(PropertySearchFilterContext.class, context); BeanValidationContext beanValidationContext = environment.peek(BeanValidationContext.class); if (beanValidationContext != null) { beanValidationContext.setCurrentProperty(propertyName); } } /** * 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(PropertySearchFilterContext.class); } }