001: /**********************************************************************************
002: *
003: * Copyright (c) 2003, 2004 The Regents of the University of Michigan, Trustees of Indiana University,
004: * Board of Trustees of the Leland Stanford, Jr., University, and The MIT Corporation
005: *
006: * Licensed under the Educational Community License Version 1.0 (the "License");
007: * By obtaining, using and/or copying this Original Work, you agree that you have read,
008: * understand, and will comply with the terms and conditions of the Educational Community License.
009: * You may obtain a copy of the License at:
010: *
011: * http://cvs.sakaiproject.org/licenses/license_1_0.html
012: *
013: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
014: * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
015: * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
016: * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
017: * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
018: *
019: **********************************************************************************/package edu.indiana.lib.twinpeaks.search;
020:
021: import edu.indiana.lib.twinpeaks.search.*;
022: import edu.indiana.lib.twinpeaks.util.*;
023:
024: import java.io.*;
025: import java.net.*;
026: import java.util.*;
027: import java.text.*;
028:
029: import org.w3c.dom.*;
030:
031: public class SearchSource {
032:
033: private static org.apache.commons.logging.Log _log = LogUtils
034: .getLog(SearchSource.class);
035:
036: /**
037: * This source is enabled (available for use)
038: */
039: private static final int ENABLED = (1 << 0);
040: /**
041: * Global configuration parameters
042: */
043: private static HashMap _globalMap;
044: /*
045: * Display name & id, description, handlers, flags
046: */
047: private String _name;
048: private String _id;
049: private String _description;
050:
051: private String _queryClassName;
052: private Class _queryClass;
053:
054: private String _searchResultClassName;
055: private Class _searchResultClass;
056:
057: private String _authority;
058: private String _domain;
059: private String _searchType;
060: private String _typeDescription;
061:
062: private HashMap _parameterMap;
063:
064: private int _flags;
065:
066: /**
067: * SearchSource instance
068: */
069: private static ArrayList _sourceList = null;
070: /**
071: * SearchSource synchronization
072: */
073: private static Object _sourceSync = new Object();
074:
075: /**
076: * Private constructor
077: */
078: private SearchSource() {
079: }
080:
081: /**
082: * Constructor
083: * @param name Search source name (used internally and for the pulldown menu)
084: * @param queryClassName Query handler
085: * @param searchResultClassName Search response handler
086: * @param resultPageClassName User result renderer
087: * @param url Base URL for query
088: * @param parameterMap Custom parameters
089: * @param flags Enabled, disabled, etc.
090: */
091: private SearchSource(String name, String description, String id,
092: String authority, String domain, String searchType,
093: String typeDescription, String queryClassName,
094: String searchResultClassName, HashMap parameterMap,
095: int flags) {
096:
097: _name = name;
098: _description = description;
099: _id = id;
100: _authority = authority;
101: _domain = domain;
102: _searchType = searchType;
103: _typeDescription = typeDescription;
104: _queryClassName = queryClassName;
105: _searchResultClassName = searchResultClassName;
106: _parameterMap = parameterMap;
107: _flags = flags;
108:
109: _log.debug("*************** name + parameters = "
110: + _parameterMap);
111: }
112:
113: /**
114: * Return the search source (repository) name
115: * @return The name of this source (eg Academic Search, ERIC)
116: */
117: public String getName() {
118: return _name;
119: }
120:
121: /**
122: * Return the search source id (a unique String)
123: * @return The name of this source (eg Academic Search
124: */
125: public String getId() {
126: return _id;
127: }
128:
129: /**
130: * Return authority information
131: * @return The authority for this source
132: */
133: public String getAuthority() {
134: return _authority;
135: }
136:
137: /**
138: * Return search domain
139: * @return The domain for this source (eg search)
140: */
141: public String getDomain() {
142: return _domain;
143: }
144:
145: /**
146: * Return the search type
147: * @return The type of search (eg keyword)
148: */
149: public String getSearchType() {
150: return _searchType;
151: }
152:
153: /**
154: * Return the search type description
155: * @return The description (eg "keyword search")
156: */
157: public String getTypeDescription() {
158: return _typeDescription;
159: }
160:
161: /**
162: * Return the search source description
163: * @return A description of this repository
164: */
165: public String getDescription() {
166: return _description;
167: }
168:
169: /**
170: * Is this source available?
171: * @return true (if available)
172: */
173: public boolean isEnabled() {
174: return (_flags & ENABLED) == ENABLED;
175: }
176:
177: /**
178: * Return a new QueryBase object for the specified search source.
179: * Class loading is defered until request time.
180: * @return A QueryBase object for this source
181: */
182: public QueryBase getQueryHandler()
183: throws java.lang.ClassNotFoundException,
184: java.lang.InstantiationException,
185: java.lang.IllegalAccessException {
186: synchronized (this ) {
187: if (_queryClass == null) {
188: _queryClass = Class.forName(_queryClassName);
189: }
190: }
191: return (QueryBase) _queryClass.newInstance();
192: }
193:
194: /**
195: * Return the query handler class name.
196: * @return Query handler class name
197: */
198: public String getQueryHandlerClassName() {
199: synchronized (this ) {
200: return _queryClassName;
201: }
202: }
203:
204: /**
205: * Return a new SearchResultBase object for the specified search source.
206: * Class loading is defered until request time.
207: * @return A SearchResultBase object for this source
208: */
209: public SearchResultBase getSearchResultHandler()
210: throws java.lang.ClassNotFoundException,
211: java.lang.InstantiationException,
212: java.lang.IllegalAccessException {
213: synchronized (this ) {
214: if (_searchResultClass == null) {
215: _searchResultClass = Class
216: .forName(_searchResultClassName);
217: }
218: }
219: return (SearchResultBase) _searchResultClass.newInstance();
220: }
221:
222: /**
223: * Return the search result handler class name.
224: * @return Result handler class name
225: */
226: public String getSearchResultHandlerClassName() {
227: synchronized (this ) {
228: return _searchResultClassName;
229: }
230: }
231:
232: /**
233: * Set a global parameter from the configuration file
234: * @param name Parameter name
235: */
236: private static void setGlobalConfiguationValue(Document document,
237: String name) {
238: Element element;
239:
240: element = DomUtils.getElement(document.getDocumentElement(),
241: name);
242: if (element != null) {
243: String text = element.getAttribute("name");
244:
245: if (!StringUtils.isNull(text)) {
246: _globalMap.put(name, text);
247: }
248: }
249: }
250:
251: /**
252: * Return a global parameter
253: * @param name Parameter name
254: * @return Parameter value (null if none)
255: */
256: public static String getGlobalConfigurationValue(String name) {
257: return (_globalMap == null) ? null : (String) _globalMap
258: .get(name);
259: }
260:
261: /**
262: * Return a mandatory global configuration value
263: * @param name The name of the cglobal configuration item
264: * @return The configured value
265: */
266: public static String getMandatoryGlobalConfigurationValue(
267: String name) {
268: String value = getGlobalConfigurationValue(name);
269:
270: if (value == null) {
271: throw new ConfigurationException(
272: "Global configuration item \"" + name
273: + "\" is not defined");
274: }
275: return value;
276: }
277:
278: /**
279: * Return a custom parameter configured for this source
280: * @param name Parameter name
281: * @return Parameter value (null if none)
282: */
283: public synchronized String getConfiguredParameter(String name) {
284: return (_parameterMap == null) ? null : (String) _parameterMap
285: .get(name);
286: }
287:
288: /**
289: * Return a custom parameter configured for this source
290: * @param name The source name (eg ERIC)
291: * @param parameterName Parameter to fetech
292: * @return The parameter value (null if none)
293: */
294: public static String getConfiguredParameter(String name,
295: String parameterName) {
296: SearchSource source = SearchSource.getSourceByName(name);
297:
298: return source.getConfiguredParameter(parameterName);
299: }
300:
301: /**
302: * Return a mandatory parameter for this source
303: * @param name The source name (eg ERIC)
304: * @param parameterName Parameter to fetech
305: * @return The parameter value
306: */
307: public static String getMandatoryParameter(String name,
308: String parameterName) {
309: SearchSource source = SearchSource.getSourceByName(name);
310: String value = source.getConfiguredParameter(parameterName);
311:
312: if (value == null) {
313: throw new ConfigurationException("\"" + parameterName
314: + "\" parameter undefined for search source: "
315: + name);
316: }
317: return value;
318: }
319:
320: /**
321: * Lookup a search source by name
322: * @param name Source name
323: * @return SearchSource object
324: */
325: public static SearchSource getSourceByName(String name) {
326:
327: verifyList();
328:
329: synchronized (_sourceSync) {
330: for (Iterator i = _sourceList.iterator(); i.hasNext();) {
331: SearchSource source = (SearchSource) i.next();
332:
333: if (source.getName().equalsIgnoreCase(name)) {
334: return source;
335: }
336: }
337: }
338: throw new ConfigurationException("Unknown search source: "
339: + name);
340: }
341:
342: /**
343: * Get the default search source
344: * @return The search source name
345: */
346: public static String getDefaultSourceName() {
347: verifyList();
348:
349: synchronized (_sourceSync) {
350: return ((SearchSource) _sourceList.get(0)).getName();
351: }
352: }
353:
354: /**
355: * Return an Iterator to the source list
356: * @return Source list Iterator
357: */
358: public static Iterator getSearchListIterator() {
359: verifyList();
360:
361: synchronized (_sourceSync) {
362: return _sourceList.iterator();
363: }
364: }
365:
366: /**
367: * Create a populated <code>SearchSource</code> list.
368: * @param xmlStream Configuration file as an InputStream
369: */
370: public static void populate(InputStream xmlStream)
371: throws DomException, SearchException {
372: SearchSource source;
373: NodeList sourceNodeList;
374: Document document;
375: int length;
376:
377: /*
378: * Only set the configuration once
379: */
380: _log
381: .debug("SearchSource.populate() starts --------------------------");
382:
383: synchronized (_sourceSync) {
384: if (_sourceList != null) {
385: _log.debug("No action required");
386: return;
387: }
388: _sourceList = new ArrayList();
389: _log.debug("Populating configuration");
390: /*
391: * Parse the configuration file
392: */
393: try {
394: document = DomUtils.parseXmlStream(xmlStream);
395: _log.info(DomUtils.serialize(document));
396: } catch (Exception exception) {
397: _log.error("DOM parse exception");
398: exception.printStackTrace();
399: throw new RuntimeException("DOM error");
400: }
401: /*
402: * Fetch global settings - OSID version specific implementations
403: */
404: _globalMap = new HashMap();
405:
406: setGlobalConfiguationValue(document,
407: "osid_20_Id_Implementation");
408:
409: /*
410: * Find our search sources (each represents an OSID Repository)
411: */
412: sourceNodeList = DomUtils.getElementList(document
413: .getDocumentElement(), "source");
414: length = sourceNodeList.getLength();
415:
416: for (int i = 0; i < length; i++) {
417: String sourceName, description, id;
418: String authority, domain, searchType, typeDescription, url;
419: String queryHandler, searchResultHandler;
420: Element element, sourceElement;
421: NodeList parameterList;
422: HashMap parameterMap;
423: int flags;
424:
425: sourceElement = (Element) sourceNodeList.item(i);
426: sourceName = sourceElement.getAttribute("name");
427:
428: if (StringUtils.isNull(sourceName)) {
429: _log.warn("Skipping un-named <source> element");
430: continue;
431: }
432: /*
433: * Search source (Repository) description and **unique** ID
434: */
435: if ((description = parseHandler(sourceElement,
436: "description")) == null) {
437: _log.warn("Missing <description> in source \""
438: + sourceName + "\"");
439: continue;
440: }
441:
442: if ((id = parseHandler(sourceElement, "id")) == null) {
443: _log.warn("Missing <id> in source \"" + sourceName
444: + "\"");
445: continue;
446: }
447: /*
448: * Query and result handler names
449: */
450: if ((queryHandler = parseHandler(sourceElement,
451: "queryhandler")) == null) {
452: _log.warn("Missing <queryhandler> in source \""
453: + sourceName + "\"");
454: continue;
455: }
456:
457: if ((searchResultHandler = parseHandler(sourceElement,
458: "responsehandler")) == null) {
459: _log.warn("Missing <responsehandler> in source \""
460: + sourceName + "\"");
461: continue;
462: }
463: /*
464: * Authority, domain, sarch type & description, URL
465: */
466: if ((authority = parseHandler(sourceElement,
467: "authority")) == null) {
468: _log.warn("Missing <authority> in source \""
469: + sourceName + "\"");
470: continue;
471: }
472:
473: if ((domain = parseHandler(sourceElement, "domain")) == null) {
474: _log.warn("Missing <domain> in source \""
475: + sourceName + "\"");
476: continue;
477: }
478:
479: if ((searchType = parseHandler(sourceElement,
480: "searchtype")) == null) {
481: _log.warn("Missing <searchtype> in source \""
482: + sourceName + "\"");
483: continue;
484: }
485:
486: if ((typeDescription = parseHandler(sourceElement,
487: "searchdescription")) == null) {
488: _log
489: .warn("Missing <searchdescription> in source \""
490: + sourceName + "\"");
491: continue;
492: }
493: /*
494: * Set options:
495: * source enabled = [true | false]
496: */
497: flags = 0;
498: if ((element = DomUtils.getElement(sourceElement,
499: "options")) != null) {
500:
501: if ("true".equalsIgnoreCase(element
502: .getAttribute("enabled"))) {
503: flags |= ENABLED;
504: }
505: }
506: /*
507: * Custom parameters?
508: */
509: parameterMap = null;
510: if ((parameterList = DomUtils.getElementList(
511: sourceElement, "parameter")) != null) {
512:
513: for (int j = 0; j < parameterList.getLength(); j++) {
514: String name, value;
515:
516: element = (Element) parameterList.item(j);
517: name = element.getAttribute("name");
518: value = element.getAttribute("value");
519:
520: if (StringUtils.isNull(name)) {
521: throw new SearchException(
522: "Invalid configuration parameter, source: \""
523: + sourceName
524: + "\", <parameter name=\""
525: + name + "\" value=\""
526: + value + "\">");
527: }
528:
529: if (parameterMap == null) {
530: parameterMap = new HashMap();
531: }
532: parameterMap.put(name, value);
533: }
534: }
535: /*
536: * Save this source
537: */
538: addSource(new SearchSource(sourceName, description, id,
539: authority, domain, searchType, typeDescription,
540: queryHandler, searchResultHandler,
541: parameterMap, flags), _sourceList);
542: }
543: }
544:
545: _log
546: .debug("SearchSource.populate() ends --------------------------");
547:
548: if (_sourceList.size() == 0) {
549: throw new SearchException("No Repositories were configured");
550: }
551: }
552:
553: /**
554: * Has source list has been populated?
555: * @return true if so
556: */
557: public static boolean isSourceListPopulated() {
558: synchronized (_sourceSync) {
559: return !((_sourceList == null) || (_sourceList.isEmpty()));
560: }
561: }
562:
563: /*
564: * Helpers
565: */
566:
567: /**
568: * Locate a handler specification
569: * @param parent Parent element for this search
570: * @param handlerName Handler to look up
571: * @return Class name for this handler
572: */
573: private static String parseHandler(Element parent,
574: String handlerName) {
575: Element element;
576: String handler;
577:
578: if ((element = DomUtils.getElement(parent, handlerName)) == null) {
579: return null;
580: }
581:
582: handler = element.getAttribute("name");
583: return (StringUtils.isNull(handler)) ? null : handler;
584: }
585:
586: /**
587: * Verify the source list has been populated
588: */
589: private static void verifyList() {
590: if (!isSourceListPopulated()) {
591: throw new SearchException(
592: "No search handlers have ben configured");
593: }
594: }
595:
596: /**
597: * Add a Search source to the appropriate list
598: * @param source SearceSource object
599: * @param list Source list
600: */
601: private static void addSource(SearchSource source, ArrayList list) {
602: list.add(source);
603: }
604:
605: private static class ConfigurationException extends
606: RuntimeException {
607: /**
608: * Thrown to indicate a configuration exception
609: * @param text Explainatory text
610: */
611: public ConfigurationException(String text) {
612: super (text);
613: }
614:
615: /**
616: * Thrown to indicate a configuration exception
617: */
618: public ConfigurationException() {
619: super ("");
620: }
621: }
622: }
|