1
2
3
4
5
6
7 package org.astrogrid.tableserver.jdbc;
8
9 import org.astrogrid.dataservice.queriers.*;
10
11 import java.io.IOException;
12 import java.lang.reflect.InvocationTargetException;
13 import java.security.Principal;
14 import java.sql.Connection;
15 import java.sql.ResultSet;
16 import java.sql.SQLException;
17 import java.sql.Statement;
18 import java.util.Date;
19 import org.astrogrid.cfg.ConfigFactory;
20 import org.astrogrid.tableserver.metadata.TableMetaDocInterpreter;
21 import org.astrogrid.dataservice.queriers.status.QuerierComplete;
22 import org.astrogrid.dataservice.queriers.status.QuerierError;
23 import org.astrogrid.dataservice.queriers.status.QuerierQuerying;
24 import org.astrogrid.query.Query;
25 import org.astrogrid.query.QueryException;
26
27 /***
28 * A general purpose SQL Querier that will (hopefully) produce bog standard
29 * realbasic SQL from the ADQL, throwing an exception if it can't be done
30 *
31 * <p>
32 * forms a basis for other implementations for different db flavours
33 * <p>
34 * NWW: altered to delay creating jdbcConnection until required by {@link #queryDatabase}. DatabaseQueriers are one-shot
35 * beasts anyhow, so this isn't a problem, but it fixes problems of moving jdbcConnection across threads when non-blocking querying is done.
36 * <p>
37 * * @author M Hill
38 */
39
40 public class JdbcPlugin extends DefaultPlugin {
41
42
43 /*** Adql -> SQL translator class */
44 public static final String SQL_TRANSLATOR = "datacenter.querier.plugin.sql.translator";
45 public static final String DEFAULT_SQL_TRANSLATOR = "org.astrogrid.tableserver.jdbc.AdqlSqlMaker";
46
47 /*** execute timeout */
48 public static final String TIMEOUT = "datacenter.sql.timeout";
49
50 /*** Connection manager */
51 private static JdbcConnections connectionManager = null;
52
53
54 /*** performs a synchronous call to the database, submitting the given query
55 * in sql form and retiirning the results as a SqlResults wrapper arond the JDBC result set.
56 * @param o a string
57 */
58 public void askQuery(Principal user, Query query, Querier querier) throws IOException, QueryException {
59
60 validateQuery(query);
61
62 String sql = "(not set)";
63 Connection jdbcConnection = null;
64
65 try {
66
67 log.debug("Making SQL");
68 SqlMaker sqlMaker = makeSqlMaker();
69 sql = sqlMaker.makeSql(query);
70
71 querier.setStatus(new QuerierQuerying(querier.getStatus(), sql));
72
73 if ((sql == null) || (sql.length() == 0)) {
74 throw new QueryException("SqlMaker returned empty SQL string for query "+query);
75 }
76
77
78
79
80 log.debug("Connecting to the database");
81 jdbcConnection = getJdbcConnection();
82
83 Statement statement = jdbcConnection.createStatement(
84 java.sql.ResultSet.TYPE_FORWARD_ONLY,
85 java.sql.ResultSet.CONCUR_READ_ONLY);
86
87
88
89
90 jdbcConnection.setAutoCommit(false);
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106 if (query.getLocalLimit() >0) {
107 statement.setMaxRows((int) query.getLocalLimit());
108 }
109
110
111 statement.setQueryTimeout(ConfigFactory.getCommonConfig().getInt(TIMEOUT, 30*60));
112
113 querier.getStatus().addDetail("Submitted to JDBC at "+new Date());
114
115 try {
116
117
118
119
120
121
122
123
124 statement.setFetchSize(Integer.MIN_VALUE);
125 }
126 catch (SQLException e) {
127
128
129
130 try {
131 statement.setFetchSize(1000);
132 }
133 catch (SQLException e2) {
134
135
136 log.info("Couldn't set JDBC fetch size: " + e2.getMessage());
137 }
138 }
139
140 log.info("Performing Query: " + sql);
141 statement.execute(sql);
142 querier.getStatus().addDetail("JDBC execution complete at "+new java.util.Date());
143
144 ResultSet results = statement.getResultSet();
145
146 if (!aborted) {
147
148 if (results == null) {
149 throw new QueryException("SQL '"+sql+"' returned null results");
150 }
151
152
153 TableResults qResults = makeSqlResults(querier, results);
154 qResults.send(query.getResultsDef(), querier.getUser());
155
156 }
157
158
159
160 }
161 catch (SQLException e) {
162 log.error("SQLException when querying database with query " + sql);
163 log.error("Exception is :" + e.toString());
164 querier.setStatus(new QuerierError(querier.getStatus(), "JDBC Query Failed",e));
165
166 throw new DatabaseAccessException(e+" using '" + sql + "': ",e);
167 }
168 finally {
169
170 try {
171 if (jdbcConnection != null) { jdbcConnection.close(); }
172 } catch (SQLException e) { }
173 }
174
175 }
176
177 /*** Returns just the number of matches rather than the list of matches */
178 public long getCount(Principal user, Query query, Querier querier) throws IOException, QueryException {
179
180 validateQuery(query);
181
182 String sql = "(not set)";
183 Connection jdbcConnection = null;
184
185 try {
186
187 SqlMaker sqlMaker = makeSqlMaker();
188
189 sql = sqlMaker.makeCountSql(query);
190
191 querier.setStatus(new QuerierQuerying(querier.getStatus(), sql));
192
193 if ((sql == null) || (sql.length() == 0)) {
194 throw new QueryException("SqlMaker returned empty SQL string for query "+query);
195 }
196
197
198 log.debug("Connecting to the database");
199 jdbcConnection = getJdbcConnection();
200 Statement statement = jdbcConnection.createStatement();
201
202 querier.getStatus().addDetail("Submitted to JDBC at "+new Date());
203
204
205 log.info("Performing Query: " + sql);
206 statement.execute(sql);
207 querier.getStatus().addDetail("JDBC execution complete at "+new java.util.Date());
208
209 ResultSet results = statement.getResultSet();
210
211
212 results.next();
213 long count = results.getLong(1);
214 results.close();
215
216 querier.getStatus().addDetail("Count="+count);
217 querier.setStatus(new QuerierComplete(querier.getStatus()));
218
219 return count;
220 }
221 catch (SQLException e) {
222 log.error("SQLException when querying database with query " + sql);
223 log.error("Exception is :" + e.toString());
224 querier.setStatus(new QuerierError(querier.getStatus(), "JDBC Query Failed",e));
225
226 throw new DatabaseAccessException(e+" using '" + sql + "': ",e);
227 }
228 finally {
229
230 try {
231 if (jdbcConnection != null) { jdbcConnection.close(); }
232 } catch (SQLException e) { }
233 }
234
235 }
236
237 /*** Throws an IllegalArgumentException if the query is not appropriate to this site */
238 public void validateQuery(Query query) {
239 try {
240 TableMetaDocInterpreter reader = new TableMetaDocInterpreter();
241 RdbmsQueryValidator validator = new RdbmsQueryValidator(reader);
242
243 validator.validateQuery(query);
244 }
245 catch (IOException ioe) {
246 log.warn("No RDBMS Resource found, not validating query");
247 }
248 }
249
250 /*** Returns the formats that this plugin can provide. Asks the results class; override in subclasse if nec */
251 public String[] getFormats() {
252 return SqlResults.listFormats();
253 }
254
255 /*** Makes SqlResults for the resultset. This means subclasses can override it
256 * to make an easy way to transform the results */
257 public TableResults makeSqlResults(Querier querier, ResultSet results) {
258 return new SqlResults(querier, results);
259 }
260
261 /***
262 * Makes the right SqlQueryMaker for this database
263 */
264 public SqlMaker makeSqlMaker() throws QuerierPluginException {
265 String makerClass = ConfigFactory.getCommonConfig().getString(
266 SQL_TRANSLATOR, DEFAULT_SQL_TRANSLATOR);
267
268 try {
269 Object o = QuerierPluginFactory.instantiate(makerClass);
270 if (o == null) {
271 throw new QuerierPluginException("Could not create the SQL plugin translator '"+makerClass+"'");
272 }
273 return (SqlMaker) o;
274 }
275 catch (ClassCastException cce) {
276 String msg = "The class '"+makerClass+
277 "' (specified in configuration key '"+SQL_TRANSLATOR+
278 "') is not a subclass of " + SqlMaker.class.getName()+
279 "; please check your configuration";
280 log.error(msg, cce);
281 throw new QuerierPluginException(msg, cce);
282 }
283 catch (ClassNotFoundException e) {
284 String msg = "Could not find class '"+makerClass+
285 "' (specified in configuration key '"+SQL_TRANSLATOR+
286 "'); please check your configuration.";
287 log.error(msg, e);
288 throw new QuerierPluginException(msg, e.getCause());
289 }
290 catch (Throwable th) {
291 if (th instanceof InvocationTargetException) {
292 th = th.getCause();
293 }
294 String msg = "Problem instantiating SQL Maker "+makerClass+", config key="+SQL_TRANSLATOR+", please see logs for more information";
295 log.error(msg, th);
296 log.error(msg, th.getCause());
297 throw new QuerierPluginException(msg, th);
298 }
299 }
300
301 /*** Creates a connection to the database */
302 protected static synchronized Connection getJdbcConnection() throws IOException, SQLException {
303
304 if (connectionManager == null) {
305 connectionManager = JdbcConnections.makeFromConfig();
306 }
307 return connectionManager.createConnection();
308
309 }
310
311
312
313
314 }
315
316
317
318