package com.breeze.save; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; // import org.hibernate.metadata.ClassMetadata; import com.breeze.metadata.AutoGeneratedKeyType; import com.breeze.metadata.Metadata; import com.breeze.metadata.MetadataHelper; import com.breeze.util.JsonGson; /** * This is the primary class for providing save interception logic; it it also the repository of * all of the information regarding the entities and their statuses associated with a save operation. * Typically this class will be subclassed so that the subclass can defined overriden versions * of the before and after save interception methods. This class also provides a number of utility methods * to allow entities to be found, added or removed from the current save pipeline. * @author IdeaBlade * */ @SuppressWarnings("unchecked") public class SaveWorkState { // input private SaveProcessor _saveProcessor; private SaveOptions _saveOptions; private List<Map> _entityMaps; // output private Map<Class, List<EntityInfo>> _saveMap = new HashMap<Class, List<EntityInfo>>(); private Map<EntityInfo, KeyMapping> _entityKeyMapping = new HashMap<EntityInfo, KeyMapping>(); private EntityErrorsException _entityErrorsException; private List<EntityInfo> _emptyList = Collections.emptyList(); /** * Constructs an instance of this class with the deserialized result of * an Http save request. * @param saveBundle */ public SaveWorkState(Map saveBundle) { this._saveOptions = new SaveOptions((Map) saveBundle.get("saveOptions")); this._entityMaps = (List<Map>) saveBundle.get("entities"); } /** * @return The SaveOptions associated with this save operation. */ public SaveOptions getSaveOptions() { return _saveOptions; } protected void setSaveProcessor(SaveProcessor saveProcessor) { _saveProcessor = saveProcessor; } /** * @param entity * @return The id of the specified entity. */ public Object getIdentifier(Object entity) { return _saveProcessor.getIdentifier(entity); } /** * @return The complete Metadata model associated with this save. */ public Metadata getMetadata() { return _saveProcessor.getMetadata(); } public void setEntityErrors(EntityErrorsException exception) { _entityErrorsException = exception; } /** Build the saveMap, and call context.beforeSaveEntity/ies */ protected void beforeSave() { for (Object o : _entityMaps) { EntityInfo entityInfo = createEntityInfoFromJson((Map) o); // don't put it in the saveMap if it was rejected by // beforeSaveEntity if (beforeSaveEntity(entityInfo)) { addEntityInfo(entityInfo); } } } protected void afterSave() { afterSaveEntities(); } // methods to override /** * Allow subclasses to process each entity before it gets included in the saveMap. * Called when each EntityInfo is materialized (before beforeSaveEntities is called). * Base implementation always returns true. * @param entityInfo * @return true if the entity should be included in the saveMap, false if not. */ protected boolean beforeSaveEntity(EntityInfo entityInfo) { return true; } /** * Allow subclasses to process the entities after they have been hooked up to one another * but before they have been added to the session. */ public void beforeSaveEntities() { } /** * Allow subclasses to process the entities just before they are ready to be committed * */ public void beforeCommit(Object context) { } /** * Allow subclasses to process the saveMap after entities are saved (and temporary keys replaced) * @param saveMap all entities which have been saved * @param keyMappings mapping of temporary keys to real keys */ public void afterSaveEntities() { } /** * Allows subclasses to plug in their own exception handling. * This method is called when saveChangesCore throws an exception. * Subclass implementations of this method should either: * 1. Throw an exception * 2. Return false (exception not handled) * 3. Return true (exception handled) and modify the SaveWorkState accordingly. * Base implementation returns false (exception not handled). * @param e Exception that was thrown by saveChangesCore * @return true (exception handled) or false (exception not handled) */ public boolean handleException(Exception e) { return false; } /** * @param clazz * @return A list of all of the EntityInfo instances associated with this class for this save operation. */ public List<EntityInfo> getEntityInfos(Class clazz) { List<EntityInfo> entityInfos = _saveMap.get(clazz); return entityInfos != null ? entityInfos : _emptyList; } /** * @param clazz * @return A list of all of the entities associated with this class for this save operation. */ public List<Object> getEntities(Class clazz) { List<Object> entities = new ArrayList<Object>(); for (EntityInfo info : getEntityInfos(clazz)) { entities.add(info.entity); } return entities; } /** * @return A list of all of the entities to be saved for this save operation. */ public List<Object> getEntities() { List<Object> entities = new ArrayList<Object>(); for (List<EntityInfo> infos : _saveMap.values()) { for (EntityInfo info : infos) { entities.add(info.entity); } } return entities; } /** * Schedule an additional entity to be saved. * @param entity The entity to be saved. * @param entityState The EntityState of the newly added entity. * @return The EntityInfo associated with the newly added entity. */ public EntityInfo addEntity(Object entity, EntityState entityState) { EntityInfo entityInfo = createEntityInfoForEntity(entity, entityState); return addEntityInfo(entityInfo); } /** * Remove an entity from being scheduled to be saved. * @param entity The entity to be removed. * @return Whether the entity was found. */ public boolean removeEntity(Object entity) { Class clazz = entity.getClass(); List<EntityInfo> entityInfos = getEntityInfos(clazz); // TODO: this can be made more efficient. for (EntityInfo entityInfo : entityInfos) { if (entityInfo.entity == entity) { entityInfos.remove(entityInfo); _saveProcessor.processRelationships(entityInfo, true); return true; } } return false; } /** * Return the EntityInfo associated with a specific entity. * @param entity * @return */ public EntityInfo findEntityInfo(Object entity) { for (EntityInfo entityInfo : getEntityInfos(entity.getClass())) { if (entityInfo.entity == entity) return entityInfo; } return null; } // /** // * Return the EntityInfo associated with a specific entity. // * @param clazz // * @param entity // * @return // */ // public EntityInfo findEntityInfo(Class clazz, Object entity) { // for (Class nextClass : findSelfAndSubclasses(clazz)) { // for (EntityInfo entityInfo : getEntityInfos(nextClass)) { // if (entityInfo.entity == entity) return entityInfo; // } // } // return null; // } public EntityInfo findEntityInfoById(Class clazz, Object id) { for (Class nextClass : findSelfAndSubclasses(clazz)) { for (EntityInfo entityInfo : getEntityInfos(nextClass)) { if (getIdentifier(entityInfo.entity).toString().equals(id.toString())) { return entityInfo; } } } return null; } public Set<Entry<Class, List<EntityInfo>>> entrySet() { return _saveMap.entrySet(); } /** * Populate a new SaveResult with the entities and keyMappings. If there are * entityErrors, populate it with those instead. */ public SaveResult toSaveResult() { if (_entityErrorsException != null) { return new SaveResult(_entityErrorsException); } else { // List<KeyMapping> keyMappings = updateAutoGeneratedKeys(); return new SaveResult(getEntities(), getKeyMappings()); } } private EntityInfo addEntityInfo(EntityInfo entityInfo) { Class clazz = entityInfo.entity.getClass(); List<EntityInfo> entityInfos = _saveMap.get(clazz); if (entityInfos == null) { entityInfos = new ArrayList<EntityInfo>(); _saveMap.put(clazz, entityInfos); } addKeyMapping(entityInfo); entityInfos.add(entityInfo); _saveProcessor.processRelationships(entityInfo, false); return entityInfo; } private void addKeyMapping(EntityInfo entityInfo) { if ((entityInfo.entityState == EntityState.Added) && (entityInfo.entityType.getAutoGeneratedKeyType() != AutoGeneratedKeyType.None)) { Object entity = entityInfo.entity; Object id = _saveProcessor.getIdentifier(entity); KeyMapping km = new KeyMapping(MetadataHelper.getEntityTypeName(entity.getClass()), id); _entityKeyMapping.put(entityInfo, km); } } private List<KeyMapping> getKeyMappings() { List<KeyMapping> list = new ArrayList<KeyMapping>(); for (Entry<EntityInfo, KeyMapping> entry : _entityKeyMapping.entrySet()) { EntityInfo entityInfo = entry.getKey(); KeyMapping km = entry.getValue(); if (km != null && km.getTempValue() != null && !entityInfo.wasCreatedOnServer) { Object id = _saveProcessor.getIdentifier(entityInfo.entity); km.setRealValue(id); list.add(km); } } return list; } private List<Class> findSelfAndSubclasses(Class clazz) { List<Class> subclasses = new ArrayList<Class>(); for (Class candidate : _saveMap.keySet()) { if (candidate.isAssignableFrom(clazz)) { subclasses.add(candidate); } } return subclasses; } private EntityInfo createEntityInfoForEntity(Object entity, EntityState entityState) { EntityInfo info = new EntityInfo(); info.entity = entity; info.entityType = getMetadata().getEntityTypeForClass(entity.getClass()); info.entityState = entityState; info.wasCreatedOnServer = true; return info; } /** * @param map * raw name-value pairs from JSON * @return populated EntityInfo */ private EntityInfo createEntityInfoFromJson(Map map) { EntityInfo info = new EntityInfo(); Map aspect = (Map) map.get("entityAspect"); map.remove("entityAspect"); String entityTypeName = (String) aspect.get("entityTypeName"); Class type = MetadataHelper.lookupClass(entityTypeName); info.entityType = getMetadata().getEntityType(entityTypeName); info.entity = JsonGson.fromMap(type, map); info.entityState = EntityState.valueOf((String) aspect .get("entityState")); info.originalValuesMap = (Map) aspect.get("originalValuesMap"); info.unmappedValuesMap = (Map) map.get("__unmapped"); info.wasCreatedOnServer = false; // AutoGeneratedKey info from the client isn't needed because we have the metadata already on the server. // Map autoKey = (Map) aspect.get("autoGeneratedKey"); // if (autoKey != null) { // info.autoGeneratedKey = new AutoGeneratedKey(info.entity, // (String) autoKey.get("propertyName"), // (String) autoKey.get("autoGeneratedKeyType")); // } return info; } }