/** * '$RCSfile$' * Purpose: A Class that searches a relational DB for elements and * attributes that have free text matches a query string, * or structured query matches to a path specified node in the * XML hierarchy. It returns a result set consisting of the * document ID for each document that satisfies the query * Copyright: 2000 Regents of the University of California and the * National Center for Ecological Analysis and Synthesis * Authors: Matt Jones * * '$Author$' * '$Date$' * '$Revision$' * * 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 edu.ucsb.nceas.metacat; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.StringTokenizer; import java.util.Vector; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringEscapeUtils; import org.apache.log4j.Logger; import org.dataone.service.exceptions.NotImplemented; import edu.ucsb.nceas.metacat.common.query.EnabledQueryEngines; import edu.ucsb.nceas.metacat.database.DBConnection; import edu.ucsb.nceas.metacat.database.DBConnectionPool; import edu.ucsb.nceas.metacat.properties.PropertyService; import edu.ucsb.nceas.metacat.shared.HandlerException; import edu.ucsb.nceas.metacat.util.AuthUtil; import edu.ucsb.nceas.metacat.util.DocumentUtil; import edu.ucsb.nceas.metacat.util.MetacatUtil; import edu.ucsb.nceas.utilities.PropertyNotFoundException; import edu.ucsb.nceas.utilities.access.AccessControlInterface; import edu.ucsb.nceas.utilities.triple.Triple; import edu.ucsb.nceas.utilities.triple.TripleCollection; /** * A Class that searches a relational DB for elements and attributes that have * free text matches a query string, or structured query matches to a path * specified node in the XML hierarchy. It returns a result set consisting of * the document ID for each document that satisfies the query */ public class DBQuery { public static final String XPATHQUERYOFFINFO = "The Metacat Path Query Engine is turned off. If you want to turn it on, please contact the administrator."; static final int ALL = 1; static final int WRITE = 2; static final int READ = 4; private String qformat = "xml"; // are we combining the query with docid list and, if so, using INTERSECT or UNION? private String operator = null; //private Connection conn = null; private String parserName = null; private Logger logMetacat = Logger.getLogger(DBQuery.class); /** true if the metacat spatial option is installed **/ private final boolean METACAT_SPATIAL = true; /** useful if you just want to grab a list of docids. Since the docids can be very long, it is a vector of vector **/ Vector docidOverride = new Vector(); // a hash table serves as query reuslt cache. Key of hashtable // is a query string and value is result xml string private static Hashtable queryResultCache = new Hashtable(); // Capacity of the query result cache private static final int QUERYRESULTCACHESIZE; static { int qryRsltCacheSize = 0; try { qryRsltCacheSize = Integer.parseInt(PropertyService.getProperty("database.queryresultCacheSize")); } catch (PropertyNotFoundException pnfe) { System.err.println("Could not get QUERYRESULTCACHESIZE property in static block: " + pnfe.getMessage()); } QUERYRESULTCACHESIZE = qryRsltCacheSize; } // Size of page for non paged query private static final int NONPAGESIZE = 99999999; /** * the main routine used to test the DBQuery utility. *

* Usage: java DBQuery * NOTE: encoding should be provided for best results * @param xmlfile the filename of the xml file containing the query */ static public void main(String[] args) { if (args.length < 1) { System.err.println("Wrong number of arguments!!!"); System.err.println("USAGE: java DBQuery [-t] [-index] "); return; } else { try { int i = 0; boolean showRuntime = false; boolean useXMLIndex = false; if (args[i].equals("-t")) { showRuntime = true; i++; } if (args[i].equals("-index")) { useXMLIndex = true; i++; } String xmlfile = args[i]; // Time the request if asked for double startTime = System.currentTimeMillis(); // Open a connection to the database //Connection dbconn = util.openDBConnection(); double connTime = System.currentTimeMillis(); // Execute the query DBQuery queryobj = new DBQuery(); Reader xml = new InputStreamReader(new FileInputStream(new File(xmlfile))); Hashtable nodelist = null; //nodelist = queryobj.findDocuments(xml, null, null, useXMLIndex); // Print the reulting document listing StringBuffer result = new StringBuffer(); String document = null; String docid = null; result.append("\n"); result.append("\n"); if (!showRuntime) { Enumeration doclist = nodelist.keys(); while (doclist.hasMoreElements()) { docid = (String) doclist.nextElement(); document = (String) nodelist.get(docid); result.append(" \n " + document + "\n \n"); } result.append("\n"); } // Time the request if asked for double stopTime = System.currentTimeMillis(); double dbOpenTime = (connTime - startTime) / 1000; double readTime = (stopTime - connTime) / 1000; double executionTime = (stopTime - startTime) / 1000; if (showRuntime) { System.out.print(" " + executionTime); System.out.print(" " + dbOpenTime); System.out.print(" " + readTime); System.out.print(" " + nodelist.size()); System.out.println(); } //System.out.println(result); //write into a file "result.txt" if (!showRuntime) { File f = new File("./result.txt"); Writer fw = new OutputStreamWriter(new FileOutputStream(f)); BufferedWriter out = new BufferedWriter(fw); out.write(result.toString()); out.flush(); out.close(); fw.close(); } } catch (Exception e) { System.err.println("Error in DBQuery.main"); System.err.println(e.getMessage()); e.printStackTrace(System.err); } } } /** * construct an instance of the DBQuery class * *

* Generally, one would call the findDocuments() routine after creating an * instance to specify the search query *

* * @param parserName the fully qualified name of a Java class implementing * the org.xml.sax.XMLReader interface */ public DBQuery() throws PropertyNotFoundException { String parserName = PropertyService.getProperty("xml.saxparser"); this.parserName = parserName; } /** * * Construct an instance of DBQuery Class * BUT accept a docid Vector that will supersede * the query.printSQL() method * * If a docid Vector is passed in, * the docids will be used to create a simple IN query * without the multiple subselects of the printSQL() method * * Using this constructor, we just check for * a docidOverride Vector in the findResultDoclist() method * * @param docids List of docids to display in the resultset */ public DBQuery(Vector docids) throws PropertyNotFoundException { // since the query will be too long to be handled, so we divided the // docids vector into couple vectors. int size = (new Integer(PropertyService.getProperty("database.appResultsetSize"))).intValue(); logMetacat.info("DBQuery.DBQuery - The size of select doicds is "+docids.size()); logMetacat.info("DBQuery.DBQuery - The application result size in metacat.properties is "+size); Vector subset = new Vector(); if (docids != null && docids.size() > size) { int index = 0; for (int i=0; i< docids.size(); i++) { if (index < size) { subset.add(docids.elementAt(i)); index ++; } else { docidOverride.add(subset); subset = new Vector(); subset.add(docids.elementAt(i)); index = 1; } } if (!subset.isEmpty()) { docidOverride.add(subset); } } else { this.docidOverride.add(docids); } String parserName = PropertyService.getProperty("xml.saxparser"); this.parserName = parserName; } /** * Method put the search result set into out printerwriter * @param resoponse the return response * @param out the output printer * @param params the paratermer hashtable * @param user the user name (it maybe different to the one in param) * @param groups the group array * @param sessionid the sessionid * @throws NotImplemented */ public void findDocuments(HttpServletResponse response, Writer out, Hashtable params, String user, String[] groups, String sessionid) throws PropertyNotFoundException { boolean useXMLIndex = (new Boolean(PropertyService.getProperty("database.usexmlindex"))) .booleanValue(); findDocuments(response, out, params, user, groups, sessionid, useXMLIndex); } /** * Method put the search result set into out printerwriter * @param resoponse the return response * @param out the output printer * @param params the paratermer hashtable * @param user the user name (it maybe different to the one in param) * @param groups the group array * @param sessionid the sessionid */ private void findDocuments(HttpServletResponse response, Writer out, Hashtable params, String user, String[] groups, String sessionid, boolean useXMLIndex) { if(!EnabledQueryEngines.getInstance().isEnabled(EnabledQueryEngines.PATHQUERYENGINE)) { try { String output = ""; output += ""; output += ""; output += XPATHQUERYOFFINFO; output += ""; out.write(output); out.close(); } catch (IOException e) { logMetacat.warn("DBQuery.findDocuments - metacat can't write the message that the pathquery is off to the client since :"+e.getMessage()); } return; } int pagesize = 0; int pagestart = 0; long transferWarnLimit = 0; if(params.containsKey("pagesize") && params.containsKey("pagestart")) { String pagesizeStr = ((String[])params.get("pagesize"))[0]; String pagestartStr = ((String[])params.get("pagestart"))[0]; if(pagesizeStr != null && pagestartStr != null) { pagesize = (new Integer(pagesizeStr)).intValue(); pagestart = (new Integer(pagestartStr)).intValue(); } } String xmlquery = null; String qformat = null; // get query and qformat try { xmlquery = ((String[])params.get("query"))[0]; logMetacat.info("DBQuery.findDocuments - SESSIONID: " + sessionid); logMetacat.info("DBQuery.findDocuments - xmlquery: " + xmlquery); qformat = ((String[])params.get("qformat"))[0]; logMetacat.info("DBQuery.findDocuments - qformat: " + qformat); } catch (Exception ee) { logMetacat.error("DBQuery.findDocuments - Couldn't retrieve xmlquery or qformat value from " +"params hashtable in DBQuery.findDocuments: " + ee.getMessage()); } // Get the XML query and covert it into a SQL statment QuerySpecification qspec = null; if ( xmlquery != null) { xmlquery = transformQuery(xmlquery); try { qspec = new QuerySpecification(xmlquery, parserName, PropertyService.getProperty("document.accNumSeparator")); } catch (Exception ee) { logMetacat.error("DBQuery.findDocuments - error generating QuerySpecification object: " + ee.getMessage()); } } if (qformat != null && qformat.equals(MetacatUtil.XMLFORMAT)) { //xml format if(response != null) { response.setContentType("text/xml"); } createResultDocument(xmlquery, qspec, out, user, groups, useXMLIndex, pagesize, pagestart, sessionid, qformat, false); }//if else { //skin format, in this case we will get whole result and sent it out response.setContentType("text/html"); Writer nonout = null; StringBuffer xml = createResultDocument(xmlquery, qspec, nonout, user, groups, useXMLIndex, pagesize, pagestart, sessionid, qformat, false); //transfer the xml to html try { long startHTMLTransform = System.currentTimeMillis(); DBTransform trans = new DBTransform(); response.setContentType("text/html"); // if the user is a moderator, then pass a param to the // xsl specifying the fact if(AuthUtil.isModerator(user, groups)){ params.put("isModerator", new String[] {"true"}); } trans.transformXMLDocument(xml.toString(), "-//NCEAS//resultset//EN", "-//W3C//HTML//EN", qformat, out, params, sessionid); long transformRunTime = System.currentTimeMillis() - startHTMLTransform; transferWarnLimit = Long.parseLong(PropertyService.getProperty("dbquery.transformTimeWarnLimit")); if (transformRunTime > transferWarnLimit) { logMetacat.warn("DBQuery.findDocuments - The time to transfrom resultset from xml to html format is " + transformRunTime); } MetacatUtil.writeDebugToFile("---------------------------------------------------------------------------------------------------------------Transfrom xml to html " + transformRunTime); MetacatUtil.writeDebugToDelimiteredFile(" " + transformRunTime, false); } catch(Exception e) { logMetacat.error("DBQuery.findDocuments - Error in MetaCatServlet.transformResultset:" +e.getMessage()); } }//else } /** * Transforms a hashtable of documents to an xml or html result and sent * the content to outputstream. Keep going untill hastable is empty. stop it. * add the QuerySpecification as parameter is for ecogrid. But it is duplicate * to xmlquery String * @param xmlquery * @param qspec * @param out * @param user * @param groups * @param useXMLIndex * @param sessionid * @return */ public StringBuffer createResultDocument(String xmlquery, QuerySpecification qspec, Writer out, String user, String[] groups, boolean useXMLIndex) { return createResultDocument(xmlquery,qspec,out, user,groups, useXMLIndex, 0, 0,"", qformat, false); } /** * * @param xmlquery * @param user * @param groups * @param useXMLIndex * @return * @throws IOException * @throws PropertyNotFoundException */ public String performPathquery(String xmlquery, String user, String[] groups) throws PropertyNotFoundException, IOException { // get the XML query and convert it to query specification xmlquery = transformQuery(xmlquery); QuerySpecification qspec = new QuerySpecification(xmlquery, parserName, PropertyService.getProperty("document.accNumSeparator")); // force it to output the results to the string buffer, not outputstream Writer nonout = null; boolean useXMLIndex = (new Boolean(PropertyService.getProperty("database.usexmlindex"))).booleanValue(); StringBuffer xml = createResultDocument(xmlquery, qspec, nonout, user, groups, useXMLIndex, 0, 0, "", qformat, true); return xml.toString(); } /* * Transforms a hashtable of documents to an xml or html result and sent * the content to outputstream. Keep going untill hastable is empty. stop it. * add the QuerySpecification as parameter is for ecogrid. But it is duplicate * to xmlquery String */ public StringBuffer createResultDocument(String xmlquery, QuerySpecification qspec, Writer out, String user, String[] groups, boolean useXMLIndex, int pagesize, int pagestart, String sessionid, String qformat, boolean includeGuid) { DBConnection dbconn = null; int serialNumber = -1; StringBuffer resultset = new StringBuffer(); //try to get the cached version first // Hashtable sessionHash = MetaCatServlet.getSessionHash(); // HttpSession sess = (HttpSession)sessionHash.get(sessionid); resultset.append("\n"); resultset.append("\n"); resultset.append(" " + pagestart + "\n"); resultset.append(" " + pagesize + "\n"); resultset.append(" " + (pagestart + 1) + "\n"); resultset.append(" " + (pagestart - 1) + "\n"); resultset.append(" "); //send out a new query if (out != null) { try { out.write(resultset.toString()); } catch (IOException e) { logMetacat.error(e.getMessage(), e); } } if (qspec != null) { try { //checkout the dbconnection dbconn = DBConnectionPool.getDBConnection("DBQuery.findDocuments"); serialNumber = dbconn.getCheckOutSerialNumber(); //print out the search result // search the doc list Vector givenDocids = new Vector(); StringBuffer resultContent = new StringBuffer(); if (docidOverride == null || docidOverride.size() == 0) { logMetacat.debug("DBQuery.createResultDocument - Not in map query"); resultContent = findResultDoclist(qspec, out, user, groups, dbconn, useXMLIndex, pagesize, pagestart, sessionid, givenDocids, qformat, includeGuid); } else { logMetacat.debug("DBQuery.createResultDocument - In map query"); // since docid can be too long to be handled. We divide it into several parts for (int i= 0; i parameterValues = new ArrayList(); StringBuffer resultsetBuffer = new StringBuffer(); String query = null; int count = 0; int index = 0; ResultDocumentSet docListResult = new ResultDocumentSet(); PreparedStatement pstmt = null; String docid = null; String guid = null; String docname = null; String doctype = null; String createDate = null; String updateDate = null; StringBuffer document = null; boolean lastpage = false; int rev = 0; double startTime = 0; int offset = 1; long startSelectionTime = System.currentTimeMillis(); ResultSet rs = null; // this is a hack for offset. in postgresql 7, if the returned docid list is too long, //the extend query which base on the docid will be too long to be run. So we // have to cut them into different parts. Page query don't need it somehow. if (out == null) { // for html page, we put everything into one page offset = (new Integer(PropertyService.getProperty("database.webResultsetSize"))).intValue(); } else { offset = (new Integer(PropertyService.getProperty("database.appResultsetSize"))).intValue(); } /* * Check the docidOverride Vector * if defined, we bypass the qspec.printSQL() method * and contruct a simpler query based on a * list of docids rather than a bunch of subselects */ // keep track of the values we add as prepared statement question marks (?) List docidValues = new ArrayList(); if ( givenDocids == null || givenDocids.size() == 0 ) { query = qspec.printSQL(useXMLIndex, docidValues); parameterValues.addAll(docidValues); } else { // condition for the docids List docidConditionValues = new ArrayList(); StringBuffer docidCondition = new StringBuffer(); docidCondition.append( " xml_documents.docid IN (" ); for (int i = 0; i < givenDocids.size(); i++) { docidCondition.append("?"); if (i < givenDocids.size()-1) { docidCondition.append(","); } docidConditionValues.add((String)givenDocids.elementAt(i)); } docidCondition.append( ") " ); // include the docids, either exclusively, or in conjuction with the query if (operator == null) { query = "SELECT xml_documents.docid, identifier.guid, docname, doctype, date_created, date_updated, xml_documents.rev " + "FROM xml_documents, identifier " + "WHERE xml_documents.docid = identifier.docid AND xml_documents.rev = identifier.rev AND "; query = query + docidCondition.toString(); parameterValues.addAll(docidConditionValues); } else { // start with the keyword query, but add conditions query = qspec.printSQL(useXMLIndex, docidValues); parameterValues.addAll(docidValues); String myOperator = ""; if (!query.endsWith("WHERE") && !query.endsWith("OR") && !query.endsWith("AND")) { if (operator.equalsIgnoreCase(QueryGroup.UNION)) { myOperator = " OR "; } else { myOperator = " AND "; } } query = query + myOperator + docidCondition.toString(); parameterValues.addAll(docidConditionValues); } } // we don't actually use this query for anything List ownerValues = new ArrayList(); String ownerQuery = getOwnerQuery(user, ownerValues); //logMetacat.debug("query: " + query); logMetacat.debug("DBQuery.findResultDoclist - owner query: " + ownerQuery); // if query is not the owner query, we need to check the permission // otherwise we don't need (owner has all permission by default) if (!query.equals(ownerQuery)) { // set user name and group qspec.setUserName(user); qspec.setGroup(groups); // Get access query String accessQuery = qspec.getAccessQuery(); if(!query.endsWith("AND")){ query = query + accessQuery; } else { query = query + accessQuery.substring(4, accessQuery.length()); } } logMetacat.debug("DBQuery.findResultDoclist - final selection query: " + query); pstmt = dbconn.prepareStatement(query); // set all the values we have collected pstmt = setPreparedStatementValues(parameterValues, pstmt); String queryCacheKey = null; // we only get cache for public if (user != null && user.equalsIgnoreCase("public") && pagesize == 0 && PropertyService.getProperty("database.queryCacheOn").equals("true")) { queryCacheKey = pstmt.toString() +qspec.getReturnDocList()+qspec.getReturnFieldList(); String cachedResult = getResultXMLFromCache(queryCacheKey); logMetacat.debug("=======DBQuery.findResultDoclist - The key of query cache is " + queryCacheKey); //System.out.println("==========the string from cache is "+cachedResult); if (cachedResult != null) { logMetacat.info("DBQuery.findResultDoclist - result from cache !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); if (out != null) { out.write(cachedResult); } resultsetBuffer.append(cachedResult); pstmt.close(); return resultsetBuffer; } } startTime = System.currentTimeMillis() / 1000; logMetacat.debug("Prepared statement after setting parameter values: " + pstmt.toString()); rs = pstmt.executeQuery(); double queryExecuteTime = System.currentTimeMillis() / 1000; logMetacat.debug("DBQuery.findResultDoclist - Time to execute select docid query is " + (queryExecuteTime - startTime)); MetacatUtil.writeDebugToFile("\n\n\n\n\n\nExecute selection query " + (queryExecuteTime - startTime)); MetacatUtil.writeDebugToDelimiteredFile(""+(queryExecuteTime - startTime), false); boolean tableHasRows = rs.next(); if(pagesize == 0) { //this makes sure we get all results if there is no paging pagesize = NONPAGESIZE; pagestart = NONPAGESIZE; } int currentIndex = 0; while (tableHasRows) { logMetacat.debug("DBQuery.findResultDoclist - getting result: " + currentIndex); docid = rs.getString(1).trim(); logMetacat.debug("DBQuery.findResultDoclist - docid: " + docid); guid = rs.getString(2).trim(); logMetacat.debug("DBQuery.findResultDoclist - guid: " + guid); docname = rs.getString(3); doctype = rs.getString(4); logMetacat.debug("DBQuery.findResultDoclist - doctype: " + doctype); createDate = rs.getString(5); updateDate = rs.getString(6); rev = rs.getInt(7); Vector returndocVec = qspec.getReturnDocList(); if (returndocVec.size() == 0 || returndocVec.contains(doctype)) { logMetacat.debug("DBQuery.findResultDoclist - NOT Back tracing now..."); document = new StringBuffer(); String completeDocid = docid + PropertyService.getProperty("document.accNumSeparator"); completeDocid += rev; document.append("").append(completeDocid).append(""); if (includeGuid) { document.append("").append(guid).append(""); } if (docname != null) { document.append("" + docname + ""); } if (doctype != null) { document.append("" + doctype + ""); } if (createDate != null) { document.append("" + createDate + ""); } if (updateDate != null) { document.append("" + updateDate + ""); } // Store the document id and the root node id docListResult.addResultDocument( new ResultDocument(docid, (String) document.toString())); logMetacat.info("DBQuery.findResultDoclist - real result: " + docid); currentIndex++; count++; }//else // when doclist reached the offset number, send out doc list and empty // the hash table if (count == offset && pagesize == NONPAGESIZE) { //if pagesize is not 0, do this later. //reset count //logMetacat.warn("############doing subset cache"); count = 0; handleSubsetResult(qspec, resultsetBuffer, out, docListResult, user, groups,dbconn, useXMLIndex, qformat); //reset docListResult docListResult = new ResultDocumentSet(); } logMetacat.debug("DBQuery.findResultDoclist - currentIndex: " + currentIndex); logMetacat.debug("DBQuery.findResultDoclist - page comparator: " + (pagesize * pagestart) + pagesize); if(currentIndex >= ((pagesize * pagestart) + pagesize)) { ResultDocumentSet pagedResultsHash = new ResultDocumentSet(); for(int i=pagesize*pagestart; i docListWarnLimit) { logMetacat.warn("DBQuery.findResultDoclist - Total time to get docid list is: " + docListTime); } MetacatUtil.writeDebugToFile("---------------------------------------------------------------------------------------------------------------Total selection: " + docListTime); MetacatUtil.writeDebugToDelimiteredFile(" "+ docListTime, false); //if docListResult is not empty, it need to be sent. if (docListResult.size() != 0) { handleSubsetResult(qspec,resultsetBuffer, out, docListResult, user, groups,dbconn, useXMLIndex, qformat); } resultsetBuffer.append("\n" + lastpage + "\n"); if (out != null) { out.write("\n" + lastpage + "\n"); } // now we only cached none-paged query and user is public if (user != null && user.equalsIgnoreCase("public") && pagesize == NONPAGESIZE && PropertyService.getProperty("database.queryCacheOn").equals("true")) { //System.out.println("the string stored into cache is "+ resultsetBuffer.toString()); storeQueryResultIntoCache(queryCacheKey, resultsetBuffer.toString()); } return resultsetBuffer; }//findReturnDoclist /* * Send completed search hashtable(part of reulst)to output stream * and buffer into a buffer stream */ private StringBuffer handleSubsetResult(QuerySpecification qspec, StringBuffer resultset, Writer out, ResultDocumentSet partOfDoclist, String user, String[]groups, DBConnection dbconn, boolean useXMLIndex, String qformat) throws Exception { double startReturnFieldTime = System.currentTimeMillis(); // check if there is a record in xml_returnfield // and get the returnfield_id and usage count int usage_count = getXmlReturnfieldsTableId(qspec, dbconn); boolean enterRecords = false; // get value of database.xmlReturnfieldCount int count = (new Integer(PropertyService .getProperty("database.xmlReturnfieldCount"))) .intValue(); // set enterRecords to true if usage_count is more than the offset // specified in metacat.properties if(usage_count > count){ enterRecords = true; } if(returnfield_id < 0){ logMetacat.warn("DBQuery.handleSubsetResult - Error in getting returnfield id from" + "xml_returnfield table"); enterRecords = false; } // get the hashtable containing the docids that already in the // xml_queryresult table logMetacat.info("DBQuery.handleSubsetResult - size of partOfDoclist before" + " docidsInQueryresultTable(): " + partOfDoclist.size()); long startGetReturnValueFromQueryresultable = System.currentTimeMillis(); Hashtable queryresultDocList = docidsInQueryresultTable(returnfield_id, partOfDoclist, dbconn); // remove the keys in queryresultDocList from partOfDoclist Enumeration _keys = queryresultDocList.keys(); while (_keys.hasMoreElements()){ partOfDoclist.remove((String)_keys.nextElement()); } long queryResultReturnValuetime = System.currentTimeMillis() - startGetReturnValueFromQueryresultable; long queryResultWarnLimit = Long.parseLong(PropertyService.getProperty("dbquery.findQueryResultsTimeWarnLimit")); if (queryResultReturnValuetime > queryResultWarnLimit) { logMetacat.warn("DBQuery.handleSubsetResult - Time to get return fields from xml_queryresult table is (Part1 in return fields) " + queryResultReturnValuetime); } MetacatUtil.writeDebugToFile("-----------------------------------------Get fields from xml_queryresult(Part1 in return fields) " + queryResultReturnValuetime); MetacatUtil.writeDebugToDelimiteredFile(" " + queryResultReturnValuetime,false); long startExtendedQuery = System.currentTimeMillis(); // backup the keys-elements in partOfDoclist to check later // if the doc entry is indexed yet Hashtable partOfDoclistBackup = new Hashtable(); Iterator itt = partOfDoclist.getDocids(); while (itt.hasNext()){ Object key = itt.next(); partOfDoclistBackup.put(key, partOfDoclist.get(key)); } logMetacat.info("DBQuery.handleSubsetResult - size of partOfDoclist after" + " docidsInQueryresultTable(): " + partOfDoclist.size()); //add return fields for the documents in partOfDoclist partOfDoclist = addReturnfield(partOfDoclist, qspec, user, groups, dbconn, useXMLIndex, qformat); long extendedQueryRunTime = startExtendedQuery - System.currentTimeMillis(); long extendedQueryWarnLimit = Long.parseLong(PropertyService.getProperty("dbquery.extendedQueryRunTimeWarnLimit")); if (extendedQueryRunTime > extendedQueryWarnLimit) { logMetacat.warn("DBQuery.handleSubsetResult - Get fields from index and node table (Part2 in return fields) " + extendedQueryRunTime); } MetacatUtil.writeDebugToFile("-----------------------------------------Get fields from extened query(Part2 in return fields) " + extendedQueryRunTime); MetacatUtil.writeDebugToDelimiteredFile(" " + extendedQueryRunTime, false); //add relationship part part docid list for the documents in partOfDocList //partOfDoclist = addRelationship(partOfDoclist, qspec, dbconn, useXMLIndex); long startStoreReturnField = System.currentTimeMillis(); Iterator keys = partOfDoclist.getDocids(); String key = null; String element = null; String query = null; int offset = (new Integer(PropertyService .getProperty("database.queryresultStringLength"))) .intValue(); while (keys.hasNext()) { key = (String) keys.next(); element = (String)partOfDoclist.get(key); // check if the enterRecords is true, elements is not null, element's // length is less than the limit of table column and if the document // has been indexed already if(enterRecords && element != null && element.length() < offset && element.compareTo((String) partOfDoclistBackup.get(key)) != 0){ query = "INSERT INTO xml_queryresult (returnfield_id, docid, " + "queryresult_string) VALUES (?, ?, ?)"; PreparedStatement pstmt = null; pstmt = dbconn.prepareStatement(query); pstmt.setInt(1, returnfield_id); pstmt.setString(2, key); pstmt.setString(3, element); dbconn.increaseUsageCount(1); try { pstmt.execute(); } catch(Exception e) { logMetacat.warn("DBQuery.handleSubsetResult - couldn't insert the element to xml_queryresult table "+e.getLocalizedMessage()); } finally { pstmt.close(); } } // A string with element String xmlElement = " " + element + ""; //send single element to output if (out != null) { out.write(xmlElement); } resultset.append(xmlElement); }//while double storeReturnFieldTime = System.currentTimeMillis() - startStoreReturnField; long storeReturnFieldWarnLimit = Long.parseLong(PropertyService.getProperty("dbquery.storeReturnFieldTimeWarnLimit")); if (storeReturnFieldTime > storeReturnFieldWarnLimit) { logMetacat.warn("DBQuery.handleSubsetResult - Time to store new return fields into xml_queryresult table (Part4 in return fields) " + storeReturnFieldTime); } MetacatUtil.writeDebugToFile("-----------------------------------------Insert new record to xml_queryresult(Part4 in return fields) " + storeReturnFieldTime); MetacatUtil.writeDebugToDelimiteredFile(" " + storeReturnFieldTime, false); Enumeration keysE = queryresultDocList.keys(); while (keysE.hasMoreElements()) { key = (String) keysE.nextElement(); element = (String)queryresultDocList.get(key); // A string with element String xmlElement = " " + element + ""; //send single element to output if (out != null) { out.write(xmlElement); } resultset.append(xmlElement); }//while double returnFieldTime = System.currentTimeMillis() - startReturnFieldTime; long totalReturnFieldWarnLimit = Long.parseLong(PropertyService.getProperty("dbquery.totalReturnFieldTimeWarnLimit")); if (returnFieldTime > totalReturnFieldWarnLimit) { logMetacat.warn("DBQuery.handleSubsetResult - Total time to get return fields is: " + returnFieldTime); } MetacatUtil.writeDebugToFile("DBQuery.handleSubsetResult - ---------------------------------------------------------------------------------------------------------------"+ "Total to get return fields " + returnFieldTime); MetacatUtil.writeDebugToDelimiteredFile("DBQuery.handleSubsetResult - "+ returnFieldTime, false); return resultset; } /** * Get the docids already in xml_queryresult table and corresponding * queryresultstring as a hashtable */ private Hashtable docidsInQueryresultTable(int returnfield_id, ResultDocumentSet partOfDoclist, DBConnection dbconn){ Hashtable returnValue = new Hashtable(); PreparedStatement pstmt = null; ResultSet rs = null; // keep track of parameter values List parameterValues = new ArrayList(); // get partOfDoclist as string for the query Iterator keylist = partOfDoclist.getDocids(); StringBuffer doclist = new StringBuffer(); while (keylist.hasNext()) { doclist.append("?,"); parameterValues.add((String) keylist.next()); }//while if (doclist.length() > 0) { doclist.deleteCharAt(doclist.length() - 1); //remove the last comma // the query to find out docids from xml_queryresult String query = "select docid, queryresult_string from " + "xml_queryresult where returnfield_id = " + returnfield_id +" and docid in ("+ doclist + ")"; logMetacat.info("DBQuery.docidsInQueryresultTable - Query to get docids from xml_queryresult:" + query); try { // prepare and execute the query pstmt = dbconn.prepareStatement(query); // bind parameter values pstmt = setPreparedStatementValues(parameterValues, pstmt); dbconn.increaseUsageCount(1); pstmt.execute(); rs = pstmt.getResultSet(); boolean tableHasRows = rs.next(); while (tableHasRows) { // store the returned results in the returnValue hashtable String key = rs.getString(1); String element = rs.getString(2); if(element != null){ returnValue.put(key, element); } else { logMetacat.info("DBQuery.docidsInQueryresultTable - Null elment found (" + "DBQuery.docidsInQueryresultTable)"); } tableHasRows = rs.next(); } rs.close(); pstmt.close(); } catch (Exception e){ logMetacat.error("DBQuery.docidsInQueryresultTable - Error getting docids from " + "queryresult: " + e.getMessage()); } } return returnValue; } /** * Method to get id from xml_returnfield table * for a given query specification */ private int returnfield_id; private int getXmlReturnfieldsTableId(QuerySpecification qspec, DBConnection dbconn){ int id = -1; int count = 1; PreparedStatement pstmt = null; ResultSet rs = null; String returnfield = qspec.getSortedReturnFieldString(); // query for finding the id from xml_returnfield String query = "SELECT returnfield_id, usage_count FROM xml_returnfield " + "WHERE returnfield_string LIKE ?"; logMetacat.info("DBQuery.getXmlReturnfieldsTableId - ReturnField Query:" + query); try { // prepare and run the query pstmt = dbconn.prepareStatement(query); pstmt.setString(1,returnfield); dbconn.increaseUsageCount(1); pstmt.execute(); rs = pstmt.getResultSet(); boolean tableHasRows = rs.next(); // if record found then increase the usage count // else insert a new record and get the id of the new record if(tableHasRows){ // get the id id = rs.getInt(1); count = rs.getInt(2) + 1; rs.close(); pstmt.close(); // increase the usage count query = "UPDATE xml_returnfield SET usage_count = ?" + " WHERE returnfield_id = ?"; logMetacat.info("DBQuery.getXmlReturnfieldsTableId - ReturnField Table Update:"+ query); pstmt = dbconn.prepareStatement(query); pstmt.setInt(1, count); pstmt.setInt(2, id); dbconn.increaseUsageCount(1); pstmt.execute(); pstmt.close(); } else { rs.close(); pstmt.close(); // insert a new record query = "INSERT INTO xml_returnfield (returnfield_string, usage_count)" + "VALUES (?, '1')"; logMetacat.info("DBQuery.getXmlReturnfieldsTableId - ReturnField Table Insert:"+ query); pstmt = dbconn.prepareStatement(query); pstmt.setString(1, returnfield); dbconn.increaseUsageCount(1); pstmt.execute(); pstmt.close(); // get the id of the new record query = "SELECT returnfield_id FROM xml_returnfield " + "WHERE returnfield_string LIKE ?"; logMetacat.info("DBQuery.getXmlReturnfieldsTableId - ReturnField query after Insert:" + query); pstmt = dbconn.prepareStatement(query); pstmt.setString(1, returnfield); dbconn.increaseUsageCount(1); pstmt.execute(); rs = pstmt.getResultSet(); if(rs.next()){ id = rs.getInt(1); } else { id = -1; } rs.close(); pstmt.close(); } } catch (Exception e){ logMetacat.error("DBQuery.getXmlReturnfieldsTableId - Error getting id from xml_returnfield in " + "DBQuery.getXmlReturnfieldsTableId: " + e.getMessage()); id = -1; } returnfield_id = id; return count; } /* * A method to add return field to return doclist hash table */ private ResultDocumentSet addReturnfield(ResultDocumentSet docListResult, QuerySpecification qspec, String user, String[]groups, DBConnection dbconn, boolean useXMLIndex, String qformat) throws Exception { PreparedStatement pstmt = null; ResultSet rs = null; String docid = null; String fieldname = null; String fieldtype = null; String fielddata = null; String relation = null; // keep track of parameter values List parameterValues = new ArrayList(); if (qspec.containsExtendedSQL()) { qspec.setUserName(user); qspec.setGroup(groups); Vector extendedFields = new Vector(qspec.getReturnFieldList()); Vector results = new Vector(); Iterator keylist = docListResult.getDocids(); StringBuffer doclist = new StringBuffer(); List doclistValues = new ArrayList(); Vector parentidList = new Vector(); Hashtable returnFieldValue = new Hashtable(); while (keylist.hasNext()) { String key = (String)keylist.next(); doclist.append("?,"); doclistValues.add(key); } if (doclist.length() > 0) { Hashtable controlPairs = new Hashtable(); doclist.deleteCharAt(doclist.length() - 1); //remove the last comma boolean tableHasRows = false; String extendedQuery = qspec.printExtendedSQL(doclist.toString(), useXMLIndex, parameterValues, doclistValues); // DO not add doclist values -- they are included in the query //parameterValues.addAll(doclistValues); logMetacat.info("DBQuery.addReturnfield - Extended query: " + extendedQuery); if(extendedQuery != null){ // long extendedQueryStart = System.currentTimeMillis(); pstmt = dbconn.prepareStatement(extendedQuery); // set the parameter values pstmt = DBQuery.setPreparedStatementValues(parameterValues, pstmt); //increase dbconnection usage count dbconn.increaseUsageCount(1); pstmt.execute(); rs = pstmt.getResultSet(); tableHasRows = rs.next(); while (tableHasRows) { ReturnFieldValue returnValue = new ReturnFieldValue(); docid = rs.getString(1).trim(); fieldname = rs.getString(2); if(qformat.toLowerCase().trim().equals("xml")) { byte[] b = rs.getBytes(3); fielddata = new String(b, 0, b.length, MetaCatServlet.DEFAULT_ENCODING); } else { fielddata = rs.getString(3); } //System.out.println("raw fielddata: " + fielddata); fielddata = MetacatUtil.normalize(fielddata); //System.out.println("normalized fielddata: " + fielddata); String parentId = rs.getString(4); fieldtype = rs.getString(5); StringBuffer value = new StringBuffer(); //handle case when usexmlindex is true differently //at one point merging the nodedata (for large text elements) was //deemed unnecessary - but now it is needed. but not for attribute nodes if (useXMLIndex || !containsKey(parentidList, parentId)) { //merge node data only for non-ATTRIBUTEs if (fieldtype != null && !fieldtype.equals("ATTRIBUTE")) { //try merging the data ReturnFieldValue existingRFV = getArrayValue(parentidList, parentId); if (existingRFV != null && !existingRFV.getFieldType().equals("ATTRIBUTE")) { fielddata = existingRFV.getFieldValue() + fielddata; } } //System.out.println("fieldname: " + fieldname + " fielddata: " + fielddata); value.append(""); value.append(fielddata); value.append(""); //set returnvalue returnValue.setDocid(docid); returnValue.setFieldValue(fielddata); returnValue.setFieldType(fieldtype); returnValue.setXMLFieldValue(value.toString()); // Store it in hastable putInArray(parentidList, parentId, returnValue); } else { // need to merge nodedata if they have same parent id and // node type is text fielddata = (String) ( (ReturnFieldValue) getArrayValue( parentidList, parentId)).getFieldValue() + fielddata; //System.out.println("fieldname: " + fieldname + " fielddata: " + fielddata); value.append(""); value.append(fielddata); value.append(""); returnValue.setDocid(docid); returnValue.setFieldValue(fielddata); returnValue.setFieldType(fieldtype); returnValue.setXMLFieldValue(value.toString()); // remove the old return value from paretnidList parentidList.remove(parentId); // store the new return value in parentidlit putInArray(parentidList, parentId, returnValue); } tableHasRows = rs.next(); } //while rs.close(); pstmt.close(); // put the merger node data info into doclistReult Enumeration xmlFieldValue = (getElements(parentidList)). elements(); while (xmlFieldValue.hasMoreElements()) { ReturnFieldValue object = (ReturnFieldValue) xmlFieldValue.nextElement(); docid = object.getDocid(); if (docListResult.containsDocid(docid)) { String removedelement = (String) docListResult. remove(docid); docListResult. addResultDocument(new ResultDocument(docid, removedelement + object.getXMLFieldValue())); } else { docListResult.addResultDocument( new ResultDocument(docid, object.getXMLFieldValue())); } } //while // double docListResultEnd = System.currentTimeMillis() / 1000; // logMetacat.warn( // "Time to prepare ResultDocumentSet after" // + " execute extended query: " // + (docListResultEnd - extendedQueryEnd)); } }//if doclist lenght is great than zero }//if has extended query return docListResult; }//addReturnfield /** * removes the tag from the beginning. This takes a * string as a param instead of a hashtable. * * @param xmlquery a string representing a query. */ private String transformQuery(String xmlquery) { xmlquery = xmlquery.trim(); int index = xmlquery.indexOf("?>"); if (index != -1) { return xmlquery.substring(index + 2, xmlquery.length()); } else { return xmlquery; } } /* * Method to store query string and result xml string into query result * cache. If the size alreay reache the limitation, the cache will be * cleared first, then store them. */ private void storeQueryResultIntoCache(String query, String resultXML) { synchronized (queryResultCache) { if (queryResultCache.size() >= QUERYRESULTCACHESIZE) { queryResultCache.clear(); } queryResultCache.put(query, resultXML); } } /* * Method to get result xml string from query result cache. * Note: the returned string can be null. */ private String getResultXMLFromCache(String query) { String resultSet = null; synchronized (queryResultCache) { try { logMetacat.info("DBQuery.getResultXMLFromCache - Get query from cache"); resultSet = (String)queryResultCache.get(query); } catch (Exception e) { resultSet = null; } } return resultSet; } /** * Method to clear the query result cache. */ public static void clearQueryResultCache() { synchronized (queryResultCache) { queryResultCache.clear(); } } /** * Set the parameter values in the prepared statement using instrospection * of the given value objects * @param parameterValues * @param pstmt * @return * @throws SQLException */ public static PreparedStatement setPreparedStatementValues(List parameterValues, PreparedStatement pstmt) throws SQLException { // set all the values we have collected int parameterIndex = 1; for (Object parameterValue: parameterValues) { if (parameterValue instanceof String) { pstmt.setString(parameterIndex, (String) parameterValue); } else if (parameterValue instanceof Integer) { pstmt.setInt(parameterIndex, (Integer) parameterValue); } else if (parameterValue instanceof Float) { pstmt.setFloat(parameterIndex, (Float) parameterValue); } else if (parameterValue instanceof Double) { pstmt.setDouble(parameterIndex, (Double) parameterValue); } else if (parameterValue instanceof Date) { pstmt.setTimestamp(parameterIndex, new Timestamp(((Date) parameterValue).getTime())); } else { pstmt.setObject(parameterIndex, parameterValue); } parameterIndex++; } return pstmt; } /* * A method to search if Vector contains a particular key string */ private boolean containsKey(Vector parentidList, String parentId) { Vector tempVector = null; for (int count = 0; count < parentidList.size(); count++) { tempVector = (Vector) parentidList.get(count); if (parentId.compareTo((String) tempVector.get(0)) == 0) { return true; } } return false; } /* * A method to put key and value in Vector */ private void putInArray(Vector parentidList, String key, ReturnFieldValue value) { Vector tempVector = null; //only filter if the field type is NOT an attribute (say, for text) String fieldType = value.getFieldType(); if (fieldType != null && !fieldType.equals("ATTRIBUTE")) { for (int count = 0; count < parentidList.size(); count++) { tempVector = (Vector) parentidList.get(count); if (key.compareTo((String) tempVector.get(0)) == 0) { tempVector.remove(1); tempVector.add(1, value); return; } } } tempVector = new Vector(); tempVector.add(0, key); tempVector.add(1, value); parentidList.add(tempVector); return; } /* * A method to get value in Vector given a key */ private ReturnFieldValue getArrayValue(Vector parentidList, String key) { Vector tempVector = null; for (int count = 0; count < parentidList.size(); count++) { tempVector = (Vector) parentidList.get(count); if (key.compareTo((String) tempVector.get(0)) == 0) { return (ReturnFieldValue) tempVector .get(1); } } return null; } /* * A method to get enumeration of all values in Vector */ private Vector getElements(Vector parentidList) { Vector enumVector = new Vector(); Vector tempVector = null; for (int count = 0; count < parentidList.size(); count++) { tempVector = (Vector) parentidList.get(count); enumVector.add(tempVector.get(1)); } return enumVector; } /* * A method to create a query to get owner's docid list */ private String getOwnerQuery(String owner, List parameterValues) { if (owner != null) { owner = owner.toLowerCase(); } StringBuffer self = new StringBuffer(); self.append("SELECT docid,docname,doctype,"); self.append("date_created, date_updated, rev "); self.append("FROM xml_documents WHERE docid IN ("); self.append("("); self.append("SELECT DISTINCT docid FROM xml_nodes WHERE \n"); self.append("nodedata LIKE '%%%' "); self.append(") \n"); self.append(") "); self.append(" AND ("); self.append(" lower(user_owner) = ?"); self.append(") "); parameterValues.add(owner); return self.toString(); } /** * format a structured query as an XML document that conforms to the * pathquery.dtd and is appropriate for submission to the DBQuery * structured query engine * * @param params The list of parameters that should be included in the * query */ public static String createSQuery(Hashtable params) throws PropertyNotFoundException { StringBuffer query = new StringBuffer(); Enumeration elements; Enumeration keys; String filterDoctype = null; String casesensitive = null; String searchmode = null; Object nextkey; Object nextelement; //add the xml headers query.append("\n"); query.append("\n"); if (params.containsKey("meta_file_id")) { query.append(""); query.append(((String[]) params.get("meta_file_id"))[0]); query.append(""); } if (params.containsKey("returndoctype")) { String[] returnDoctypes = ((String[]) params.get("returndoctype")); for (int i = 0; i < returnDoctypes.length; i++) { String doctype = (String) returnDoctypes[i]; if (!doctype.equals("any") && !doctype.equals("ANY") && !doctype.equals("")) { query.append("").append(doctype); query.append(""); } } } if (params.containsKey("filterdoctype")) { String[] filterDoctypes = ((String[]) params.get("filterdoctype")); for (int i = 0; i < filterDoctypes.length; i++) { query.append("").append(filterDoctypes[i]); query.append(""); } } if (params.containsKey("returnfield")) { String[] returnfield = ((String[]) params.get("returnfield")); for (int i = 0; i < returnfield.length; i++) { query.append("").append(returnfield[i]); query.append(""); } } if (params.containsKey("owner")) { String[] owner = ((String[]) params.get("owner")); for (int i = 0; i < owner.length; i++) { query.append("").append(owner[i]); query.append(""); } } if (params.containsKey("site")) { String[] site = ((String[]) params.get("site")); for (int i = 0; i < site.length; i++) { query.append("").append(site[i]); query.append(""); } } //allows the dynamic switching of boolean operators if (params.containsKey("operator")) { query.append(""); } else { //the default operator is UNION query.append(""); } if (params.containsKey("casesensitive")) { casesensitive = ((String[]) params.get("casesensitive"))[0]; } else { casesensitive = "false"; } if (params.containsKey("searchmode")) { searchmode = ((String[]) params.get("searchmode"))[0]; } else { searchmode = "contains"; } //anyfield is a special case because it does a //free text search. It does not have a //tag. This allows for a free text search within the structured //query. This is useful if the INTERSECT operator is used. if (params.containsKey("anyfield")) { String[] anyfield = ((String[]) params.get("anyfield")); //allow for more than one value for anyfield for (int i = 0; i < anyfield.length; i++) { if (anyfield[i] != null && !anyfield[i].equals("")) { query.append("" + StringEscapeUtils.escapeXml(anyfield[i]) + ""); } } } //this while loop finds the rest of the parameters //and attempts to query for the field specified //by the parameter. elements = params.elements(); keys = params.keys(); while (keys.hasMoreElements() && elements.hasMoreElements()) { nextkey = keys.nextElement(); nextelement = elements.nextElement(); //make sure we aren't querying for any of these //parameters since the are already in the query //in one form or another. Vector ignoredParams = new Vector(); ignoredParams.add("returndoctype"); ignoredParams.add("filterdoctype"); ignoredParams.add("action"); ignoredParams.add("qformat"); ignoredParams.add("anyfield"); ignoredParams.add("returnfield"); ignoredParams.add("owner"); ignoredParams.add("site"); ignoredParams.add("operator"); ignoredParams.add("sessionid"); ignoredParams.add("pagesize"); ignoredParams.add("pagestart"); ignoredParams.add("searchmode"); // Also ignore parameters listed in the properties file // so that they can be passed through to stylesheets String paramsToIgnore = PropertyService .getProperty("database.queryignoredparams"); StringTokenizer st = new StringTokenizer(paramsToIgnore, ","); while (st.hasMoreTokens()) { ignoredParams.add(st.nextToken()); } if (!ignoredParams.contains(nextkey.toString())) { //allow for more than value per field name for (int i = 0; i < ((String[]) nextelement).length; i++) { if (!((String[]) nextelement)[i].equals("")) { query.append("" + "" + //add the query value StringEscapeUtils.escapeXml(((String[]) nextelement)[i]) + "" + //add the path to query by nextkey.toString() + ""); } } } } query.append(""); //append on the end of the xml and return the result as a string return query.toString(); } /** * format a simple free-text value query as an XML document that conforms * to the pathquery.dtd and is appropriate for submission to the DBQuery * structured query engine * * @param value the text string to search for in the xml catalog * @param doctype the type of documents to include in the result set -- use * "any" or "ANY" for unfiltered result sets */ public static String createQuery(String value, String doctype) { StringBuffer xmlquery = new StringBuffer(); xmlquery.append("\n"); xmlquery.append(""); if (!doctype.equals("any") && !doctype.equals("ANY")) { xmlquery.append(""); xmlquery.append(doctype).append(""); } xmlquery.append(""); //chad added - 8/14 //the if statement allows a query to gracefully handle a null //query. Without this if a nullpointerException is thrown. if (!value.equals("")) { xmlquery.append(""); xmlquery.append("").append(value).append(""); xmlquery.append(""); } xmlquery.append(""); xmlquery.append(""); return (xmlquery.toString()); } /** * format a simple free-text value query as an XML document that conforms * to the pathquery.dtd and is appropriate for submission to the DBQuery * structured query engine * * @param value the text string to search for in the xml catalog */ public static String createQuery(String value) { return createQuery(value, "any"); } /** * Check for "READ" permission on @docid for @user and/or @group from DB * connection */ private boolean hasPermission(String user, String[] groups, String docid) throws SQLException, Exception { // Check for READ permission on @docid for @user and/or @groups PermissionController controller = new PermissionController(docid); return controller.hasPermission(user, groups, AccessControlInterface.READSTRING); } /** * Get all docIds list for a data packadge * * @param dataPackageDocid, the string in docId field of xml_relation table */ private Vector getCurrentDocidListForDataPackage(String dataPackageDocid) { DBConnection dbConn = null; int serialNumber = -1; Vector docIdList = new Vector();//return value PreparedStatement pStmt = null; ResultSet rs = null; String docIdInSubjectField = null; String docIdInObjectField = null; // Check the parameter if (dataPackageDocid == null || dataPackageDocid.equals("")) { return docIdList; }//if //the query stirng String query = "SELECT subject, object from xml_relation where docId = ?"; try { dbConn = DBConnectionPool .getDBConnection("DBQuery.getCurrentDocidListForDataPackage"); serialNumber = dbConn.getCheckOutSerialNumber(); pStmt = dbConn.prepareStatement(query); //bind the value to query pStmt.setString(1, dataPackageDocid); //excute the query pStmt.execute(); //get the result set rs = pStmt.getResultSet(); //process the result while (rs.next()) { //In order to get the whole docIds in a data packadge, //we need to put the docIds of subject and object field in // xml_relation //into the return vector docIdInSubjectField = rs.getString(1);//the result docId in // subject field docIdInObjectField = rs.getString(2);//the result docId in // object field //don't put the duplicate docId into the vector if (!docIdList.contains(docIdInSubjectField)) { docIdList.add(docIdInSubjectField); } //don't put the duplicate docId into the vector if (!docIdList.contains(docIdInObjectField)) { docIdList.add(docIdInObjectField); } }//while //close the pStmt pStmt.close(); }//try catch (SQLException e) { logMetacat.error("DBQuery.getCurrentDocidListForDataPackage - Error in getDocidListForDataPackage: " + e.getMessage()); }//catch finally { try { pStmt.close(); }//try catch (SQLException ee) { logMetacat.error("DBQuery.getCurrentDocidListForDataPackage - SQL Error: " + ee.getMessage()); }//catch finally { DBConnectionPool.returnDBConnection(dbConn, serialNumber); }//fianlly }//finally return docIdList; }//getCurrentDocidListForDataPackadge() /** * Get all docIds list for a data packadge * * @param dataPackageDocid, the string in docId field of xml_relation table */ private Vector getOldVersionDocidListForDataPackage(String dataPackageDocidWithRev) { Vector docIdList = new Vector();//return value Vector tripleList = null; String xml = null; // Check the parameter if (dataPackageDocidWithRev == null || dataPackageDocidWithRev.equals("")) { return docIdList; }//if try { //initial a documentImpl object DocumentImpl packageDocument = new DocumentImpl(dataPackageDocidWithRev); //transfer to documentImpl object to string xml = packageDocument.toString(); //create a tripcollection object TripleCollection tripleForPackage = new TripleCollection( new StringReader(xml)); //get the vetor of triples tripleList = tripleForPackage.getCollection(); for (int i = 0; i < tripleList.size(); i++) { //put subject docid into docIdlist without duplicate if (!docIdList.contains(((Triple) tripleList.elementAt(i)) .getSubject())) { //put subject docid into docIdlist docIdList.add(((Triple) tripleList.get(i)).getSubject()); } //put object docid into docIdlist without duplicate if (!docIdList.contains(((Triple) tripleList.elementAt(i)) .getObject())) { docIdList.add(((Triple) (tripleList.get(i))).getObject()); } }//for }//try catch (Exception e) { logMetacat.error("DBQuery.getCurrentDocidListForDataPackage - General error: " + e.getMessage()); }//catch // return result return docIdList; }//getDocidListForPackageInXMLRevisions() /** * Check if the docId is a data packadge id. If the id is a data packadage * id, it should be store in the docId fields in xml_relation table. So we * can use a query to get the entries which the docId equals the given * value. If the result is null. The docId is not a packadge id. Otherwise, * it is. * * @param docId, the id need to be checked */ private boolean isDataPackageId(String docId) { boolean result = false; PreparedStatement pStmt = null; ResultSet rs = null; String query = "SELECT docId from xml_relation where docId = ?"; DBConnection dbConn = null; int serialNumber = -1; try { dbConn = DBConnectionPool .getDBConnection("DBQuery.isDataPackageId"); serialNumber = dbConn.getCheckOutSerialNumber(); pStmt = dbConn.prepareStatement(query); //bind the value to query pStmt.setString(1, docId); //execute the query pStmt.execute(); rs = pStmt.getResultSet(); //process the result if (rs.next()) //There are some records for the id in docId fields { result = true;//It is a data packadge id } pStmt.close(); }//try catch (SQLException e) { logMetacat.error("DBQuery.isDataPackageId - SQL Error: " + e.getMessage()); } finally { try { pStmt.close(); }//try catch (SQLException ee) { logMetacat.error("DBQuery.isDataPackageId - SQL Error in isDataPackageId: " + ee.getMessage()); }//catch finally { DBConnectionPool.returnDBConnection(dbConn, serialNumber); }//finally }//finally return result; }//isDataPackageId() public String getOperator() { return operator; } /** * Specifies if and how docid overrides should be included in the general query * @param operator null, UNION, or INTERSECT (see QueryGroup) */ public void setOperator(String operator) { this.operator = operator; } public String getQformat() { return qformat; } public void setQformat(String qformat) { this.qformat = qformat; } /** * Check if the user has the permission to export data package * * @param conn, the connection * @param docId, the id need to be checked * @param user, the name of user * @param groups, the user's group */ private boolean hasPermissionToExportPackage(String docId, String user, String[] groups) throws Exception { //DocumentImpl doc=new DocumentImpl(conn,docId); return DocumentImpl.hasReadPermission(user, groups, docId); } /** * Get the current Rev for a docid in xml_documents table * * @param docId, the id need to get version numb If the return value is -5, * means no value in rev field for this docid */ private int getCurrentRevFromXMLDoumentsTable(String docId) throws SQLException { int rev = -5; PreparedStatement pStmt = null; ResultSet rs = null; String query = "SELECT rev from xml_documents where docId = ?"; DBConnection dbConn = null; int serialNumber = -1; try { dbConn = DBConnectionPool .getDBConnection("DBQuery.getCurrentRevFromXMLDocumentsTable"); serialNumber = dbConn.getCheckOutSerialNumber(); pStmt = dbConn.prepareStatement(query); //bind the value to query pStmt.setString(1, docId); //execute the query pStmt.execute(); rs = pStmt.getResultSet(); //process the result if (rs.next()) //There are some records for rev { rev = rs.getInt(1); ;//It is the version for given docid } else { rev = -5; } }//try catch (SQLException e) { logMetacat.error("DBQuery.getCurrentRevFromXMLDoumentsTable - SQL Error: " + e.getMessage()); throw e; }//catch finally { try { pStmt.close(); }//try catch (SQLException ee) { logMetacat.error( "DBQuery.getCurrentRevFromXMLDoumentsTable - SQL Error: " + ee.getMessage()); }//catch finally { DBConnectionPool.returnDBConnection(dbConn, serialNumber); }//finally }//finally return rev; }//getCurrentRevFromXMLDoumentsTable /** * put a doc into a zip output stream * * @param docImpl, docmentImpl object which will be sent to zip output * stream * @param zipOut, zip output stream which the docImpl will be put * @param packageZipEntry, the zip entry name for whole package */ private void addDocToZipOutputStream(DocumentImpl docImpl, ZipOutputStream zipOut, String packageZipEntry) throws ClassNotFoundException, IOException, SQLException, McdbException, Exception { byte[] byteString = null; ZipEntry zEntry = null; byteString = docImpl.getBytes(); //use docId as the zip entry's name String fullDocId = docImpl.getDocID() + PropertyService.getProperty("document.accNumSeparator") + docImpl.getRev(); zEntry = new ZipEntry(packageZipEntry + "/metadata/" + fullDocId ); zEntry.setSize(byteString.length); zipOut.putNextEntry(zEntry); zipOut.write(byteString, 0, byteString.length); zipOut.closeEntry(); }//addDocToZipOutputStream() /** * Transfer a docid vetor to a documentImpl vector. The documentImpl vetor * only inlcudes current version. If a DocumentImple object couldn't find * for a docid, then the String of this docid was added to vetor rather * than DocumentImple object. * * @param docIdList, a vetor hold a docid list for a data package. In * docid, there is not version number in it. */ private Vector getCurrentAllDocumentImpl(Vector docIdList) throws McdbException, Exception { //Connection dbConn=null; Vector documentImplList = new Vector(); int rev = 0; // Check the parameter if (docIdList.isEmpty()) { return documentImplList; }//if //for every docid in vector for (int i = 0; i < docIdList.size(); i++) { try { //get newest version for this docId String smartDocid = DocumentUtil.getSmartDocId((String) docIdList.elementAt(i)); rev = getCurrentRevFromXMLDoumentsTable(smartDocid); // There is no record for this docId in xml_documents table if (rev == -5) { // Rather than put DocumentImple object, put a String // Object(docid) // into the documentImplList documentImplList.add((String) docIdList.elementAt(i)); // Skip other code continue; } String docidPlusVersion = smartDocid + PropertyService.getProperty("document.accNumSeparator") + rev; //create new documentImpl object DocumentImpl documentImplObject = new DocumentImpl( docidPlusVersion); //add them to vector documentImplList.add(documentImplObject); }//try catch (Exception e) { logMetacat.error("DBQuery.getCurrentAllDocumentImpl - General error: " + e.getMessage()); // continue the for loop continue; } }//for return documentImplList; } /** * Transfer a docid vetor to a documentImpl vector. If a DocumentImple * object couldn't find for a docid, then the String of this docid was * added to vetor rather than DocumentImple object. * * @param docIdList, a vetor hold a docid list for a data package. In * docid, t here is version number in it. */ private Vector getOldVersionAllDocumentImpl(Vector docIdList) { //Connection dbConn=null; Vector documentImplList = new Vector(); String siteCode = null; String uniqueId = null; int rev = 0; // Check the parameter if (docIdList.isEmpty()) { return documentImplList; }//if //for every docid in vector for (int i = 0; i < docIdList.size(); i++) { String docidPlusVersion = (String) (docIdList.elementAt(i)); try { //create new documentImpl object DocumentImpl documentImplObject = new DocumentImpl( docidPlusVersion); //add them to vector documentImplList.add(documentImplObject); }//try catch (McdbDocNotFoundException notFoundE) { logMetacat.error("DBQuery.getOldVersionAllDocument - Error finding doc " + docidPlusVersion + " : " + notFoundE.getMessage()); // Rather than add a DocumentImple object into vetor, a String // object // - the doicd was added to the vector documentImplList.add(docidPlusVersion); // Continue the for loop continue; }//catch catch (Exception e) { logMetacat.error( "DBQuery.getOldVersionAllDocument - General error: " + e.getMessage()); // Continue the for loop continue; }//catch }//for return documentImplList; }//getOldVersionAllDocumentImple /** * put a data file into a zip output stream * * @param docImpl, docmentImpl object which will be sent to zip output * stream * @param zipOut, the zip output stream which the docImpl will be put * @param packageZipEntry, the zip entry name for whole package */ private void addDataFileToZipOutputStream(DocumentImpl docImpl, ZipOutputStream zipOut, String packageZipEntry) throws ClassNotFoundException, IOException, SQLException, McdbException, Exception { byte[] byteString = null; ZipEntry zEntry = null; // this is data file; add file to zip String filePath = PropertyService.getProperty("application.datafilepath"); if (!filePath.endsWith("/")) { filePath += "/"; } String fileName = docImpl.getDocID() + PropertyService.getProperty("document.accNumSeparator") + docImpl.getRev(); String entityName = docImpl.getDocname(); filePath = filePath + fileName; zEntry = new ZipEntry(packageZipEntry + "/data/" + fileName + "-" + entityName); zipOut.putNextEntry(zEntry); FileInputStream fin = null; try { fin = new FileInputStream(filePath); byte[] buf = new byte[4 * 1024]; // 4K buffer int b = fin.read(buf); while (b != -1) { zipOut.write(buf, 0, b); b = fin.read(buf); } fin.close(); zipOut.closeEntry(); }//try catch (IOException ioe) { logMetacat.error("DBQuery.addDataFileToZipOutputStream - I/O error: " + ioe.getMessage()); } finally { IOUtils.closeQuietly(fin); } }//addDataFileToZipOutputStream() /** * create a html summary for data package and put it into zip output stream * * @param docImplList, the documentImpl ojbects in data package * @param zipOut, the zip output stream which the html should be put * @param packageZipEntry, the zip entry name for whole package */ private void addHtmlSummaryToZipOutputStream(Vector docImplList, ZipOutputStream zipOut, String packageZipEntry) throws Exception { StringBuffer htmlDoc = new StringBuffer(); ZipEntry zEntry = null; byte[] byteString = null; InputStream source; DBTransform xmlToHtml; //create a DBTransform ojbect xmlToHtml = new DBTransform(); //head of html htmlDoc.append(""); for (int i = 0; i < docImplList.size(); i++) { // If this String object, this means it is missed data file if ((((docImplList.elementAt(i)).getClass()).toString()) .equals("class java.lang.String")) { htmlDoc.append(""); htmlDoc.append("Data File: "); htmlDoc.append(dataFileid).append("
"); htmlDoc.append("


"); }//if else if ((((DocumentImpl) docImplList.elementAt(i)).getDoctype()) .compareTo("BIN") != 0) { //this is an xml file so we can // transform it. //transform each file individually then concatenate all of the //transformations together. //for metadata xml title htmlDoc.append("

"); htmlDoc.append(((DocumentImpl) docImplList.elementAt(i)) .getDocID()); //htmlDoc.append("."); //htmlDoc.append(((DocumentImpl)docImplList.elementAt(i)).getRev()); htmlDoc.append("

"); //do the actual transform Writer docString = new StringWriter(); xmlToHtml.transformXMLDocument( ((DocumentImpl) docImplList.elementAt(i)).toString(), ((DocumentImpl) docImplList.elementAt(i)).getDoctype(), //"-//NCEAS//eml-generic//EN", "-//W3C//HTML//EN", qformat, docString, null, null); htmlDoc.append(docString.toString()); htmlDoc.append("




"); }//if else { //this is a data file so we should link to it in the html htmlDoc.append(""); htmlDoc.append("Data File: "); htmlDoc.append(dataFileid).append("
"); htmlDoc.append("


"); }//else }//for htmlDoc.append(""); // use standard encoding even though the different docs might have use different encodings, // the String objects in java should be correct and able to be encoded as the same Metacat default byteString = htmlDoc.toString().getBytes(MetaCatServlet.DEFAULT_ENCODING); zEntry = new ZipEntry(packageZipEntry + "/metadata.html"); zEntry.setSize(byteString.length); zipOut.putNextEntry(zEntry); zipOut.write(byteString, 0, byteString.length); zipOut.closeEntry(); //dbConn.close(); }//addHtmlSummaryToZipOutputStream /** * put a data packadge into a zip output stream * * @param docId, which the user want to put into zip output stream,it has version * @param out, a servletoutput stream which the zip output stream will be * put * @param user, the username of the user * @param groups, the group of the user */ public ZipOutputStream getZippedPackage(String docIdString, ServletOutputStream out, String user, String[] groups, String passWord) throws ClassNotFoundException, IOException, SQLException, McdbException, NumberFormatException, Exception { ZipOutputStream zOut = null; String elementDocid = null; DocumentImpl docImpls = null; //Connection dbConn = null; Vector docIdList = new Vector(); Vector documentImplList = new Vector(); Vector htmlDocumentImplList = new Vector(); String packageId = null; String rootName = "package";//the package zip entry name String docId = null; int version = -5; // Docid without revision docId = DocumentUtil.getDocIdFromString(docIdString); // revision number version = DocumentUtil.getVersionFromString(docIdString); //check if the reqused docId is a data package id if (!isDataPackageId(docId)) { /* * Exception e = new Exception("The request the doc id " * +docIdString+ " is not a data package id"); */ //CB 1/6/03: if the requested docid is not a datapackage, we just // zip //up the single document and return the zip file. if (!hasPermissionToExportPackage(docId, user, groups)) { Exception e = new Exception("User " + user + " does not have permission" + " to export the data package " + docIdString); throw e; } docImpls = new DocumentImpl(docIdString); //checking if the user has the permission to read the documents if (DocumentImpl.hasReadPermission(user, groups, docImpls .getDocID())) { zOut = new ZipOutputStream(out); //if the docImpls is metadata if ((docImpls.getDoctype()).compareTo("BIN") != 0) { //add metadata into zip output stream addDocToZipOutputStream(docImpls, zOut, rootName); }//if else { //it is data file addDataFileToZipOutputStream(docImpls, zOut, rootName); htmlDocumentImplList.add(docImpls); }//else }//if zOut.finish(); //terminate the zip file return zOut; } // Check the permission of user else if (!hasPermissionToExportPackage(docIdString, user, groups)) { Exception e = new Exception("User " + user + " does not have permission" + " to export the data package " + docIdString); throw e; } else //it is a packadge id { //store the package id packageId = docId; //get current version in database int currentVersion = getCurrentRevFromXMLDoumentsTable(packageId); //If it is for current version (-1 means user didn't specify // revision) if ((version == -1) || version == currentVersion) { //get current version number version = currentVersion; //get package zip entry name //it should be docId.revsion.package rootName = packageId + PropertyService.getProperty("document.accNumSeparator") + version + PropertyService.getProperty("document.accNumSeparator") + "package"; //get the whole id list for data packadge docIdList = getCurrentDocidListForDataPackage(packageId); //get the whole documentImple object documentImplList = getCurrentAllDocumentImpl(docIdList); }//if else if (version > currentVersion || version < -1) { throw new Exception("The user specified docid: " + docId + "." + version + " doesn't exist"); }//else if else //for an old version { rootName = docIdString + PropertyService.getProperty("document.accNumSeparator") + "package"; //get the whole id list for data packadge docIdList = getOldVersionDocidListForDataPackage(docIdString); //get the whole documentImple object documentImplList = getOldVersionAllDocumentImpl(docIdList); }//else // Make sure documentImplist is not empty if (documentImplList.isEmpty()) { throw new Exception( "Couldn't find component for data package: " + packageId); }//if zOut = new ZipOutputStream(out); //put every element into zip output stream for (int i = 0; i < documentImplList.size(); i++) { // if the object in the vetor is String, this means we couldn't // find // the document locally, we need find it remote if ((((documentImplList.elementAt(i)).getClass()).toString()) .equals("class java.lang.String")) { // Get String object from vetor String documentId = (String) documentImplList.elementAt(i); logMetacat.info("DBQuery.getZippedPackage - docid: " + documentId); // Get doicd without revision String docidWithoutRevision = DocumentUtil.getDocIdFromString(documentId); logMetacat.info("DBQuery.getZippedPackage - docidWithoutRevsion: " + docidWithoutRevision); // Get revision String revision = DocumentUtil.getRevisionStringFromString(documentId); logMetacat.info("DBQuery.getZippedPackage - revision from docIdentifier: " + revision); // Zip entry string String zipEntryPath = rootName + "/data/"; // Create a RemoteDocument object RemoteDocument remoteDoc = new RemoteDocument( docidWithoutRevision, revision, user, passWord, zipEntryPath); // Here we only read data file from remote metacat String docType = remoteDoc.getDocType(); if (docType != null) { if (docType.equals("BIN")) { // Put remote document to zip output remoteDoc.readDocumentFromRemoteServerByZip(zOut); // Add String object to htmlDocumentImplList String elementInHtmlList = remoteDoc .getDocIdWithoutRevsion() + PropertyService.getProperty("document.accNumSeparator") + remoteDoc.getRevision(); htmlDocumentImplList.add(elementInHtmlList); }//if }//if }//if else { //create a docmentImpls object (represent xml doc) base on // the docId docImpls = (DocumentImpl) documentImplList.elementAt(i); //checking if the user has the permission to read the // documents String fullDocId = docImpls.getDocID() + PropertyService.getProperty("document.accNumSeparator") + docImpls.getRev(); if (DocumentImpl.hasReadPermission(user, groups, fullDocId )) { //if the docImpls is metadata if ((docImpls.getDoctype()).compareTo("BIN") != 0) { //add metadata into zip output stream addDocToZipOutputStream(docImpls, zOut, rootName); //add the documentImpl into the vetor which will // be used in html htmlDocumentImplList.add(docImpls); }//if else { //it is data file addDataFileToZipOutputStream(docImpls, zOut, rootName); htmlDocumentImplList.add(docImpls); }//else }//if }//else }//for //add html summary file addHtmlSummaryToZipOutputStream(htmlDocumentImplList, zOut, rootName); zOut.finish(); //terminate the zip file //dbConn.close(); return zOut; }//else }//getZippedPackage() private class ReturnFieldValue { private String docid = null; //return field value for this docid private String fieldValue = null; private String xmlFieldValue = null; //return field value in xml // format private String fieldType = null; //ATTRIBUTE, TEXT... public void setDocid(String myDocid) { docid = myDocid; } public String getDocid() { return docid; } public void setFieldValue(String myValue) { fieldValue = myValue; } public String getFieldValue() { return fieldValue; } public void setXMLFieldValue(String xml) { xmlFieldValue = xml; } public String getXMLFieldValue() { return xmlFieldValue; } public void setFieldType(String myType) { fieldType = myType; } public String getFieldType() { return fieldType; } } /** * a class to store one result document consisting of a docid and a document */ private class ResultDocument { public String docid; public String document; public ResultDocument(String docid, String document) { this.docid = docid; this.document = document; } } /** * a private class to handle a set of resultDocuments */ private class ResultDocumentSet { private Vector docids; private Vector documents; public ResultDocumentSet() { docids = new Vector(); documents = new Vector(); } /** * adds a result document to the set */ public void addResultDocument(ResultDocument rd) { if(rd.docid == null) return; if(rd.document == null) rd.document = ""; docids.addElement(rd.docid); documents.addElement(rd.document); } /** * gets an iterator of docids */ public Iterator getDocids() { return docids.iterator(); } /** * gets an iterator of documents */ public Iterator getDocuments() { return documents.iterator(); } /** * returns the size of the set */ public int size() { return docids.size(); } /** * tests to see if this set contains the given docid */ private boolean containsDocid(String docid) { for(int i=0; i