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;
}
}