/********************************************************************************** * $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; } }