package com.googlecode.tawus.components; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.apache.tapestry5.BindingConstants; import org.apache.tapestry5.Block; import org.apache.tapestry5.ComponentAction; import org.apache.tapestry5.ComponentResources; import org.apache.tapestry5.PropertyOverrides; import org.apache.tapestry5.annotations.Component; import org.apache.tapestry5.annotations.Environmental; import org.apache.tapestry5.annotations.Parameter; import org.apache.tapestry5.annotations.Property; import org.apache.tapestry5.annotations.SupportsInformalParameters; import org.apache.tapestry5.beaneditor.BeanModel; import org.apache.tapestry5.beaneditor.PropertyModel; import org.apache.tapestry5.corelib.components.PropertyDisplay; import org.apache.tapestry5.corelib.internal.InternalMessages; import org.apache.tapestry5.internal.BeanValidationContext; import org.apache.tapestry5.internal.BeanValidationContextImpl; import org.apache.tapestry5.internal.beaneditor.BeanModelUtils; 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.BeanEditContext; import org.apache.tapestry5.services.BeanModelSource; import org.apache.tapestry5.services.Environment; import org.apache.tapestry5.services.FormSupport; import com.googlecode.tawus.annotations.NonUpdatable; import com.googlecode.tawus.internal.table.TableColumn; import com.googlecode.tawus.internal.table.TableColumnEncoder; import com.googlecode.tawus.services.EntityDAOLocator; /** * An alternative bean editor which uses table instead of divs The reorder * parameter has been modified to accomidate the same. A reorder parameter can * take a list of row configurations. separated by comma(,) * <p/> * Each row configuration consists of a list of column configurations separated * by semicolon(',') * <p/> * Each column configuration consisting of four fields separated by * <strong>'/'</strong>. The order is * fieldName/columnSeparator/rowSeparator/rowClass */ @SupportsInformalParameters public class EntityEditor { /** * Component action to prepare the model and object */ static class Prepare implements ComponentAction<EntityEditor> { private static final long serialVersionUID = 1L; /** * {@inheritDocs} */ public void execute(EntityEditor component) { component.doPrepare(); } /** * {@inheritDocs} */ @Override public String toString() { return "BeanEditor.doPrepare()"; } } /** * Component action to cleanup after rendering */ static class CleanupEnvironment implements ComponentAction<EntityEditor> { private static final long serialVersionUID = 1L; /** * {@inheritDocs} */ public void execute(EntityEditor component) { component.cleanupEnvironment(); } /** * {@inheritDocs} */ @Override public String toString() { return "BeanEditor.cleanupEnviroment()"; } } /** Cleanup component environment instance */ private final CleanupEnvironment CLEANUP_ENVIRONMENT = new CleanupEnvironment(); static class ReadOnlyOverrides implements PropertyOverrides { private PropertyOverrides delegate; public ReadOnlyOverrides(PropertyOverrides delegate) { this.delegate = delegate; } public Messages getOverrideMessages() { return delegate.getOverrideMessages(); } public Block getOverrideBlock(String name) { return delegate.getOverrideBlock(name + "ReadOnly"); } } /** Object to be edited */ @Parameter(autoconnect = true) private Object object; @SuppressWarnings("unused") @Parameter(value = "true", defaultPrefix = BindingConstants.LITERAL) @Property private boolean showHelp; @Parameter(value = "true", allowNull = false) @Property private boolean updatable; @Inject private Environment environment; @Inject @Property(write = false) private ComponentResources resources; /** Bean model, if not provided one is generated at setup */ @SuppressWarnings("rawtypes") @Parameter @Property private BeanModel model; /** Properties to be added to the editor */ @Parameter(defaultPrefix = BindingConstants.LITERAL) private String add; /** Properties to be included in the editor for removal */ @Parameter(defaultPrefix = BindingConstants.LITERAL) private String include; /** Colspan multiple to be used **/ @Parameter(value = "2", allowNull = false) private int colspanMultiple; /** Property field to be used */ @SuppressWarnings("unused") @Component(parameters = { "object=object", "overrides=overrides", "model=model", "showHelp=prop:showHelp", "cssClassPrefix=currentColumn.property", "columnspan=prop:fieldColspan", "rowspan=prop:currentColumn.rowspan", "property=prop:currentColumn.property" }) private EntityPropertyEditor propertyField; @SuppressWarnings("unused") @Component(id = "propertyDisplay", parameters = { "overrides=readOnlyOverrides", "object=object", "model=propertyModel" }) private PropertyDisplay propertyDisplay; /** Property Overrides to be used, by default use container's resources */ @Parameter(value = "this", allowNull = false) @Property(write = false) private PropertyOverrides overrides; /** Current row */ @Property private String currentRow; /** Current column */ @Property private TableColumn currentColumn; /** Column encoder */ @SuppressWarnings("unused") @Property private TableColumnEncoder columnEncoder; private Object cachedObject; /* Form support for executing ComponentActions */ @Environmental private FormSupport formSupport; /* Bean model source to generate model */ @Inject @Property private BeanModelSource modelSource; @Inject private EntityDAOLocator locator; @Inject private Block _read; @Inject private Block _write; public Block getComponent() { return getReadOnly() ? _read : _write; } /** * Get object being edited * * @return object being edited */ public Object getObject() { return cachedObject; } public boolean getReadOnly() { return !updatable || (locator.get(object.getClass()).getIdentifier(getObject()) != null && model.get(currentColumn.getProperty()).getAnnotation(NonUpdatable.class) != null); } public PropertyOverrides getReadOnlyOverrides() { return new ReadOnlyOverrides(overrides); } /** * Get rows */ @SuppressWarnings("unchecked") public List<String> getRows() { if(include == null) { return model.getPropertyNames(); } else { return Arrays.asList(include.trim().split(TableColumn.ROW_SEPARATOR)); } } /** * Get columns of a particular row */ public List<TableColumn> getColumns() { final List<TableColumn> columns = new ArrayList<TableColumn>(); for(String col : currentRow.split(TableColumn.COLUMN_SEPARATOR)) { String[] colParts = col.split(TableColumn.FIELD_SEPARATOR); columns.add(new TableColumn(colParts, colspanMultiple)); } return columns; } public PropertyModel getPropertyModel() { return model.get(currentColumn.getProperty()); } public int getFieldColspan() { return currentColumn.getColspan() - 1; } @SuppressWarnings("unchecked") public void doPrepare() { if(model == null) { @SuppressWarnings("rawtypes") Class type = object != null ? object.getClass() : resources.getBoundType("object"); model = modelSource.createEditModel(type, overrides.getOverrideMessages()); BeanModelUtils.modify(model, add, null, null, null); } if(object == null) { try { object = model.newInstance(); } catch(Exception ex) { String message = InternalMessages.failureInstantiatingObject(model.getBeanType(), resources.getCompleteId(), ex); throw new TapestryException(message, resources.getLocation(), ex); } refreshBeanValidationContext(); } /** Push bean context */ BeanEditContext context = new BeanEditContext() { public Class<?> getBeanClass() { return model.getBeanType(); } public <T extends Annotation> T getAnnotation(Class<T> type) { return getBeanClass().getAnnotation(type); } }; cachedObject = object; environment.push(BeanEditContext.class, context); } private void refreshBeanValidationContext() { if(environment.peek(BeanValidationContext.class) != null) { environment.pop(BeanValidationContext.class); environment.push(BeanValidationContext.class, new BeanValidationContextImpl(object)); } } void setupRender() { formSupport.storeAndExecute(this, new Prepare()); } void cleanupRender() { formSupport.storeAndExecute(this, CLEANUP_ENVIRONMENT); } public void cleanupEnvironment() { environment.pop(BeanEditContext.class); } public String getHelpText() { final Messages messages = overrides.getOverrideMessages(); final String message; if(messages.contains(currentColumn.getProperty() + "-help")) { message = messages.get(currentColumn.getProperty() + "-help"); } else { message = ""; } return message; } void inject(ComponentResources resources, PropertyOverrides overrides, BeanModelSource source, Environment env) { this.resources = resources; this.overrides = overrides; this.modelSource = source; this.environment = env; } }