package org.sakaiproject.citation.util.impl; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.SAXException; public class CQL2XServerFindCommand extends org.xml.sax.helpers.DefaultHandler implements org.sakaiproject.citation.util.api.CQL2MetasearchCommand { // logger private static final org.apache.commons.logging.Log LOG = org.apache.commons.logging.LogFactory.getLog( "org.sakaibrary.common.search.impl.CQL2XServerFindCommand" ); // index mappings private static final java.util.Map INDEX_MAP = new java.util.HashMap(); static { INDEX_MAP.put( "keyword", "WRD" ); INDEX_MAP.put( "title", "WTI" ); INDEX_MAP.put( "author", "WAU" ); INDEX_MAP.put( "subject", "WSU" ); INDEX_MAP.put( "year", "WYR" ); } // boolean relation mappings private static final java.util.Map BOOL_RELATION_MAP = new java.util.HashMap(); static { BOOL_RELATION_MAP.put( "and", "%20AND%20" ); BOOL_RELATION_MAP.put( "or", "%20OR%20" ); } // for SAX Parsing SAXParser saxParser; StringBuilder textBuffer; StringBuilder searchClause; boolean inSearchClause; java.util.Stack cqlStack; public CQL2XServerFindCommand() { // initialize stack cqlStack = new java.util.Stack(); // initialize 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( "CQL2XServerFindCommand() SAX exception: " + sxe.getMessage(), x ); } catch (ParserConfigurationException pce) { // Parser with specified options can't be built LOG.warn( "CQL2XServerFindCommand() SAX parser cannot be built with " + "specified options" ); } } /** * Converts a CQL-formatted search query into a format that the X-Server * can understand. Uses org.z3950.zing.cql.CQLNode.toXCQL() and SAX Parsing * to convert the cqlSearchQuery into an X-Server find_command. * * @param cqlSearchQuery CQL-formatted search query. * @return X-Server find_command or null if cqlSearchQuery is null or empty. * @see org.z3950.zing.cql.CQLNode.toXCQL() */ public String doCQL2MetasearchCommand( String cqlSearchQuery ) { if( cqlSearchQuery == null || cqlSearchQuery.equals( "" ) ) { return null; } org.z3950.zing.cql.CQLParser parser = new org.z3950.zing.cql.CQLParser(); org.z3950.zing.cql.CQLNode root = null; try { // parse the criteria root = parser.parse( cqlSearchQuery ); } catch( java.io.IOException ioe ) { LOG.warn( "CQL2XServerFindCommand.doCQL2MetasearchCommand() IO " + "exception while parsing: " + ioe.getMessage() ); } catch( org.z3950.zing.cql.CQLParseException e ) { LOG.warn( "CQL2XServerFindCommand.doCQL2MetasearchCommand() CQL " + "parsing exception while parsing: " + e.getMessage() ); } if (root == null) { return null; } String cqlXml = root.toXCQL( 0 ); // get cqlXml as a stream java.io.ByteArrayInputStream byteInputStream = null; try { byteInputStream = new java.io.ByteArrayInputStream( cqlXml.getBytes( "UTF8" ) ); } catch( java.io.UnsupportedEncodingException uee ) { LOG.warn( "CQL2XServerFindCommand.doCQL2MetasearchCommand() " + "unsupported encoding: " + uee.getMessage() ); } if (byteInputStream == null) { return null; } // clear the stack cqlStack.removeAllElements(); // run the parser try { saxParser.parse( byteInputStream, this ); byteInputStream.close(); } catch( java.io.IOException ioe ) { LOG.warn( "CQL2XServerFindCommand.doCQL2MetasearchCommand() " + "unable to close byteStream: " + ioe.getMessage() ); } catch( org.xml.sax.SAXException sxe ) { // Error generated by this application // (or a parser-initialization error) Exception x = sxe; if (sxe.getException() != null) { x = sxe.getException(); } LOG.warn( "CQL2XServerFindCommand() SAX exception: " + sxe.getMessage(), x ); } return ( String ) cqlStack.pop(); } //---------------------------------- // DEFAULT HANDLER IMPLEMENTATIONS - //---------------------------------- /** * Receive notification of the beginning of an element. * * @see org.xml.sax.helpers.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( "searchClause" ) ) { inSearchClause = true; } } /** * Receive notification of the end of an element. * * @see org.xml.sax.helpers.DefaultHandler */ public void endElement( String namespaceURI, String sName, String qName ) throws SAXException { // extract data extractDataFromText( qName ); // clear flags if( qName.equals( "searchClause" ) ) { inSearchClause = false; } } /** * Receive notification of character data inside an element. * * @see org.xml.sax.helpers.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( "" ) && !element.equals( "triple" ) ) { return; } // check for a boolean relation value if( !inSearchClause && element.equals( "value" ) ) { cqlStack.push( text ); } // construct a search clause if( inSearchClause ) { if( searchClause == null ) { searchClause = new StringBuilder(); } if( element.equals( "index" ) ) { searchClause.append( translateIndex( text ) ); } else if( element.equals( "value" ) ) { // relation value should always be '=' searchClause.append( text ); } else if( element.equals( "term" ) ) { searchClause.append( "(" + text + ")" ); cqlStack.push( searchClause.toString() ); searchClause = null; } } // evaluate expression so far if we hit a </triple> if( element.equals( "triple" ) ) { String rightOperand = ( String ) cqlStack.pop(); String leftOperand = ( String ) cqlStack.pop(); String booleanRelation = ( String ) cqlStack.pop(); cqlStack.push( leftOperand + translateBooleanRelation( booleanRelation ) + rightOperand ); } textBuffer = null; } private String translateIndex( String cqlIndex ) { String xserverIndex = ( String ) INDEX_MAP.get( cqlIndex ); if( xserverIndex == null || xserverIndex.equals( "" ) ) { LOG.warn( "CQL2XServerFindCommand.translateIndex() - null/empty index" ); // default to keyword xserverIndex = "WRD"; } return xserverIndex; } private String translateBooleanRelation( String booleanRelation ) { String xserverBoolean = ( String ) BOOL_RELATION_MAP.get( booleanRelation ); if( xserverBoolean == null || xserverBoolean.equals( "" ) ) { LOG.warn( "CQL2XServerFindCommand.translateIndex() - null/empty boolean relation" ); // default to and xserverBoolean = "%20AND%20"; } return xserverBoolean; } }