View Javadoc

1   /*$Id: AdqlSqlMaker.java,v 1.2 2005/03/21 18:45:55 mch Exp $
2    * Created on 27-Nov-2003
3    *
4    * Copyright (C) AstroGrid. All rights reserved.
5    *
6    * This software is published under the terms of the AstroGrid
7    * Software License version 1.2, a copy of which has been included
8    * with this distribution in the LICENSE.txt file.
9    *
10   **/
11  package org.astrogrid.tableserver.jdbc;
12  
13  
14  
15  import java.io.FileInputStream;
16  import java.io.IOException;
17  import java.io.InputStream;
18  import java.io.StringWriter;
19  import javax.xml.parsers.ParserConfigurationException;
20  import javax.xml.transform.Transformer;
21  import javax.xml.transform.TransformerConfigurationException;
22  import javax.xml.transform.TransformerException;
23  import javax.xml.transform.TransformerFactory;
24  import javax.xml.transform.dom.DOMSource;
25  import javax.xml.transform.stream.StreamResult;
26  import javax.xml.transform.stream.StreamSource;
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.astrogrid.cfg.ConfigFactory;
30  import org.astrogrid.query.Query;
31  import org.astrogrid.query.QueryException;
32  import org.astrogrid.query.adql.Adql074Writer;
33  import org.astrogrid.xml.DomHelper;
34  import org.w3c.dom.Document;
35  import org.w3c.dom.Element;
36  import org.xml.sax.SAXException;
37  
38  /***
39   * A translator that creates ADQL SQL from a query. This might be used to generate
40   * SQL for RDBMSs that have the ADQL functions built into them as procedures/etc
41   *
42   */
43  public class AdqlSqlMaker implements SqlMaker {
44  
45     private static final Log log = LogFactory.getLog(AdqlSqlMaker.class);
46     
47     /***
48      * Constructs an SQL statement for the given Query.  Uses the ADQL generator
49      * and XSLT style sheets - there may be a better way of doing this!
50      */
51     public String makeSql(Query query) throws QueryException {
52        
53        //botch fix for cone searches which don't specify table
54        /* not needed any more
55        if ( ((query.getScope() == null) || (query.getScope().length==0)) && (query.getCriteria() instanceof Function)) {
56           if ( ((Function) query.getCriteria()).getName().toUpperCase().equals("CIRCLE")) {
57              query.setScope(new String[] { ConfigFactory.getCommonConfig().getProperty(StdSqlWriter.CONE_SEARCH_TABLE_KEY) });
58           }
59        }
60         */
61  
62        if ((query.getScope() == null) || (query.getScope().length==0)) {
63           throw new QueryException("No scope (FROM) given in query "+query);
64        }
65        
66        try {
67           //Create an ADQL string documnet from the query
68           String adqlTxt = Adql074Writer.makeAdql(query);
69           Document adqlDom = DomHelper.newDocument(adqlTxt);
70           
71           //translate it
72           String sql = useXslt(adqlDom.getDocumentElement());
73           return sql;
74           
75        }
76        catch (SAXException e) {
77           throw new RuntimeException("Adql074Writer produced invalid XML from query "+query,e);
78        }
79        catch (IOException e) {
80           throw new RuntimeException("Server error:"+e,e);
81        }
82        
83     }
84  
85     /***
86      * Constructs an SQL count statement for the given Query.
87      */
88     public String makeCountSql(Query query) throws QueryException {
89  
90        //get ordinary SQL
91        String sql = makeSql(query);
92        
93        //remove anything between SELECT and FROM and replace with COUNT(*)
94        int selectIdx = sql.indexOf("SELECT");
95        int fromIdx = sql.indexOf("FROM");
96        String countSql = sql.substring(0,selectIdx+6)+" COUNT(*) "+sql.substring(fromIdx);
97        return countSql;
98     }
99  
100    
101    /*** Uses Xslt to do the translations */
102    public String useXslt(Element adql) throws QueryException {
103       
104       String namespaceURI = adql.getNamespaceURI();
105       
106       if ((namespaceURI==null) || (namespaceURI.length()==0)) {
107          throw new QueryException("No namespace specified in query document, so don't know what it is");
108       }
109       
110       String xsltDoc = null;
111       InputStream xsltIn = null;
112       String whereIsDoc = null; //used for debug/trace/error messages
113       
114       //work out which translator sheet to use
115       
116       //see if there's a config property set
117       String key = "datacenter.sqlmaker.xslt."+namespaceURI.replaceAll(":","_");
118       xsltDoc = ConfigFactory.getCommonConfig().getString(key, null);
119       
120       try {
121          if (xsltDoc != null) {
122             //use config-specified sheet
123             xsltIn = new FileInputStream(xsltDoc);
124             whereIsDoc = "File "+xsltDoc;
125          }
126          else {
127             //not given in configuration file - look in subdirectory of class as resource
128             if (namespaceURI.equals("http://tempuri.org/adql")) { //assume v0.5
129                //xsltDoc = "adql05-2-sql.xsl";
130                throw new QueryException("ADQL 0.5 is no longer supported.  Please submit ADQL 0.7.4 documents");
131             }
132             if (namespaceURI.equals("http://www.ivoa.net/xml/ADQL/v0.7.4")) {
133                xsltDoc = "adql074-2-sql.xsl";
134             }
135             else if (namespaceURI.equals("http://www.ivoa.net/xml/ADQL/v0.7.3")) {
136                xsltDoc = "adql073-2-sql.xsl";
137             }
138             else if (namespaceURI.equals("http://www.ivoa.net/xml/ADQL/v0.8")) {
139                xsltDoc = "adql08-2-sql.xsl";
140             }
141             else if (namespaceURI.equals("http://astrogrid.org/sadql/v1.1")) {
142                xsltDoc = "sadql1.1-2-sql.xsl";
143             }
144             
145             if (xsltDoc == null) {
146                throw new RuntimeException("No builtin xslt for ADQL namespace '"+namespaceURI+"'; set configuration key '" + key+"'");
147             }
148             
149             //find specified sheet as resource of this class
150             xsltIn = StdSqlMaker.class.getResourceAsStream("./xslt/"+xsltDoc);
151             whereIsDoc = StdSqlMaker.class+" resource ./xslt/"+xsltDoc;
152             
153             //if above doesn't work, try doing by hand for Tomcat ClassLoader
154             if (xsltIn == null) {
155                String path = StdSqlMaker.class.getPackage().toString().replace('.', '/').substring(8)+"/xslt/"+xsltDoc;
156                xsltIn = StdSqlMaker.class.getClassLoader().getResourceAsStream(path);
157             }
158 
159             //sometimes it won't even find it then if it's in a JAR.  Look in class path.  However
160             //*assume* it's in classpath, as we don't know what the classpath is during unit tests.
161             if (xsltIn == null) {
162                log.warn("Could not find builtin ADQL->SQL transformer doc '"+whereIsDoc+"', looking in classpath...");
163                
164                xsltIn = this.getClass().getClassLoader().getResourceAsStream(xsltDoc);
165                whereIsDoc = xsltDoc+" in classpath at "+this.getClass().getClassLoader().getResource(xsltDoc);
166             }
167             
168             if (xsltIn == null) {
169                throw new QueryException("Could not find ADQL->SQL transformer doc "+xsltDoc);
170             }
171          }
172          
173          //create transformer
174          log.debug("Transforming ADQL ["+namespaceURI+"] using Xslt doc at '"+whereIsDoc+"'");
175          TransformerFactory tFactory = TransformerFactory.newInstance();
176          try {
177             tFactory.setAttribute("UseNamespaces", Boolean.FALSE);
178          }
179          catch (IllegalArgumentException iae) {
180             //ignore - if UseNamepsaces is unsupported, it will chuck an exception, and
181             //we don't want to use namespaces anyway so taht's fine
182          }
183          Transformer transformer = tFactory.newTransformer(new StreamSource(xsltIn));
184          
185          //transform
186          StringWriter sw = new StringWriter();
187          transformer.transform(new DOMSource(adql), new StreamResult(sw));
188          String sql = sw.toString();
189          
190          //tidy it up - remove new lines and double spaces
191          sql = sql.replaceAll("\n","");
192          sql = sql.replaceAll("\r","");
193          while (sql.indexOf("  ")>-1) { sql = sql.replaceAll("  ", " "); }
194          
195          //botch botch botch - for some reason transformers sometimes add <?xml tag to beginning
196          if (sql.startsWith("<?")) {
197             sql = sql.substring(sql.indexOf("?>")+2);
198          }
199          //botch botch botch - something funny with ADQL 0.7.3 schema to do with comparisons
200          sql = sql.replaceAll("&gt;", ">").replaceAll("&lt;", "<");
201          
202          log.debug("Used '"+xsltDoc+"' to translate ADQL ("+namespaceURI+") to '"+sql+"'");
203          
204          return sql;
205       }
206       catch (TransformerConfigurationException tce) {
207          throw new QueryException(tce+" (using xslt sheet "+xsltDoc+")",tce);
208       }
209       catch (TransformerException te) {
210          throw new QueryException(te+" translating ADQL->SQL using "+xsltDoc,te);
211       }
212       catch (IOException ioe) {
213          throw new QueryException(ioe+" Opening XSLT sheet "+xsltDoc,ioe);
214       }
215       
216    }
217    
218 }
219 
220 
221 /*
222  $Log: AdqlSqlMaker.java,v $
223  Revision 1.2  2005/03/21 18:45:55  mch
224  Naughty big lump of changes
225 
226  Revision 1.1  2005/03/10 16:42:55  mch
227  Split fits, sql and xdb
228 
229  Revision 1.1.1.1  2005/02/17 18:37:35  mch
230  Initial checkin
231 
232  Revision 1.1.1.1  2005/02/16 17:11:24  mch
233  Initial checkin
234 
235  Revision 1.2.12.4  2004/12/10 12:37:13  mch
236  Cone searches to look in metadata, lots of metadata interpreterrs
237 
238  Revision 1.2.12.3  2004/12/08 23:23:37  mch
239  Made SqlWriter and AdqlWriter implement QueryVisitor
240 
241  Revision 1.2.12.2  2004/12/08 18:36:40  mch
242  Added Vizier, rationalised SqlWriters etc, separated out TableResults from QueryResults
243 
244  Revision 1.2.12.1  2004/12/03 11:56:43  mch
245  switched from using stylesheet to dedicated SQL maker
246 
247  Revision 1.2  2004/11/03 01:35:18  mch
248  PAL_MCH_Candidate2 merge Part II
249 
250  Revision 1.1.2.1  2004/10/27 00:43:39  mch
251  Started adding getCount, some resource fixes, some jsps
252 
253  
254  */
255 
256 
257