Java tutorial
/* $This file is distributed under the terms of the license in /doc/license.txt$ */ package edu.cornell.mannlib.vitro.webapp.dao.jena; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.hp.hpl.jena.ontology.OntModel; import com.hp.hpl.jena.rdf.listeners.StatementListener; import com.hp.hpl.jena.rdf.model.ResourceFactory; import com.hp.hpl.jena.rdf.model.Statement; import com.hp.hpl.jena.shared.Lock; import com.hp.hpl.jena.vocabulary.OWL; import com.hp.hpl.jena.vocabulary.RDF; import com.hp.hpl.jena.vocabulary.RDFS; import edu.cornell.mannlib.vitro.webapp.application.ApplicationUtils; import edu.cornell.mannlib.vitro.webapp.beans.VClass; import edu.cornell.mannlib.vitro.webapp.beans.VClassGroup; import edu.cornell.mannlib.vitro.webapp.dao.VClassGroupDao; import edu.cornell.mannlib.vitro.webapp.dao.VClassGroupsForRequest; import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary; import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; import edu.cornell.mannlib.vitro.webapp.dao.filtering.WebappDaoFactoryFiltering; import edu.cornell.mannlib.vitro.webapp.dao.filtering.filters.VitroFilterUtils; import edu.cornell.mannlib.vitro.webapp.dao.filtering.filters.VitroFilters; import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchEngine; import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchEngineException; import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchFacetField; import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchFacetField.Count; import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchQuery; import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchResponse; import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexer; import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexer.Event; import edu.cornell.mannlib.vitro.webapp.search.VitroSearchTermNames; import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; import edu.cornell.mannlib.vitro.webapp.utils.threads.VitroBackgroundThread; /** * This is a cache of classgroups with classes. Each class should have a count * of individuals. These counts are cached so they don't have to be recomputed. * * The cache is updated asynchronously by the thread RebuildGroupCacheThread. * A synchronous rebuild can be performed with VClassGroupCache.doSynchronousRebuild() * * This class should handle the condition where the search engine is not available. * It will not throw an exception but it will return a empty list of classes * or class groups. * * VClassGroupCache.doSynchronousRebuild() and the RebuildGroupCacheThread will try * to connect to the search engine a couple of times and then give up. * * As of VIVO release 1.4, the counts come from the search index. If the * search index is not built or if there were problems building the index, * the class counts from VClassGroupCache will be incorrect. */ public class VClassGroupCache implements SearchIndexer.Listener { private static final Log log = LogFactory.getLog(VClassGroupCache.class); private static final String ATTRIBUTE_NAME = "VClassGroupCache"; private static final boolean ORDER_BY_DISPLAYRANK = true; private static final boolean INCLUDE_UNINSTANTIATED = true; private static final boolean DONT_INCLUDE_INDIVIDUAL_COUNT = false; /** * This is the cache of VClassGroups. It is a list of VClassGroups. If this * is null then the cache is not built. */ private List<VClassGroup> _groupList; /** * Also keep track of the classes here, makes it easier to get the counts * and other information */ private Map<String, VClass> VclassMap = new HashMap<String, VClass>(); private final RebuildGroupCacheThread _cacheRebuildThread; /** * Need a pointer to the context to get DAOs and models. */ private final ServletContext context; private VClassGroupCache(ServletContext context) { this.context = context; this._groupList = null; if (StartupStatus.getBean(context).isStartupAborted()) { _cacheRebuildThread = null; return; } /* Need to register for changes of rdf:type for individuals in abox * and for changes of classgroups for classes. */ VClassGroupCacheChangeListener bccl = new VClassGroupCacheChangeListener(); ModelContext.registerListenerForChanges(context, bccl); _cacheRebuildThread = new RebuildGroupCacheThread(this); _cacheRebuildThread.setDaemon(true); _cacheRebuildThread.start(); } public synchronized VClassGroup getGroup(String vClassGroupURI) { if (vClassGroupURI == null || vClassGroupURI.isEmpty()) return null; List<VClassGroup> cgList = getGroups(); for (VClassGroup cg : cgList) { if (vClassGroupURI.equals(cg.getURI())) return cg; } return null; } public synchronized List<VClassGroup> getGroups() { //try to build the cache if it doesn't exist if (_groupList == null) { doSynchronousRebuild(); } if (_groupList == null) { requestCacheUpdate(); return Collections.emptyList(); } else { return _groupList; } } // Get specific VClass corresponding to Map public synchronized VClass getCachedVClass(String classUri) { //try to build the cache if it doesn't exist if (VclassMap == null) { doSynchronousRebuild(); } if (VclassMap == null) { requestCacheUpdate(); return null; } else { if (VclassMap.containsKey(classUri)) { return VclassMap.get(classUri); } else { return null; } } } protected synchronized void setCache(List<VClassGroup> newGroups, Map<String, VClass> classMap) { _groupList = newGroups; VclassMap = classMap; } public void requestCacheUpdate() { log.debug("requesting update"); _cacheRebuildThread.informOfQueueChange(); } protected void requestStop() { if (_cacheRebuildThread != null) { _cacheRebuildThread.kill(); try { _cacheRebuildThread.join(); } catch (InterruptedException e) { //don't log message since shutting down } } } protected VClassGroupDao getVCGDao() { return ModelAccess.on(context).getWebappDaoFactory().getVClassGroupDao(); } public void doSynchronousRebuild() { //try to rebuild a couple times since the search engine may not yet be up. int attempts = 0; int maxTries = 5; SearchEngineException exception = null; while (attempts < maxTries) { try { attempts++; rebuildCacheUsingSearch(this); break; } catch (SearchEngineException e) { exception = e; try { Thread.sleep(1000); } catch (InterruptedException e1) { /*ignore interrupt*/} } } if (exception != null) log.error("Could not rebuild cache. " + exception.getCause().getMessage()); } /** * Handle notification of events from the IndexBuilder. */ @Override public void receiveSearchIndexerEvent(Event event) { switch (event.getType()) { case STOP_URIS: log.debug("rebuilding because of IndexBuilder " + event.getType()); requestCacheUpdate(); break; default: log.debug("ignoring event type " + event.getType()); break; } } /* **************** static utility methods ***************** */ /** * Use getVClassGroupCache(ServletContext) to get a VClassGroupCache. */ public static VClassGroupCache getVClassGroupCache(ServletContext sc) { return (VClassGroupCache) sc.getAttribute(ATTRIBUTE_NAME); } /** * Use getVClassGroups(HttpServletRequest) to get a language-aware image of * the cached groups and classes. */ public static VClassGroupsForRequest getVClassGroups(HttpServletRequest req) { return new VClassGroupsForRequest(req, getVClassGroupCache(req.getSession().getServletContext())); } /** * Method that rebuilds the cache. This will use a WebappDaoFactory and a SearchEngine. */ protected static void rebuildCacheUsingSearch(VClassGroupCache cache) throws SearchEngineException { long start = System.currentTimeMillis(); WebappDaoFactory wdFactory = ModelAccess.on(cache.context).getWebappDaoFactory(); SearchEngine searchEngine = ApplicationUtils.instance().getSearchEngine(); VitroFilters vFilters = VitroFilterUtils.getPublicFilter(cache.context); VClassGroupDao vcgDao = new WebappDaoFactoryFiltering(wdFactory, vFilters).getVClassGroupDao(); List<VClassGroup> groups = vcgDao.getPublicGroupsWithVClasses(ORDER_BY_DISPLAYRANK, INCLUDE_UNINSTANTIATED, DONT_INCLUDE_INDIVIDUAL_COUNT); addCountsUsingSearch(groups, searchEngine); cache.setCache(groups, classMapForGroups(groups)); log.debug("msec to build cache: " + (System.currentTimeMillis() - start)); } protected static Map<String, VClass> classMapForGroups(List<VClassGroup> groups) { Map<String, VClass> newClassMap = new HashMap<String, VClass>(); for (VClassGroup vcg : groups) { List<VClass> vclasses = vcg.getVitroClassList(); for (VClass vclass : vclasses) { String classUri = vclass.getURI(); if (!newClassMap.containsKey(classUri)) { newClassMap.put(classUri, vclass); } } } return newClassMap; } /** * Add the Individual count to classes in groups. * @throws SearchEngineException */ protected static void addCountsUsingSearch(List<VClassGroup> groups, SearchEngine searchEngine) throws SearchEngineException { if (groups == null || searchEngine == null) return; for (VClassGroup group : groups) { addClassCountsToGroup(group, searchEngine); } } protected static void addClassCountsToGroup(VClassGroup group, SearchEngine searchEngine) throws SearchEngineException { if (group == null) return; String groupUri = group.getURI(); String facetOnField = VitroSearchTermNames.RDFTYPE; SearchQuery query = searchEngine.createQuery().setRows(0) .setQuery(VitroSearchTermNames.CLASSGROUP_URI + ":" + groupUri).addFacetFields(facetOnField). //facet on type to get counts for classes in classgroup setFacetMinCount(0); log.debug("query: " + query); SearchResponse rsp = searchEngine.query(query); //Get individual count long individualCount = rsp.getResults().getNumFound(); log.debug("Number of individuals found " + individualCount); group.setIndividualCount((int) individualCount); //get counts for classes SearchFacetField ff = rsp.getFacetField(facetOnField); if (ff != null) { List<Count> counts = ff.getValues(); if (counts != null) { for (Count ct : counts) { if (ct != null) { String classUri = ct.getName(); long individualsInClass = ct.getCount(); setClassCount(group, classUri, individualsInClass); } } } else { log.debug("no Counts found for FacetField " + facetOnField); } } else { log.debug("no FaccetField found for " + facetOnField); } } protected static void setClassCount(VClassGroup group, String classUri, long individualsInClass) { for (VClass clz : group) { if (clz.getURI().equals(classUri)) { clz.setEntityCount((int) individualsInClass); } } } protected static boolean isClassNameChange(Statement stmt, OntModel jenaOntModel) { // Check if the stmt is a rdfs:label change and that the // subject is an owl:Class. if (RDFS.label.equals(stmt.getPredicate())) { jenaOntModel.enterCriticalSection(Lock.READ); try { return jenaOntModel.contains(ResourceFactory.createStatement( ResourceFactory.createResource(stmt.getSubject().getURI()), RDF.type, OWL.Class)); } finally { jenaOntModel.leaveCriticalSection(); } } else { return false; } } /* ******************** RebuildGroupCacheThread **************** */ protected class RebuildGroupCacheThread extends VitroBackgroundThread { private final VClassGroupCache cache; private long queueChangeMillis = 0L; private boolean rebuildRequested = false; private volatile boolean die = false; private int failedAttempts = 0; private final int maxFailedAttempts = 5; RebuildGroupCacheThread(VClassGroupCache cache) { super("VClassGroupCache.RebuildGroupCacheThread"); this.cache = cache; } @Override public void run() { while (!die) { int delay; if (!rebuildRequested) { log.debug("rebuildGroupCacheThread.run() -- nothing to do, sleep"); delay = 1000 * 60; } else if ((System.currentTimeMillis() - queueChangeMillis) < 500) { log.debug("rebuildGroupCacheThread.run() -- delay start of rebuild"); delay = 500; } else { setWorkLevel(WorkLevel.WORKING); rebuildRequested = false; try { rebuildCacheUsingSearch(cache); log.debug("rebuildGroupCacheThread.run() -- rebuilt cache "); failedAttempts = 0; delay = 100; } catch (SearchEngineException e) { failedAttempts++; if (failedAttempts >= maxFailedAttempts) { log.error("Could not build VClassGroupCache. " + "Could not connect with the search engine after " + failedAttempts + " attempts.", e.getCause()); rebuildRequested = false; failedAttempts = 0; delay = 1000; } else { rebuildRequested = true; delay = (int) ((Math.pow(2, failedAttempts)) * 1000); log.debug("Could not connect with the search engine, will attempt " + "again in " + delay + " msec."); } } catch (Exception ex) { log.error("could not build cache", ex); delay = 1000; } setWorkLevel(WorkLevel.IDLE); } if (delay > 0) { synchronized (this) { try { wait(delay); } catch (InterruptedException e) { log.warn("Waiting " + delay + " milliseconds, but interrupted.", e); } } } } log.debug("rebuildGroupCacheThread.run() -- die()"); } synchronized void informOfQueueChange() { queueChangeMillis = System.currentTimeMillis(); rebuildRequested = true; this.notifyAll(); } synchronized void kill() { die = true; this.notifyAll(); } } /* ****************** Jena Model Change Listener***************************** */ /** * Listen for changes to what class group classes are in and their display rank. */ protected class VClassGroupCacheChangeListener extends StatementListener { @Override public void addedStatement(Statement stmt) { checkAndDoUpdate(stmt); } @Override public void removedStatement(Statement stmt) { checkAndDoUpdate(stmt); } protected void checkAndDoUpdate(Statement stmt) { if (stmt == null) return; if (log.isDebugEnabled()) { log.debug("subject: " + stmt.getSubject().getURI()); log.debug("predicate: " + stmt.getPredicate().getURI()); } if (RDF.type.getURI().equals(stmt.getPredicate().getURI())) { requestCacheUpdate(); } else if (VitroVocabulary.IN_CLASSGROUP.equals(stmt.getPredicate().getURI())) { requestCacheUpdate(); } else if (VitroVocabulary.DISPLAY_RANK.equals(stmt.getPredicate().getURI())) { requestCacheUpdate(); } else { OntModel jenaOntModel = ModelAccess.on(context).getOntModel(); if (isClassNameChange(stmt, jenaOntModel)) { requestCacheUpdate(); } } } } /* ******************** ServletContextListener **************** */ public static class Setup implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { ServletContext context = sce.getServletContext(); VClassGroupCache vcgc = new VClassGroupCache(context); vcgc.requestCacheUpdate(); context.setAttribute(ATTRIBUTE_NAME, vcgc); log.info("VClassGroupCache added to context"); SearchIndexer searchIndexer = ApplicationUtils.instance().getSearchIndexer(); searchIndexer.addListener(vcgc); log.info("VClassGroupCache set to listen to events from IndexBuilder"); } @Override public void contextDestroyed(ServletContextEvent sce) { Object o = sce.getServletContext().getAttribute(ATTRIBUTE_NAME); if (o instanceof VClassGroupCache) { ((VClassGroupCache) o).requestStop(); } } } }