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(">", ">").replaceAll("<", "<");
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