001 package de.deepamehta.core.impl; 002 003 import de.deepamehta.core.TopicType; 004 import de.deepamehta.core.AssociationType; 005 import de.deepamehta.core.model.AssociationTypeModel; 006 import de.deepamehta.core.model.TopicTypeModel; 007 008 009 import java.util.HashMap; 010 import java.util.Map; 011 import java.util.logging.Logger; 012 013 014 015 /** 016 * A memory-cache for type definitions: topic types and association types. 017 * <p> 018 * Types are accessed by the {@link get} methods. They are lazy-loaded from the DB. 019 * <p> 020 * This class is internally used by the {@link EmbeddedService}. The plugin developer accesses topic types via the 021 * {@link de.deepamehta.core.service.DeepaMehtaService#getTopicType} core service call. 022 */ 023 class TypeCache { 024 025 // ---------------------------------------------------------------------------------------------- Instance Variables 026 027 private Map<String, TopicType> topicTypes = new HashMap(); // key: topic type URI 028 private Map<String, AssociationType> assocTypes = new HashMap(); // key: assoc type URI 029 030 private EmbeddedService dms; 031 032 private EndlessRecursionProtection endlessRecursionProtection = new EndlessRecursionProtection(); 033 034 private Logger logger = Logger.getLogger(getClass().getName()); 035 036 // ---------------------------------------------------------------------------------------------------- Constructors 037 038 TypeCache(EmbeddedService dms) { 039 this.dms = dms; 040 } 041 042 // ----------------------------------------------------------------------------------------- Package Private Methods 043 044 TopicType getTopicType(String topicTypeUri) { 045 TopicType topicType = topicTypes.get(topicTypeUri); 046 if (topicType == null) { 047 topicType = loadTopicType(topicTypeUri); 048 putTopicType(topicType); 049 } 050 return topicType; 051 } 052 053 AssociationType getAssociationType(String assocTypeUri) { 054 AssociationType assocType = assocTypes.get(assocTypeUri); 055 if (assocType == null) { 056 assocType = loadAssociationType(assocTypeUri); 057 putAssociationType(assocType); 058 } 059 return assocType; 060 } 061 062 // --- 063 064 void putTopicType(TopicType topicType) { 065 topicTypes.put(topicType.getUri(), topicType); 066 } 067 068 void putAssociationType(AssociationType assocType) { 069 assocTypes.put(assocType.getUri(), assocType); 070 } 071 072 // --- 073 074 void removeTopicType(String topicTypeUri) { 075 logger.info("### Removing topic type \"" + topicTypeUri + "\" from type cache"); 076 if (topicTypes.remove(topicTypeUri) == null) { 077 throw new RuntimeException("Topic type \"" + topicTypeUri + "\" not found in type cache"); 078 } 079 } 080 081 void removeAssociationType(String assocTypeUri) { 082 logger.info("### Removing association type \"" + assocTypeUri + "\" from type cache"); 083 if (assocTypes.remove(assocTypeUri) == null) { 084 throw new RuntimeException("Association type \"" + assocTypeUri + "\" not found in type cache"); 085 } 086 } 087 088 // ------------------------------------------------------------------------------------------------- Private Methods 089 090 private TopicType loadTopicType(String topicTypeUri) { 091 TopicType topicType = null; 092 try { 093 logger.info("Loading topic type \"" + topicTypeUri + "\""); 094 endlessRecursionProtection.check(topicTypeUri); 095 // 096 TopicTypeModel model = dms.typeStorage.getTopicType(topicTypeUri); 097 topicType = new AttachedTopicType(model, dms); 098 return topicType; 099 } finally { 100 // Note: if loading fails (e.g. type URI is invalid) the protection counter must be decremented. 101 // Otherwise a 2nd load try would raise a bogus "Endless recursion" exception. 102 if (topicType == null) { 103 endlessRecursionProtection.uncheck(topicTypeUri); 104 } 105 } 106 } 107 108 private AssociationType loadAssociationType(String assocTypeUri) { 109 AssociationType assocType = null; 110 try { 111 logger.info("Loading association type \"" + assocTypeUri + "\""); 112 endlessRecursionProtection.check(assocTypeUri); 113 // 114 AssociationTypeModel model = dms.typeStorage.getAssociationType(assocTypeUri); 115 assocType = new AttachedAssociationType(model, dms); 116 return assocType; 117 } finally { 118 // Note: if loading fails (e.g. type URI is invalid) the protection counter must be decremented. 119 // Otherwise a 2nd load try would raise a bogus "Endless recursion" exception. 120 if (assocType == null) { 121 endlessRecursionProtection.uncheck(assocTypeUri); 122 } 123 } 124 } 125 126 // --- 127 128 private class EndlessRecursionProtection { 129 130 private Map<String, Integer> callCount = new HashMap(); 131 132 private void check(String typeUri) { 133 int count = incCount(typeUri); 134 if (count >= 2) { 135 throw new RuntimeException("Endless recursion while loading type \"" + typeUri + 136 "\" (count=" + count + ")"); 137 } 138 } 139 140 private void uncheck(String typeUri) { 141 decCount(typeUri); 142 } 143 144 // --- 145 146 private int incCount(String typeUri) { 147 Integer count = callCount.get(typeUri); 148 if (count == null) { 149 count = 0; 150 } 151 count++; 152 callCount.put(typeUri, count); 153 return count; 154 } 155 156 private int decCount(String typeUri) { 157 Integer count = callCount.get(typeUri); 158 // Note: null check is not required here. Decrement always follows increment. 159 count--; 160 callCount.put(typeUri, count); 161 return count; 162 } 163 } 164 }