All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.cornell.mannlib.vitro.webapp.dao.jena.VClassGroupCache Maven / Gradle / Ivy

/* $This file is distributed under the terms of the license in LICENSE$ */
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 org.apache.jena.ontology.OntModel;
import org.apache.jena.rdf.listeners.StatementListener;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ResourceFactory;
import org.apache.jena.rdf.model.Statement;
import org.apache.jena.shared.Lock;
import org.apache.jena.vocabulary.OWL;
import org.apache.jena.vocabulary.RDF;
import org.apache.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.dao.jena.event.BulkUpdateEvent;
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 _groupList;

    /**
     * Also keep track of the classes here, makes it easier to get the counts
     * and other information */
    private Map VclassMap = new HashMap();


    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 cgList = getGroups();
        for (VClassGroup cg : cgList) {
            if (vClassGroupURI.equals(cg.getURI()))
                return cg;
        }
        return null;
    }

    public synchronized List 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 newGroups, Map classMap){
        _groupList = newGroups;
        VclassMap = classMap;
    }

    private boolean paused = false;
    private boolean updateRequested = false;

    public void pause() {
        this.paused = true;
    }

    public void unpause() {
        this.paused = false;
        if(updateRequested) {
            updateRequested = false;
            requestCacheUpdate();
        }
    }

    public void requestCacheUpdate() {
        log.debug("requesting update");
        if(paused) {
            updateRequested = true;
        } else {
            _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 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 classMapForGroups( List groups){
        Map newClassMap = new HashMap();
        for (VClassGroup vcg : groups) {
            List 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 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 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.equals(stmt.getPredicate())) {
                requestCacheUpdate();
            } else if (VitroVocabulary.IN_CLASSGROUP.equals(stmt.getPredicate().getURI())) {
                requestCacheUpdate();
            } else if(VitroVocabulary.DISPLAY_RANK.equals(stmt.getPredicate().getURI())){
            	requestCacheUpdate();
            } else if (RDFS.label.equals(stmt.getPredicate())){
                OntModel jenaOntModel = ModelAccess.on(context).getOntModelSelector().getTBoxModel();
                if( isClassNameChange(stmt, jenaOntModel) ) {
                    requestCacheUpdate();
                }
            }
        }

        public void notifyEvent(Model model, Object event) {
            if (event instanceof BulkUpdateEvent) {
                if(((BulkUpdateEvent) event).getBegin()) {
                    pause();
                } else {
                    unpause();
                }
            }
        }

    }

    /* ******************** 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();
            }
        }
    }



}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy