Java tutorial
/* * ==================================================================== * * This code is subject to the freebxml License, Version 1.1 * * Copyright (c) 2001 - 2003 freebxml.org. All rights reserved. * * $Header: /cvsroot/ebxmlrr/omar/src/java/org/freebxml/omar/server/cache/ClassificationSchemeCache.java,v 1.24 2007/06/06 21:54:13 psterk Exp $ * ==================================================================== */ package org.freebxml.omar.server.cache; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import javax.xml.bind.JAXBException; import javax.xml.registry.JAXRException; import javax.xml.registry.RegistryException; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheException; import net.sf.ehcache.Element; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.freebxml.omar.common.CanonicalConstants; import org.freebxml.omar.common.Utility; import org.freebxml.omar.server.common.RegistryProperties; import org.freebxml.omar.server.common.ServerRequestContext; import org.oasis.ebxml.registry.bindings.rim.AuditableEventType; import org.oasis.ebxml.registry.bindings.rim.ClassificationNodeType; import org.oasis.ebxml.registry.bindings.rim.ClassificationSchemeType; import org.oasis.ebxml.registry.bindings.rim.ObjectRefType; import org.oasis.ebxml.registry.bindings.rim.RegistryObjectType; /** * The server side classification scheme cache for registry server. * Specific cache for classification schemes. * * TODO: * Need to execute queries as RegistryOperator so cache is complete and then later do filtering when returning results * based on user privileges. * * @author Farrukh Najmi * @author Doug Bunting */ class ClassificationSchemeCache extends AbstractCache { private static final Log log = LogFactory.getLog(ClassificationSchemeCache.class); String defaultSchemeCacheDepth = "4"; private static ClassificationSchemeCache instance = null; private Cache pathToNodeCache = null; protected ClassificationSchemeCache() { defaultSchemeCacheDepth = RegistryProperties.getInstance() .getProperty("omar.server.cache.ClassificationSchemeCache.depth", defaultSchemeCacheDepth); //Key is id, value is a ClassificationSchemeType internalCache = cacheMgr.getCache(ClassificationSchemeCache.class.getName()); } /* * This method is used to get the Cache used to map a CN key to a CN * instance value */ private Cache getPathToNodeCache() throws RegistryException { // Use lazy instantiation if (pathToNodeCache == null) { String cacheName = ClassificationSchemeCache.class.getName() + ".pathToNodeCache"; pathToNodeCache = cacheMgr.getCache(cacheName); if (pathToNodeCache == null) { try { cacheMgr.addCache(cacheName); } catch (Throwable t) { throw new RegistryException(t); } pathToNodeCache = cacheMgr.getCache(cacheName); } } return pathToNodeCache; } public synchronized static ClassificationSchemeCache getInstance() { if (instance == null) { instance = new ClassificationSchemeCache(); } return instance; } /** * Initializes the cache. */ protected void initialize() { if (primeCacheEvent.equalsIgnoreCase("onCacheInit")) { ServerRequestContext context = null; try { context = getCacheContext("ClassificationSchemeCache.initialize"); primeCache(context); } catch (RegistryException e) { // Ignore whatever caused getCacheContext() to fail. } finally { try { if (null != context) { // Caches never write to dB so no need to commit context.rollback(); } } catch (RegistryException e) { log.warn(e); } } } } /** * Re-initialize this cache, preloading unless such is completely * turned off. * * May be slightly incomplete since onEvent() context may * be owned by an unprivileged user. * */ private void reset(ServerRequestContext context) { try { synchronized (internalCache) { internalCache.removeAll(); cacheIsPrimed = false; } if (!primeCacheEvent.equalsIgnoreCase("never")) { primeCache(context); } } catch (IOException e) { //Should not happen ever. log.error(e); } } /** * Prime the cache, preloading all schemes and many of their * classification nodes. */ protected void primeCache(ServerRequestContext context) { long startTime = 0; if (log.isTraceEnabled()) { log.trace("primeCache: started"); startTime = System.currentTimeMillis(); } // Prime cache by pre-loading all schemes and (to configured // depths) classification nodes below them. try { updateScheme(context, "%", true); } catch (Exception e) { // Exceptions during priming are most likely of the "table not // found" ilk, especially during dB load. In any case, no // priming exception should prevent use of public methods from // this class. } if (log.isTraceEnabled()) { long endTime = System.currentTimeMillis(); log.trace("primeCache: ended. elapsedTime:" + (endTime - startTime)); } } private int getDepthForScheme(String schemeId) { String depthProp = "omar.server.cache.ClassificationSchemeCache.depth." + schemeId.replace(':', '.'); int schemeDepth = Integer .parseInt(RegistryProperties.getInstance().getProperty(depthProp, defaultSchemeCacheDepth)); //log.trace("SchemeId: " + schemeId + " depth: " + schemeDepth); return schemeDepth; } /** * Gets all schemes. Note that descendent nodes of schemes up to the * depth level that is configured for the cache may also be loaded into * the object cache. */ public List getAllClassificationSchemes(ServerRequestContext context) throws RegistryException { List result = null; try { primeCacheOnFirstUse(context); synchronized (internalCache) { if (cacheIsPrimed) { result = getAllClassificationSchemesFromCache(); } } // If not, we mostly update the cache outside the lock. if (null == result) { result = updateScheme(context, "%", true /* false */); } } catch (CacheException e) { throw new RegistryException(e); } return result; } /* * Gets all ClassificationSchemes from internal cache. * Assumes cache has been primed. * */ private List getAllClassificationSchemesFromCache() throws CacheException { List schemes = new ArrayList(); List keys = internalCache.getKeys(); Iterator iter = keys.iterator(); while (iter.hasNext()) { Serializable key = (Serializable) iter.next(); Element elem = internalCache.get(key); if (elem != null) { ClassificationSchemeType scheme = (ClassificationSchemeType) elem.getValue(); schemes.add(scheme); } } return schemes; } /** * Gets schemes that match id pattern. Note that descendent nodes of * schemes up to the depth level that is configured for the cache may * also be loaded into the object cache. */ public List getClassificationSchemesById(ServerRequestContext context, String idPattern) throws RegistryException { List matchedSchemes = null; try { primeCacheOnFirstUse(context); if (Utility.getInstance().isValidRegistryId(idPattern)) { //Valid id: check if it is in cache Element elem = internalCache.get(idPattern); if (null == elem) { //Cache miss. Get from dB matchedSchemes = updateScheme(context, idPattern, true /* false */ ); } else { ClassificationSchemeType scheme = (ClassificationSchemeType) elem.getValue(); //Cache hit matchedSchemes = Collections.singletonList(scheme); } } else { //Not a valid id: must be a pattern List schemes = getAllClassificationSchemes(context); if (idPattern.equals("%")) { matchedSchemes = schemes; } else { matchedSchemes = new ArrayList(); Iterator iter = schemes.iterator(); while (iter.hasNext()) { ClassificationSchemeType scheme = (ClassificationSchemeType) iter.next(); String schemeId = scheme.getId(); if (idPattern.equalsIgnoreCase(schemeId)) { matchedSchemes.add(scheme); } else if (schemeId.matches(idPattern)) { matchedSchemes.add(scheme); } } } } } catch (CacheException e) { throw new RegistryException(e); } return matchedSchemes; } /** * Gets ClassificationNodes whose parent's id matches the specified id. * * @param parentId The id of parent ClassificationNode or ClassificationScheme. Must be an exact id rather than an id pattern. */ public List getClassificationNodesByParentId(ServerRequestContext context, String parentId) throws RegistryException { List childConcepts = null; return childConcepts; } protected void updateCacheEntry(ServerRequestContext context, RegistryObjectType ro) throws RegistryException { // ??? i18n throw new RegistryException("Internal error. Unimplemented function should not have been called."); /* RegistryObjectType scheme = null; ClassificationSchemeType schemeParent = null; ClassificationNodeType nodeParent = null; if (ro instanceof ClassificationSchemeType) { scheme = ro; } else if (ro instanceof ClassificationNodeType) { scheme = ((ClassificationNodeType)ro).; children = nodeParent.getClassificationNode(); } */ } /** * Put specified schemes into this cache. Optionally, add descendent * nodes of schemes (up to the depth that is configured for the cache) * to the object cache, hopefully improving later cache tree * traversals. */ private void putClassificationSchemes(ServerRequestContext context, List schemes, final boolean loadChildren, final boolean nowPrimed) throws RegistryException { if (schemes != null) { HashMap map = new HashMap(50); // Convert provided List into a Map. Iterator iter = schemes.iterator(); while (iter.hasNext()) { ClassificationSchemeType scheme = (ClassificationSchemeType) iter.next(); String schemeId = scheme.getId(); map.put(schemeId, scheme); if (loadChildren) { // Add any child nodes to the object cache loadChildren(context, scheme, getDepthForScheme(schemeId)); } } synchronized (internalCache) { iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); Element elem = new Element((Serializable) entry.getKey(), (Serializable) entry.getValue()); internalCache.put(elem); } if (nowPrimed) { cacheIsPrimed = true; } } } } /** * Loads all schemes from the database into the cache.<br> * This method is not expected to be used after the priming event (if * the cache is primed). However, when this method is used more than * once, this cache imitates the TRANSACTION_NONE transaction isolation * because the objects are generally read from the database in the * current context. Updated but not committed objects are stored in * the cache and will not be removed if the transaction rolls back. */ private List updateScheme(ServerRequestContext context, String schemeIdPattern, final boolean loadChildren) throws RegistryException { String sqlQuery = "SELECT DISTINCT scheme.* FROM ClassScheme scheme WHERE scheme.id LIKE ?"; ArrayList queryParams = new ArrayList(); queryParams.add(schemeIdPattern); List newSchemes = executeQueryInternal(context, sqlQuery, queryParams, "ClassScheme"); // If the pattern was just "%", the cache will now be primed. putClassificationSchemes(context, newSchemes, loadChildren, schemeIdPattern.equals("%")); return newSchemes; } /** * Loads the child nodes of specified parent as nested objects within parent. * * @param parent the ClassificationScheme or ClassificationNode parent whose children are desired. * * @param depth the number of levels of descendents caches for each ClassificationScheme in ClassificationSchemeCache<br /> * depth of 0 means cache parent only<br /> * depth of 1 means cache immediate child nodes of parent<br /> * depth of 2 means cache immediate child nodes and grandchild nodes of parent<br /> * depth of -1 means cache all descendent nodes of parent */ private void loadChildren(ServerRequestContext context, RegistryObjectType parent, final int depth) throws RegistryException { if (depth != 0) { String sqlQuery = "SELECT node.* FROM ClassificationNode node WHERE node.parent=? ORDER BY node.code"; ArrayList queryParams = new ArrayList(); queryParams.add(parent.getId()); List childNodes = executeQueryInternal(context, sqlQuery, queryParams, "ClassificationNode"); if (childNodes.size() == 0) { try { // Add a transient slot to indicate that child count is // 0, making sure to not add a duplicate slot. This // will alow JAXR Provider to optimize by not trying to // fetch these non-existent children // ??? How long will this change last? For example, // ??? will the next context rollback remove this // ??? transient slot? May also be inefficient if // ??? child node count is already 0. Finally, we have // ??? seen inconsistent Derby errors in // ??? SlotDAO.getSlotsByParent() which may relate to a // ??? problem in this code. HashMap slotsMap = bu.getSlotsFromRegistryObject(parent); parent.getSlot().clear(); slotsMap.put(bu.CANONICAL_SLOT_NODE_PARENT_CHILD_NODE_COUNT, "0"); bu.addSlotsToRegistryObject(parent, slotsMap); } catch (JAXBException e) { //No big harm done as this is a optmization flag only. log.warn(e); } } else { // Put child nodes in objectCache objectCache.putRegistryObjects(childNodes); //Now recurse and load each child. int newDepth = depth - 1; Iterator iter = childNodes.iterator(); while (iter.hasNext()) { ClassificationNodeType childNode = (ClassificationNodeType) iter.next(); loadChildren(context, childNode, newDepth); } } } } /** * Clear all affectedObjects in AuditableEvent from cache. When * called, internalCache may be out of date with respect to dB (where * transaction has been committed) and objectCache (where affected * classification nodes have already been processed).<br> * This code keeps the cache primed if it was primed earlier. The side * effect of this choice is every other context (separate transaction) * immediately knows about the just-committed changes. That is, this * cache imitates TRANSACTION_READ_COMMITTED transaction isolation * unless the caching event setting is "never". */ public void onEvent(ServerRequestContext context, AuditableEventType ae) { final String eventType = ae.getEventType(); final boolean justRemove = primeCacheEvent.equalsIgnoreCase("never"); final boolean wasChanged = eventType.equalsIgnoreCase(CanonicalConstants.CANONICAL_EVENT_TYPE_ID_Created) || eventType.equalsIgnoreCase(CanonicalConstants.CANONICAL_EVENT_TYPE_ID_Updated) || eventType.equalsIgnoreCase(CanonicalConstants.CANONICAL_EVENT_TYPE_ID_Versioned); final boolean wasRemoved = eventType.equalsIgnoreCase(CanonicalConstants.CANONICAL_EVENT_TYPE_ID_Deleted); Set schemesToRemove = new HashSet(); HashMap schemesToUpdate = new HashMap(); primeCacheOnFirstUse(context); if (wasChanged || wasRemoved) { try { List affectedObjects = ae.getAffectedObjects().getObjectRef(); Iterator iter = affectedObjects.iterator(); while (iter.hasNext()) { ObjectRefType ref = (ObjectRefType) iter.next(); String objectId = ref.getId(); RegistryObjectType ro = (RegistryObjectType) context.getAffectedObjectsMap().get(objectId); if (null == ro) { // In case missing (removed?) object was a scheme schemesToRemove.add(objectId); } else { if (ro instanceof ClassificationSchemeType) { if (wasRemoved || justRemove) { schemesToRemove.add(objectId); } else { schemesToUpdate.put(objectId, ro); } } else if (ro instanceof ClassificationNodeType) { String schemeId = bu.getSchemeIdForRegistryObject(ro); // Handle case where a node in a scheme has been // added, deleted or updated. if (justRemove) { schemesToRemove.add(schemeId); } else if (!(schemesToRemove.contains(schemeId) || schemesToUpdate.containsKey(schemeId))) { ClassificationSchemeType scheme = (ClassificationSchemeType) getRegistryObjectInternal( context, schemeId, "ClassScheme"); if (null != scheme) { schemesToUpdate.put(schemeId, scheme); // ??? Why is this necessary for all // ??? schemes loaded? loadChildren(context, scheme, getDepthForScheme(schemeId)); } } } } } } catch (JAXRException e) { log.error(e); //Just update all schemes to be safe in case of any errors reset(context); // Make following block a no-op. schemesToRemove.clear(); schemesToUpdate.clear(); } } synchronized (internalCache) { final int oldSize = internalCache.getSize(); Iterator iter = schemesToRemove.iterator(); while (iter.hasNext()) { String objectId = (String) iter.next(); internalCache.remove(objectId); } if (justRemove) { // Cache may become primed regardless of primeCacheEvent // setting, pay attention if we have undone that. if (oldSize != internalCache.getSize()) { cacheIsPrimed = false; } } else if (schemesToUpdate.size() > 0) { addClassificationSchemesToCache(schemesToUpdate.values()); } } } private void addClassificationSchemesToCache(Collection schemes) { Iterator iter = schemes.iterator(); while (iter.hasNext()) { ClassificationSchemeType scheme = (ClassificationSchemeType) iter.next(); Element elem = new Element(scheme.getId(), (Serializable) scheme); internalCache.put(elem); } } /** * This method is used to retrieve a ClassificationNode based on its path. * Note: if an invalid path is passed to this method, this method will * return null * * @param path * A String containing the ClassificationNodeType path * @return * A ClassificationNodeType instance that matches the path */ public ClassificationNodeType getClassificationNodeByPath(String path) throws RegistryException { ClassificationNodeType cn = null; ServerRequestContext context = null; try { Element elem = getPathToNodeCache().get(path); if (elem == null) { String likeOrEqual = "="; if (path.indexOf('%') != -1) { likeOrEqual = "LIKE"; } String tableName = "ClassificationNode"; // COMMENT 1: // HIEOS/AMS: Commented the following lines of code. No need to convert 'path' to upper case // and subsequently compare using SQL's UPPER function (Using this prevents // evaluation of indices on 'cn.path'). // String sqlQuery = "SELECT cn.* from ClassificationNode cn WHERE UPPER(cn.path) " + // likeOrEqual + " ? ORDER BY cn.path ASC"; String sqlQuery = "SELECT cn.* from ClassificationNode cn WHERE cn.path " + likeOrEqual + " ? ORDER BY cn.path ASC"; ArrayList queryParams = new ArrayList(); // HIEOS/AMS: See COMMENT 1 // queryParams.add(path.toUpperCase()); queryParams.add(path); context = new ServerRequestContext("ClassificationSchemeCache.getClassificationNodeByPath", null); List results = executeQueryInternal(context, sqlQuery, queryParams, tableName); if (results.size() == 0) { return null; } cn = (ClassificationNodeType) results.get(0); elem = new Element((Serializable) path, (Serializable) cn); getPathToNodeCache().put(elem); } else { cn = (ClassificationNodeType) elem.getValue(); } } catch (RegistryException re) { throw re; } catch (Throwable t) { throw new RegistryException(t); } finally { if (context != null) { context.rollback(); } } return cn; } }