All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.indiana.lib.twinpeaks.search.singlesearch.musepeer.Query Maven / Gradle / Ivy

There is a newer version: 23.3
Show newest version
/**********************************************************************************
 *
 * Copyright (c) 2006, 2007, 2008, 2009 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.singlesearch.musepeer;

import edu.indiana.lib.twinpeaks.search.*;
import edu.indiana.lib.twinpeaks.search.singlesearch.CqlParser;
import edu.indiana.lib.twinpeaks.util.*;

import java.util.*;

import lombok.extern.slf4j.Slf4j;
import org.w3c.dom.*;

/**
 * Send a query to the Musepeer  interface
 */
@Slf4j
public class Query extends HttpTransactionQueryBase
{
	/**
	 * Records displayed "per page"
	 */
	public static final String RECORDS_PER_PAGE = "10";
	/**
	 * Records to fetch from each search target
	 */
	private static final String RECORDS_PER_TARGET = "50";
	/**
	 * Unique name for this search application
	 */
	private final String APPLICATION = SessionContext.uniqueSessionName(this);
	/**
	 * Error text: No logged-in session
	 */
	private static final String NO_SESSION = "Not logged on. Please logon first.";
	/**
	 * Database for this request
	 */
	private String 	_database;
	/**
	 * Muse syntax search criteria for this request (see parseRequest())
	 */
	private String _museSearchString;
  /**
   * Total number of target databases
   */
  private int _targetCount;
	/**
	 * Local ID (for the current transaction)
	 */
	private long _transactionId;

	/**
	 * Constructor
	 */
	public Query()
	{
		super();
	}

	/**
	 * Parse user request parameters.
	 * @param parameterMap Request details (name=value pairs)
	 */
	public void parseRequest(Map parameterMap)
	{
		String action, searchCriteria;

		super.parseRequest(parameterMap);
		/*
		 * These cannot be null by the time we get here
		 */
		if ((getRequestParameter("guid") == null)   ||
				(getRequestParameter("url")  == null))
		{
			throw new IllegalArgumentException("Missing GUID or URL");
		}

		action = getRequestParameter("action");
		log.debug("*** Beginning action: " + action);

		if ("startsearch".equalsIgnoreCase(action))
		{
			if ((getRequestParameter("targets")   == null)  ||
					(getRequestParameter("username")  == null)  ||
					(getRequestParameter("password")  == null))
			{
				throw new IllegalArgumentException("Missing target list, username, or password");
			}
  		/*
  		 * Now deal with the search criteria (CQL syntax)
  		 */
      searchCriteria = parseCql(getRequestParameter("searchString"));
  		getSessionContext().put("SEARCH_QUERY", searchCriteria);
    }
  }

	/**
	 * Search
	 */
	public void doQuery()
	{
		Document 	document;
		String		action;
		/*
		 * Get the logical "database" (a name for the configuration for this search)
		 */
		_database = getRequestParameter("database");
		/*
		 * We'll manage redirects, and submit with POST
		 */
		setRedirectBehavior(REDIRECT_MANAGED);
		setQueryMethod(METHOD_POST);
		/*
		 * Save the URL and query text
		 */
		setUrl(getRequestParameter("url"));
		setSearchString(getSearchString());
		/*
		 * New search?
		 */
		action = getRequestParameter("action");
		if (action.equalsIgnoreCase("startSearch"))
		{
		  /*
		   * Initialize a new search context block.  Augment the standard
		   * (synchronous) initialization with the necessary asynchronous
		   * setup (an asynchronous search with initialization in progress).
		   */
			StatusUtils.initialize(getSessionContext(), getRequestParameter("targets"));
			StatusUtils.setAsyncSearch(getSessionContext());
			StatusUtils.setAsyncInit(getSessionContext());
			/*
			 * LOGOFF any previous session
			 */
			clearParameters();

			doLogoffCommand();
			submit();

			setSessionId(null);
			displayXml("Logoff", getResponseDocument());
			/*
			 * LOGON
			 */
			clearParameters();

			doLogonCommand();
			submit();

			displayXml("Login", getResponseDocument());
			validateResponse("LOGON");
			/*
			 * SEARCH - on success, set a PROGRESS command time limit (in seconds)
			 */
			clearParameters();

			doSearchCommand();
			submit();

			displayXml("Search", getResponseDocument());
			validateResponse("SEARCH");

			setSearchStatusTimeout(60);
			return;
		}
    /*
     * PROGRESS
     *
     * Still doing asynchronous initialization?  If so, pick up the search
     * status.  Throw "no assets ready" (to try again) if the estimates aren't
     * available yet...
     */
    if (StatusUtils.doingAsyncInit(getSessionContext()))
    {
    	clearParameters();

  		doProgressCommand();
  	  submit();
  		displayXml("Progress command done", getResponseDocument());

  		validateResponse("PROGRESS");
		  if (!setSearchStatus(getResponseDocument()))
			{
			  throw new SearchException(SearchException.ASSET_NOT_READY);
			}
      /*
       * Asynchronous initialization is finished now.  If we found no hits,
       * loop one more time to let hasNextAsset() reflect that fact...
       */
 	    StatusUtils.clearAsyncInit(getSessionContext());

			if (StatusUtils.getActiveTargetCount(getSessionContext()) == 0)
			{
			  throw new SearchException(SearchException.ASSET_NOT_READY);
			}
		}
		/*
		 * NEXT or MORE
		 *
		 * Fetch additional results
		 */
		doResultsCommand();
		displayXml("Results", getResponseDocument());
	}

	/*
	 * MusePeer API Helpers
	 */

	/**
	 * Generate a LOGON command
	 */
	private void doLogonCommand() throws SearchException
	{
		String 	username, password;

		username = getRequestParameter("username");
		password = getRequestParameter("password");

		log.debug("Logging in as \"" + username + "\"");

		setParameter("action", "logon");

		setParameter("userID", username);
		setParameter("userPwd", password);

		setParameter("templateFile", "xml/index.xml");
		setParameter("errorTemplate", "xml/error.xml");
	}

	/**
	 * Generate a LOGOFF command
	 */
	private void doLogoffCommand() throws SearchException
	{
		setParameter("action", "logoff");
		setParameter("sessionID", getSessionId());

		setParameter("templateFile", "xml/index.xml");
		setParameter("errorTemplate", "xml/error.xml");
	}

	/**
	 * Generate a SEARCH command
	 */
	private void doSearchCommand() throws SearchException
	{
		StringTokenizer targetParser;
		String searchCriteria, searchFilter, targets;
		String pageSize, sessionId, sortBy;
		/*
		 * Set search criteria
		 */
		searchCriteria = getSearchString();
		log.debug("Search criteria: " + searchCriteria);
		/*
		 * Generate the search command
		 */
		setParameter("action",    "search");
		setParameter("xml",       "true");
		setParameter("sessionID", getSessionId());

		setParameter("queryStatement", searchCriteria);

		targets       = getRequestParameter("targets");
		targetParser  = new StringTokenizer(targets);
		_targetCount  = targetParser.countTokens();

		while (targetParser.hasMoreTokens())
		{
      String target  = targetParser.nextToken();

		  setParameter("dbList", target);

 			log.debug("SEARCH: added DB " + target);
		}
    /*
     * Start an asynchronous query (no results are returned) to set things up
     * for a subsequent PROGRESS (status) command
     */
    setParameter("limitsMaxPerSource", getPageSize());
		setParameter("limitsMaxPerPage",   "0");
    /*
     * Formatting
     */
		setFormattingFiles();
	}

	/**
	 * Generate a PROGRESS (status) command
	 */
	private void doProgressCommand() throws SearchException
	{
		setParameter("action",  "progress");
		setParameter("xml",     "true");

		setParameter("sessionID", getSessionId());
		setParameter("searchReferenceID", getReferenceId());

		setParameter("errorTemplate", "xml/error.xml");
		setParameter("errorFormat",   "error2XML.xsl");
	}

	/**
	 * Generate a pagination (NEXT or PREVIOUS) command
	 * @param page Pagination (next | previous)
	 * @param firstRecord First record to retrieve
	 */
	private void doPaginationCommand(String page, int firstRecord, int pageSize)
	{
    String    start = Integer.toString(firstRecord);
    String    total = Integer.toString(firstRecord - 1);

		log.debug("Using result set name \"" + getResultSetName() + "\"");
    /*
     * Action, identification
     */
		setParameter("action", page);
		setParameter("xml",   "true");

		setParameter("sessionID", getSessionId());
		setParameter("searchReferenceID", getReferenceId());
		setParameter("resultSet", getResultSetName());
    /*
     * Active database(s)
     */
    setDbList();
    /*
     * Record, page, host requirements
     */
    log.debug("PAGE: start = " + start
                +     ", first = " + start
                +     ", total = " + total
                +     ", pageSize = " + pageSize);

		setParameter("start", start);
		setParameter("firstRetrievedRecord", start);
  	setParameter("limitsMaxPerSource", String.valueOf(pageSize));
		setParameter("limitsMaxPerPage", String.valueOf(pageSize));
    /*
     * Formatting
     */
		setFormattingFiles();
	}

	/**
	 * Generate a MORE data command
	 * @param firstRecord First record to retrieve
	 * @param pageSize The number of results we want
	 */
	private void doMoreCommand(int firstRecord, int pageSize, int totalRemaining)
	{
    String    start = Integer.toString(firstRecord);
    String    first = Integer.toString(firstRecord - Math.min(pageSize, totalRemaining)); // pageSize);
    String    total = Integer.toString(firstRecord - 1);
    String    limit = Integer.toString(Math.min(pageSize, totalRemaining));

		log.debug("MORE: using result set name \"" + getResultSetName() + "\"");
		log.debug("MORE: queryStatement = " + getSearchString());

    /*
     * Action, identification
     */
		setParameter("action", "more");
		setParameter("actionType", "SEARCH");
		setParameter("xml", "true");

		setParameter("sessionID", getSessionId());
		setParameter("searchReferenceID", getReferenceId());
		setParameter("resultSet", getResultSetName());

		setParameter("queryStatement", getSearchString());
    /*
     * Active database(s)
     */
    setDbList();
    /*
     * Record, page, host requirements
     */
    log.debug("MORE: start = " + start
                +     ", first = " + first
                +     ", total = " + total
                +     ", pageSize = " + pageSize
                +     ", remaining = " + totalRemaining
                +     ", page limit = " + limit);

		setParameter("start", start);
    setParameter("firstRetrievedRecord", first);
    setParameter("limitsMaxPerSource", limit);
  	setParameter("limitsMaxPerPage", limit);
    /*
     * Formatting
     */
		setFormattingFiles();
  }

	/**
	 * Fetch more results
	 */
	private void doResultsCommand() throws SearchException
	{
 		int start           = getSessionContext().getInt("startRecord");
    int pageSize        = Integer.parseInt(getPageSize());
    int totalRemaining  = StatusUtils.getAllRemainingHits(getSessionContext());


    log.debug(pageSize + " VS " + totalRemaining);
    /*
     * The first page of results?
     */
    if (start == 1)
    {
      /*
       * Reduce requested page size to match the remaining result count and
       * fetch the results ...
       */
   	  clearParameters();
   		doPaginationCommand("previous", start,  Math.min(pageSize, totalRemaining));

   		submit();
   		validateResponse("PREVIOUS");
      return;
    }
    /*
     * The normal case, use MORE to pick up the results
     */
	  clearParameters();
	  doMoreCommand(start, pageSize, totalRemaining);

    submit();
    validateResponse("MORE");
	}

  /*
   * Helpers
   */

  /**
   * Set up the list of common server-side formatting files
   */
  private void setFormattingFiles()
  {
		setParameter("recordFormat",    "raw.xsl");
		setParameter("headerTemplate",  "xml/list-header.xml");
		setParameter("footerTemplate",  "xml/list-footer.xml");
		setParameter("errorTemplate",   "xml/error.xml");
		setParameter("errorFormat",     "error2XML.xsl");
  }

  /**
   * Set up the active database parameter(s) for MORE, NEXT, PREVIOUS
   */
  private void setDbList()
  {
    Iterator targetIterator;
    int count;

		targetIterator = StatusUtils.getStatusMapEntrySetIterator(getSessionContext());
		count = 0;

		while (targetIterator.hasNext())
		{
			Map.Entry entry   = (Map.Entry) targetIterator.next();
      String    target  = (String) entry.getKey();

		  Map       map     = StatusUtils.getStatusMapForTarget(getSessionContext(), target);
		  String    status  = (String) map.get("STATUS");

      if ("ACTIVE".equals(status))
      {
			  setParameter("dbList", target);
        count++;
      }
		}
		log.debug(count + " active database(s)");
  }

	/**
	 * Initial response validation and command cleanup/post-processing activities.
	 * 
    *
  • Verify the response format (an ERROR?) *
  • If no error, perform any cleanup required for the command in question *
*

* @param action Server activity (SEARCH, LOGON, etc) */ private void validateResponse(String action) throws SearchException { Document document; Element element; String message, errorText; log.debug("VALIDATE: " + action); /* * Verify this response corresponds to anticipated server activity */ document = getResponseDocument(); element = document.getDocumentElement(); if ((element!= null) && (element.getTagName().equals(action))) { /* * Success - handle any post-processing required for this action */ if (action.equals("LOGON")) { String sessionId; /* * We just logged in. Save the session ID. */ element = DomUtils.getElement(element, "SESSION_ID"); sessionId = DomUtils.getText(element); setSessionId(sessionId); log.debug("Saved Muse session ID \"" + sessionId + "\""); return; } if (action.equals("SEARCH") || action.equals("MORE")) { Element searchElement; String id; /* * A search (or "more results") command. Save the reference ID. */ searchElement = element; element = DomUtils.getElement(element, "REFERENCE_ID"); id = DomUtils.getText(element); setReferenceId(id); log.debug("Saved search reference ID \"" + getReferenceId() + "\""); /* * For the initial search, save the result set name as well. */ if (action.equals("SEARCH")) { element = DomUtils.getElement(searchElement, "RESULT_SET_NAME"); id = DomUtils.getText(element); setResultSetName(id); log.debug("Saved result set name \"" + getResultSetName() + "\""); } return; } /* * No cleanup activities for this action */ log.debug("No \"cleanup\" activities implemented for " + action); return; } /* * An error - see if we can decipher it */ element = document.getDocumentElement(); if ((element != null) && (element.getTagName().equals("ERROR"))) { element = DomUtils.getElement(element, "MESSAGE"); } if (element == null) { errorText = action + ": Unexpected document format"; log.debug("{} {}", errorText, document); StatusUtils.setGlobalError(getSessionContext(), errorText); throw new SearchException(errorText); } /* * Format and log the error */ message = DomUtils.getText(element); errorText = action + " error: " + (StringUtils.isNull(message) ? "*unknown*" : message); log.debug("{} {}", errorText, document); /* * Session timeout is a special case * * -- Re-initialize (clear the query URL) * -- Set "global failure" status * -- Throw the timeout exception */ if (message.endsWith(NO_SESSION)) { removeQueryUrl(APPLICATION); StatusUtils.setGlobalError(getSessionContext(), "Session timed out"); throw new SessionTimeoutException(); } /* * Set final status, abort */ StatusUtils.setGlobalError(getSessionContext(), errorText); throw new SearchException(errorText); } /** * Save the current search status (estimated hits, etc.) as session * context information (status obtained by the PROGRESS command). * * @param document Server response * @param rootElement Document root * @return true If all targets have responded */ private boolean setSearchStatus(Document document) throws SearchException { Element rootElement = document.getDocumentElement(); NodeList nodeList = DomUtils.getElementList(rootElement, "ITEM"); Element statusElement = DomUtils.getElement(rootElement, "STATUS"); String status = "0"; String finalStatus = "unknown"; boolean timedOut = searchTimedOut(); int targetCount = nodeList.getLength(); int active = 0; int total = 0; int totalHits = 0; int complete = 0; /* * Update the status map for each target */ for (int i = 0; i < targetCount; i++) { Element recordElement = (Element) nodeList.item(i); HashMap map; String text, target; Element element; int estimate, hits; /* * Look for the target (database name) */ element = DomUtils.selectFirstElementByAttributeValue(recordElement, "ENTRY", "key", "targetID"); target = DomUtils.getText(element); map = StatusUtils.getStatusMapForTarget(getSessionContext(), target); /* * Get the current search status (we show this as "percent complete") */ element = DomUtils.selectFirstElementByAttributeValue(recordElement, "ENTRY", "key", "status"); if ((status = DomUtils.getText(element)) == null) { status = "0"; /* * No status value; if the search will never start, mark it complete */ element = DomUtils.selectFirstElementByAttributeValue(recordElement, "ENTRY", "key", "message"); if ((text = DomUtils.getText(element)) != null) { if ("Not Started".equals(text)) { status = "100"; } } } /* * Find the estimated match count */ element = DomUtils.selectFirstElementByAttributeValue(recordElement, "ENTRY", "key", "estimate"); if ((text = DomUtils.getText(element)) == null) { text = "0"; } estimate = Integer.parseInt(text); /* * Any hits? (unused for now) */ /******************************************************************************* element = DomUtils.selectFirstElementByAttributeValue(recordElement, "ENTRY", "key", "hits"); if ((text = DomUtils.getText(element)) == null) { text = "0"; } hits = Integer.parseInt(text); *******************************************************************************/ /* * Add this estimate to the grand total. * * Do we need to check for? * * (hits > 0) * (status.equals("100")) */ map.put("ESTIMATE", "0"); map.put("STATUS", "DONE"); if (estimate > 0) { map.put("ESTIMATE", String.valueOf(estimate)); total += estimate; map.put("STATUS", "ACTIVE"); active++; status = "100"; } /* * Is this search complete? */ map.put("PERCENT_COMPLETE", status); if ("100".equals(status)) { complete++; } log.debug("****** Target: " + target + ", status = " + status + ", all searches complete? " + (complete == targetCount) + ", timedout? " + timedOut); } /* * Save in session context: * * -- The largest number of records we could possibly return * -- The count of "in progress" searches */ getSessionContext().putInt("TOTAL_ESTIMATE", total); getSessionContext().putInt("maxRecords", total); getSessionContext().putInt("active", active); /* * Determine final status */ finalStatus = "not finished"; if (statusElement != null) { String commandStatus = DomUtils.getText(statusElement); if ("1".equals(commandStatus)) { finalStatus = "complete"; } } return (finalStatus.equals("complete") || timedOut); } /* * Search status (PROGRESS command) timers */ private static final long ONE_SECOND = 1000; private long _timeLimit; /** * Set the search status timout * @param numberOfSeconds Seconds (from now) until the search times out */ private void setSearchStatusTimeout(long numberOfSeconds) { _timeLimit = System.currentTimeMillis() + (numberOfSeconds * ONE_SECOND); } /** * Has the current search timed out? */ private boolean searchTimedOut() { return System.currentTimeMillis() >= _timeLimit; } /* * Getters & setters */ /** * Get the number of requested search targets (databases) * @return The count of target DBs */ private int getTargetCount() { return _targetCount; } /** * Determine the page size (the number of results to request from the server) * @return The page size (as a String) */ private String getPageSize() { return "30"; } /** * Fetch the Muse session ID * @return The session ID (null until a Muse LOGON has taken place) */ private String getSessionId() { return (String) getSessionContext().get("SESSION_ID"); } /** * Set the Muse session ID * @param sessionId The session ID */ private void setSessionId(String sessionId) { getSessionContext().put("SESSION_ID", sessionId); } /** * Fetch the search reference id * @return The reference id (null until a search is done) */ private String getReferenceId() { return (String) getSessionContext().get("REFERENCE_ID"); } /** * Save the Muse search reference * @param referenceNumber The reference number for this search */ private void setReferenceId(String referenceNumber) { getSessionContext().put("REFERENCE_ID", referenceNumber); } /** * Fetch the search result set name * @return the default result set for this search */ private String getResultSetName() { return (String) getSessionContext().get("RESULT_SET_NAME"); } /** * Save the name of the search result set * @param name The result set name */ private void setResultSetName(String name) { getSessionContext().put("RESULT_SET_NAME", name); } /** * Fetch the (Muse format) search string (overrides HttpTransactionQueryBase) * @return The native Muse query text */ public String getSearchString() { return (String) getSessionContext().get("SEARCH_QUERY"); } /* * Helpers */ /** * Parse CQL search queries into a crude take on the Muse format. * @param cql String containing a cql query * @return Muse search criteria */ private String parseCql(String cql) throws IllegalArgumentException { CqlParser parser; String result; log.debug( "Initial CQL Criteria: " + cql ); parser = new CqlParser(); result = parser.doCQL2MetasearchCommand(cql); log.debug("Processed Result: " + result); return result; } /** * Get an element from the server response * @Element parent Look for named element here * @param elementName Element name * @return The first occurance of the named element (null if none) */ private Element getElement(Element parent, String elementName) { try { Element root = parent; if (root == null) { root = getResponseDocument().getDocumentElement(); } return DomUtils.getElement(root, elementName); } catch (Exception exception) { throw new SearchException(exception.toString()); } } /** * Get an element from the server response (search from document root) * @param elementName Element name * @return The first occurance of the named element (null if none) */ private Element getElement(String elementName) { return getElement(null, elementName); } /** * Debugging: Display XML (write a Document or Element to the log) * * @param text Label text for this document * @param xmlObject XML Document or Element to display */ private void displayXml(String text, Object xmlObject) { log.debug("{} {}", text, xmlObject); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy