001: package org.sakaiproject.citation.util.impl;
002:
003: import javax.xml.parsers.ParserConfigurationException;
004: import javax.xml.parsers.SAXParser;
005: import javax.xml.parsers.SAXParserFactory;
006:
007: import org.xml.sax.Attributes;
008: import org.xml.sax.SAXException;
009:
010: public class CQL2XServerFindCommand extends
011: org.xml.sax.helpers.DefaultHandler implements
012: org.sakaiproject.citation.util.api.CQL2MetasearchCommand {
013:
014: // logger
015: private static final org.apache.commons.logging.Log LOG = org.apache.commons.logging.LogFactory
016: .getLog("org.sakaibrary.common.search.impl.CQL2XServerFindCommand");
017:
018: // index mappings
019: private static final java.util.Map INDEX_MAP = new java.util.HashMap();
020: static {
021: INDEX_MAP.put("keyword", "WRD");
022: INDEX_MAP.put("title", "WTI");
023: INDEX_MAP.put("author", "WAU");
024: INDEX_MAP.put("subject", "WSU");
025: INDEX_MAP.put("year", "WYR");
026: }
027:
028: // boolean relation mappings
029: private static final java.util.Map BOOL_RELATION_MAP = new java.util.HashMap();
030: static {
031: BOOL_RELATION_MAP.put("and", "%20AND%20");
032: BOOL_RELATION_MAP.put("or", "%20OR%20");
033: }
034:
035: // for SAX Parsing
036: SAXParser saxParser;
037: StringBuffer textBuffer;
038: StringBuffer searchClause;
039: boolean inSearchClause;
040: java.util.Stack cqlStack;
041:
042: public CQL2XServerFindCommand() {
043: // initialize stack
044: cqlStack = new java.util.Stack();
045:
046: // initialize SAX Parser
047: SAXParserFactory factory;
048:
049: factory = SAXParserFactory.newInstance();
050: factory.setNamespaceAware(true);
051: try {
052: saxParser = factory.newSAXParser();
053: } catch (SAXException sxe) {
054: // Error generated by this application
055: // (or a parser-initialization error)
056: Exception x = sxe;
057:
058: if (sxe.getException() != null) {
059: x = sxe.getException();
060: }
061:
062: LOG.warn("CQL2XServerFindCommand() SAX exception: "
063: + sxe.getMessage(), x);
064: } catch (ParserConfigurationException pce) {
065: // Parser with specified options can't be built
066: LOG
067: .warn("CQL2XServerFindCommand() SAX parser cannot be built with "
068: + "specified options");
069: }
070: }
071:
072: /**
073: * Converts a CQL-formatted search query into a format that the X-Server
074: * can understand. Uses org.z3950.zing.cql.CQLNode.toXCQL() and SAX Parsing
075: * to convert the cqlSearchQuery into an X-Server find_command.
076: *
077: * @param cqlSearchQuery CQL-formatted search query.
078: * @return X-Server find_command or null if cqlSearchQuery is null or empty.
079: * @see org.z3950.zing.cql.CQLNode.toXCQL()
080: */
081: public String doCQL2MetasearchCommand(String cqlSearchQuery) {
082: if (cqlSearchQuery == null || cqlSearchQuery.equals("")) {
083: return null;
084: }
085:
086: org.z3950.zing.cql.CQLParser parser = new org.z3950.zing.cql.CQLParser();
087: org.z3950.zing.cql.CQLNode root = null;
088: try {
089: // parse the criteria
090: root = parser.parse(cqlSearchQuery);
091: } catch (java.io.IOException ioe) {
092: LOG
093: .warn("CQL2XServerFindCommand.doCQL2MetasearchCommand() IO "
094: + "exception while parsing: "
095: + ioe.getMessage());
096: } catch (org.z3950.zing.cql.CQLParseException e) {
097: LOG
098: .warn("CQL2XServerFindCommand.doCQL2MetasearchCommand() CQL "
099: + "parsing exception while parsing: "
100: + e.getMessage());
101: }
102: String cqlXml = root.toXCQL(0);
103:
104: // get cqlXml as a stream
105: java.io.ByteArrayInputStream byteInputStream = null;
106: try {
107: byteInputStream = new java.io.ByteArrayInputStream(cqlXml
108: .getBytes("UTF8"));
109: } catch (java.io.UnsupportedEncodingException uee) {
110: LOG
111: .warn("CQL2XServerFindCommand.doCQL2MetasearchCommand() "
112: + "unsupported encoding: "
113: + uee.getMessage());
114: }
115:
116: // clear the stack
117: cqlStack.removeAllElements();
118:
119: // run the parser
120: try {
121: saxParser.parse(byteInputStream, this );
122: byteInputStream.close();
123: } catch (java.io.IOException ioe) {
124: LOG
125: .warn("CQL2XServerFindCommand.doCQL2MetasearchCommand() "
126: + "unable to close byteStream: "
127: + ioe.getMessage());
128: } catch (org.xml.sax.SAXException sxe) {
129: // Error generated by this application
130: // (or a parser-initialization error)
131: Exception x = sxe;
132:
133: if (sxe.getException() != null) {
134: x = sxe.getException();
135: }
136:
137: LOG.warn("CQL2XServerFindCommand() SAX exception: "
138: + sxe.getMessage(), x);
139: }
140:
141: return (String) cqlStack.pop();
142: }
143:
144: //----------------------------------
145: // DEFAULT HANDLER IMPLEMENTATIONS -
146: //----------------------------------
147:
148: /**
149: * Receive notification of the beginning of an element.
150: *
151: * @see org.xml.sax.helpers.DefaultHandler
152: */
153: public void startElement(String namespaceURI, String sName,
154: String qName, Attributes attrs) throws SAXException {
155: // set flags to avoid overwriting duplicate tag data
156: if (qName.equals("searchClause")) {
157: inSearchClause = true;
158: }
159: }
160:
161: /**
162: * Receive notification of the end of an element.
163: *
164: * @see org.xml.sax.helpers.DefaultHandler
165: */
166: public void endElement(String namespaceURI, String sName,
167: String qName) throws SAXException {
168: // extract data
169: extractDataFromText(qName);
170:
171: // clear flags
172: if (qName.equals("searchClause")) {
173: inSearchClause = false;
174: }
175: }
176:
177: /**
178: * Receive notification of character data inside an element.
179: *
180: * @see org.xml.sax.helpers.DefaultHandler
181: */
182: public void characters(char[] buf, int offset, int len)
183: throws SAXException {
184: // store character data
185: String text = new String(buf, offset, len);
186:
187: if (textBuffer == null) {
188: textBuffer = new StringBuffer(text);
189: } else {
190: textBuffer.append(text);
191: }
192: }
193:
194: //-------------------------
195: // PRIVATE HELPER METHODS -
196: //-------------------------
197:
198: private void extractDataFromText(String element) {
199: if (textBuffer == null) {
200: return;
201: }
202:
203: String text = textBuffer.toString().trim();
204: if (text.equals("") && !element.equals("triple")) {
205: return;
206: }
207:
208: // check for a boolean relation value
209: if (!inSearchClause && element.equals("value")) {
210: cqlStack.push(text);
211: }
212:
213: // construct a search clause
214: if (inSearchClause) {
215: if (searchClause == null) {
216: searchClause = new StringBuffer();
217: }
218:
219: if (element.equals("index")) {
220: searchClause.append(translateIndex(text));
221: } else if (element.equals("value")) {
222: // relation value should always be '='
223: searchClause.append(text);
224: } else if (element.equals("term")) {
225: searchClause.append("(" + text + ")");
226: cqlStack.push(searchClause.toString());
227: searchClause = null;
228: }
229: }
230:
231: // evaluate expression so far if we hit a </triple>
232: if (element.equals("triple")) {
233: String rightOperand = (String) cqlStack.pop();
234: String leftOperand = (String) cqlStack.pop();
235: String booleanRelation = (String) cqlStack.pop();
236:
237: cqlStack.push(leftOperand
238: + translateBooleanRelation(booleanRelation)
239: + rightOperand);
240: }
241:
242: textBuffer = null;
243: }
244:
245: private String translateIndex(String cqlIndex) {
246: String xserverIndex = (String) INDEX_MAP.get(cqlIndex);
247:
248: if (xserverIndex == null || xserverIndex.equals("")) {
249: LOG
250: .warn("CQL2XServerFindCommand.translateIndex() - null/empty index");
251: // default to keyword
252: xserverIndex = "WRD";
253: }
254:
255: return xserverIndex;
256: }
257:
258: private String translateBooleanRelation(String booleanRelation) {
259: String xserverBoolean = (String) BOOL_RELATION_MAP
260: .get(booleanRelation);
261:
262: if (xserverBoolean == null || xserverBoolean.equals("")) {
263: LOG
264: .warn("CQL2XServerFindCommand.translateIndex() - null/empty boolean relation");
265: // default to and
266: xserverBoolean = "%20AND%20";
267: }
268:
269: return xserverBoolean;
270: }
271: }
|