/** * '$RCSfile: DataquerySpecification.java,v $' * Purpose: A Class that represents a structured query, and can be * constructed from an XML serialization conforming to * pathquery.dtd. The printSQL() method can be used to print * a SQL serialization of the query. * Copyright: 2000 Regents of the University of California and the * National Center for Ecological Analysis and Synthesis * Authors: Matt Jones * * '$Author: leinfelder $' * '$Date: 2009-01-08 18:51:48 $' * '$Revision: 1.18 $' * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.ecoinformatics.datamanager.dataquery; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.StringReader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Stack; import java.util.Vector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.ecoinformatics.datamanager.DataManager; import org.ecoinformatics.datamanager.database.ANDRelation; import org.ecoinformatics.datamanager.database.Condition; import org.ecoinformatics.datamanager.database.ConditionInterface; import org.ecoinformatics.datamanager.database.DatabaseConnectionPoolInterface; import org.ecoinformatics.datamanager.database.Join; import org.ecoinformatics.datamanager.database.LogicalRelation; import org.ecoinformatics.datamanager.database.ORRelation; import org.ecoinformatics.datamanager.database.Query; import org.ecoinformatics.datamanager.database.SelectionItem; import org.ecoinformatics.datamanager.database.StaticSelectionItem; import org.ecoinformatics.datamanager.database.SubQueryClause; import org.ecoinformatics.datamanager.database.TableItem; import org.ecoinformatics.datamanager.database.Union; import org.ecoinformatics.datamanager.database.WhereClause; import org.ecoinformatics.datamanager.download.DocumentHandler; import org.ecoinformatics.datamanager.download.EcogridEndPointInterface; import org.ecoinformatics.datamanager.download.document.DocumentDataPackageHandler; import org.ecoinformatics.datamanager.parser.Attribute; import org.ecoinformatics.datamanager.parser.DataPackage; import org.ecoinformatics.datamanager.parser.Entity; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.helpers.XMLReaderFactory; import edu.ucsb.nceas.utilities.OrderedMap; /** * A Class that represents a data query, and can be constructed from an * XML serialization conforming to * * @see dataquery.xsd. The Data Manager Library should be used to execute the * Query or Union provided by this class. * Note that the Datapackages involved in the Query are given by the getDataPackages() method */ public class DataquerySpecification extends DefaultHandler { private DatabaseConnectionPoolInterface connectionPool; private EcogridEndPointInterface ecogridEndPoint; /** A string buffer to store query */ private StringBuffer xml = new StringBuffer(); private StringBuffer textBuffer = new StringBuffer(); private String parserName = null; // Query data structures used temporarily during XML parsing private Stack elementStack = new Stack(); private Stack datapackageStack = new Stack(); private Stack queryStack = new Stack(); private Stack entityStack = new Stack(); private Vector attributes = new Vector(); private Stack conditionStack = new Stack(); private Stack logicalStack = new Stack(); private Stack subQueryStack = new Stack(); private LogicalRelation currentLogical = null; private LogicalRelation currentSubQueryLogical = null; private DocumentDataPackageHandler ddpHandler = null; private DataPackage metadataPackage = null; private boolean inCondition = false; private static Log log = LogFactory.getLog(DataquerySpecification.class); private Map fetchedDatapackages = new HashMap(); private Map metadataDatapackages = new HashMap(); private List queryList = new ArrayList(); private Union union = null; /** * construct an instance of the QuerySpecification class * * @param queryspec * the XML representation of the query (should conform to * dataquery.xsd) as a Reader * @param parserName * the fully qualified name of a Java Class implementing the * org.xml.sax.Parser interface */ public DataquerySpecification( Reader queryspec, String parserName, DatabaseConnectionPoolInterface connectionPool, EcogridEndPointInterface ecogridEndPoint) throws IOException { super(); //for the DataManager this.connectionPool = connectionPool; this.ecogridEndPoint = ecogridEndPoint; // Initialize the class variables this.parserName = parserName; // Initialize the parser and read the queryspec XMLReader parser = initializeParser(); if (parser == null) { log.error("SAX parser not instantiated properly."); } try { parser.parse(new InputSource(queryspec)); } catch (SAXException e) { log.error("error parsing data in " + "DataquerySpecification.DataquerySpecification"); log.error(e.getMessage()); } } /** * construct an instance of the DataquerySpecification class * * @param queryspec * the XML representation of the query (should conform to * dataquery.xsd) as a String * @param parserName * the fully qualified name of a Java Class implementing the * org.xml.sax.Parser interface */ public DataquerySpecification( String queryspec, String parserName, DatabaseConnectionPoolInterface connectionPool, EcogridEndPointInterface ecogridEndPoint) throws IOException { this(new StringReader(queryspec), parserName, connectionPool, ecogridEndPoint); } /** * Returns the Query generated after parsing XML. * In cases where a Union is requested, this method will return only the first * Query used in the Union. * @return a single query object that can be used with the DataManager */ public Query getQuery() { return (Query) queryList.get(0); } /** * Returns the Union (if any) generated after parsing XML. * In cases where a Union is requested, this method will return only the first * Query used in the Union. * @return a single query object that can be used with the DataManager */ public Union getUnion() { return union; } /** * Needed when using the DataManager to select data using a generated Query or Union * @return */ public DataPackage[] getDataPackages() { return (DataPackage[]) datapackageStack.toArray(new DataPackage[0]); } /** * Set up the SAX parser for reading the XML serialized query */ private XMLReader initializeParser() { XMLReader parser = null; // Set up the SAX document handlers for parsing try { // Get an instance of the parser parser = XMLReaderFactory.createXMLReader(parserName); // Set the ContentHandler to this instance parser.setContentHandler(this); // Set the error Handler to this instance parser.setErrorHandler(this); } catch (Exception e) { System.err.println("Error in QuerySpcecification.initializeParser " + e.toString()); } return parser; } private void startQuery(BasicNode currentNode) { Query query = new Query(); boolean distinct = Boolean.parseBoolean(currentNode.getAttribute("distinct")); query.setDistinct(distinct); queryStack.push(query); } private void startSubquery(BasicNode currentNode) { Query query = new Query(); subQueryStack.push(query); } private void startDatapackage(BasicNode currentNode) { String docId = currentNode.getAttribute("id"); DataPackage datapackage = (DataPackage) fetchedDatapackages.get(docId); if (datapackage == null) { //read metadata document DocumentHandler dh = new DocumentHandler(); dh.setDocId(docId); dh.setEcogridEndPointInterface(ecogridEndPoint); InputStream metadataInputStream = null; try { metadataInputStream = dh.downloadDocument(); } catch (Exception e1) { log.error("could not download document: " + docId); e1.printStackTrace(); } //parse as DataPackage try { datapackage = DataManager.getInstance( connectionPool, connectionPool.getDBAdapterName()).parseMetadata( metadataInputStream); } catch (Exception e) { log.error( "could not parse metadata given by docid: " + docId); } //prime the data? try { DataManager.getInstance( connectionPool, connectionPool.getDBAdapterName()).loadDataToDB( datapackage, ecogridEndPoint); } catch (Exception e) { log.error( "could not load data given by docid: " + docId); } //save for later fetchedDatapackages.put(docId, datapackage); } //save it for later - preserving order/nesting datapackageStack.push(datapackage); } private void startEntity(BasicNode currentNode) { //get the entity and save it for later DataPackage datapackage = (DataPackage) datapackageStack.peek(); Entity entity = null; String idAttribute = currentNode.getAttribute("id"); String indexAttribute = currentNode.getAttribute("index"); String nameAttribute = currentNode.getAttribute("name"); //metadata if (idAttribute != null) { ddpHandler = new DocumentDataPackageHandler(connectionPool); ddpHandler.setDocId(idAttribute); ddpHandler.setEcogridEndPointInterface(ecogridEndPoint); ddpHandler.setAttributeMap(new OrderedMap()); //set it up for attributes //place holder for items in condition/joins if (inCondition) { metadataPackage = new DataPackage(idAttribute); } } //indexed data else if (indexAttribute != null) { int index = Integer.parseInt(indexAttribute); entity = datapackage.getEntityList()[index]; } //named data else if (nameAttribute != null) { entity = datapackage.getEntity(nameAttribute); } //save for later entityStack.push(entity); } private void startAttribute(BasicNode currentNode) { attributes.add(currentNode); } private void startCondition(BasicNode currentNode) { inCondition = true; //indicates we are in a condition ConditionInterface condition = null; if (currentNode.getAttribute("type").equals("subquery")) { condition = new SubQueryClause(null, null, null, null); } else if (currentNode.getAttribute("type").equals("join")) { condition = new Join(null, null, null, null); } else { condition = new Condition(null, null, null, null); } conditionStack.push(condition); } private void startAnd(BasicNode currentNode) { //create new AND and add it to the current relation (for nesting) ANDRelation and = new ANDRelation(); if (subQueryStack.isEmpty()) { if (currentLogical != null) { currentLogical.addANDRelation(and); } //then set the current logical to the new AND currentLogical = and; logicalStack.push(currentLogical); } else { if (currentSubQueryLogical != null) { currentSubQueryLogical.addANDRelation(and); } //then set the current logical to the new AND currentSubQueryLogical = and; logicalStack.push(currentSubQueryLogical); } } private void startOr(BasicNode currentNode) { ORRelation or = new ORRelation(); if (subQueryStack.isEmpty()) { if (currentLogical != null) { currentLogical.addORRelation(or); } currentLogical = or; logicalStack.push(currentLogical); } else { if (currentSubQueryLogical != null) { currentSubQueryLogical.addORRelation(or); } //then set the current logical to the new AND currentSubQueryLogical = or; logicalStack.push(currentSubQueryLogical); } } /** * callback method used by the SAX Parser when the start tag of an element * is detected. Used in this context to parse and store the query * information in class variables. */ public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { log.debug("<" + localName + ">"); BasicNode currentNode = new BasicNode(localName); //write element name into xml buffer. xml.append("<"); xml.append(localName); // add attributes to BasicNode here if (atts != null) { int len = atts.getLength(); for (int i = 0; i < len; i++) { currentNode.setAttribute(atts.getLocalName(i), atts.getValue(i)); xml.append(" "); xml.append(atts.getLocalName(i)); xml.append("=\""); xml.append(atts.getValue(i)); xml.append("\""); } } xml.append(">"); elementStack.push(currentNode); //process the nodes if (currentNode.getTagName().equals("query")) { startQuery(currentNode); } if (currentNode.getTagName().equals("subquery")) { startSubquery(currentNode); } if (currentNode.getTagName().equals("datapackage")) { startDatapackage(currentNode); } if (currentNode.getTagName().equals("entity")) { startEntity(currentNode); } if (currentNode.getTagName().equals("attribute")) { startAttribute(currentNode); } if (currentNode.getTagName().equals("condition")) { startCondition(currentNode); } if (currentNode.getTagName().equals("and")) { startAnd(currentNode); } if (currentNode.getTagName().equals("or")) { startOr(currentNode); } } private void endUnion(BasicNode leaving) { //only instantiate it when it is used union = new Union(); //add all the queries for (int i = 0; i < queryList.size(); i++) { union.addQuery((Query) queryList.get(i)); } String orderAttr = leaving.getAttribute("order"); if (orderAttr != null) { boolean orderQueryList = Boolean.parseBoolean(orderAttr); union.setOrderQueryList(orderQueryList); } } private void endQuery(BasicNode leaving) { //pop, done with the query, add to the union (even for single query) Query query = (Query) queryStack.pop(); queryList.add(query); } private void endSubquery(BasicNode leaving) { //pop, done with the subquery subQueryStack.pop(); } private void constructEntity() { //try to load the metadata if we need to if (ddpHandler != null) { try { //if this part of the selection area, then continue constructing it if (!inCondition) { metadataPackage = ddpHandler.loadDataToDB(); metadataDatapackages.put( metadataPackage.getPackageId(), metadataPackage); } //look up the existing one metadataPackage = (DataPackage) metadataDatapackages.get(metadataPackage.getPackageId()); //out with the null, in with the new entityStack.pop(); entityStack.push(metadataPackage.getEntityList()[0]); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } //look up the attribute from this entity Entity entity = (Entity) entityStack.peek(); //process the attributes for (int i = 0; i < attributes.size(); i++ ) { BasicNode leaving = (BasicNode) attributes.get(i); Attribute attribute = null; String indexAttribute = leaving.getAttribute("index"); if (indexAttribute != null) { int index = Integer.parseInt(indexAttribute); attribute = entity.getAttributes()[index]; } else { //TODO allow multiple matches on "name"? String nameAttribute = leaving.getAttribute("name"); attribute = entity.getAttributeList().getAttribute(nameAttribute); } //process conditions here if (!conditionStack.isEmpty()) { //intitial part of the condition ConditionInterface condition = (ConditionInterface) conditionStack.pop(); if (condition instanceof Condition) { condition = new Condition(entity, attribute, null, null); } else if (condition instanceof SubQueryClause) { Query subQuery = null; if (subQueryStack.isEmpty()) { subQuery = new Query(); } else { subQuery = (Query) subQueryStack.peek(); } SelectionItem selection = new SelectionItem(entity, attribute); subQuery.addSelectionItem(selection); // if we have the operator from before, we should use it String operator = ((SubQueryClause) condition).getOperator(); condition = new SubQueryClause(entity, attribute, operator, subQuery); } else if (condition instanceof Join) { if (!((Join)condition).isLeftSet()) { ((Join)condition).setLeft(entity, attribute); } else if (!((Join)condition).isRightSet()) { ((Join)condition).setRight(entity, attribute); } } conditionStack.push(condition); } else { Query query = (Query) queryStack.peek(); //create the selectionItem and add to the query SelectionItem selection = new SelectionItem(entity, attribute); query.addSelectionItem(selection); } }//loop // done with those attributes.clear(); } private void endEntity(BasicNode leaving) { // construct the entity constructEntity(); //in selection if (conditionStack.isEmpty()) { //pop, done with the entity Entity entity = (Entity) entityStack.pop(); //add to query Query query = (Query) queryStack.peek(); query.addTableItem(new TableItem(entity)); } if (!subQueryStack.isEmpty()) { //pop, done with the entity Entity entity = (Entity) entityStack.pop(); Query subQuery = (Query) subQueryStack.peek(); subQuery.addTableItem(new TableItem(entity)); } //reset the document datapackage handler if (ddpHandler != null) { //done with this ddpHandler = null; } } private void endPathexpr(BasicNode leaving) { String labelAttribute = leaving.getAttribute("label"); ddpHandler.getAttributeMap().put(labelAttribute, textBuffer.toString().trim()); } private void endAttribute(BasicNode leaving) { //moved to end of entity } private void endOperator(BasicNode leaving) { String operator = textBuffer.toString().trim(); ConditionInterface condition = (ConditionInterface) conditionStack.peek(); if (condition instanceof Condition) { ((Condition) condition).setOperator(operator); } if (condition instanceof SubQueryClause) { ((SubQueryClause) condition).setOperator(operator); } } private void endValue(BasicNode leaving) { Object value = textBuffer.toString().trim(); //try some numeric options //TODO incorporate type information from metadata into these decisions. try { value = Integer.parseInt((String) value); } catch (Exception e) { try { value = Double.parseDouble((String) value); } catch (Exception e2) { // do nothing - treat it as a String } } ConditionInterface condition = (ConditionInterface) conditionStack.peek(); if (condition instanceof Condition) { ((Condition) condition).setValue(value); } } private void endWhere(BasicNode leaving) { //in subquery? if (!subQueryStack.isEmpty()) { WhereClause where = new WhereClause((ConditionInterface)null); if (currentSubQueryLogical != null) { if (currentSubQueryLogical instanceof ANDRelation) { where = new WhereClause((ANDRelation)currentSubQueryLogical); } else { } } else if (!conditionStack.isEmpty()) { //done with the condition now, we can pop it ConditionInterface condition = (ConditionInterface) conditionStack.pop(); where = new WhereClause(condition); } Query subQuery = (Query) subQueryStack.peek(); subQuery.setWhereClause(where); } else { //set up the shell WhereClause where = new WhereClause((ConditionInterface)null); if (!conditionStack.isEmpty()) { //done with the condition now, we can pop it ConditionInterface condition = (ConditionInterface) conditionStack.pop(); where = new WhereClause(condition); } else if (currentLogical != null) { if (currentLogical instanceof ANDRelation) { where = new WhereClause((ANDRelation)currentLogical); } else { where = new WhereClause((ORRelation)currentLogical); } } //set the where clause Query query = (Query) queryStack.peek(); query.setWhereClause(where); } } private void endCondition(BasicNode leaving) { inCondition = false; if (!subQueryStack.isEmpty()) { if (currentSubQueryLogical != null) { //do something? currentSubQueryLogical.addCondtionInterface((ConditionInterface) conditionStack.pop()); } } else if (currentLogical != null) { currentLogical.addCondtionInterface((ConditionInterface) conditionStack.pop()); } } private void endAnd(BasicNode leaving) { if (!subQueryStack.isEmpty()) { //set the current logical to what's on the stack currentSubQueryLogical = (LogicalRelation) logicalStack.pop(); } else { //set the current logical to what's on the stack currentLogical = (LogicalRelation) logicalStack.pop(); } } private void endOr(BasicNode leaving) { if (!subQueryStack.isEmpty()) { //set the current logical to what's on the stack currentSubQueryLogical = (LogicalRelation) logicalStack.pop(); } else { currentLogical = (LogicalRelation) logicalStack.pop(); } } private void endStaticItem(BasicNode leaving) { String name = leaving.getAttribute("name"); String value = leaving.getAttribute("value"); StaticSelectionItem selection = new StaticSelectionItem(name, value); Query query = (Query) queryStack.peek(); query.addSelectionItem(selection); } /** * callback method used by the SAX Parser when the end tag of an element is * detected. Used in this context to parse and store the query information * in class variables. */ public void endElement(String uri, String localName, String qName) throws SAXException { //log.debug("leaving: " + localName); BasicNode leaving = (BasicNode) elementStack.pop(); if (leaving.getTagName().equals("union")) { endUnion(leaving); } if (leaving.getTagName().equals("query")) { endQuery(leaving); } if (leaving.getTagName().equals("subquery")) { endSubquery(leaving); } if (leaving.getTagName().equals("entity")) { endEntity(leaving); } if (leaving.getTagName().equals("pathexpr")) { endPathexpr(leaving); } if (leaving.getTagName().equals("attribute")) { endAttribute(leaving); } if (leaving.getTagName().equals("operator")) { endOperator(leaving); } if (leaving.getTagName().equals("value")) { endValue(leaving); } if (leaving.getTagName().equals("where")) { endWhere(leaving); } if (leaving.getTagName().equals("condition")) { endCondition(leaving); } if (leaving.getTagName().equals("and")) { endAnd(leaving); } if (leaving.getTagName().equals("or")) { endOr(leaving); } if (leaving.getTagName().equals("staticItem")) { endStaticItem(leaving); } String normalizedXML = textBuffer.toString().trim(); //log.debug("================xml "+normalizedXML); xml.append(normalizedXML); xml.append(""); //rest textBuffer textBuffer = new StringBuffer(); log.debug(""); } /** * Gets query string in xml format (original form) */ public String getXML() { return xml.toString(); } /** * callback method used by the SAX Parser when the text sequences of an xml * stream are detected. Used in this context to parse and store the query * information in class variables. */ public void characters(char ch[], int start, int length) { // buffer all text nodes for same element. This is for text was splited // into different nodes String text = new String(ch, start, length); textBuffer.append(text); //clean up fro debug text=text.trim(); if (text != null && text.length() > 0) { log.debug(text); } } /** * create a String description of the query that this instance represents. * This should become a way to get the XML serialization of the query. */ public String toString() { return xml.toString(); } }