Java tutorial
/* * ==================================================================== * This file is part of the ebXML Registry by Icar Cnr v3.2 * ("eRICv32" in the following disclaimer). * * "eRICv32" is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * "eRICv32" is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License Version 3 * along with "eRICv32". If not, see <http://www.gnu.org/licenses/>. * * eRICv32 is a forked, derivative work, based on: * - freebXML Registry, a royalty-free, open source implementation of the ebXML Registry standard, * which was published under the "freebxml License, Version 1.1"; * - ebXML OMAR v3.2 Edition, published under the GNU GPL v3 by S. Krushe & P. Arwanitis. * * All derivative software changes and additions are made under * * Copyright (C) 2013 Ing. Antonio Messina <messina@pa.icar.cnr.it> * * This software consists of voluntary contributions made by many * individuals on behalf of the freebxml Software Foundation. For more * information on the freebxml Software Foundation, please see * "http://www.freebxml.org/". * * This product includes software developed by the Apache Software * Foundation (http://www.apache.org/). * * ==================================================================== */ package it.cnr.icar.eric.server.cache; import it.cnr.icar.eric.common.CanonicalConstants; import it.cnr.icar.eric.common.Utility; import it.cnr.icar.eric.server.common.RegistryProperties; import it.cnr.icar.eric.server.common.ServerRequestContext; 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 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.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("eric.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. context = null; } 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 = "eric.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. */ @SuppressWarnings("unchecked") private List<?> getAllClassificationSchemesFromCache() throws CacheException { @SuppressWarnings("rawtypes") 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. */ @SuppressWarnings({ "rawtypes", "unchecked" }) 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<String, ClassificationSchemeType> map = new HashMap<String, ClassificationSchemeType>(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()) { @SuppressWarnings("rawtypes") 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<String> queryParams = new ArrayList<String>(); 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 */ @SuppressWarnings("static-access") 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<String> queryParams = new ArrayList<String>(); 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<String, Serializable> 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<String> schemesToRemove = new HashSet<String>(); HashMap<String, RegistryObjectType> schemesToUpdate = new HashMap<String, RegistryObjectType>(); primeCacheOnFirstUse(context); if (wasChanged || wasRemoved) { try { List<ObjectRefType> affectedObjects = ae.getAffectedObjects().getObjectRef(); Iterator<ObjectRefType> iter = affectedObjects.iterator(); while (iter.hasNext()) { ObjectRefType ref = iter.next(); String objectId = ref.getId(); RegistryObjectType ro = 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) { int oldSize; // xxx pa 110816 added try / catch for CacheException (ehcache 1.0 effect?) try { oldSize = internalCache.getSize(); Iterator<String> iter = schemesToRemove.iterator(); while (iter.hasNext()) { String objectId = 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()); } } catch (CacheException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } private void addClassificationSchemesToCache(Collection<RegistryObjectType> schemes) { Iterator<RegistryObjectType> iter = schemes.iterator(); while (iter.hasNext()) { ClassificationSchemeType scheme = (ClassificationSchemeType) iter.next(); Element elem = new Element(scheme.getId(), 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"; String sqlQuery = "SELECT cn.* from ClassificationNode cn WHERE UPPER(cn.path) " + likeOrEqual + " ? ORDER BY cn.path ASC"; ArrayList<String> queryParams = new ArrayList<String>(); queryParams.add(path.toUpperCase()); 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(path, 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; } }