/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/citations/trunk/citations-osid/xserver/src/java/org/sakaibrary/xserver/XServer.java $
* $Id: XServer.java 105079 2012-02-24 23:08:11Z ottenhoff@longsight.com $
***********************************************************************************
*
* Copyright (c) 2006, 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 org.sakaibrary.xserver;
//Util imports
import java.util.ArrayList;
//I/O imports
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
//URL/Network Connectivity imports
import java.net.MalformedURLException;
import java.net.URL;
import java.net.HttpURLConnection;
//SAX XML parsing imports
import org.sakaibrary.osid.repository.xserver.SearchStatusProperties;
import org.sakaibrary.xserver.session.MetasearchSession;
import org.sakaibrary.xserver.session.MetasearchSessionManager;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.ParserConfigurationException;
public class XServer extends DefaultHandler {
//
// Number of records the Xserver should fetch at one time
//
// The is relevant for "merge_more/merge_more_set" activities
//
public static final int XSERVER_RECORDS_TO_FETCH = 10;
// debugging
boolean printXML = false;
/* constants */
private static final org.apache.commons.logging.Log LOG =
org.apache.commons.logging.LogFactory.getLog(
"org.sakaibrary.xserver.XServer" );
private static final String XSLT_FILE = "/xsl/xserver2sakaibrary.xsl";
/* fields coming from searchProperties */
private String guid; // required
private String username; // required
private String password; // required
private String xserverBaseUrl; // required
private ArrayList searchSourceIds; // required
private String sortBy;
private Integer pageSize;
private Integer startRecord;
/* session variables */
private MetasearchSessionManager msm;
private String sessionId;
private String foundGroupNumber;
private String mergedGroupNumber;
private String setNumber;
/* other member variables */
// findResultSets keeps track of all result sets found
private ArrayList findResultSets;
// check authorization from X-server
private String auth;
// SAXParser variables
private SAXParser saxParser;
// text buffer to hold SAXParser character data
private StringBuilder textBuffer;
// create parser flags
private boolean parsingMergeSort = false;
// merge control flag
private boolean singleSearchSource;
//--------------
// Constructor -
//--------------
/**
* Creates a new XServer object ready to communicate with the
* MetaLib X-server. Reads searchProperties, sets up SAX Parser, and
* sets up session management for this object.
*/
public XServer( String guid )
throws XServerException {
this.guid = guid;
// setup the SAX parser
SAXParserFactory factory;
factory = SAXParserFactory.newInstance();
factory.setNamespaceAware( true );
try {
saxParser = factory.newSAXParser();
} catch (SAXException sxe) {
// Error generated by this application
// (or a parser-initialization error)
Exception x = sxe;
if (sxe.getException() != null) {
x = sxe.getException();
}
LOG.warn( "XServer() SAX exception in trying to get a new SAXParser " +
"from SAXParserFactory: " + sxe.getMessage(), x );
throw new RuntimeException( "XServer() SAX exception: " + sxe.getMessage(), x );
} catch (ParserConfigurationException pce) {
// Parser with specified options can't be built
LOG.warn( "XServer() SAX parser cannot be built with specified options" );
throw new RuntimeException( "XServer() SAX parser cannot be built with " +
"specified options: " + pce.getMessage(), pce );
}
// load session state
msm = MetasearchSessionManager.getInstance();
MetasearchSession metasearchSession = msm.getMetasearchSession( guid );
if( metasearchSession == null ) {
// bad state management
throw new RuntimeException( "XServer() - ehcache MetasearchSession is " +
"NULL :: guid is " + guid );
}
// get X-Server base URL
xserverBaseUrl = metasearchSession.getBaseUrl();
if( !metasearchSession.isLoggedIn() ) {
// need to login
username = metasearchSession.getUsername();
password = metasearchSession.getPassword();
if( !loginURL( username, password ) ) {
// authorization failed
throw new XServerException( "XServer.loginURL()",
"authorization failed." );
}
// login success
metasearchSession.setLoggedIn( true );
metasearchSession.setSessionId( sessionId );
}
// get search properties
org.osid.shared.Properties searchProperties = metasearchSession.
getSearchProperties();
try {
searchSourceIds = ( ArrayList ) searchProperties.getProperty(
"searchSourceIds" ); // empty TODO
sortBy = ( String ) searchProperties.getProperty( "sortBy" );
pageSize = ( Integer ) searchProperties.getProperty( "pageSize" );
startRecord = ( Integer ) searchProperties.getProperty( "startRecord" );
} catch( org.osid.shared.SharedException se ) {
LOG.warn( "XServer() failed to get search properties - will assign " +
"defaults", se );
}
// assign defaults if necessary
// TODO assign the updated values to the session... searchProperties is read-only, need to add additional fields to MetasearchSession.
if( sortBy == null ) {
sortBy = "rank";
}
if( pageSize == null ) {
pageSize = new Integer( 10 );
}
if( startRecord == null ) {
startRecord = new Integer( 1 );
}
// check args
if( startRecord.intValue() <= 0 ) {
LOG.warn( "XServer() - startRecord must be set to 1 or higher." );
startRecord = null;
startRecord = new Integer( 1 );
}
// add/update this MetasearchSession in the cache
msm.putMetasearchSession( guid, metasearchSession );
}
//------------------------------------
// METALIB X-SERVICE IMPLEMENTATIONS -
//------------------------------------
/**
* Logs a user into the X-server using URL Syntax for communications.
* Uses the login X-service.
*
* @param username String representing user username
* @param password String representing user password
*
* @return boolean true if authorization succeeds, false otherwise.
*
* @throws XServerException if login fails due to X-server error
*/
private boolean loginURL( String username, String password )
throws XServerException {
// build URL query string
StringBuilder query = new StringBuilder( xserverBaseUrl );
query.append( "?op=login_request&user_name=" + username +
"&user_password=" + password );
// connect to URL and get response
java.io.ByteArrayOutputStream xml = doURLConnection( query.toString() );
if( printXML ) {
// print xml
LOG.debug( xml.toString() );
}
// run SAX Parser
try {
saxParseXML( new java.io.ByteArrayInputStream( xml.toByteArray() ) );
} catch( SAXException sxe ) {
// Error generated by this application
// (or a parser-initialization error)
Exception x = sxe;
if (sxe.getException() != null) {
x = sxe.getException();
}
LOG.warn( "loginURL() SAX exception: " + sxe.getMessage(),
x );
} catch (IOException ioe) {
// I/O error
LOG.warn( "loginURL() IO exception", ioe );
}
// return whether or not the login was successful
return( loginSuccessful() );
}
/**
* Finds records within the given sources using the given find command
* query. Uses the find X-service.
*
* @param findCommand String representing find_request_command. See
* <a href="http://searchtools.lib.umich.edu/X/?op=explain&func=find">
* find</a> explanation from MetaLib X-Server to see how
* find_request_command should be built.
*
* @param waitFlag String representing the wait_flag. A "Y" indicates
* the X-server will not produce a response until the find command has
* completed. Full information about the group and each search set will
* be returned.
* <br></br>
* A "N" indicates the X-server will immediately respond with the group
* number while the find continues to run in the background. The user
* can then use the findGroupInfo method to poll for results.
*
* @throws XServerException if find fails due to X-server error
*/
private void findURL( String findCommand, String waitFlag )
throws XServerException {
// build a query string containing all sources that need to be searched
StringBuilder findBaseString = new StringBuilder();
for( int i = 0; i < searchSourceIds.size(); i++ ) {
findBaseString.append( "&find_base_001=" + ( String )
searchSourceIds.get( i ) );
}
// build URL query string
StringBuilder query = new StringBuilder( xserverBaseUrl );
query.append( "?op=find_request" +
"&wait_flag=" + waitFlag +
"&find_request_command=" + findCommand +
findBaseString.toString() +
"&session_id=" + sessionId );
// connect to URL and get response
java.io.ByteArrayOutputStream xml = doURLConnection( query.toString() );
if( printXML ) {
// print xml
LOG.debug( xml.toString() );
}
// run SAX Parser
try {
saxParseXML( new java.io.ByteArrayInputStream( xml.toByteArray() ) );
} catch (SAXException sxe) {
// Error generated by this application
// (or a parser-initialization error)
Exception x = sxe;
if (sxe.getException() != null) {
x = sxe.getException();
}
LOG.warn( "findURL() SAX exception: " + sxe.getMessage(), x );
} catch (IOException ioe) {
// I/O error
LOG.warn( "findURL() IO exception", ioe );
}
}
/**
* Gets information on a result set group which has already been created
* using the find command in asynchronous mode (waitFlag set to "N")
*
* @throws XServerException if find_group_info fails due to X-Server error
*/
private void findGroupInfoURL() throws XServerException {
findResultSets = new java.util.ArrayList();
StringBuilder query = new StringBuilder( xserverBaseUrl );
query.append( "?op=find_group_info_request" +
"&group_number=" + foundGroupNumber +
"&session_id=" + sessionId );
// connect to URL and get response
java.io.ByteArrayOutputStream xml = doURLConnection( query.toString() );
if( printXML ) {
// print xml
LOG.debug( xml.toString() );
}
// run SAX Parser
try {
saxParseXML( new java.io.ByteArrayInputStream( xml.toByteArray() ) );
} catch (SAXException sxe) {
// Error generated by this application
// (or a parser-initialization error)
Exception x = sxe;
if (sxe.getException() != null) {
x = sxe.getException();
}
LOG.warn( "findGroupInfoURL() SAX exception: " + sxe.getMessage(), x );
} catch (IOException ioe) {
// I/O error
LOG.warn( "findGroupInfoURL() IO exception", ioe );
}
}
/**
* Finds records within the given sources using the given find command
* query. Uses the find X-service.
*
* @param action valid values: merge, merge_more, remerge, sort_only
* @param primarySortKey valid values: rank, title, author, year, database
*
* @throws XServerException if mergeSort fails due to X-server error
*/
private void mergeSortURL( String action, String primarySortKey )
throws XServerException {
if( primarySortKey == null ) {
// default to rank
primarySortKey = "rank";
}
// build URL query string
//
// Limit the number of records fetched to a predefined maximum,
// XSERVER_RECORDS_TO_FETCH. This limit applies only to the
// merge_more and merge_more_set actions - it's ignored for others.
//
StringBuilder query = new StringBuilder( xserverBaseUrl );
query.append( "?op=merge_sort_request" +
"&group_number=" + foundGroupNumber +
"&action=" + action +
"&primary_sort_key=" + primarySortKey +
"&session_id=" + sessionId +
"&fetch_more_records=" + XSERVER_RECORDS_TO_FETCH );
// connect to URL and get response
java.io.ByteArrayOutputStream xml = doURLConnection( query.toString() );
if( printXML ) {
// print xml
LOG.debug( xml.toString() );
}
// run SAX Parser
try {
saxParseXML( new java.io.ByteArrayInputStream( xml.toByteArray() ) );
} catch (SAXException sxe) {
// Error generated by this application
// (or a parser-initialization error)
Exception x = sxe;
if (sxe.getException() != null) {
x = sxe.getException();
}
LOG.warn( "mergeSortURL() SAX exception: " + sxe.getMessage(), x );
} catch (IOException ioe) {
// I/O error
LOG.warn( "mergeSortURL() IO exception", ioe );
}
}
/**
* Presents records found in the given set. Displays records in full MARC
* format.
*
* @param setNumber identifier for a set to obtain records from
* @param setEntry how many/which records to present
* @throws XServerException
*/
private ByteArrayOutputStream presentURL( String setNumber, String setEntry )
throws XServerException {
// build URL query string
StringBuilder query = new StringBuilder( xserverBaseUrl );
query.append( "?op=present_request" +
"&set_number=" + setNumber +
"&set_entry=" + setEntry +
"&format=marc" +
"&view=full" +
// "&view=customize" +
// "&field=VOL%23%23" +
// "&field=YR%23%23%23" +
// "&field=ISSUE" +
// "&field=PAGES" +
// "&field=ISSU%23" +
// "&field=PAGE%23" +
// "&field=DATE%23" +
// "&field=JT%23%23%23" +
// "&field=DOI%23%23" +
// "&field=245%23%23" + // title
// "&field=520%23%23" + // abstract
// "&field=100%23%23" + // author
// "&field=700%23%23" + // secondary authors
// "&field=022%23%23" + // issn
"&session_id=" + sessionId );
// connect to URL and get response
ByteArrayOutputStream xml = doURLConnection( query.toString() );
if( printXML ) {
// print xml
LOG.debug( xml.toString() );
}
return xml;
}
/**
* Returns a metasearchStatus Type Properties object describing this search's
* status.
*
* @return metasearchStatus org.osid.shared.Properties
*/
public org.osid.shared.Properties getSearchStatusProperties() {
MetasearchSession metasearchSession = msm.getMetasearchSession( guid );
return new SearchStatusProperties( metasearchSession.getSearchStatusProperties() );
}
/**
* Runs a blocking search of the X-Server and returns the response xml.
*
* @param numAssets number of records presented from the X-Server. Must be 0
* or greater.
* @return ByteArrayInputStream encapsulating response xml from the X-Server
* @throws XServerException in case of X-Server error
*/
public ByteArrayInputStream getRecordsXML( int numAssets )
throws XServerException, org.osid.repository.RepositoryException {
// check args
if( numAssets < 0 ) {
LOG.warn( "getRecordsXML() - numAssets below zero." );
numAssets = 0;
}
// check session state
if( !checkSessionState() ) {
// throw invalid session exception (TODO use of RepositoryException = bad)
throw new org.osid.repository.RepositoryException(
org.sakaibrary.osid.repository.xserver.
MetasearchException.SESSION_TIMED_OUT );
}
/* figure out whether to merge or not */
MetasearchSession metasearchSession = msm.getMetasearchSession( guid );
setNumber = metasearchSession.getRecordsSetNumber();
if( setNumber == null ) {
// null setNumber indicates multiple search sources, do a merge
LOG.debug( "getRecordsXML() - doing merge, set number is null" );
mergeSortURL( "merge", sortBy );
// we'll be getting a new setNumber for the merged set, store it
metasearchSession.setRecordsSetNumber( setNumber );
metasearchSession.setMergedGroupNumber( mergedGroupNumber );
// add/update this MetasearchSession in the cache
msm.putMetasearchSession( guid, metasearchSession );
} else {
if( !singleSearchSource ) {
// do a merge_more if we're working with multiple search sources
LOG.debug( "getRecordsXML() - doing merge_more, set number " +
"is " + setNumber );
mergeSortURL( "merge_more", sortBy );
}
}
// determine which records to pull from the X-Server
java.text.DecimalFormat df = new java.text.DecimalFormat( "000000000" );
String setEntryStart;
String setEntryEnd;
int setEntryStartValue;
// starting record
if( numAssets == 0 ) {
// just beginning a search
setEntryStart = df.format( startRecord.intValue() );
setEntryStartValue = startRecord.intValue();
} else {
// already conducted a search, continue from where we left off
setEntryStart = df.format( numAssets + 1 );
setEntryStartValue = numAssets + 1;
}
// ending record
Integer numRecords = ( singleSearchSource ) ?
metasearchSession.getNumRecordsFetched() : metasearchSession.getNumRecordsMerged();
if( numAssets == numRecords.intValue() ) {
// we've already returned all the records that the X-Server has.
// need to wait longer
// TODO - dangerous to throw a RepositoryException here...
throw new org.osid.repository.RepositoryException(
org.sakaibrary.osid.repository.xserver.
MetasearchException.ASSET_NOT_FETCHED );
}
int setEntryEndValue = numRecords.intValue();
//
// Ensure that our minimum page size is at least as large as the number
// of records the Xserver could return. Based on that, determine the
// record count for two pages.
//
int minPage = Math.max(pageSize.intValue(), XSERVER_RECORDS_TO_FETCH);
int twoPage = (minPage * 2) + (setEntryStartValue - 1);
//
// Never cache more than two pages of results
//
if (setEntryEndValue > twoPage)
{
setEntryEndValue = twoPage;
}
/* **** original code ***********
if( numRecords.intValue() >= pageSize.intValue() + setEntryStartValue - 1 ) {
setEntryEndValue = pageSize.intValue() + setEntryStartValue - 1;
if( numRecords.intValue() >= pageSize.intValue() * 2 + setEntryStartValue - 1 ) {
// watch out if the user sets pageSize very large...
setEntryEndValue = pageSize.intValue() * 2 + setEntryStartValue - 1;
}
}
** **** end original code ********/
setEntryEnd = df.format( setEntryEndValue );
LOG.debug( "getRecordsXML() - presenting records: " +
setEntryStart + "-" + setEntryEnd );
// run the present X-Service
ByteArrayOutputStream cleanXml = presentURL( setNumber,
setEntryStart + "-" + setEntryEnd );
// transform the cleaned up xml
XMLTransform xmlTransform = new XMLTransform( XSLT_FILE, cleanXml );
ByteArrayOutputStream transformedXml = xmlTransform.transform();
// return transformed xml bytes
return new ByteArrayInputStream( transformedXml.toByteArray() );
}
public void initAsynchSearch( String criteria,
java.util.ArrayList sourceIds )
throws XServerException {
this.searchSourceIds = sourceIds;
LOG.debug( "initAsynchSearch() - searchSourceIds: " + searchSourceIds.size() );
if( searchSourceIds.size() == 1 ) {
// only one search source - do not need to merge
singleSearchSource = true;
} else {
singleSearchSource = false;
}
LOG.debug( "initAsynchSearch() - find_command: " + criteria );
// run the find X-Service in non-blocking mode
findURL( criteria, "N" );
// add/update this MetasearchSession in the cache
MetasearchSession metasearchSession = msm.getMetasearchSession( guid );
metasearchSession.setFoundGroupNumber( foundGroupNumber );
metasearchSession.setSingleSearchSource( singleSearchSource );
msm.putMetasearchSession( guid, metasearchSession );
}
public void updateSearchStatusProperties()
throws XServerException, org.osid.repository.RepositoryException {
// check session state
if( !checkSessionState() ) {
// throw invalid session exception (TODO use of RepositoryException = bad)
throw new org.osid.repository.RepositoryException(
org.sakaibrary.osid.repository.xserver.
MetasearchException.SESSION_TIMED_OUT );
}
// run the find_group_info X-Service
findGroupInfoURL();
// setup search status properties
MetasearchSession metasearchSession = msm.getMetasearchSession( guid );
java.util.Properties searchStatusProperties =
metasearchSession.getSearchStatusProperties();
// set up other variables to determine search status properties
java.util.ArrayList databaseNames = new java.util.ArrayList();
java.util.HashMap databaseMap;
String status = null;
String statusMessage = null;
int numRecordsFound = 0;
int numRecordsFetched = 0;
int numRecordsMerged = 0;
int delayHint = 2500; // 2.5 seconds
boolean ready = false;
boolean fetching = false;
boolean searching = false;
boolean timeout = false;
boolean error = false;
// collect findGroupInfoURL results
for( int i = 0; i < findResultSets.size(); i++ ) {
FindResultSetBean frsb = ( FindResultSetBean ) findResultSets.get( i );
// separate MERGESET info
if( frsb.getBaseName().equals( "MERGESET" ) ) {
setNumber = frsb.getSetNumber();
if( frsb.getStatus().equals( "DONE" ) ) {
status = "ready";
statusMessage = "X-Server is ready to return records.";
numRecordsMerged = Integer.parseInt( frsb.getNumDocs() );
} else if( frsb.getStatus().equals( "FORK" ) ||
frsb.getStatus().equals( "FIND" ) ) {
status = "searching";
statusMessage = "X-Server is currently searching. Please wait.";
} else if( frsb.getStatus().equals( "FETCH" ) ) {
status = "fetching";
statusMessage = "X-Server is currently fetching records. Please wait.";
} else if( frsb.getStatus().equals( "STOP" ) ) {
status = "timeout";
statusMessage = "X-Server session has timed out. Please start a new session.";
} else if( frsb.getStatus().equals( "ERROR" ) ) {
status = "error";
statusMessage = "An X-Server error has occurred (" +
frsb.getFindErrorText() + "). Please verify your search criteria is correct and try again.";
}
} else {
setNumber = ( singleSearchSource ) ? frsb.getSetNumber() : null;
// create a new Map entry for this database
databaseMap = new java.util.HashMap();
databaseMap.put( "databaseName", frsb.getFullName() );
if( frsb.getStatus().equals( "FORK" ) ||
frsb.getStatus().equals( "FIND" ) ) {
searching = true;
databaseMap.put( "status", "searching" );
databaseMap.put( "statusMessage", "Currently searching. Please wait." );
} else if( frsb.getStatus().equals( "FETCH" ) ) {
fetching = true;
databaseMap.put( "status", "fetching" );
databaseMap.put( "statusMessage", "Currently fetching records. Please wait." );
databaseMap.put( "numRecordsFound", new Integer( frsb.getNumDocs() ) );
numRecordsFound += Integer.parseInt( frsb.getNumDocs() );
} else if( frsb.getStatus().equals( "DONE1" ) ) {
ready = true;
databaseMap.put( "status", "ready" );
databaseMap.put( "statusMessage", "Fetched 10 records." );
databaseMap.put( "numRecordsFound", new Integer( frsb.getNumDocs() ) );
databaseMap.put( "numRecordsFetched", new Integer( 10 ) );
numRecordsFound += Integer.parseInt( frsb.getNumDocs() );
numRecordsFetched += 10;
} else if( frsb.getStatus().equals( "DONE2" ) ) {
ready = true;
databaseMap.put( "status", "ready" );
databaseMap.put( "statusMessage", "Fetched 20 records." );
databaseMap.put( "numRecordsFound", new Integer( frsb.getNumDocs() ) );
databaseMap.put( "numRecordsFetched", new Integer( 20 ) );
numRecordsFound += Integer.parseInt( frsb.getNumDocs() );
numRecordsFetched += 20;
} else if( frsb.getStatus().equals( "DONE3" ) ) {
ready = true;
databaseMap.put( "status", "ready" );
databaseMap.put( "statusMessage", "Fetched 30 records." );
databaseMap.put( "numRecordsFound", new Integer( frsb.getNumDocs() ) );
databaseMap.put( "numRecordsFetched", new Integer( 30 ) );
numRecordsFound += Integer.parseInt( frsb.getNumDocs() );
numRecordsFetched += 30;
} else if( frsb.getStatus().equals( "DONE" ) ) {
if( Integer.parseInt( frsb.getNumDocs() ) > 0 ) {
// have results
ready = true;
databaseMap.put( "status", "ready" );
databaseMap.put( "statusMessage", "Fetched ALL records." );
databaseMap.put( "numRecordsFound", new Integer( frsb.getNumDocs() ) );
databaseMap.put( "numRecordsFetched", new Integer( frsb.getNumDocs() ) );
numRecordsFound += Integer.parseInt( frsb.getNumDocs() );
numRecordsFetched += Integer.parseInt( frsb.getNumDocs() );
} else {
// no results
databaseMap.put( "status", "empty" );
databaseMap.put( "statusMessage", "No records found." );
databaseMap.put( "numRecordsFound", new Integer( frsb.getNumDocs() ) );
databaseMap.put( "numRecordsFetched", new Integer( 0 ) );
}
} else if( frsb.getStatus().equals( "STOP" ) ) {
timeout = true;
databaseMap.put( "status", "timeout" );
databaseMap.put( "statusMessage", "X-Server session has timed out. Please start a new session." );
numRecordsFound += Integer.parseInt( frsb.getNumDocs() );
} else if( frsb.getStatus().equals( "ERROR" ) ) {
error = true;
databaseMap.put( "status", "error" );
databaseMap.put( "statusMessage", "An X-Server error has occurred (" +
frsb.getFindErrorText() + "). Please verify your search criteria is correct and try again." );
statusMessage = "An X-Server error has occurred (" +
frsb.getFindErrorText() + "). Please verify your search criteria is correct and try again.";
numRecordsFound += Integer.parseInt( frsb.getNumDocs() );
}
// add this Map to the Properties object
searchStatusProperties.put( frsb.getFullName(), databaseMap );
// add the database name to databaseNames array
databaseNames.add( frsb.getFullName() );
}
}
// determine status of search set
if( status == null ) {
// a merge has not been done
if( ready ) {
searchStatusProperties.put( "status", "ready" );
searchStatusProperties.put( "statusMessage", "X-Server is ready to return records." );
} else if( fetching ) {
searchStatusProperties.put( "status", "fetching" );
searchStatusProperties.put( "statusMessage", "Currently searching. Please wait." );
} else if( searching ) {
searchStatusProperties.put( "status", "searching" );
searchStatusProperties.put( "statusMessage", "Currently fetching records. Please wait." );
} else if( timeout ) {
searchStatusProperties.put( "status", "timeout" );
searchStatusProperties.put( "statusMessage", "X-Server session has timed out. Please start a new session." );
} else if( error ) {
searchStatusProperties.put( "status", "error" );
searchStatusProperties.put( "statusMessage", statusMessage );
} else if( !ready ) {
// absolutely no records found
searchStatusProperties.put( "status", "empty" );
searchStatusProperties.put( "statusMessage", "No records found for your query." );
}
} else {
// a merge has been done
searchStatusProperties.put( "status", status );
searchStatusProperties.put( "statusMessage", statusMessage );
}
// update properties
searchStatusProperties.put( "delayHint", new Integer( delayHint ) );
searchStatusProperties.put( "databaseNames", databaseNames );
searchStatusProperties.put( "numRecordsFound", new Integer( numRecordsFound ) );
searchStatusProperties.put( "numRecordsFetched", new Integer( numRecordsFetched ) );
searchStatusProperties.put( "numRecordsMerged", new Integer( numRecordsMerged ) );
// add/update this MetasearchSession in the cache
metasearchSession.setSearchStatusProperties( searchStatusProperties );
metasearchSession.setRecordsSetNumber( setNumber );
metasearchSession.setNumRecordsFound( new Integer( numRecordsFound ) );
metasearchSession.setNumRecordsFetched( new Integer( numRecordsFetched ) );
metasearchSession.setNumRecordsMerged( new Integer( numRecordsMerged ) );
msm.putMetasearchSession( guid, metasearchSession );
}
//-----------------------------
// PUBLIC DATA ACCESS METHODS |
//-----------------------------
/**
* Returns the list of find result sets found during this session. This
* method should be called only after calling the findURL method.
*
* @return array of FindResultSetBeans encapsulating a list of result sets
* provided by the find X-service data
*/
public ArrayList getFindResultSets() {
return findResultSets;
}
//----------------------------------
// DEFAULT HANDLER IMPLEMENTATIONS -
//----------------------------------
/**
* Receive notification of the beginning of an element.
*
* @see DefaultHandler
*/
public void startElement( String namespaceURI, String sName,
String qName, Attributes attrs ) throws SAXException {
// set flags to avoid overwriting duplicate tag data
if( qName.equals( "merge_sort_response" ) ) {
parsingMergeSort = true;
}
}
/**
* Receive notification of the end of an element.
*
* @see DefaultHandler
*/
public void endElement( String namespaceURI, String sName, String qName )
throws SAXException {
// extract data
extractDataFromText( qName );
// clear flags
if( qName.equals( "merge_sort_response" ) ) {
parsingMergeSort = false;
}
}
/**
* Receive notification of character data inside an element.
*
* @see DefaultHandler
*/
public void characters( char[] buf, int offset, int len )
throws SAXException {
// store character data
String text = new String( buf, offset, len );
if( textBuffer == null ) {
textBuffer = new StringBuilder( text );
} else {
textBuffer.append( text );
}
}
//-------------------------
// PRIVATE HELPER METHODS -
//-------------------------
private void extractDataFromText( String element ) {
if( textBuffer == null ) {
return;
}
String text = textBuffer.toString().trim();
if( text.equals( "" ) ) {
return;
}
/* login */
else if( element.equals( "session_id" ) ) {
sessionId = text;
} else if( element.equals( "auth" ) ) {
auth = text;
}
/* find */
else if( element.equals( "group_number" ) ) {
// merge_sort will also return a group_number
if( parsingMergeSort ) {
mergedGroupNumber = text;
} else {
foundGroupNumber = text;
}
}
/* find_group_info */
else if( element.equals( "base" ) ) {
// add FindResultSetBean to FindResultSet array, findResultSets
findResultSets.add( new FindResultSetBean( text ) );
}
else if( element.equals( "full_name" ) ) {
// result set's resource full name
( (FindResultSetBean)findResultSets.get( findResultSets.size() - 1 ) ).
setFullName( text );
}
else if( element.equals( "base_001" ) ) {
// result set resource id
( (FindResultSetBean)findResultSets.get( findResultSets.size() - 1 ) ).
setSourceId( text );
}
else if( element.equals( "set_number" ) ) {
// result set's set number
( (FindResultSetBean)findResultSets.get( findResultSets.size() - 1 ) ).
setSetNumber( text );
}
else if( element.equals( "find_status" ) ) {
// result set's status
( (FindResultSetBean)findResultSets.get( findResultSets.size() - 1 ) ).
setStatus( text );
}
else if( element.equals( "find_error_text" ) ) {
// if status is ERROR, extract error text
( (FindResultSetBean)findResultSets.get( findResultSets.size() - 1 ) ).
setFindErrorText( text );
}
else if( element.equals( "no_of_documents" ) ) {
if( !parsingMergeSort ) {
// number of documents in result set
( (FindResultSetBean)findResultSets.get( findResultSets.size() - 1 ) ).
setNumDocs( text );
} else {
MetasearchSession ms = msm.getMetasearchSession(guid);
ms.setNumRecordsMerged( new Integer( text ) );
msm.putMetasearchSession(guid, ms);
}
}
/* merge_sort */
else if( element.equals( "new_set_number" ) ) {
setNumber = text;
}
textBuffer = null;
}
/**
* Check for invalid session state
*/
private boolean checkSessionState() {
// a search (find X-Service) should have been conducted
MetasearchSession metasearchSession = msm.getMetasearchSession( guid );
if( metasearchSession == null ||
!metasearchSession.isLoggedIn() ||
metasearchSession.getSessionId() == null ||
metasearchSession.getFoundGroupNumber() == null ||
metasearchSession.getSearchProperties() == null ) {
if( metasearchSession == null ) {
LOG.error( "checkSessionState() - session state out of sync:" +
"\n guid: " + guid +
"\n MetasearchSession: null" );
} else {
LOG.error( "checkSessionState() - session state out of sync:" +
"\n guid: " + guid +
"\n MetasearchSession: " + metasearchSession +
"\n logged in: " + metasearchSession.isLoggedIn() +
"\n sessionId: " + metasearchSession.getSessionId() +
"\n foundGroupNumber: " + metasearchSession.getFoundGroupNumber() +
"\n searchProperties: " + metasearchSession.getSearchProperties() );
}
return false;
} else {
this.sessionId = metasearchSession.getSessionId();
this.foundGroupNumber = metasearchSession.getFoundGroupNumber();
this.singleSearchSource = metasearchSession.isSingleSearchSource();
return true;
}
}
/**
* Setup a URL Connection, get an InputStream for parsing
*
* @param urlQuery String with URL Query for X-server
*/
private ByteArrayOutputStream doURLConnection( String urlQuery )
throws XServerException {
ByteArrayOutputStream xml = null;
URL url = null;
HttpURLConnection urlConn = null;
try {
// define URL
url = new URL( urlQuery );
// open a connection to X-server
urlConn = ( HttpURLConnection )url.openConnection();
XMLCleanup xmlCleanup = new XMLCleanup();
xml = xmlCleanup.cleanup( urlConn.getInputStream() );
// disconnect
urlConn.disconnect();
} catch( MalformedURLException mue ) {
LOG.warn( "doURLConnection() malformed URL" );
wrapXServerException( null, "Error in connecting to X-Server. Please contact Citations Helper Administrator." );
} catch( IOException ioe ) {
LOG.warn( "doURLConnection() IOException, connection failed" );
wrapXServerException( null, "Error in connecting to X-Server. Please contact Citations Helper Administrator." );
} catch( XServerException xse ) {
LOG.warn( "doURLConnection() - XServerException: " +
xse.getErrorCode() + " - " + xse.getErrorText() );
wrapXServerException( xse.getErrorCode(), xse.getErrorText() + "Please contact Citations Helper Administrator." );
}
return xml;
}
private void wrapXServerException( String errorCode, String errorMsg ) throws XServerException
{
// update searchStatusProperties
MetasearchSession metasearchSession = msm.getMetasearchSession( guid );
java.util.Properties searchStatusProperties = metasearchSession.
getSearchStatusProperties();
searchStatusProperties.put( "status", "error" );
searchStatusProperties.put( "statusMessage", errorMsg );
metasearchSession.setSearchStatusProperties(searchStatusProperties);
msm.putMetasearchSession( guid, metasearchSession );
// throw the XServerException now that status has been updated
throw new XServerException( errorCode, errorMsg );
}
/**
* Initiate the SAX Parser with the given InputStream.
*
* @param is InputStream to parse
*
* @throws IOException
* @throws SAXException
*/
private void saxParseXML( InputStream is )
throws IOException, SAXException {
// run the SAX Parser
saxParser.parse( is, this );
is.close();
}
/**
* Validate login X-service
*
* @return true if succesful, false otherwise
*/
private boolean loginSuccessful() {
if( auth != null && auth.equals( "N" ) ) {
return false;
}
return true;
}
}