KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > triactive > jdo > store > TJDOSQLQuery


1 /*
2  * Copyright 2004 (C) TJDO.
3  * All rights reserved.
4  *
5  * This software is distributed under the terms of the TJDO License version 1.0.
6  * See the terms of the TJDO License in the documentation provided with this software.
7  *
8  * $Id: TJDOSQLQuery.java,v 1.11 2004/01/25 22:31:27 jackknifebarber Exp $
9  */

10
11 package com.triactive.jdo.store;
12
13 import com.triactive.jdo.ClassNotPersistenceCapableException;
14 import com.triactive.jdo.PersistenceManager;
15 import com.triactive.jdo.model.ClassMetaData;
16 import com.triactive.jdo.model.FieldMetaData;
17 import com.triactive.jdo.util.IntArrayList;
18 import com.triactive.jdo.util.MacroString;
19 import java.sql.Connection JavaDoc;
20 import java.sql.PreparedStatement JavaDoc;
21 import java.sql.ResultSet JavaDoc;
22 import java.sql.ResultSetMetaData JavaDoc;
23 import java.sql.SQLException JavaDoc;
24 import java.util.ArrayList JavaDoc;
25 import java.util.Collection JavaDoc;
26 import java.util.HashSet JavaDoc;
27 import java.util.Iterator JavaDoc;
28 import java.util.Map JavaDoc;
29 import java.util.List JavaDoc;
30 import javax.jdo.Extent;
31 import javax.jdo.JDODataStoreException;
32 import javax.jdo.JDOFatalInternalException;
33 import javax.jdo.JDOFatalUserException;
34 import javax.jdo.JDOUserException;
35 import org.apache.log4j.Category;
36
37
38 /**
39  * A JDO query that uses the default JQOQL language.
40  *
41  * @author <a HREF="mailto:mmartin5@austin.rr.com">Mike Martin</a>
42  * @version $Revision: 1.11 $
43  *
44  * @see Query
45  */

46
47 public class TJDOSQLQuery extends Query
48 {
49     private static final Category LOG = Category.getInstance(TJDOSQLQuery.class);
50
51     private transient final String JavaDoc tjdoSqlText;
52
53     private transient String JavaDoc jdbcSqlText = null;
54     private transient int[] fieldNumbers = null;
55     private transient ColumnMapping[] fieldMappings = null;
56     private transient List fieldColumnNames = null;
57     private transient List parameterOccurrences = null;
58
59
60     /**
61      * Constructs a new query instance having the same criteria as the given
62      * query.
63      *
64      * @param pm The persistence manager.
65      * @param storeMgr The store manager.
66      * @param tjdoSqlText The SQL text of the query.
67      */

68
69     public TJDOSQLQuery(PersistenceManager pm, StoreManager storeMgr, String JavaDoc tjdoSqlText)
70     {
71         super(pm, storeMgr);
72
73     candidateClass = null;
74     filter = null;
75     imports = null;
76     variables = null;
77     parameters = null;
78     ordering = null;
79
80     this.tjdoSqlText = tjdoSqlText;
81     }
82
83
84     protected void discardCompiled()
85     {
86         super.discardCompiled();
87
88         jdbcSqlText = null;
89     fieldNumbers = null;
90     fieldMappings = null;
91         fieldColumnNames = null;
92     parameterOccurrences = null;
93     }
94
95
96     public boolean equals(Object JavaDoc obj)
97     {
98         if (obj == this)
99             return true;
100
101         if (!(obj instanceof TJDOSQLQuery) || !super.equals(obj))
102             return false;
103
104         return tjdoSqlText.equals(((TJDOSQLQuery)obj).tjdoSqlText);
105     }
106
107
108     /**
109      * Set the candidate Extent to query.
110      *
111      * <p>This implementation always throws a JDOUserException since this
112      * concept doesn't apply to TJDOSQL queries.
113      *
114      * @param pcs the Candidate Extent.
115      *
116      * @exception JDOUserException Always thrown.
117      *
118      * @see javax.jdo.Query#setCandidates(javax.jdo.Extent)
119      */

120
121     public void setCandidates(Extent pcs)
122     {
123     throw new JDOUserException("Candidate extents not applicable to TJDOSQL queries");
124     }
125
126
127     /**
128      * Set the candidate Collection to query.
129      *
130      * <p>This implementation always throws a JDOUserException since this
131      * concept doesn't apply to TJDOSQL queries.
132      *
133      * @param pcs the Candidate collection.
134      *
135      * @exception JDOUserException Always thrown.
136      *
137      * @see javax.jdo.Query#setCandidates(java.util.Collection)
138      */

139
140     public void setCandidates(Collection JavaDoc pcs)
141     {
142     throw new JDOUserException("Candidate collections not applicable to TJDOSQL queries");
143     }
144
145
146     /**
147      * Set the filter for the query.
148      *
149      * <p>This implementation always throws a JDOUserException since this
150      * concept doesn't apply to TJDOSQL queries.
151      *
152      * @param filter the query filter.
153      *
154      * @exception JDOUserException Always thrown.
155      *
156      * @see javax.jdo.Query#setFilter
157      */

158
159     public void setFilter(String JavaDoc filter)
160     {
161     throw new JDOUserException("Filter strings not applicable to TJDOSQL queries");
162     }
163
164
165     /**
166      * Declare the unbound variables to be used in the query.
167      *
168      * <p>This implementation always throws a JDOUserException since this
169      * concept doesn't apply to TJDOSQL queries.
170      *
171      * @param variables the variables separated by semicolons.
172      *
173      * @exception JDOUserException Always thrown.
174      *
175      * @see javax.jdo.Query#declareVariables
176      */

177
178     public void declareVariables(String JavaDoc variables)
179     {
180     throw new JDOUserException("Variables are not applicable to TJDOSQL queries");
181     }
182
183
184     /**
185      * Set the ordering specification for the result Collection.
186      *
187      * <p>This implementation always throws a JDOUserException since this
188      * concept doesn't apply to TJDOSQL queries.
189      *
190      * @param ordering the ordering specification.
191      *
192      * @exception JDOUserException Always thrown.
193      *
194      * @see javax.jdo.Query#setOrdering
195      */

196
197     public void setOrdering(String JavaDoc ordering)
198     {
199     throw new JDOUserException("Ordering must be set via explicit ORDER BY in the SQL text for TJDOSQL queries");
200     }
201
202
203     /**
204      * Verify the elements of the query and provide a hint to the query to
205      * prepare and optimize an execution plan.
206      *
207      * @see javax.jdo.Query#compile
208      */

209
210     public void compile()
211     {
212         if (!isCompiled)
213         {
214             super.compile();
215
216             generateQueryStatement();
217
218             isCompiled = true;
219         }
220     }
221
222
223     private void generateQueryStatement()
224     {
225         if (candidateClass == null)
226             throw new JDOUserException("No candidate class provided");
227
228     final ClassMetaData cmd = ClassMetaData.forClass(candidateClass);
229
230     if (cmd == null)
231         throw new ClassNotPersistenceCapableException(candidateClass);
232
233     if (cmd.requiresExtent())
234             throw new JDOUserException("Invalid candidate class for TJDOSQL, must not have an extent (use requires-extent=\"false\" in the JDO metadata): " + candidateClass.getName());
235
236     if (cmd.getIdentityType() != ClassMetaData.NO_IDENTITY)
237             throw new JDOUserException("Invalid candidate class for TJDOSQL, must use non-durable identity (use identity-type=\"nondurable\" in the JDO metadata): " + candidateClass.getName());
238
239         if (cmd.getPCSuperclass() != null)
240             throw new PersistentSuperclassNotAllowedException(candidateClass);
241
242         int fieldCount = cmd.getFieldCount();
243         IntArrayList fn = new IntArrayList(fieldCount);
244         fieldMappings = new ColumnMapping[fieldCount];
245         fieldColumnNames = new ArrayList(fieldCount);
246
247         for (int fieldNumber = 0; fieldNumber < fieldCount; ++fieldNumber)
248         {
249             FieldMetaData fmd = cmd.getFieldRelative(fieldNumber);
250         String JavaDoc fieldName = fmd.getName();
251         Class JavaDoc fieldType = fmd.getType();
252
253             switch (fmd.getPersistenceModifier())
254             {
255                 case FieldMetaData.PERSISTENCE_MODIFIER_NONE:
256                 default:
257                     throw new JDOFatalInternalException("Invalid persistence modifier on field " + fieldName);
258
259                 case FieldMetaData.PERSISTENCE_MODIFIER_TRANSACTIONAL:
260                     break;
261
262                 case FieldMetaData.PERSISTENCE_MODIFIER_PERSISTENT:
263                     Mapping m = dba.getMapping(fieldType);
264
265                     if (!(m instanceof ColumnMapping))
266                         throw new JDOFatalUserException("Mapping " + m + " not suitable for a TJDOSQL result column, field = " + fieldName);
267
268                     fieldMappings[fieldNumber] = (ColumnMapping)m;
269                     fn.add(fieldNumber);
270                     fieldColumnNames.add(new ColumnIdentifier(dba, fieldName, fieldType, Role.NONE).getSQLIdentifier());
271                     break;
272             }
273
274         }
275
276         if (fn.isEmpty())
277             throw new JDOFatalUserException("View class has no persistent fields: " + candidateClass.getName());
278
279         fieldNumbers = fn.toArray();
280
281         /*
282          * Generate the actual JDBC SQL text by processing the embedded macros
283          * in the given TJDO SQL text. The parameterOccurrences list collects
284          * the names of all of the parameters in the order in which they occur
285          * in the statement.
286          */

287     parameterOccurrences = new ArrayList();
288
289         MacroString ms = new MacroString(candidateClass, imports, tjdoSqlText);
290
291         jdbcSqlText = ms.substituteMacros(new MacroString.MacroHandler()
292             {
293                 public void onIdentifierMacro(MacroString.IdentifierMacro im)
294                 {
295                     if (im.clazz.equals(candidateClass))
296                     {
297                         if (im.fieldName == null)
298                             throw new JDOUserException("Invalid macro, query result classes have no table: " + im);
299                         if (im.subfieldName != null)
300                             throw new JDOUserException("No such field in query result class " + im.clazz.getName() + ": " + im);
301                         int fieldNumber = cmd.getRelativeFieldNumber(im.fieldName);
302
303                         if (fieldNumber < 0)
304                             throw new JDOUserException("No such field in query result class " + im.clazz.getName() + ": " + im);
305                         im.value = (String JavaDoc)fieldColumnNames.get(fieldNumber);
306                     }
307                     else
308                         storeMgr.resolveIdentifierMacro(im);
309                 }
310
311                 public void onParameterMacro(MacroString.ParameterMacro pm)
312                 {
313             parameterOccurrences.add(pm.parameterName);
314         }
315             }
316         );
317     }
318
319
320     /**
321      * Execute the query and return the filtered Collection.
322      *
323      * @param parameters the Map containing all of the parameters.
324      *
325      * @return the filtered Collection.
326      *
327      * @see javax.jdo.Query#executeWithMap(Map)
328      * @see #executeWithArray(Object[] parameters)
329      */

330
331     public Object JavaDoc executeWithMap(Map JavaDoc parameters)
332     {
333         compile();
334
335         if (parameters.size() != parameterNames.size())
336             throw new JDOUserException("Incorrect number of parameters: " + parameters.size() + ", s/b " + parameterNames.size());
337
338         QueryResult qr = null;
339
340         try
341         {
342             Connection JavaDoc conn = pm.getConnection(false);
343
344             try
345             {
346                 PreparedStatement JavaDoc ps = conn.prepareStatement(jdbcSqlText);
347
348                 try
349                 {
350
351                     Iterator JavaDoc i = parameterOccurrences.iterator();
352                     int stmtParamNum = 1;
353
354                     while (i.hasNext())
355                     {
356                         String JavaDoc paramName = (String JavaDoc)i.next();
357             Class JavaDoc paramType = (Class JavaDoc)parameterTypesByName.get(paramName);
358
359                         if (!parameters.containsKey(paramName))
360                             throw new JDOUserException("Required parameter " + paramName + " not provided");
361                         if (paramType == null)
362                             throw new JDOUserException("Undeclared parameter " + paramName + " occurred in SQL text");
363
364                         Mapping m = dba.getMapping(paramType);
365                         Object JavaDoc paramValue = parameters.get(paramName);
366
367                         if (!(m instanceof ColumnMapping))
368                             throw new JDOUserException("Illegal parameter type, param = " + paramName + " type = " + paramType.getName());
369
370                         ((ColumnMapping)m).setObject(pm, ps, stmtParamNum++, paramValue);
371                     }
372
373                     long startTime = System.currentTimeMillis();
374
375                     ResultSet JavaDoc rs = ps.executeQuery();
376
377                     if (LOG.isDebugEnabled())
378                         LOG.debug("Time = " + (System.currentTimeMillis() - startTime) + " ms: " + jdbcSqlText);
379
380                     try
381                     {
382                         int[] columnNumbersByField = new int[fieldMappings.length];
383                         ResultSetMetaData JavaDoc rsmd = rs.getMetaData();
384                         HashSet JavaDoc remainingColumnNames = new HashSet JavaDoc(fieldColumnNames);
385
386                         int colCount = rsmd.getColumnCount();
387
388                         for (int colNum = 1; colNum <= colCount; ++colNum)
389                         {
390                             String JavaDoc colName = rsmd.getColumnName(colNum);
391                             int fieldNumber = fieldColumnNames.indexOf(colName);
392
393                             if (fieldNumber >= 0)
394                             {
395                                 columnNumbersByField[fieldNumber] = colNum;
396                                 remainingColumnNames.remove(colName);
397                             }
398                         }
399
400                         if (!remainingColumnNames.isEmpty())
401                             throw new JDOUserException("Expected columns missing from result set: " + remainingColumnNames);
402
403
404                         qr = new QueryResult(this,
405                                              new TransientIDROF(pm,
406                                                                 candidateClass,
407                                                                 fieldNumbers,
408                                                                 fieldMappings,
409                                                                 columnNumbersByField),
410                                              rs);
411                     }
412                     finally
413                     {
414                         if (qr == null)
415                             rs.close();
416                     }
417                 }
418                 finally
419                 {
420                     if (qr == null)
421                         ps.close();
422                 }
423             }
424             finally
425             {
426                 pm.releaseConnection(conn);
427             }
428         }
429         catch (SQLException JavaDoc e)
430         {
431             throw dba.newDataStoreException("Error executing query: " + jdbcSqlText, e);
432         }
433
434         queryResults.add(qr);
435
436         return qr;
437     }
438 }
439
Popular Tags