/* * 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/>. */ package no.sesat.search.query.token; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.locks.ReentrantReadWriteLock; import no.sesat.commons.ioc.ContextWrapper; import no.sesat.search.query.Clause; import no.sesat.search.query.Query; import no.sesat.search.site.Site; import org.apache.log4j.Logger; /** * TokenEvaluateFactory provides knowledge about which implementation of * {@link TokenEvaluator} that can handle a particular token. * * This class is not synchronised (Except for the evaluateTerm, evaluateClause, and evaluateQuery methods). * Manual synhronisation must be taken when calling operate or setter methods from inside SearchCommand classes. * * * @version $Id$ */ public class TokenEvaluationEngineImpl implements TokenEvaluationEngine { // Constants ----------------------------------------------------- private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool(); private static final Logger LOG = Logger.getLogger(TokenEvaluationEngineImpl.class); private static final String ERR_TOKENTYPE_WIHOUT_IMPL = "Token type not known or implemented. "; private static final String DEBUG_POOL_COUNT = "Pool size: "; private static final String ERR_METHOD_CLOSED_TO_OTHER_THREADS = "TokenPredicate.evaluate(..) can only be used by same thread that created TokenEvaluationEngine!"; private static final String ERR_ENGINE_MISSING_STATE = "TokenEvaluationEngine must have state assigned"; // Attributes ----------------------------------------------------- private final Context context; private State state; private volatile Thread owningThread = Thread.currentThread(); private final transient Map<TokenPredicate,TokenEvaluator> evaluatorCache = new HashMap<TokenPredicate,TokenEvaluator>(); /** threading lock to the cache maps since they are not synchronised, * and it's overkill to make them Hashtables. **/ private final transient ReentrantReadWriteLock evaluatorCacheGate = new ReentrantReadWriteLock(); // Constructors ----------------------------------------------------- /** * Create a new TokenEvaluationEngine. * * @param cxt context to work within. */ public TokenEvaluationEngineImpl(final Context cxt) { if(LOG.isDebugEnabled() && EXECUTOR instanceof ThreadPoolExecutor){ final ThreadPoolExecutor tpe = (ThreadPoolExecutor)EXECUTOR; LOG.debug(DEBUG_POOL_COUNT + tpe.getActiveCount() + '/' + tpe.getPoolSize()); } context = cxt; } // Public ----------------------------------------------------- @Override public TokenEvaluator getEvaluator(final TokenPredicate token) throws EvaluationException { TokenEvaluator result = null; try{ evaluatorCacheGate.readLock().lock(); result = evaluatorCache.get(token); }finally{ evaluatorCacheGate.readLock().unlock(); } if(null == result){ try{ evaluatorCacheGate.writeLock().lock(); for(EvaluatorType type : EvaluatorType.getInstances()){ final AbstractEvaluatorFactory factory = AbstractEvaluatorFactory.instanceOf( ContextWrapper.wrap( AbstractEvaluatorFactory.Context.class, context, type )); if(factory.isResponsibleFor(token)){ LOG.trace("Evaluator for " + token + " found by " + type.getEvaluatorFactoryClassName()); result = factory.getEvaluator(token); break; } } if(null == result){ // no evaluator has been defined for this predicate. it must always be false then. result = ALWAYS_FALSE_EVALUATOR; } evaluatorCache.put(token, result); }finally{ evaluatorCacheGate.writeLock().unlock(); } } return result; } @Override public String getQueryString() { return context.getQueryString(); } @Override public Site getSite() { return context.getSite(); } @Override public synchronized boolean evaluateTerm( final TokenPredicate predicate, final String term) throws EvaluationException{ return evaluateInAnyThread(predicate, new EvaluationState(term, Collections.EMPTY_SET, Collections.EMPTY_SET)); } @Override public synchronized boolean evaluateClause( final TokenPredicate predicate, final Clause clause) throws EvaluationException{ return evaluateInAnyThread(predicate, new EvaluationState(clause)); } @Override public synchronized boolean evaluateQuery( final TokenPredicate predicate, final Query query) throws EvaluationException{ return evaluateInAnyThread(predicate, query.getEvaluationState()); } @Override public boolean evaluate(final TokenPredicate token) throws EvaluationException{ // process if(Thread.currentThread() != getOwningThread()){ throw new IllegalStateException(ERR_METHOD_CLOSED_TO_OTHER_THREADS); } // check that the evaluation hasn't already been done // we can only check against the knownPredicates because with the possiblePredicates we are not sure whether // the evaluation is for the building of the known and possible predicate list // (during query parsing)(in which // case we could perform the check) or if we are scoring and need to know if the // possible predicate is really // applicable now (in the context of the whole query). final Set<TokenPredicate> knownPredicates = getState().getKnownPredicates(); if(null != knownPredicates && knownPredicates.contains(token)){ return true; } final TokenEvaluator evaluator = getEvaluator(token); if(null != getState().getTerm()){ // Single term or clause evaluation return evaluator.evaluateToken(token, getState().getTerm(), getQueryString()); }else if(null != getState().getQuery()){ // Whole query evaluation return getState().getPossiblePredicates().contains(token) && evaluator.evaluateToken(token, null, getQueryString()); } throw new IllegalStateException(ERR_ENGINE_MISSING_STATE); } @Override public State getState() { return state; } @Override public void setState(final State state) { this.state = state; } // private ----------------------------------------------------- private boolean evaluateInAnyThread( final TokenPredicate predicate, final State state) throws EvaluationException{ final Thread origThread = owningThread; try{ // setup the engine's required state before any evaluation process setState(state); // temporarily change owningThread to allow this thread to evaluate owningThread = Thread.currentThread(); // run the evaluation process return evaluate(predicate); }finally{ setState(null); owningThread = origThread; } } private synchronized Thread getOwningThread() { return owningThread; } // inner classes ----------------------------------------------------- }