/********************************************************************************** * * Copyright (c) 2003, 2004, 2007, 2008 The Sakai Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * **********************************************************************************/ package edu.indiana.lib.twinpeaks.search; import edu.indiana.lib.twinpeaks.search.*; import edu.indiana.lib.twinpeaks.util.*; import java.io.*; import java.net.*; import java.util.*; import java.text.*; import org.w3c.dom.*; public class SearchSource { private static org.apache.commons.logging.Log _log = LogUtils.getLog(SearchSource.class); /** * This source is enabled (available for use) */ private static final int ENABLED = (1 << 0); /** * Global configuration parameters */ private static HashMap _globalMap; /* * Display name & id, description, handlers, flags */ private String _name; private String _id; private String _description; private String _queryClassName; private Class _queryClass; private String _searchResultClassName; private Class _searchResultClass; private String _authority; private String _domain; private String _searchType; private String _typeDescription; private HashMap _parameterMap; private int _flags; /** * SearchSource instance */ private static ArrayList _sourceList = null; /** * SearchSource synchronization */ private static Object _sourceSync = new Object(); /** * Private constructor */ private SearchSource() { } /** * Constructor * @param name Search source name (used internally and for the pulldown menu) * @param queryClassName Query handler * @param searchResultClassName Search response handler * @param resultPageClassName User result renderer * @param url Base URL for query * @param parameterMap Custom parameters * @param flags Enabled, disabled, etc. */ private SearchSource(String name, String description, String id, String authority, String domain, String searchType, String typeDescription, String queryClassName, String searchResultClassName, HashMap parameterMap, int flags) { _name = name; _description = description; _id = id; _authority = authority; _domain = domain; _searchType = searchType; _typeDescription = typeDescription; _queryClassName = queryClassName; _searchResultClassName = searchResultClassName; _parameterMap = parameterMap; _flags = flags; _log.debug("*************** name + parameters = " + _parameterMap); } /** * Return the search source (repository) name * @return The name of this source (eg Academic Search, ERIC) */ public String getName() { return _name; } /** * Return the search source id (a unique String) * @return The name of this source (eg Academic Search */ public String getId() { return _id; } /** * Return authority information * @return The authority for this source */ public String getAuthority() { return _authority; } /** * Return search domain * @return The domain for this source (eg search) */ public String getDomain() { return _domain; } /** * Return the search type * @return The type of search (eg keyword) */ public String getSearchType() { return _searchType; } /** * Return the search type description * @return The description (eg "keyword search") */ public String getTypeDescription() { return _typeDescription; } /** * Return the search source description * @return A description of this repository */ public String getDescription() { return _description; } /** * Is this source available? * @return true (if available) */ public boolean isEnabled() { return (_flags & ENABLED) == ENABLED; } /** * Return a new QueryBase object for the specified search source. * Class loading is defered until request time. * @return A QueryBase object for this source */ public QueryBase getQueryHandler() throws java.lang.ClassNotFoundException, java.lang.InstantiationException, java.lang.IllegalAccessException { synchronized (this) { if (_queryClass == null) { _queryClass = Class.forName(_queryClassName); } return (QueryBase) _queryClass.newInstance(); } } /** * Return the query handler class name. * @return Query handler class name */ public String getQueryHandlerClassName() { synchronized (this) { return _queryClassName; } } /** * Return a new SearchResultBase object for the specified search source. * Class loading is defered until request time. * @return A SearchResultBase object for this source */ public SearchResultBase getSearchResultHandler() throws java.lang.ClassNotFoundException, java.lang.InstantiationException, java.lang.IllegalAccessException { synchronized (this) { if (_searchResultClass == null) { _searchResultClass = Class.forName(_searchResultClassName); } return (SearchResultBase) _searchResultClass.newInstance(); } } /** * Return the search result handler class name. * @return Result handler class name */ public String getSearchResultHandlerClassName() { synchronized (this) { return _searchResultClassName; } } /** * Set a global parameter from the configuration file * @param name Parameter name */ private static void setGlobalConfiguationValue(Document document, String name) { Element element; element = DomUtils.getElement(document.getDocumentElement(), name); if (element != null) { String text = element.getAttribute("name"); if (!StringUtils.isNull(text)) { _globalMap.put(name, text); } } } /** * Return a global parameter * @param name Parameter name * @return Parameter value (null if none) */ public static String getGlobalConfigurationValue(String name) { return (_globalMap == null) ? null : (String) _globalMap.get(name); } /** * Return a mandatory global configuration value * @param name The name of the cglobal configuration item * @return The configured value */ public static String getMandatoryGlobalConfigurationValue(String name) { String value = getGlobalConfigurationValue(name); if (value == null) { throw new ConfigurationException("Global configuration item \"" + name + "\" is not defined"); } return value; } /** * Return a custom parameter configured for this source * @param name Parameter name * @return Parameter value (null if none) */ public synchronized String getConfiguredParameter(String name) { return (_parameterMap == null) ? null : (String) _parameterMap.get(name); } /** * Return a custom parameter configured for this source * @param name The source name (eg ERIC) * @param parameterName Parameter to fetech * @return The parameter value (null if none) */ public static String getConfiguredParameter(String name, String parameterName) { SearchSource source = SearchSource.getSourceByName(name); return source.getConfiguredParameter(parameterName); } /** * Return a mandatory parameter for this source * @param name The source name (eg ERIC) * @param parameterName Parameter to fetech * @return The parameter value */ public static String getMandatoryParameter(String name, String parameterName) { SearchSource source = SearchSource.getSourceByName(name); String value = source.getConfiguredParameter(parameterName); if (value == null) { throw new ConfigurationException("\"" + parameterName + "\" parameter undefined for search source: " + name); } return value; } /** * Lookup a search source by name * @param name Source name * @return SearchSource object */ public static SearchSource getSourceByName(String name) { verifyList(); synchronized (_sourceSync) { for (Iterator i = _sourceList.iterator(); i.hasNext(); ) { SearchSource source = (SearchSource) i.next(); if (source.getName().equalsIgnoreCase(name)) { return source; } } } throw new ConfigurationException("Unknown search source: " + name); } /** * Get the default search source * @return The search source name */ public static String getDefaultSourceName() { verifyList(); synchronized (_sourceSync) { return ((SearchSource) _sourceList.get(0)).getName(); } } /** * Return an Iterator to the source list * @return Source list Iterator */ public static Iterator getSearchListIterator() { verifyList(); synchronized (_sourceSync) { return _sourceList.iterator(); } } /** * Create a populated <code>SearchSource</code> list. * @param xmlStream Configuration file as an InputStream */ public static void populate(InputStream xmlStream) throws DomException, SearchException { SearchSource source; NodeList sourceNodeList; Document document; int length; /* * Only set the configuration once */ _log.debug("SearchSource.populate() starts --------------------------"); synchronized (_sourceSync) { if (_sourceList != null) { _log.debug("No action required"); return; } _sourceList = new ArrayList(); _log.debug("Populating configuration"); /* * Parse the configuration file */ try { document = DomUtils.parseXmlStream(xmlStream); _log.info(DomUtils.serialize(document)); } catch (Exception exception) { _log.error("DOM parse exception"); exception.printStackTrace(); throw new RuntimeException("DOM error"); } /* * Fetch global settings - OSID version specific implementations */ _globalMap = new HashMap(); setGlobalConfiguationValue(document, "osid_20_Id_Implementation"); /* * Find our search sources (each represents an OSID Repository) */ sourceNodeList = DomUtils.getElementList(document.getDocumentElement(), "source"); length = sourceNodeList.getLength(); for (int i = 0; i < length; i++) { String sourceName, description, id; String authority, domain, searchType, typeDescription, url; String queryHandler, searchResultHandler; Element element, sourceElement; NodeList parameterList; HashMap parameterMap; int flags; sourceElement = (Element) sourceNodeList.item(i); sourceName = sourceElement.getAttribute("name"); if (StringUtils.isNull(sourceName)) { _log.warn("Skipping un-named <source> element"); continue; } /* * Search source (Repository) description and **unique** ID */ if ((description = parseHandler(sourceElement, "description")) == null) { _log.warn("Missing <description> in source \"" + sourceName + "\""); continue; } if ((id = parseHandler(sourceElement, "id")) == null) { _log.warn("Missing <id> in source \"" + sourceName + "\""); continue; } /* * Query and result handler names */ if ((queryHandler = parseHandler(sourceElement, "queryhandler")) == null) { _log.warn("Missing <queryhandler> in source \"" + sourceName + "\""); continue; } if ((searchResultHandler = parseHandler(sourceElement, "responsehandler")) == null) { _log.warn("Missing <responsehandler> in source \"" + sourceName + "\""); continue; } /* * Authority, domain, sarch type & description, URL */ if ((authority = parseHandler(sourceElement, "authority")) == null) { _log.warn("Missing <authority> in source \"" + sourceName + "\""); continue; } if ((domain = parseHandler(sourceElement, "domain")) == null) { _log.warn("Missing <domain> in source \"" + sourceName + "\""); continue; } if ((searchType = parseHandler(sourceElement, "searchtype")) == null) { _log.warn("Missing <searchtype> in source \"" + sourceName + "\""); continue; } if ((typeDescription = parseHandler(sourceElement, "searchdescription")) == null) { _log.warn("Missing <searchdescription> in source \"" + sourceName + "\""); continue; } /* * Set options: * source enabled = [true | false] */ flags = 0; if ((element = DomUtils.getElement(sourceElement, "options")) != null) { if ("true".equalsIgnoreCase(element.getAttribute("enabled"))) { flags |= ENABLED; } } /* * Custom parameters? */ parameterMap = null; if ((parameterList = DomUtils.getElementList(sourceElement, "parameter")) != null) { for (int j = 0; j < parameterList.getLength(); j++) { String name, value; element = (Element) parameterList.item(j); name = element.getAttribute("name"); value = element.getAttribute("value"); if (StringUtils.isNull(name)) { throw new SearchException( "Invalid configuration parameter, source: \"" + sourceName + "\", <parameter name=\"" + name + "\" value=\"" + value + "\">"); } if (parameterMap == null) { parameterMap = new HashMap(); } parameterMap.put(name, value); } } /* * Save this source */ addSource(new SearchSource(sourceName, description, id, authority, domain, searchType, typeDescription, queryHandler, searchResultHandler, parameterMap, flags), _sourceList); } } _log.debug("SearchSource.populate() ends --------------------------"); if (_sourceList.size() == 0) { throw new SearchException("No Repositories were configured"); } } /** * Has source list has been populated? * @return true if so */ public static boolean isSourceListPopulated() { synchronized (_sourceSync) { return !((_sourceList == null) || (_sourceList.isEmpty())); } } /* * Helpers */ /** * Locate a handler specification * @param parent Parent element for this search * @param handlerName Handler to look up * @return Class name for this handler */ private static String parseHandler(Element parent, String handlerName) { Element element; String handler; if ((element = DomUtils.getElement(parent, handlerName)) == null) { return null; } handler = element.getAttribute("name"); return (StringUtils.isNull(handler)) ? null : handler; } /** * Verify the source list has been populated */ private static void verifyList() { if (!isSourceListPopulated()) { throw new SearchException("No search handlers have ben configured"); } } /** * Add a Search source to the appropriate list * @param source SearceSource object * @param list Source list */ private static void addSource(SearchSource source, ArrayList list) { list.add(source); } private static class ConfigurationException extends RuntimeException { /** * Thrown to indicate a configuration exception * @param text Explainatory text */ public ConfigurationException(String text) { super(text); } /** * Thrown to indicate a configuration exception */ public ConfigurationException() { super(""); } } }