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    }