// $HeadURL$ // $Id$ // // Copyright © 2006, 2010, 2011, 2012 by the President and Fellows of Harvard College. // // Screensaver is an open-source project developed by the ICCB-L and NSRB labs // at Harvard Medical School. This software is distributed under the terms of // the GNU General Public License. package edu.harvard.med.screensaver.ui.arch.view; import java.io.Serializable; import org.apache.log4j.Logger; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import edu.harvard.med.screensaver.db.GenericEntityDAO; import edu.harvard.med.screensaver.model.AuditedAbstractEntity; import edu.harvard.med.screensaver.model.Entity; import edu.harvard.med.screensaver.model.activities.AdministrativeActivityType; import edu.harvard.med.screensaver.model.users.AdministratorUser; import edu.harvard.med.screensaver.model.users.ScreensaverUser; import edu.harvard.med.screensaver.policy.EntityEditPolicy; import edu.harvard.med.screensaver.ui.arch.searchresults.EntityUpdateSearchResults; import edu.harvard.med.screensaver.ui.arch.view.aspects.UICommand; import edu.harvard.med.screensaver.util.DevelopmentException; import edu.harvard.med.screensaver.util.NullSafeUtils; public abstract class EditableEntityViewerBackingBean<E extends Entity<? extends Serializable>> extends EntityViewerBackingBean<E> implements EditableEntityViewer<E> { protected static Logger log = Logger.getLogger(EditableEntityViewerBackingBean.class); private EntityEditPolicy _entityEditPolicy; private EntityUpdateSearchResults _entityUpdateHistoryBrowser; private boolean _isEditMode; private String _updateComments; public EditableEntityViewerBackingBean(EditableEntityViewerBackingBean<E> thisProxy, Class<E> entityClass, String viewerActionResult, GenericEntityDAO dao) { super(thisProxy, entityClass, viewerActionResult, dao); getIsPanelCollapsedMap().put("updateHistory", true); } /** * @motivation this is for the spring library (CGLIB2) */ protected EditableEntityViewerBackingBean() { } @Transactional @Override public void setEntity(E entity) { _isEditMode = false; super.setEntity(entity); } abstract protected String postEditAction(EditResult editResult); public EditableEntityViewerBackingBean<E> getThisProxy() { return (EditableEntityViewerBackingBean<E>) super.getThisProxy(); } public EntityEditPolicy getEntityEditPolicy() { return _entityEditPolicy; } public void setEntityEditPolicy(EntityEditPolicy entityEditPolicy) { _entityEditPolicy = entityEditPolicy; } protected void recordUpdateActivity(String updateMessage) { recordUpdateActivity(AdministrativeActivityType.ENTITY_UPDATE, updateMessage); } protected void recordUpdateActivity() { recordUpdateActivity("saved" + (getUpdateComments() == null ? "" : (": " + getUpdateComments()))); _updateComments = null; } protected void recordUpdateActivity(AdministrativeActivityType adminActivityType, String comments) { E entity = getEntity(); if (entity instanceof AuditedAbstractEntity) { AdministratorUser admin = (AdministratorUser) getCurrentScreensaverUser().getScreensaverUser(); admin = getDao().reloadEntity(admin, false, ScreensaverUser.activitiesPerformed.castToSubtype(AdministratorUser.class)); ((AuditedAbstractEntity) entity).createUpdateActivity(adminActivityType, admin, comments); } } public String getUpdateComments() { return _updateComments; } public void setUpdateComments(String updateComments) { _updateComments = updateComments; } public boolean isEditMode() { return _isEditMode; } protected void setEditMode(boolean isEditMode) { _isEditMode = isEditMode; } public boolean isReadOnly() { if (_entityEditPolicy == null || getEntity() == null) { return true; } return !!!((Boolean) getEntity().acceptVisitor(_entityEditPolicy)).booleanValue(); } public boolean isEditable() { return !isReadOnly(); } public boolean isDeleteSupported() { return false; } @UICommand /*final*/ public String cancel() { setEditMode(false); if (getEntity() == null || getEntity().isTransient()) { String editAction = postEditAction(EditResult.CANCEL_NEW); _setEntity(null); // no longer a valid entity, since new edit was canceled (note that we do this after calling postEditAction() in case the method implementation needs to inspect the current entity. return editAction; } return postEditAction(EditResult.CANCEL_EDIT); } @UICommand /*final*/ public String edit() { setEditMode(true); return view(); } /** * @param newEntity a transient or detached entity, with all relationships initialized that will be needed by the * viewer; the entity will be directly modified and later persisted, so the object network must contain * distinct instances of any entity that is reachable from multiple relationships */ @UICommand // TODO: determine if this propagation setting is necessary for safety @Transactional(propagation = Propagation.NOT_SUPPORTED) /*final*/ public String editNewEntity(E newEntity) { if (newEntity == null) { throw new DevelopmentException("attempted to edit a null entity"); } if (!!!newEntity.isTransient()) { throw new DevelopmentException("attempted to edit a non-transient entity"); } if (_entityEditPolicy == null || !!!((Boolean) newEntity.acceptVisitor(_entityEditPolicy)).booleanValue()) { showMessage("restrictedOperation", "add new " + newEntity.getClass().getSimpleName()); return REDISPLAY_PAGE_ACTION_RESULT; } _setEntity(null); // prevent initializeViewer() implementations from erroneously calling getEntity() initializeNewEntity(newEntity); initializeViewer(newEntity); _setEntity(newEntity); setEditMode(true); return view(); } /** * Subclasses should override this method if property values need to be set to defaults. It will <i>not</i> be called * within a transaction/session, and the method should <i>not</i> fetch any * additional entities (to avoid session reattachment problems) */ protected void initializeNewEntity(E entity) { } @UICommand @Transactional /*final*/ public String save() { EditResult editResult = getEntity().isTransient() ? EditResult.SAVE_NEW : EditResult.SAVE_EDIT; if (!validateEntity(getEntity())) { return REDISPLAY_PAGE_ACTION_RESULT; } setEditMode(false); // leave edit mode only after validation occurs, in order to remain in edit mode if validation failed _setEntity(getDao().mergeEntity(getEntity())); updateEntityProperties(getEntity()); recordUpdateActivity(); getDao().flush(); getDao().clear(); // forces entity to be reloaded from db in view mode; this ensures that newly created entities have entityViewPolicy injected; also, any problems with persistence will be caught immediately after save during UI testing return postEditAction(editResult); } /** * Subclasses should override this method to perform validation of the entity * prior to saving it. The entity may be transient (when creating new * entities) or detached (when updating existing entities). The method <i>should not</i> load new entities into the * session (as this may cause NonUniqueObjectExceptions when the entity is later flushed). * * @param entity * @return true if entity state is valid, otherwise false */ protected boolean validateEntity(E entity) { return true; } /** * Subclasses should override this method if there are entity properties that * need to be updated from UI components that were not directly bound to the * entity properties. This method will be called within a transaction. The * specified entity will be managed by the Hibernate session and will reflect * the new value of all properties bound to the UI view. The method cannot * perform validations on data at this point (it is too late to do so!); use * {@link #validateEntity(Entity)}. * * @param entity */ protected void updateEntityProperties(E entity) { } public EntityUpdateSearchResults getEntityUpdateSearchResults() { return _entityUpdateHistoryBrowser; } public void setEntityUpdateHistoryBrowser(EntityUpdateSearchResults entityUpdateHistoryBrowser) { _entityUpdateHistoryBrowser = entityUpdateHistoryBrowser; } @Override public boolean isUpdateHistoryCapable() { return _entityUpdateHistoryBrowser != null && getEntity() instanceof AuditedAbstractEntity; } @UICommand public String viewUpdateHistory() { if (isUpdateHistoryCapable()) { getEntityUpdateSearchResults().searchForParentEntity((AuditedAbstractEntity) getEntity()); return BROWSE_ENTITY_UPDATE_HISTORY; } return REDISPLAY_PAGE_ACTION_RESULT; } @UICommand @Transactional /** * @motivation provide a convenient default implementation as delete support is off by default */ public String delete() { return null; } public void showFieldInputError(String fieldName, String message) { showMessage("invalidUserInput", fieldName, NullSafeUtils.toString(message, "")); // TODO: fix // showMessageForLocalComponentId(getFacesContext(), // fieldName, // "invalidUserInput", // NullSafeUtils.toString(message, "")); } }