/* Copyright (2005-2012) Schibsted ASA * This file is part of Possom. * * Possom is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Possom 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Possom. If not, see <http://www.gnu.org/licenses/>. * * Created on May 11, 2007, 10:15:37 PM */ package no.sesat.search.view.velocity; import org.apache.velocity.runtime.resource.ResourceManagerImpl; import org.apache.velocity.runtime.resource.Resource; import org.apache.velocity.exception.ResourceNotFoundException; import org.apache.velocity.exception.ParseErrorException; import org.apache.log4j.Logger; import org.apache.commons.lang.time.StopWatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.text.MessageFormat; import java.util.concurrent.ThreadPoolExecutor; /** * A faster replacement for {@link org.apache.velocity.runtime.resource.ResourceManagerImpl} that avoids * doing resource loading and parsing while holding a global exclusive lock. This implementation creates new resource * instances instead of updating existing ones and loads and parses them in the background. This removes the need for * locking and the only synchronization done is whatever synchronization measures the cache is taking. Resources not yet * in the cache are loaded synchronously. The reduced locking is achieved at the expense of resources possibly getting * loaded several times during startup or reload. * * * @version $Id$ */ public final class QuickResourceManagerImpl extends ResourceManagerImpl { private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool(); private static final String RESOURCE_NOT_FOUND = "ResourceManager :'{0}'' not found in any resource loader."; private static final String RESOURCE_PARSE_EXCEPTION = "ResourceManager.getResource() parse exception on "; private static final String RESOURCE_EXCEPTION = "ResourceManager.getResource() exception on "; private static final String LOADED_VELOCITY_RESOURCE = "Loaded velocity resource {0} in {1}"; private static final String CHECKED_MODIFICATION = "Checked modification of velocity resource {0} in {1}"; private static final Logger LOG = Logger.getLogger(QuickResourceManagerImpl.class); private static final String DEBUG_POOL_COUNT = "Pool size: "; /** {@inheritDoc} * Overridden to layer caching capabilities. */ @Override public Resource getResource(final String name, final int type, final String encoding) throws Exception { final Resource resource = globalCache.get(type + name); if (resource != null) { // Use cached resource for this invocation but also start a thread to update cache with a brand new // resource instance. if (resource.requiresChecking()) { // Touch the resource so that a closely following caller won't trigger an update thread. // Keeps updates of the same resource from piling up when traffic is high. resource.touch(); EXECUTOR.submit(new Loader(resource, name, type, encoding)); } return resource; } else { return new Loader(resource, name, type, encoding).load(); } } /** * Class capable of loading a resource and updating the cache. */ private final class Loader implements Runnable { private final String name; private final int type; private final String enc; private final String key; private final Resource oldResource; private boolean modified = false; /** * Creates a new resource loader. The supplied <tt>oldResource</tt> will not be refreshed but a new one will * replace it in the cache. This should be safe with regards to the rest of velocity since the default cache * implementation is backed by a LRUMap discarding resources at will. * * @param oldResource The resource being refreshed. Null if loaded for the first time. * @param name The name of the resource to load. * @param type The type of the resource to load. * @param enc The encoding of the resource. */ private Loader(final Resource oldResource, final String name, final int type, final String enc) { this.oldResource = oldResource; this.name = name; this.type = type; this.enc = enc; key = type + name; if(LOG.isDebugEnabled() && EXECUTOR instanceof ThreadPoolExecutor){ final ThreadPoolExecutor tpe = (ThreadPoolExecutor)EXECUTOR; LOG.debug(DEBUG_POOL_COUNT + tpe.getActiveCount() + '/' + tpe.getPoolSize()); } } /** * Loads the resource if it has been modified since it was last loaded. * * @return the resource. * @throws ResourceNotFoundException if the resource wasn't found. * @throws ParseErrorException if the resource couldn't be parsed. * @throws Exception ? */ private Resource load() throws Exception { final StopWatch stopWatch = new StopWatch(); try { stopWatch.start(); modified = oldResource == null || oldResource.isSourceModified(); stopWatch.split(); if (modified) { final Resource newResource = loadResource(name, type, enc); if (newResource.getResourceLoader().isCachingOn()) { globalCache.put(key, newResource); } return newResource; } else { return oldResource; } } catch (ResourceNotFoundException rnfe) { LOG.error(MessageFormat.format(RESOURCE_NOT_FOUND, name)); throw rnfe; } catch (ParseErrorException pee) { LOG.error(RESOURCE_PARSE_EXCEPTION + name, pee); throw pee; } catch (RuntimeException re) { throw re; } catch (Exception e) { LOG.error(RESOURCE_EXCEPTION + name, e); throw e; } finally { stopWatch.stop(); if (null != oldResource && LOG.isInfoEnabled()) { LOG.info(MessageFormat.format(CHECKED_MODIFICATION, key, stopWatch.toSplitString())); } if (modified && LOG.isDebugEnabled()) { LOG.debug(MessageFormat.format(LOADED_VELOCITY_RESOURCE, key, stopWatch.toString())); } } } /** * Loads resource if it has been modified since it was last loaded. */ @Override public void run() { try { load(); } catch (ResourceNotFoundException rnfe) { LOG.error(MessageFormat.format(RESOURCE_NOT_FOUND, name)); } catch (ParseErrorException pee) { LOG.error(RESOURCE_PARSE_EXCEPTION + name, pee); } catch (RuntimeException re) { throw re; } catch (Exception e) { LOG.error(RESOURCE_EXCEPTION + name, e); } } } }