package org.jboss.seam.framework; import static org.jboss.seam.international.StatusMessage.Severity.INFO; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.Create; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.Transactional; import org.jboss.seam.core.Expressions; import org.jboss.seam.core.Expressions.ValueExpression; /** * Base class for components which provide persistence * operations to a managed entity instance. This class * may be reused by either configuration or extension, * and may be bound directly to a view, or accessed by * some intermediate Seam component. * * @author Gavin King * */ @Scope(ScopeType.CONVERSATION) public abstract class Home<T, E> extends MutableController<T> { private static final long serialVersionUID = -5462396456614090423L; private Object id; protected E instance; private Class<E> entityClass; protected ValueExpression<T> newInstance; private ValueExpression deletedMessage; private ValueExpression createdMessage; private ValueExpression updatedMessage; /** * Add a {@link javax.faces.application.FacesMessage} and log a message when * the entity instance is updated. * * Utility method to add a {@link javax.faces.application.FacesMessage} from * the Seam managed resource bundle or, if not specified in the resource * bundle, from {@link #getUpdatedMessage()} and log the entity when the * managed entity is updated. * * @see #getUpdatedMessage() * @see #getUpdatedMessageKey() */ protected void updatedMessage() { debug("updated entity #0 #1", getEntityClass().getName(), getId()); getStatusMessages().addFromResourceBundleOrDefault( INFO, getUpdatedMessageKey(), getUpdatedMessage().getExpressionString() ); } /** * Add a {@link javax.faces.application.FacesMessage} and log a message when * the entity instance is deleted. * * Utility method to add a {@link javax.faces.application.FacesMessage} from * the Seam managed resource bundle or, if not specified in the resource * bundle, from {@link #getDeletedMessage()} and log the entity when the * managed entity is deleted. * * @see #getDeletedMessage() * @see #getDeletedMessageKey() */ protected void deletedMessage() { debug("deleted entity #0 #1", getEntityClass().getName(), getId()); getStatusMessages().addFromResourceBundleOrDefault( INFO, getDeletedMessageKey(), getDeletedMessage().getExpressionString() ); } /** * Add a {@link javax.faces.application.FacesMessage} and log a message when * the entity instance is created. * * Utility method to add a {@link javax.faces.application.FacesMessage} from * the Seam managed resource bundle or, if not specified in the resource * bundle, from {@link #getUpdatedMessage()} and log the entity when the * managed entity is updated. * * @see #getCreatedMessage() * @see #getCreatedMessageKey() */ protected void createdMessage() { debug("created entity #0 #1", getEntityClass().getName(), getId()); getStatusMessages().addFromResourceBundleOrDefault( INFO, getCreatedMessageKey(), getCreatedMessage().getExpressionString() ); } /** * Run on {@link Home} instantiation to check the Home component is in a * valid state. * <br /> * Validates that the class of the entity to be managed has been specified. */ @Create public void create() { if ( getEntityClass()==null ) { throw new IllegalStateException("entityClass is null"); } initDefaultMessages(); } protected void initDefaultMessages() { Expressions expressions = new Expressions(); if (createdMessage == null) { createdMessage = expressions.createValueExpression("Successfully created"); } if (updatedMessage == null) { updatedMessage = expressions.createValueExpression("Successfully updated"); } if (deletedMessage == null) { deletedMessage = expressions.createValueExpression("Successfully deleted"); } } /** * Get the managed entity, using the id from {@link #getId()} to load it from * the Persistence Context or creating a new instance if the id is not * defined. * * @see #getId() */ @Transactional public E getInstance() { joinTransaction(); if (instance==null) { initInstance(); } return instance; } /** * Clear the managed entity (and id), allowing the {@link EntityHome} to be * reused. */ public void clearInstance() { setInstance(null); setId(null); } /** * Load the instance if the id is defined otherwise create a new instance * <br /> * Utility method called by {@link #getInstance()} to load the instance from * the Persistence Context if the id is defined. Otherwise a new instance is * created. * * @see #find() * @see #createInstance() */ protected void initInstance() { if ( isIdDefined() ) { if ( !isTransactionMarkedRollback() ) { //we cache the instance so that it does not "disappear" //after remove() is called on the instance //is this really a Good Idea?? setInstance( find() ); } } else { setInstance( createInstance() ); } } /** * Hook method called to allow the implementation to join the current * transaction when necessary. */ protected void joinTransaction() {} /** * Hook method called by {@link #initInstance()} to allow the implementation * to load the entity from the Persistence Context. */ protected E find() { return null; } /** * Utility method called by the framework when no entity is found in the * Persistence Context. */ protected E handleNotFound() { throw new EntityNotFoundException( getId(), getEntityClass() ); } /** * Create a new instance of the entity. * <br /> * Utility method called by {@link #initInstance()} to create a new instance * of the entity. */ protected E createInstance() { if (newInstance!=null) { return (E) newInstance.getValue(); } else if (getEntityClass()!=null) { try { return getEntityClass().newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } else { return null; } } /** * Get the class of the entity being managed. * <br /> * If not explicitly specified, the generic type of implementation is used. */ public Class<E> getEntityClass() { if (entityClass == null) { Type type = getClass().getGenericSuperclass(); if (type instanceof ParameterizedType) { ParameterizedType paramType = (ParameterizedType) type; if (paramType.getActualTypeArguments().length == 2) { // likely dealing with -> new EntityHome<Person>().getEntityClass() if (paramType.getActualTypeArguments()[1] instanceof TypeVariable) { throw new IllegalArgumentException("Could not guess entity class by reflection"); } // likely dealing with -> new Home<EntityManager, Person>() { ... }.getEntityClass() else { entityClass = (Class<E>) paramType.getActualTypeArguments()[1]; } } else { // likely dealing with -> new PersonHome().getEntityClass() where PersonHome extends EntityHome<Person> entityClass = (Class<E>) paramType.getActualTypeArguments()[0]; } } else { throw new IllegalArgumentException("Could not guess entity class by reflection"); } } return entityClass; } /** * Set the class of the entity being managed. * <br /> * Useful for configuring {@link Home} components from * <code>components.xml</code>. */ public void setEntityClass(Class<E> entityClass) { this.entityClass = entityClass; } /** * Get the id of the object being managed. */ public Object getId() { return id; } /** * Set/change the entity being managed by id. * * @see #assignId(Object) */ public void setId(Object id) { if ( setDirty(this.id, id) ) setInstance(null); this.id = id; } /** * Set the id of entity being managed. * <br /> * Does not alter the instance so used if the id of the managed object is * changed. * * @see #setId(Object) */ protected void assignId(Object id) { setDirty(this.id, id); this.id = id; } /** * Returns true if the id of the object managed is known. */ public boolean isIdDefined() { return getId()!=null && !"".equals( getId() ); } /** * Set/change the entity being managed. */ public void setInstance(E instance) { setDirty(this.instance, instance); this.instance = instance; } /** * {@link javax.el.ValueExpression} to execute to load a new instance. * <br /> * Mainly used when configuring the {@link Home} components in * <code>components.xml</code>. */ public ValueExpression getNewInstance() { return newInstance; } /** * {@link javax.el.ValueExpression} to execute to load a new instance. * <br /> * Mainly used when configuring the {@link Home} components in * <code>components.xml</code>. */ public void setNewInstance(ValueExpression newInstance) { this.newInstance = newInstance; } /** * Message displayed to user when the managed entity is created. */ public ValueExpression getCreatedMessage() { return createdMessage; } /** * Message displayed to user when the managed entity is created. */ public void setCreatedMessage(ValueExpression createdMessage) { this.createdMessage = createdMessage; } /** * Message displayed to user when the managed entity is deleted. */ public ValueExpression getDeletedMessage() { return deletedMessage; } /** * Message displayed to user when the managed entity is deleted. */ public void setDeletedMessage(ValueExpression deletedMessage) { this.deletedMessage = deletedMessage; } /** * Message displayed to user when the managed entity is updated. */ public ValueExpression getUpdatedMessage() { return updatedMessage; } /** * Message displayed to user when the managed entity is updated. */ public void setUpdatedMessage(ValueExpression updatedMessage) { this.updatedMessage = updatedMessage; } /** * The prefix of the key to look up messages in the Seam managed resource * bundle. * <br /> * By default the simple name of the class suffixed with an underscore. */ protected String getMessageKeyPrefix() { String className = getEntityClass().getName(); return className.substring( className.lastIndexOf('.') + 1 ) + '_'; } /** * The key to look up in the Seam managed resource bundle the message * displayed when the managed entity is created. * <br /> * By default the {@link #getMessageKeyPrefix()} suffixed with created. */ protected String getCreatedMessageKey() { return getMessageKeyPrefix() + "created"; } /** * The key to look up in the Seam managed resource bundle the message * displayed when the managed entity is updated. * <br /> * By default the {@link #getMessageKeyPrefix()} suffixed with updated. */ protected String getUpdatedMessageKey() { return getMessageKeyPrefix() + "updated"; } /** * The key to look up in the Seam managed resource bundle the message * displayed when the managed entity is deleted. * <br /> * By default the {@link #getMessageKeyPrefix()} suffixed with deleted. */ protected String getDeletedMessageKey() { return getMessageKeyPrefix() + "deleted"; } /** * Raise events when a CRUD operation succeeds. * <br /> * Utility method to raise two events: an event of type * <code>org.jboss.seam.afterTransactionSuccess</code> is raised, along with * an event of type * <code>org.jboss.seam.afterTransactionSuccess.<entityName></code>. */ protected void raiseAfterTransactionSuccessEvent() { raiseTransactionSuccessEvent("org.jboss.seam.afterTransactionSuccess"); String simpleEntityName = getSimpleEntityName(); if (simpleEntityName != null) { raiseTransactionSuccessEvent("org.jboss.seam.afterTransactionSuccess." + simpleEntityName); } } /** * The simple name of the managed entity */ protected String getSimpleEntityName() { String name = getEntityName(); if (name != null) { return name.lastIndexOf(".") > 0 && name.lastIndexOf(".") < name.length() ? name.substring(name.lastIndexOf(".") + 1, name.length()) : name; } else { return null; } } /** * Hook method to get the name of the managed entity */ protected abstract String getEntityName(); }