changeset 65:74cd973f6ece

more work on stale entity update problem. cache compare function works now.
author casties
date Fri, 27 Jan 2017 19:55:33 +0100
parents 83b209aa4226
children 3e4b05a6cb47
files .hgignore src/main/java/org/mpi/openmind/cache/AbstractCacheService.java src/main/java/org/mpi/openmind/cache/CacheService.java src/main/java/org/mpi/openmind/cache/WrapperService.java src/main/java/org/mpi/openmind/repository/bo/Attribute.java
diffstat 5 files changed, 324 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Thu Jan 26 19:37:25 2017 +0100
+++ b/.hgignore	Fri Jan 27 19:55:33 2017 +0100
@@ -18,4 +18,6 @@
 syntax: regexp
 ^src/main/resources/log4j\.properties$
 syntax: regexp
-^src/main/resources/openmind\.properties$
\ No newline at end of file
+^src/main/resources/openmind\.properties$
+syntax: regexp
+^bin$
\ No newline at end of file
--- a/src/main/java/org/mpi/openmind/cache/AbstractCacheService.java	Thu Jan 26 19:37:25 2017 +0100
+++ b/src/main/java/org/mpi/openmind/cache/AbstractCacheService.java	Fri Jan 27 19:55:33 2017 +0100
@@ -27,7 +27,7 @@
 	
 	private PersistenceService persistenceService;
 	
-	private DuplexMap<Entity, String, Long> entMap;
+	protected DuplexMap<Entity, String, Long> entMap;
 	private Map<String, Boolean> entLoadedByOC;
 	
 	public AbstractCacheService(){
@@ -178,7 +178,15 @@
 	}
 	
 	
-	public boolean isEntityCurrent(Entity ent) {
+	/**
+	 * Check if the given Entity is up-to-date with the cache.
+	 * 
+	 * Compares accessTime only.
+	 * 
+	 * @param ent
+	 * @return
+	 */
+	public boolean isLWEntityCurrent(Entity ent) {
 		Entity cacheEnt = this.entMap.getValuesByOwnKey(ent.getId());
 		if (cacheEnt == null) {
 			// not in cache
@@ -197,7 +205,7 @@
 	//******************************************
 	
 	//AttKey(OC, attName), SrcId
-	private TripleMap<Attribute, AttKey, Long, Long> attMap;
+	protected TripleMap<Attribute, AttKey, Long, Long> attMap;
 	private Map<Long, Boolean> attLoadedBySrcId;
 	private Map<AttKey, Boolean> attLoadedByOCAndName;
 	
@@ -243,7 +251,7 @@
 	}
 	
 	/**
-	 * Return a list of (cloned) Attributes of the Entity.
+	 * Return a list of (cloned) Attributes with the given source id.
 	 * 
 	 * Loads Attributes from the database into the cache.
 	 * 
@@ -258,25 +266,32 @@
 		return list;
 	}
 	
-	public List<Attribute> getAttsBySrcIdReadOnly(Long srcId){
-		//logger.info("getAttsBySrcIdReadOnly srcId=" + srcId);
+	/**
+	 * Return a list of (non-cloned) Attributes with the given source id from the cache.
+	 * 
+	 * Loads Attributes from the database into the cache.
+	 * 
+	 * @param srcId
+	 * @return
+	 */
+	public List<Attribute> getAttsBySrcIdReadOnly(Long srcId) {
 		if(attLoadedBySrcId.get(srcId) == null || attLoadedBySrcId.get(srcId) == false){
 			synchronized (attMap) {
-				if(attLoadedBySrcId.get(srcId) == null){
-					//logger.info("getAttsBySrcIdReadOnly fromDB entId=" + srcId);
+				if(attLoadedBySrcId.get(srcId) == null) {
 					attLoadedBySrcId.put(srcId, false);
-					List<Attribute> list = 
-						getPs().getAllAttributes(srcId, -1);
-					for(Attribute att : list){
+					// load from db
+					List<Attribute> list = getPs().getAllAttributes(srcId, -1);
+					for (Attribute att : list) {
 						// update access timestamp
 						att.setAccessTime(System.currentTimeMillis());
+						// put in cache
 						this.attMap.put(att.getKey(), att);
 					}
 					attLoadedBySrcId.put(srcId, true);
-					//logger.info("STARTING getAttsBySrcIdReadOnly srcId=" + srcId + " - size=" + list.size());
 				}
 			}
 		}
+		// return attributes from cache
 		return this.attMap.getValuesByBKey(srcId);
 	}
 	
@@ -304,11 +319,36 @@
 		return this.attMap.getValuesByAKey(ocNameKey);
 	}
 	
+
+	/**
+	 * Check if the given Attribute is up-to-date with the cache.
+	 * 
+	 * Compares accessTime only.
+	 * 
+	 * @param att
+	 * @return
+	 */
+	public boolean isAttCurrent(Attribute att) {
+		Attribute cacheAtt = this.attMap.get(att.getKey());
+		if (cacheAtt == null) {
+			// not in cache
+			return true;
+		}
+		long ct = cacheAtt.getAccessTime();
+		long et = att.getAccessTime();
+		if (ct == 0 || et == 0 || et > ct) {
+			logger.error("Weird cache access times! old="+Long.toString(ct)+" new="+Long.toString(et));
+		}
+		return (ct == et);
+	}
+	
+
+	
 	//******************************************
 	//************Relations*********************
 	//******************************************
 	//RelKey<>, src, tar, relId
-	private PentaMap<Relation, RelKey, Long, Long, String, Long> relMap;
+	protected PentaMap<Relation, RelKey, Long, Long, String, Long> relMap;
 	private Map<Long, Boolean> relLoadedBySrcId;
 	private Map<Long, Boolean> relLoadedByTarId;
 	private Map<String, Boolean> relLoadedByName;
@@ -384,6 +424,14 @@
         return relMap.getValuesByBKey(srcId);
     }
 	
+	/**
+	 * Return Relations with matching target id from cache.
+     * 
+     * Loads Relations from db and stores in cache if not in cache.
+     * 
+	 * @param tarId
+	 * @return
+	 */
 	public List<Relation> getRelsByTarId(Long tarId){
 		if(relLoadedByTarId.get(tarId) == null || relLoadedByTarId.get(tarId) == false){
 			synchronized (relMap) {
@@ -441,6 +489,29 @@
 		return relMap.getValuesByDKey(name);
 	}
 	
+	/**
+	 * Check if the given Relation is up-to-date with the cache.
+	 * 
+	 * Compares accessTime only.
+	 * 
+	 * @param rel
+	 * @return
+	 */
+	public boolean isRelCurrent(Relation rel) {
+		Relation cacheRel = this.relMap.get(rel.getKey());
+		if (cacheRel == null) {
+			// not in cache
+			return true;
+		}
+		long ct = cacheRel.getAccessTime();
+		long et = rel.getAccessTime();
+		if (ct == 0 || et == 0 || et > ct) {
+			logger.error("Weird cache access times! old="+Long.toString(ct)+" new="+Long.toString(et));
+		}
+		return (ct == et);
+	}
+	
+
 	public PersistenceService getPs() {
 		return persistenceService;
 	}
--- a/src/main/java/org/mpi/openmind/cache/CacheService.java	Thu Jan 26 19:37:25 2017 +0100
+++ b/src/main/java/org/mpi/openmind/cache/CacheService.java	Fri Jan 27 19:55:33 2017 +0100
@@ -7,6 +7,7 @@
 import java.util.List;
 import java.util.Map;
 
+import org.apache.commons.lang.StringUtils;
 import org.apache.log4j.Logger;
 import org.mpi.openmind.repository.bo.Attribute;
 import org.mpi.openmind.repository.bo.Entity;
@@ -372,6 +373,233 @@
 		}
 	}
 	
+
+	/**
+	 * Returns if this Entity and its Attributes and Relations are up-to-date with the cache.
+	 * 
+	 * Checks accessTimes only.
+	 * 
+	 * @param ent
+	 * @return
+	 */
+	public boolean isEntityCurrent(Entity ent) {
+		// check entity
+		if (!isLWEntityCurrent(ent)) {
+			return false;
+		}
+		
+		if (ent.isLightweight()) {
+			logger.error("Can not check attributes and relations of lightweight entity!");
+			return false;
+		}
+		Long entId = ent.getId();
+		
+		// check attached attributes
+		List<Attribute> entAtts = ent.getAttributes();
+		List<Attribute> cacheAtts = attMap.getValuesByBKey(entId);
+		for (Attribute att : entAtts) {
+			if (!isAttCurrent(att)) {
+				return false;
+			} else {
+				if (!cacheAtts.remove(att)) {
+					return false;
+				}
+			}
+		}		
+		// check additional attributes in cache
+		if (!cacheAtts.isEmpty()) {
+			for (Attribute ca : cacheAtts) {
+				// ignore empty attributes
+				if (!StringUtils.isEmpty(ca.getOwnValue())) {
+					return false;
+				}
+			}
+		}
+		
+		// check attached source relations
+		List<Relation> entSrcRels = ent.getSourceRelations();
+		List<Relation> cacheSrcRels = relMap.getValuesByBKey(entId);
+		for (Relation rel: entSrcRels) {
+			if (!isRelCurrent(rel)) {
+				return false;
+			} else {
+				if (!cacheSrcRels.remove(rel)) {
+					return false;
+				}
+			}
+		}
+		if (!cacheSrcRels.isEmpty()) {
+			return false;
+		}
+		// check attached target relations
+		List<Relation> entTarRels = ent.getTargetRelations();
+		List<Relation> cacheTarRels = relMap.getValuesByCKey(entId);
+		for (Relation rel: entTarRels) {
+			if (!isRelCurrent(rel)) {
+				return false;
+			} else {
+				if (!cacheTarRels.remove(rel)) {
+					return false;
+				}
+			}
+		}
+		if (!cacheTarRels.isEmpty()) {
+			return false;
+		}	
+		
+		return true;
+	}
+
+	
+	public class EntityDiff {
+		public List<Attribute> addedAttributes = new ArrayList<Attribute>();
+		public List<Attribute> removedAttributes = new ArrayList<Attribute>();
+		public List<Attribute> modifiedAttributes = new ArrayList<Attribute>();
+		public List<Relation> addedSrcRels = new ArrayList<Relation>();
+		public List<Relation> removedSrcRels = new ArrayList<Relation>();
+		public List<Relation> modifiedSrcRels = new ArrayList<Relation>();
+		public List<Relation> addedTarRels = new ArrayList<Relation>();
+		public List<Relation> removedTarRels = new ArrayList<Relation>();
+		public List<Relation> modifiedTarRels = new ArrayList<Relation>();
+	}
+
+	/**
+	 * Returns an EntityDiff with the differences between the given Entity and the current cache.
+	 * 
+	 * Ignores empty Attributes. Compares only content if acceptNewIDs.
+	 * 
+	 * @param ent
+	 * @return
+	 * @throws Exception
+	 */
+	public EntityDiff diffEntityCache(Entity ent, boolean acceptNewIDs) throws Exception {
+		if (ent.isLightweight()) {
+			throw new Exception("Can not diff attributes and relations of lightweight entity!");
+		}
+		Long entId = ent.getId();
+		EntityDiff diff = new EntityDiff();
+		
+		/*
+		 * check attributes
+		 */
+		List<Attribute> entAtts = ent.getAttributes();
+		List<Attribute> cacheAtts = attMap.getValuesByBKey(entId);
+		// check attached attributes and compare to cached
+		for (Attribute att : entAtts) {
+			if (!isAttCurrent(att)) {
+				diff.modifiedAttributes.add(att);
+			}
+			if (!cacheAtts.remove(att)) {
+				boolean found = false;
+				for (Attribute ca : cacheAtts) {
+					if (ca.getId() == att.getId()) {
+						cacheAtts.remove(ca);
+						found = true;
+						break;
+					} else if (acceptNewIDs && ca.equalsContent(att)) {
+						// same content is good enough
+						cacheAtts.remove(ca);
+						found = true;
+						break;
+					}
+				}
+				if (!found) {
+					diff.addedAttributes.add(att);
+				}
+			}
+		}
+		if (!cacheAtts.isEmpty()) {
+			// more cached attributes
+			for (Attribute ca : cacheAtts) {
+				// ignore empty attributes
+				if (!StringUtils.isEmpty(ca.getOwnValue())) {
+					diff.removedAttributes.add(ca);
+				}
+			}
+		}
+		
+		/*
+		 * source relations
+		 */
+		List<Relation> entSrcRels = ent.getSourceRelations();
+		List<Relation> cacheSrcRels = relMap.getValuesByBKey(entId);
+		// check attached source relations and compare to cached
+		for (Relation rel : entSrcRels) {
+			if (!isRelCurrent(rel)) {
+				diff.modifiedSrcRels.add(rel);
+			}
+			if (!cacheSrcRels.remove(rel)) {
+				boolean found = false;
+				for (Relation cr : cacheSrcRels) {
+					if (cr.getId() == rel.getId()) {
+						cacheSrcRels.remove(cr);
+						found = true;
+						break;
+					} else if (acceptNewIDs && cr.equalsContent(rel)) {
+						// same content is good enough
+						cacheSrcRels.remove(cr);
+						found = true;
+						break;
+					}
+				}
+				if (!found) {
+					diff.addedSrcRels.add(rel);
+				}
+			}
+		}
+		if (!cacheSrcRels.isEmpty()) {
+			for (Relation rel : cacheSrcRels) {
+				diff.removedSrcRels.add(rel);
+			}
+		}
+		
+		/*
+		 * target relations
+		 */
+		List<Relation> entTarRels = ent.getTargetRelations();
+		List<Relation> cacheTarRels = relMap.getValuesByCKey(entId);
+		// check attached target relations and compare to cached
+		for (Relation rel : entTarRels) {
+			if (!isRelCurrent(rel)) {
+				diff.modifiedTarRels.add(rel);
+			}
+			if (!cacheTarRels.remove(rel)) {
+				boolean found = false;
+				for (Relation cr : cacheTarRels) {
+					if (cr.getId() == rel.getId()) {
+						cacheTarRels.remove(cr);
+						found = true;
+						break;
+					} else if (acceptNewIDs && cr.equalsContent(rel)) {
+						// same content is good enough
+						cacheTarRels.remove(cr);
+						found = true;
+						break;
+					}
+				}
+				if (!found) {
+					diff.addedTarRels.add(rel);
+				}
+			}
+		}
+		if (!cacheTarRels.isEmpty()) {
+			for (Relation rel : cacheTarRels) {
+				diff.removedTarRels.add(rel);
+			}
+		}
+		
+		// return null if diff is empty
+		if (diff.addedAttributes.isEmpty() && diff.removedAttributes.isEmpty() && diff.modifiedAttributes.isEmpty()
+				&& diff.addedSrcRels.isEmpty() && diff.removedSrcRels.isEmpty() && diff.modifiedSrcRels.isEmpty()
+				&& diff.addedTarRels.isEmpty() && diff.removedTarRels.isEmpty() && diff.modifiedTarRels.isEmpty()) {
+			return null;
+		}
+		return diff;
+	}
+	
+
+	
+	
 	
 	public List<ViewerAttribute> getViewerAttributes(Long page){
 		return getViewerAttMap().getValuesByAKey(page);
--- a/src/main/java/org/mpi/openmind/cache/WrapperService.java	Thu Jan 26 19:37:25 2017 +0100
+++ b/src/main/java/org/mpi/openmind/cache/WrapperService.java	Fri Jan 27 19:55:33 2017 +0100
@@ -11,6 +11,7 @@
 
 import org.apache.commons.lang.StringUtils;
 import org.apache.log4j.Logger;
+import org.mpi.openmind.cache.CacheService.EntityDiff;
 import org.mpi.openmind.repository.bo.Attribute;
 import org.mpi.openmind.repository.bo.Entity;
 import org.mpi.openmind.repository.bo.Node;
@@ -666,6 +667,8 @@
 		// check if entity is up to date with cache
 		if (!cache.isEntityCurrent(entity)) {
 			logger.error("Entity to save not up to date with cache!");
+			EntityDiff diff = cache.diffEntityCache(entity, true);
+			logger.debug(diff);
 		}
 		
 		// save in database
--- a/src/main/java/org/mpi/openmind/repository/bo/Attribute.java	Thu Jan 26 19:37:25 2017 +0100
+++ b/src/main/java/org/mpi/openmind/repository/bo/Attribute.java	Fri Jan 27 19:55:33 2017 +0100
@@ -178,6 +178,12 @@
 		this.possibleValues = possibleValues;
 	}
 
+	/**
+	 * Returns if the other Attribute has the same ownValue and name.
+	 * 
+	 * @param other
+	 * @return
+	 */
 	public boolean equalsContent(Attribute other){
 		if(other == null)
 			return false;