/*
* Copyright (C) 2010 eXo Platform SAS.
*
* This 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 2.1 of the License, or (at your option)
* any later version.
*
* This software 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 this software; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF
* site: http://www.fsf.org.
*/
package org.xcmis.search;
import org.apache.commons.lang.Validate;
import org.xcmis.search.config.SearchServiceConfiguration;
import org.xcmis.search.content.ContentEntry;
import org.xcmis.search.content.ContentModificationListener;
import org.xcmis.search.content.IndexModificationException;
import org.xcmis.search.content.command.InvocationContext;
import org.xcmis.search.content.command.index.ModifyIndexCommand;
import org.xcmis.search.content.command.query.ExecuteSelectorCommand;
import org.xcmis.search.content.command.query.ProcessQueryCommand;
import org.xcmis.search.content.interceptors.ContentReaderInterceptor;
import org.xcmis.search.content.interceptors.InterceptorChain;
import org.xcmis.search.content.interceptors.QueryProcessorInterceptor;
import org.xcmis.search.content.interceptors.QueryableIndexStorage;
import org.xcmis.search.model.Query;
import org.xcmis.search.query.QueryExecutionException;
import org.xcmis.search.query.Searcher;
import org.xcmis.search.query.optimize.CriteriaBasedOptimizer;
import org.xcmis.search.query.plan.SimplePlaner;
import org.xcmis.search.result.ScoredRow;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Main entry point to the search service.
*
*/
public class SearchService implements Startable, ContentModificationListener, Searcher
{
/**
* Configuration of search service.
*/
protected final SearchServiceConfiguration configuration;
private final InterceptorChain interceptorChain;
/**
* Default invocation context.
*/
private final InvocationContext defaultInvocationContext;
/**
* @param configuration
* SearchServiceConfiguration
* @throws SearchServiceException
*/
public SearchService(SearchServiceConfiguration configuration) throws SearchServiceException
{
Validate.notNull(configuration, "The configuration argument may not be null");
Validate.notNull(configuration.getContentReader(), "The configuration.getContentReader() may not be null");
this.configuration = configuration;
this.interceptorChain = new InterceptorChain(configuration.getContentReader());
this.defaultInvocationContext = configuration;
addQueryableIndexStorageInterceptor(interceptorChain);
interceptorChain.addBeforeInterceptor(new QueryProcessorInterceptor(new SimplePlaner(),
new CriteriaBasedOptimizer()), QueryableIndexStorage.class);
}
/**
* Execute query.
*
* @param query
* Query
* @return
* @throws InvalidQueryException
* , QueryExecutionException
*/
@SuppressWarnings("unchecked")
public List<ScoredRow> execute(Query query) throws InvalidQueryException, QueryExecutionException
{
if (defaultInvocationContext == null)
{
throw new QueryExecutionException("DefaultInvocationContext can't be null");
}
return execute(query, Collections.EMPTY_MAP);
}
/**
* Execute query.
*
* @param query
* Query
* @param bindVariablesValues
* Map<String, Object>
* @return List<ScoredRow>
* @throws InvalidQueryException
* , QueryExecutionException
*/
public List<ScoredRow> execute(Query query, Map<String, Object> bindVariablesValues) throws InvalidQueryException,
QueryExecutionException
{
if (defaultInvocationContext == null)
{
throw new QueryExecutionException("DefaultInvocationContext can't be null");
}
return execute(query, bindVariablesValues, defaultInvocationContext);
}
/**
* @see org.xcmis.search.query.Searcher#execute(org.xcmis.search.model.Query,
* java.util.Map, org.xcmis.search.content.command.InvocationContext)
*/
@SuppressWarnings("unchecked")
public List<ScoredRow> execute(Query query, Map<String, Object> bindVariablesValues,
InvocationContext invocationContext) throws InvalidQueryException, QueryExecutionException
{
ProcessQueryCommand processQueryCommand = new ProcessQueryCommand(query, bindVariablesValues);
try
{
return (List<ScoredRow>)interceptorChain.invoke(invocationContext, processQueryCommand);
}
catch (Throwable e)
{
throw new InvalidQueryException(e.getLocalizedMessage(), e);
}
}
/**
* @see org.xcmis.search.Startable#start()
*/
public void start()
{
interceptorChain.start();
}
/**
* @see org.xcmis.search.Startable#stop()
*/
public void stop()
{
interceptorChain.stop();
}
/**
* @see org.xcmis.search.content.ContentModificationListener#update(org.xcmis.search.content.ContentEntry,
* java.lang.String)
*/
public void update(ContentEntry addedEntry, String removedEntry) throws IndexModificationException
{
List<ContentEntry> addedEntries = new ArrayList<ContentEntry>(1);
if (addedEntry != null)
{
addedEntries.add(addedEntry);
}
Set<String> removedSet = new HashSet<String>(1);
if (removedEntry != null)
{
removedSet.add(removedEntry);
}
update(addedEntries, removedSet);
}
/**
* @see org.xcmis.search.content.ContentModificationListener#update(java.util.List,
* java.util.Set)
*/
public void update(List<ContentEntry> addedEntries, Set<String> removedEntries) throws IndexModificationException
{
update(addedEntries, removedEntries, defaultInvocationContext);
}
/**
* @see org.xcmis.search.content.ContentModificationListener#update(java.util.List,
* java.util.Set, org.xcmis.search.content.command.InvocationContext)
*/
public void update(List<ContentEntry> addedEntries, Set<String> removedEntries, InvocationContext invocationContext)
throws IndexModificationException
{
ModifyIndexCommand modifyIndexCommand = new ModifyIndexCommand(addedEntries, removedEntries);
try
{
interceptorChain.invoke(invocationContext, modifyIndexCommand);
}
catch (IndexModificationException e)
{
throw e;
}
catch (Throwable e)
{
throw new IndexModificationException(e.getLocalizedMessage(), e);
}
}
/**
* Add interceptors that handle {@link ModifyIndexCommand} and
* {@link ExecuteSelectorCommand}
*
* @param interceptorChain
* InterceptorChain
* @throws SearchServiceException
* if error occurs
*/
protected void addQueryableIndexStorageInterceptor(InterceptorChain interceptorChain) throws SearchServiceException
{
String className = configuration.getIndexConfuguration().getQueryableIndexStorage();
try
{
Class<?> queryableIndexStorageClass = Class.forName(className);
if (QueryableIndexStorage.class.isAssignableFrom(queryableIndexStorageClass))
{
Constructor<QueryableIndexStorage> constructor =
(Constructor<QueryableIndexStorage>)queryableIndexStorageClass.getConstructor(configuration.getClass());
QueryableIndexStorage queryableIndexStorage = constructor.newInstance(configuration);
interceptorChain.addBeforeInterceptor(queryableIndexStorage, ContentReaderInterceptor.class);
}
else
{
throw new SearchServiceException(className + "is no assignable from " + QueryableIndexStorage.class);
}
}
catch (ClassNotFoundException e)
{
throw new SearchServiceException(e.getLocalizedMessage(), e);
}
catch (SecurityException e)
{
throw new SearchServiceException(e.getLocalizedMessage(), e);
}
catch (NoSuchMethodException e)
{
throw new SearchServiceException(e.getLocalizedMessage(), e);
}
catch (IllegalArgumentException e)
{
throw new SearchServiceException(e.getLocalizedMessage(), e);
}
catch (InstantiationException e)
{
throw new SearchServiceException(e.getLocalizedMessage(), e);
}
catch (IllegalAccessException e)
{
throw new SearchServiceException(e.getLocalizedMessage(), e);
}
catch (InvocationTargetException e)
{
throw new SearchServiceException(e.getTargetException());
}
}
}