KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > storage > search > implementation > database > BasicQueryHandler


1 /*
2 This software is OSI Certified Open Source Software.
3 OSI Certified is a certification mark of the Open Source Initiative.
4
5 The license (Mozilla version 1.0) can be read at the MMBase site.
6 See http://www.MMBase.org/license
7
8 */

9 package org.mmbase.storage.search.implementation.database;
10
11 import java.sql.*;
12 import java.util.*;
13 import javax.sql.DataSource JavaDoc;
14
15 import org.mmbase.cache.NodeCache;
16 import org.mmbase.cache.Cache;
17 import org.mmbase.bridge.NodeManager;
18 import org.mmbase.core.CoreField;
19 import org.mmbase.module.core.*;
20 import org.mmbase.storage.implementation.database.DatabaseStorageManager;
21 import org.mmbase.storage.implementation.database.DatabaseStorageManagerFactory;
22 import org.mmbase.storage.search.*;
23 import org.mmbase.util.logging.*;
24 import org.mmbase.storage.search.implementation.ModifiableQuery;
25
26
27 /**
28  * Basic implementation using a database.
29  * Uses a {@link org.mmbase.storage.search.implementation.database.SqlHandler SqlHandler}
30  * to create SQL string representations of search queries.
31  * <p>
32  * In order to execute search queries, these are represented as SQL strings
33  * by the handler, and in this form executed on the database.
34  *
35  * @author Rob van Maris
36  * @version $Id: BasicQueryHandler.java,v 1.52 2006/07/08 06:54:00 michiel Exp $
37  * @since MMBase-1.7
38  */

39 public class BasicQueryHandler implements SearchQueryHandler {
40
41     /** Empty StepField array. */
42     private static final StepField[] STEP_FIELD_ARRAY = new StepField[0];
43
44
45     private static final Logger log = Logging.getLoggerInstance(BasicQueryHandler.class);
46
47     /** Sql handler used to generate SQL statements. */
48     private SqlHandler sqlHandler = null;
49
50     /** MMBase instance. */
51     private MMBase mmbase = null;
52
53     /**
54      * Default constructor.
55      *
56      * @param sqlHandler The handler use to create SQL string representations
57      * of search queries.
58      */

59     public BasicQueryHandler(SqlHandler sqlHandler) {
60         this.sqlHandler = sqlHandler;
61         mmbase = MMBase.getMMBase();
62     }
63
64
65     // javadoc is inherited
66
public List getNodes(SearchQuery query, MMObjectBuilder builder) throws SearchQueryException {
67
68         List results;
69         Connection con = null;
70         Statement stmt = null;
71         String JavaDoc sqlString = null;
72
73         try {
74             // Flag, set if offset must be supported by skipping results.
75
boolean mustSkipResults =
76                 (query.getOffset() != SearchQuery.DEFAULT_OFFSET) &&
77                 (sqlHandler.getSupportLevel(SearchQueryHandler.FEATURE_OFFSET, query) == SearchQueryHandler.SUPPORT_NONE);
78
79
80             // Flag, set if sql handler supports maxnumber.
81
boolean sqlHandlerSupportsMaxNumber = sqlHandler.getSupportLevel(SearchQueryHandler.FEATURE_MAX_NUMBER, query) != SearchQueryHandler.SUPPORT_NONE;
82
83             // report about offset and max support (for debug purposes)
84
if (log.isDebugEnabled()) {
85                 log.debug("Database offset support = " + (sqlHandler.getSupportLevel(SearchQueryHandler.FEATURE_OFFSET, query) != SearchQueryHandler.SUPPORT_NONE));
86                 log.debug("mustSkipResults = " + mustSkipResults);
87                 log.debug("Database max support = " + sqlHandlerSupportsMaxNumber);
88             }
89
90             sqlString = createSqlString(query, mustSkipResults, sqlHandlerSupportsMaxNumber);
91
92             log.debug("sql: " + sqlString);
93             
94             // Execute the SQL... ARGH !!! Has to move!
95
// get connection...
96
DataSource JavaDoc dataSource = ((DatabaseStorageManagerFactory) mmbase.getStorageManagerFactory()).getDataSource();
97             con = dataSource.getConnection();
98             stmt = con.createStatement();
99             ResultSet rs = stmt.executeQuery(sqlString);
100
101             try {
102                 if (mustSkipResults) {
103                     log.debug("skipping results, to provide weak support for offset");
104                     for (int i = 0; i < query.getOffset(); i++) {
105                         rs.next();
106                     }
107                 }
108
109                 // Now store results as cluster-/real nodes.
110
StepField[] fields = (StepField[]) query.getFields().toArray(STEP_FIELD_ARRAY);
111                 int maxNumber = query.getMaxNumber();
112
113                 // now, we dispatch the reading of the result set to the right function wich instantiates Nodes of the right type.
114
if (builder instanceof ClusterBuilder) {
115                     results = readNodes((ClusterBuilder) builder, fields, rs, sqlHandlerSupportsMaxNumber, maxNumber, query.getSteps().size());
116                 } else if (builder instanceof ResultBuilder) {
117                     results = readNodes((ResultBuilder) builder, fields, rs, sqlHandlerSupportsMaxNumber, maxNumber);
118                 } else {
119                     results = readNodes(builder, fields, rs, sqlHandlerSupportsMaxNumber, maxNumber);
120                 }
121             } finally {
122                 rs.close();
123             }
124         } catch (SQLException e) {
125             // Something went wrong, log exception
126
// and rethrow as SearchQueryException.
127
if (log.isDebugEnabled()) {
128                 log.debug("Query failed:" + query + "\n" + e + Logging.stackTrace(e));
129             }
130             throw new SearchQueryException("Query '" + (sqlString == null ? "" + query.toString() : sqlString) + "' failed: " + e.getClass().getName() + ": " + e.getMessage(), e);
131         } finally {
132             closeConnection(con, stmt);
133         }
134
135         return results;
136     }
137
138     /**
139      * Safely close a database connection and/or a database statement.
140      * @param con The connection to close. Can be <code>null</code>.
141      * @param stmt The statement to close, prior to closing the connection. Can be <code>null</code>.
142      */

143     protected void closeConnection(Connection con, Statement stmt) {
144         try {
145             if (stmt != null) {
146                 stmt.close();
147             }
148         } catch (Exception JavaDoc g) {}
149         try {
150             if (con != null) {
151                 con.close();
152             }
153         } catch (Exception JavaDoc g) {}
154     }
155
156     /**
157      * Makes a String of a query, taking into consideration if the database supports offset and
158      * maxnumber features. The resulting String is an SQL query which can be fed to the database.
159      */

160
161     private String JavaDoc createSqlString(SearchQuery query, boolean mustSkipResults, boolean sqlHandlerSupportsMaxNumber) throws SearchQueryException {
162         int maxNumber = query.getMaxNumber();
163         // Flag, set if maxnumber must be supported by truncating results.
164
boolean mustTruncateResults = (maxNumber != SearchQuery.DEFAULT_MAX_NUMBER) && (! sqlHandlerSupportsMaxNumber);
165         String JavaDoc sqlString;
166        if (mustSkipResults) { // offset not supported, but needed
167
log.debug("offset used in query and not supported in database.");
168            ModifiableQuery modifiedQuery = new ModifiableQuery(query);
169            modifiedQuery.setOffset(SearchQuery.DEFAULT_OFFSET);
170
171            if (mustTruncateResults) {
172                log.debug("max used in query but not supported in database.");
173                // Weak support for offset, weak support for maxnumber:
174
modifiedQuery.setMaxNumber(SearchQuery.DEFAULT_MAX_NUMBER); // apply no maximum, but truncate result
175
} else if (maxNumber != SearchQuery.DEFAULT_MAX_NUMBER) {
176                log.debug("max used in query and supported by database.");
177                // Because offset is not supported add max with the offset.
178
// Weak support for offset, sql handler supports maxnumber:
179
modifiedQuery.setMaxNumber(query.getOffset() + maxNumber);
180            }
181            sqlString = sqlHandler.toSql(modifiedQuery, sqlHandler);
182
183        } else {
184            log.debug("offset not used or offset is supported by the database.");
185            if (mustTruncateResults) {
186                log.debug("max used in query but not supported in database.");
187                // Sql handler supports offset, or not offset is specified.
188
// weak support for maxnumber:
189
ModifiableQuery modifiedQuery = new ModifiableQuery(query);
190                modifiedQuery.setMaxNumber(SearchQuery.DEFAULT_MAX_NUMBER); // apply no maximum, but truncate result
191
sqlString = sqlHandler.toSql(modifiedQuery, sqlHandler);
192            } else {
193                // Offset not used, maxnumber not used.
194
log.debug("no need for modifying Query");
195                sqlString = sqlHandler.toSql(query, sqlHandler);
196            }
197        }
198        // TODO: test maximum sql statement length is not exceeded.
199
return sqlString;
200     }
201
202     /**
203      * Read the result list and creates a List of ClusterNodes.
204      */

205     private List readNodes(ClusterBuilder builder, StepField[] fields, ResultSet rs, boolean sqlHandlerSupportsMaxNumber, int maxNumber, int numberOfSteps) throws SQLException {
206         List results = new ArrayList();
207         DatabaseStorageManager storageManager = (DatabaseStorageManager)mmbase.getStorageManager();
208
209         boolean storesAsFile = builder.getMMBase().getStorageManagerFactory().hasOption(org.mmbase.storage.implementation.database.Attributes.STORES_BINARY_AS_FILE);
210         // Truncate results to provide weak support for maxnumber.
211
try {
212             while (rs.next() && (results.size()<maxNumber || maxNumber==-1)) {
213                 try {
214                     ClusterNode node = new ClusterNode(builder, numberOfSteps);
215                     node.start();
216
217                     int j = 1;
218                     // make use of Node-cache to fill fields
219
// especially XML-fields can be heavy, otherwise (Documnents must be instantiated)
220
for (int i = 0; i < fields.length; i++) {
221                         String JavaDoc fieldName = fields[i].getFieldName(); // why not getAlias first?
222
Step step = fields[i].getStep();
223                         String JavaDoc alias = step.getAlias();
224                         if (alias == null) {
225                             // Use tablename as alias when no alias is specified.
226
alias = step.getTableName();
227                         }
228                         CoreField field = builder.getField(alias + '.' + fieldName);
229                         if (field.getType() == CoreField.TYPE_BINARY) continue;
230                         Object JavaDoc value = storageManager.getValue(rs, j++, field, false);
231                         node.storeValue(alias + '.' + fieldName, value);
232                     }
233                     node.clearChanged();
234                     node.finish();
235                     results.add(node);
236                 } catch (Exception JavaDoc e) {
237                     // log error, but continue with other nodes
238
log.error(e.getMessage(), e);
239                 }
240             }
241         } catch (SQLException sqe) {
242             // log error, but return results.
243
log.error(sqe);
244         }
245         return results;
246     }
247
248     /**
249      * Read the result list and creates a List of ResultNodes
250      */

251     private List readNodes(ResultBuilder builder, StepField[] fields, ResultSet rs, boolean sqlHandlerSupportsMaxNumber, int maxNumber) throws SQLException {
252         List results = new ArrayList();
253         DatabaseStorageManager storageManager = (DatabaseStorageManager)mmbase.getStorageManager();
254
255         boolean storesAsFile = builder.getMMBase().getStorageManagerFactory().hasOption(org.mmbase.storage.implementation.database.Attributes.STORES_BINARY_AS_FILE);
256         // Truncate results to provide weak support for maxnumber.
257
try {
258             while (rs.next() && (maxNumber>results.size() || maxNumber==-1)) {
259                 try {
260                     ResultNode node = new ResultNode(builder);
261                     node.start();
262                     int j = 1;
263                     for (int i = 0; i < fields.length; i++) {
264                         String JavaDoc fieldName = fields[i].getAlias();
265                         if (fieldName == null) {
266                             fieldName = fields[i].getFieldName();
267                         }
268                         CoreField field = builder.getField(fieldName);
269                         if (field != null && field.getType() == CoreField.TYPE_BINARY) continue;
270                         Object JavaDoc value = storageManager.getValue(rs, j++, field, false);
271                         node.storeValue(fieldName, value);
272                     }
273                     node.clearChanged();
274                     node.finish();
275                     results.add(node);
276                 } catch (Exception JavaDoc e) {
277                     // log error, but continue with other nodes
278
log.error(e.getMessage(), e);
279                 }
280             }
281         } catch (SQLException sqe) {
282             // log error, but return results.
283
log.error(sqe);
284         }
285         return results;
286     }
287
288     /**
289      * Read the result list and creates a List of normal MMObjectNodes.
290      */

291     private List readNodes(MMObjectBuilder builder, StepField[] fields, ResultSet rs, boolean sqlHandlerSupportsMaxNumber, int maxNumber) throws SQLException {
292         List results= new ArrayList();
293         DatabaseStorageManager storageManager = (DatabaseStorageManager)mmbase.getStorageManager();
294
295         boolean storesAsFile = builder.getMMBase().getStorageManagerFactory().hasOption(org.mmbase.storage.implementation.database.Attributes.STORES_BINARY_AS_FILE);
296         // determine indices of queried fields
297
Map fieldIndices = new HashMap();
298         Step nodeStep = fields[0].getStep();
299         int j = 1;
300         for (int i = 0; i < fields.length; i++) {
301             if (fields[i].getType() == CoreField.TYPE_BINARY) continue;
302             Integer JavaDoc index = new Integer JavaDoc(j++);
303             if (fields[i].getStep() == nodeStep) {
304                 String JavaDoc fieldName = fields[i].getFieldName();
305                 CoreField field = builder.getField(fieldName);
306                 if (field == null) {
307                     log.warn("Did not find the field '" + fieldName + "' in builder " + builder);
308                     continue; // could this happen?
309
}
310                 fieldIndices.put(field, index);
311             }
312         }
313
314         // Test if ALL fields are queried
315
List builderFields = builder.getFields(NodeManager.ORDER_CREATE);
316         StringBuffer JavaDoc missingFields = null;
317         for (Iterator f = builderFields.iterator(); f.hasNext();) {
318             CoreField field = (CoreField)f.next();
319             if (field.inStorage()) {
320                 if (field.getType() == CoreField.TYPE_BINARY) continue;
321                 if (fieldIndices.get(field) == null) {
322                     if (missingFields == null) {
323                         missingFields = new StringBuffer JavaDoc(field.getName());
324                     } else {
325                         missingFields.append(", ").append(field.getName());
326                     }
327                 }
328             }
329         }
330
331         // if not all field are queried, this is a virtual node
332
boolean isVirtual = missingFields != null;
333         if (isVirtual) {
334             log.warn("This query returns virtual nodes (not querying: '" + missingFields + "')");
335         }
336
337         // Truncate results to provide weak support for maxnumber.
338
try {
339             NodeCache nodeCache = NodeCache.getCache();
340             Cache typeCache = Cache.getCache("TypeCache");
341             int builderType = builder.getObjectType();
342             Integer JavaDoc oTypeInteger = new Integer JavaDoc(builderType);
343             while (rs.next() && (maxNumber > results.size() || maxNumber==-1)) {
344                 try {
345                     /*
346                      * This while statement does not deal with mmbase inheritance
347                      * It creates nodes based on the builder passed in. Nodes with
348                      * subtypes of this builder are only filled with the field values
349                      * of this builder. Builders of a subtype are not stored in the nodeCache
350                      * to limit the time scope of these nodes, because they are not complete.
351                      */

352                     
353                     MMObjectNode node;
354                     if (!isVirtual) {
355                         node = new MMObjectNode(builder, false);
356                     } else {
357                         node = new VirtualNode(builder);
358                     }
359                     node.start();
360                     for (Iterator i = builder.getFields(NodeManager.ORDER_CREATE).iterator(); i.hasNext(); ) {
361                         CoreField field = (CoreField)i.next();
362                         if (! field.inStorage()) continue;
363                         Integer JavaDoc index = (Integer JavaDoc) fieldIndices.get(field);
364                         Object JavaDoc value = null;
365                         String JavaDoc fieldName = field.getName();
366                         if (index != null) {
367                             value = storageManager.getValue(rs, index.intValue(), field, true);
368                         } else {
369                             java.sql.Blob JavaDoc b = null;
370                             if (field.getType() == CoreField.TYPE_BINARY && storesAsFile) {
371                                 log.debug("Storage did not return data for '" + fieldName + "', supposing it on disk");
372                                 // must have been a explicitely specified 'blob' field
373
b = storageManager.getBlobValue(node, field, true);
374                             } else if (field.getType() == CoreField.TYPE_BINARY) {
375                                 // binary fields never come directly from the database
376
value = MMObjectNode.VALUE_SHORTED;
377                             } else if (! isVirtual){
378                                 // field wasn't returned by the db - this must be a Virtual node, otherwise fail!
379
// (this shoudln't occur)
380
throw new IllegalStateException JavaDoc("Storage did not return data for field '" + fieldName + "'");
381                             }
382                             if (b != null) {
383                                 if (b.length() == -1) {
384                                     value = MMObjectNode.VALUE_SHORTED;
385                                 } else {
386                                     value = b.getBytes(0L, (int) b.length());
387                                 }
388                             }
389                         }
390                         node.storeValue(fieldName, value);
391                     }
392                     node.clearChanged();
393                     node.finish();
394
395                     // The following code fills the type- and node-cache as far as this is possible at this stage.
396
// (provided the node is persistent)
397
if (! isVirtual) {
398                         int otype = node.getOType();
399                         Integer JavaDoc number = new Integer JavaDoc(node.getNumber());
400                         if (otype == builderType) {
401                             MMObjectNode cacheNode = (MMObjectNode) nodeCache.get(number);
402                             if (cacheNode != null) {
403                                 node = cacheNode;
404                             } else {
405                                 nodeCache.put(number, node);
406                             }
407                             typeCache.put(number, oTypeInteger);
408                         } else {
409                             typeCache.put(number, new Integer JavaDoc(otype));
410                         }
411                     }
412
413                     results.add(node);
414                 } catch (Exception JavaDoc e) {
415                     // log error, but continue with other nodes
416
log.error(e.getMessage(), e);
417                 }
418             }
419         } catch (SQLException sqe) {
420             // log error, but return results.
421
log.error(sqe);
422         }
423         return results;
424     }
425
426
427     // javadoc is inherited
428
public int getSupportLevel(int feature, SearchQuery query) throws SearchQueryException {
429         int supportLevel;
430         switch (feature) {
431         case SearchQueryHandler.FEATURE_OFFSET:
432             // When sql handler does not support OFFSET, this query handler
433
// provides weak support by skipping resultsets.
434
// (falls through)
435
case SearchQueryHandler.FEATURE_MAX_NUMBER:
436             // When sql handler does not support MAX NUMBER, this query
437
// handler provides weak support by truncating resultsets.
438
int handlerSupport = sqlHandler.getSupportLevel(feature, query);
439             if (handlerSupport == SearchQueryHandler.SUPPORT_NONE) {
440                 // TODO: implement weak support.
441
//supportLevel = SearchQueryHandler.SUPPORT_WEAK;
442
supportLevel = SearchQueryHandler.SUPPORT_NONE;
443             } else {
444                 supportLevel = handlerSupport;
445             }
446             break;
447
448         default:
449             supportLevel = sqlHandler.getSupportLevel(feature, query);
450         }
451         return supportLevel;
452     }
453
454     // javadoc is inherited
455
public int getSupportLevel(Constraint constraint, SearchQuery query) throws SearchQueryException {
456         return sqlHandler.getSupportLevel(constraint, query);
457     }
458
459 }
460
Popular Tags