KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > core > util > StorageConnector


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

10 package org.mmbase.core.util;
11
12 import java.util.*;
13
14 import org.mmbase.bridge.Field;
15 import org.mmbase.bridge.NodeQuery;
16 import org.mmbase.cache.*;
17 import org.mmbase.core.CoreField;
18 import org.mmbase.module.corebuilders.*;
19 import org.mmbase.module.core.*;
20 import org.mmbase.storage.*;
21 import org.mmbase.storage.util.Index;
22 import org.mmbase.storage.search.*;
23 import org.mmbase.storage.search.implementation.*;
24 import org.mmbase.util.QueryConvertor;
25 import org.mmbase.util.logging.Logger;
26 import org.mmbase.util.logging.Logging;
27
28 /**
29  * A StorageConnector object is associated with a specific builder.
30  * It provides methods for loading nodes from the cloud (using the search query classes),
31  * either indivbidual nodes or nodelists.
32  *
33  * @since MMBase-1.8
34  * @author Pierre van Rooden
35  * @version $Id: StorageConnector.java,v 1.11 2006/08/29 14:47:28 michiel Exp $
36  */

37 public class StorageConnector {
38
39     /**
40      * Max length of a query, informix = 32.0000 so we assume a bit less for other databases (???).
41      */

42     private static final int MAX_QUERY_SIZE = 20000;
43
44     private static final Logger log = Logging.getLoggerInstance(StorageConnector.class);
45
46     /**
47      * Determines whether the cache need be refreshed.
48      * Seems useless, as this value is never changed (always true)
49      * @see #processSearchResults
50      */

51     /*
52      public static final boolean REPLACE_CACHE = true;
53     */

54
55     /**
56      * Whenever a list should always return the correct types of nodes
57      * old behaviour is not...
58      * This is needed, when you want for example use the following code:
59      * <pre>
60      * MMObjectNode node = MMObjectBuilder.getNode(123);
61      * Enumeration relations = node.getRelations("posrel");
62      * while(enumeration.hasNext()) {
63      * MMObjectNode posrel = (MMObjectNode) enumeration.getElement();
64      * int pos = posrel.getIntValue("pos");
65      * }
66      * </pre>
67      * When the return of correct node types is the following code has to be used..
68      * <pre>
69      * MMObjectNode node = MMObjectBuilder.getNode(123);
70      * Enumeration relations = node.getRelations("posrel");
71      * while(enumeration.hasNext()) {
72      * MMObjectNode posrel = (MMObjectNode) enumeration.getElement();
73      * // next lines is needed when the return of correct nodes is not true
74      * posrel = posrel.parent.getNode(posrel.getNumber());
75      * // when the line above is skipped, the value of pos will always be -1
76      * int pos = posrel.getIntValue("pos");
77      * }
78      * </pre>
79      * Maybe this should be fixed in some otherway,.. but when we want to use the inheritance you
80      * _really_ need this thing turned into true.
81      */

82     /*
83      private static boolean CORRECT_NODE_TYPES = true;
84     */

85
86     /**
87      * Maximum number of nodes to return on a query (-1 means no limit, and is also the default)
88      */

89     protected int maxNodesFromQuery = -1;
90
91     /**
92      * @javadoc
93      */

94     protected final MMObjectBuilder builder;
95
96     // indices for the storage layer
97
private Map indices = new HashMap();
98
99     /**
100      * @javadoc
101      */

102     public StorageConnector(MMObjectBuilder builder) {
103         this.builder = builder;
104     }
105
106     /**
107      * Determine the number of objects in this table.
108      * @return The number of entries in the table.
109      */

110     public int size() {
111         try {
112             return builder.getMMBase().getStorageManager().size(builder);
113         } catch (StorageException se) {
114             log.error(se.getMessage());
115             return -1;
116         }
117     }
118
119     /**
120      * Check whether the table is accessible.
121      * In general, this means the table does not exist. Please note that this routine may
122      * also return false if the table is inaccessible due to insufficient rights.
123      * @return <code>true</code> if the table is accessible, <code>false</code> otherwise.
124      */

125     public boolean created() {
126         try {
127             return builder.getMMBase().getStorageManager().exists(builder);
128         } catch (StorageException se) {
129             log.error(se.getMessage() + Logging.stackTrace(se));
130             return false;
131         }
132     }
133
134     public Map getIndices() {
135         return indices;
136     }
137
138     public void addIndex(Index index) {
139         if (index != null && index.getParent() == builder) {
140             indices.put(index.getName(),index);
141         }
142     }
143
144     public void addIndices(List indexList) {
145         if (indexList != null ) {
146             for (Iterator i = indexList.iterator(); i.hasNext(); ) {
147                 addIndex((Index)i.next());
148             }
149         }
150     }
151
152     public Index getIndex(String JavaDoc key) {
153         return (Index)indices.get(key);
154     }
155
156     public synchronized Index createIndex(String JavaDoc key) {
157         Index index = getIndex(key);
158         if (index == null) {
159             index = new Index(builder, key);
160             indices.put(key,index);
161         }
162         return index;
163     }
164
165     public void addToIndex(String JavaDoc key, Field field) {
166         createIndex(key).add(field);
167     }
168
169     public void removeFromIndex(String JavaDoc key, Field field) {
170         Index index = createIndex(key);
171         if (index != null) {
172             index.remove(field);
173         }
174     }
175
176     public boolean isInIndex(String JavaDoc key, Field field) {
177         Index index = getIndex(key);
178         return index != null && index.contains(field);
179     }
180
181
182
183     // retrieve nodes
184
/**
185      * Retrieves a node based on it's number (a unique key).
186      * @todo when something goes wrong, the method currently catches the exception and returns null.
187      * It should actually throw a NotFoundException instead.
188      * @param number The number of the node to search for
189      * @param useCache If false, a fresh copy is returned.
190      * @return <code>null</code> if the node does not exist, the key is invalid,or a
191      * <code>MMObjectNode</code> containing the contents of the requested node.
192      */

193     public MMObjectNode getNode(final int number, final boolean useCache) throws StorageException {
194         if (log.isDebugEnabled()) {
195             log.trace("Getting node with number " + number);
196         }
197         if (number < 0) {
198             throw new IllegalArgumentException JavaDoc("Tried to obtain node from builder '" + builder.getTableName() + "' with an illegal number = " + number);
199         }
200         MMObjectNode node = null;
201
202         Integer JavaDoc numberValue = new Integer JavaDoc(number);
203         // try cache if indicated to do so
204
node = builder.getNodeFromCache(numberValue);
205         if (node != null) {
206             log.trace("Found in cache!");
207             if (useCache) {
208                 return node;
209             } else {
210                 return new MMObjectNode(node);
211             }
212         }
213
214         MMBase mmb = builder.getMMBase();
215         // not in cache. We are going to put it in.
216
// retrieve node's objecttype
217
MMObjectBuilder nodeBuilder = getBuilderForNode(number);
218         // use storage factory if present
219
log.debug("Getting node from storage");
220         node = mmb.getStorageManager().getNode(nodeBuilder, number);
221         // store in cache if indicated to do so
222
if (useCache) {
223             if (log.isDebugEnabled()) {
224                 log.debug("Caching node from storage" + node);
225             }
226             node = builder.safeCache(numberValue, node);
227         }
228         if (log.isDebugEnabled()) {
229             log.debug("Returning " + node);
230         }
231         if (useCache) {
232             return node;
233         } else {
234             return new MMObjectNode(node);
235         }
236     }
237
238     public MMObjectBuilder getBuilderForNode(final int number) {
239         MMBase mmb = builder.getMMBase();
240         MMObjectBuilder nodeBuilder = builder;
241         int nodeType = getNodeType(number);
242         if (nodeType < 0) {
243             // the node does not exists, which according to javadoc should return null
244
throw new StorageNotFoundException("Cannot determine node type of node with number =" + number);
245         }
246         // if the type is not for the current builder, determine the real builder
247
if (nodeType != builder.getNumber()) {
248             if (log.isDebugEnabled()) {
249                 log.debug(" " + nodeType + "!=" + builder.getNumber());
250             }
251             String JavaDoc builderName = mmb.getTypeDef().getValue(nodeType);
252             if (builderName == null) {
253                 log.error("The nodetype name of node #" + number + " could not be found (nodetype # " + nodeType + "), taking '" + builder.getTableName() + "'");
254                 builderName = builder.getTableName();
255             }
256             nodeBuilder = mmb.getBuilder(builderName);
257             if (nodeBuilder == null) {
258                 log.warn("Node #" + number + "'s builder " + builderName + "(" + nodeType + ") is not loaded, taking 'object'.");
259                 nodeBuilder = mmb.getBuilder("object");
260             }
261         }
262         return nodeBuilder;
263     }
264
265     /**
266      * Retrieves an object's type. If necessary, the type is added to the cache.
267      * @todo when something goes wrong, the method currently catches the exception and returns -1.
268      * It should actually throw a NotFoundException instead.
269      * @param number The number of the node to search for
270      * @return an <code>int</code> value which is the object type (otype) of the node.
271      */

272     public int getNodeType(int number) throws StorageException {
273         if (number < 0 ) {
274             throw new IllegalArgumentException JavaDoc("node number was invalid (" + number + " < 0)" );
275         } else {
276             return builder.getMMBase().getStorageManager().getNodeType(number);
277         }
278     }
279
280     // Search and query methods on a table
281

282     /**
283      * Convert virtual nodes to real nodes based on their otype
284      *
285      * Normally a multirelations-search will return virtual nodes. These nodes
286      * will only contain values which where specified in the field-vector.
287      * This method will make real nodes of those virtual nodes.
288      *
289      * @param virtuals containing virtual nodes
290      * @return List containing real nodes, directly from this Builders
291      */

292     public List getNodes(Collection virtuals) throws SearchQueryException {
293         List result = new ArrayList();
294
295         int numbersSize = 0;
296         NodeSearchQuery query = new NodeSearchQuery(builder);
297         BasicStep step = (BasicStep) query.getSteps().get(0);
298         List subResult = new ArrayList();
299
300         Iterator i = virtuals.iterator();
301         while(i.hasNext()) {
302             MMObjectNode node = (MMObjectNode) i.next();
303
304             // check if this node is already in cache
305
//Integer number = Integer.valueOf(node.getNumber()); // 1.5 only
306
Integer JavaDoc number = new Integer JavaDoc(node.getNumber());
307             if(builder.isNodeCached(number)) {
308                 result.add(builder.getNodeFromCache(number));
309                 // else seek it with a search on builder in db
310
} else {
311                 numbersSize += ("," + number).length();
312                 subResult.add(number);
313                 step.addNode(number.intValue());
314             }
315
316             if(numbersSize > MAX_QUERY_SIZE) {
317                 addSubResult(query, subResult, result);
318                 query = new NodeSearchQuery(builder);
319                 step = (BasicStep) query.getSteps().get(0);
320                 numbersSize = 0;
321                 subResult.clear();
322             }
323         }
324
325         // now that we have a comma seperated string of numbers, we can
326
// the search with a where-clause containing this list
327
if(numbersSize > 0) {
328             addSubResult(query, subResult, result);
329         } // else everything from cache
330

331         // check that we didnt loose any nodes
332
assert assertSizes(virtuals, result);
333
334         return result;
335     }
336
337     /**
338      * @param query Query with nodestep with added nodes.
339      * @param subResult List of Integer
340      * @param result List to which the real nodes must be added.
341      * @since MMBase-1.8.2
342      */

343     protected void addSubResult(final NodeSearchQuery query, final List subResult, final List result) throws SearchQueryException {
344         List rawNodes = getRawNodes(query, true);
345         // convert this list to a map, for easy reference when filling result.
346
// would the creation of this Map not somehow be avoidable?
347
Map rawMap = new HashMap();
348         Iterator i = rawNodes.iterator();
349         while (i.hasNext()) {
350             MMObjectNode n = (MMObjectNode) i.next();
351             //rawMap.put(Integer.valueOf(n.getNumber()), n); // 1.5 only..
352
rawMap.put(new Integer JavaDoc(n.getNumber()), n);
353         }
354         Iterator j = subResult.iterator();
355         while (j.hasNext()) {
356             Integer JavaDoc n = (Integer JavaDoc) j.next();
357             result.add(rawMap.get(n));
358         }
359     }
360
361     /**
362      * @since MMBase-1.8.2
363      */

364     protected boolean assertSizes(Collection virtuals, Collection result) {
365         if (virtuals.size() != result.size()) {
366             log.error(" virtuals " + virtuals + " result " + result);
367             return false;
368         } else {
369             return true;
370         }
371     }
372     /**
373      * Counts number of nodes matching a specified constraint.
374      * The constraint is specified by a query that selects nodes of
375      * a specified type, which must be the nodetype corresponding
376      * to this builder.
377      *
378      * @param query The query.
379      * @return The number of nodes.
380      * @throws IllegalArgumentException when an invalid argument is supplied.
381      * @throws SearchQueryException when failing to retrieve the data.
382      */

383     public int count(SearchQuery query) throws SearchQueryException {
384         // Test if nodetype corresponds to builder.
385
verifyBuilderQuery(query);
386
387         // Wrap in modifiable query, replace fields by one count field.
388
ModifiableQuery modifiedQuery = new ModifiableQuery(query);
389         Step step = (Step) query.getSteps().get(0);
390         CoreField numberField = builder.getField(MMObjectBuilder.FIELD_NUMBER);
391         AggregatedField field = new BasicAggregatedField(step, numberField, AggregatedField.AGGREGATION_TYPE_COUNT);
392         List newFields = new ArrayList(1);
393         newFields.add(field);
394         modifiedQuery.setFields(newFields);
395
396         AggregatedResultCache cache = AggregatedResultCache.getCache();
397
398         List results = (List) cache.get(modifiedQuery);
399         if (results == null) {
400             // Execute query, return result.
401
results = builder.getMMBase().getSearchQueryHandler().getNodes(modifiedQuery, new ResultBuilder(builder.getMMBase(), modifiedQuery));
402             cache.put(modifiedQuery, results);
403         }
404         ResultNode result = (ResultNode) results.get(0);
405         return result.getIntValue(MMObjectBuilder.FIELD_NUMBER);
406     }
407
408     private void verifyBuilderQuery(SearchQuery query) throws SearchQueryException {
409         String JavaDoc builderName = null;
410         if (query instanceof NodeQuery) {
411             builderName = ((NodeQuery)query).getNodeManager().getName();
412         } else if (query instanceof NodeSearchQuery) {
413             builderName = ((NodeSearchQuery)query).getBuilder().getTableName();
414         }
415         if (builderName != null && !builderName.equals(builder.getTableName())) {
416             throw new IllegalArgumentException JavaDoc("Query passed runs on '" + builderName + "' but was passed to '" + builder.getTableName() + "'");
417         }
418     }
419
420     /**
421      * Returns the Cache which should be used for the result of a certain query. The current
422      * implementation only makes the distinction between queries for the 'related nodes caches' and
423      * for the 'node list caches'. Multilevel queries are not done here, so are at the moment not
424      * anticipated.
425      *
426      * It returns a Map rather then a Cache. The idea behind this is that if in the future a
427      * query-result can be in more than one cache, a kind of 'chained map' can be returned, to
428      * reflect that.
429      * @todo Perhaps other usefull parameters like query-duration and query-result could be added
430      * (in that case searching a result should certainly returns such a chained map, because then of
431      * course you don't have those).
432      */

433     protected Map getCache(SearchQuery query) {
434         List steps = query.getSteps();
435         if (steps.size() == 3) {
436             Step step0 = (Step) steps.get(0);
437             Collection nodes = step0.getNodes();
438             if (nodes != null && nodes.size() == 1) {
439                 return RelatedNodesCache.getCache();
440             }
441         }
442         return NodeListCache.getCache();
443
444     }
445
446     /**
447      * Returns nodes matching a specified constraint.
448      * The constraint is specified by a query that selects nodes of
449      * a specified type, which must be the nodetype corresponding
450      * to this builder.
451      *
452      * Cache is used, but not filled (because this function is used to calculate subresults)
453      *
454      * @param query The query.
455      * @param useCache if true, the querycache is used
456      * @return The nodes.
457      * @throws IllegalArgumentException When the nodetype specified
458      * by the query is not the nodetype corresponding to this builder.
459      */

460     private List getRawNodes(SearchQuery query, boolean useCache) throws SearchQueryException {
461         // Test if nodetype corresponds to builder.
462
verifyBuilderQuery(query);
463         List results = useCache ? (List) getCache(query).get(query) : null;
464
465         // if unavailable, obtain from storage
466
if (results == null) {
467             log.debug("result list is null, getting from storage");
468             results = builder.getMMBase().getSearchQueryHandler().getNodes(query, builder);
469         } else {
470             if (log.isDebugEnabled()) {
471                 log.debug("Found from cache" + ((Cache) getCache(query)).getName() + " " + results);
472             }
473         }
474         return results;
475     }
476
477     /**
478      * Returns nodes matching a specified constraint.
479      * The constraint is specified by a query that selects nodes of
480      * a specified type, which must be the nodetype corresponding
481      * to this builder.
482      *
483      * @param query The query.
484      * @return The nodes.
485      * @throws IllegalArgumentException When the nodetype specified
486      * by the query is not the nodetype corresponding to this builder.
487      */

488     public List getNodes(SearchQuery query) throws SearchQueryException {
489         return getNodes(query, true);
490     }
491
492     /**
493      * Returns nodes matching a specified constraint.
494      * The constraint is specified by a query that selects nodes of
495      * a specified type, which must be the nodetype corresponding
496      * to this builder.
497      *
498      * @param query The query.
499      * @param useCache if true, the querycache is used
500      * @return The nodes.
501      * @throws IllegalArgumentException When the nodetype specified
502      * by the query is not the nodetype corresponding to this builder.
503      */

504     public List getNodes(SearchQuery query, boolean useCache) throws SearchQueryException {
505         List results = getRawNodes(query, useCache);
506         // TODO (later): implement maximum set by maxNodesFromQuery?
507
// Perform necessary postprocessing.
508
processSearchResults(results);
509         if (useCache) {
510             getCache(query).put(query, results);
511         }
512         return results;
513     }
514
515     /**
516      * Returns all the nodes from the associated builder.
517      * @return The nodes.
518      */

519     public List getNodes() throws SearchQueryException {
520         return getNodes(new NodeSearchQuery(builder));
521     }
522
523     /**
524      * Performs some necessary postprocessing on nodes retrieved from a
525      * search query.
526      * This consists of the following actions:
527      * <ul>
528      * <li>Stores retrieved nodes in the node cache, or
529      * <li>Replace partially retrieved nodes in the result by complete nodes.
530      * Nodes are partially retrieved when their type is a inheriting type
531      * of this builder's type, having additional fields. For these nodes
532      * additional queries are performed to retrieve the complete nodes.
533      * <li>Removes nodes with invalid node number from the result.
534      * </ul>
535      *
536      * @param results The nodes. After returning, partially retrieved nodes
537      * in the result are replaced <em>in place</em> by complete nodes.
538      */

539     private void processSearchResults(List results) {
540         Map convert = new HashMap();
541         int convertCount = 0;
542         int convertedCount = 0;
543         int cacheGetCount = 0;
544         int cachePutCount = 0;
545
546         ListIterator resultsIterator = results.listIterator();
547         while (resultsIterator.hasNext()) {
548             MMObjectNode node = (MMObjectNode) resultsIterator.next();
549             Integer JavaDoc number = new Integer JavaDoc(node.getNumber());
550             if(number.intValue() < 0) {
551                 // never happened to me, and never should!
552
log.error("invalid node found, node number was invalid:" + node.getNumber()+", storage invalid?");
553                 // dont know what to do with this node,...
554
// remove it from the results, continue to the next one!
555
resultsIterator.remove();
556                 continue;
557             }
558
559             boolean fromCache = false;
560             // only active when builder loaded (oType != -1)
561
// maybe we got the wrong node typeback, if so
562
// try to retrieve the correct node from the cache first
563
int oType = builder.getNumber();
564             if(oType != -1 && oType != node.getOType()){
565                 // try to retrieve the correct node from the
566
// nodecache
567
MMObjectNode cachedNode = builder.getNodeFromCache(number);
568                 if(cachedNode != null) {
569                     node = cachedNode;
570                     resultsIterator.set(node);
571                     fromCache = true;
572                     cacheGetCount ++;
573                 } else {
574                     // add this node to the list of nodes that still need to
575
// be converted..
576
// we dont request the builder here, for this we need the
577
// typedef table, which could generate an additional query..
578
Integer JavaDoc nodeType = new Integer JavaDoc(node.getOType());
579                     Set nodes = (Set) convert.get(nodeType);
580                     // create an new entry for the type, if not yet there...
581
if (nodes == null) {
582                         nodes = new HashSet();
583                         convert.put(nodeType, nodes);
584                     }
585                     nodes.add(node);
586                     convertCount ++;
587                 }
588 /*
589             } else if (oType == node.getOType()) {
590                 MMObjectNode oldNode = builder.getNodeFromCache(number);
591                 // when we want to use cache also for new found nodes
592                 // and cache may not be replaced, use the one from the
593                 // cache..
594                 if(!REPLACE_CACHE && oldNode != null) {
595                     node = oldNode;
596                     resultsIterator.set(node);
597                     fromCache = true;
598                     cacheGetCount++;
599                 }
600             } else {
601                 // skipping everything, our builder hasnt been started yet...
602 */

603             }
604
605             // we can add the node to the cache _if_
606
// it was not from cache already, and it
607
// is of the correct type..
608
if(!fromCache && oType == node.getOType()) {
609                 // can someone tell me what this has to do?
610
// clear the changed signal
611
node.clearChanged(); // huh?
612
node = builder.safeCache(number, node);
613                 cachePutCount++;
614             }
615         }
616
617         if (/* CORRECT_NODE_TYPES && */ convert.size() > 0){
618             // retieve the nodes from the builders....
619
// and put them into one big hashmap (integer/node)
620
// after that replace all the nodes in result, that
621
// were invalid.
622
Map convertedNodes = new HashMap();
623
624             // process all the different types (builders)
625
Iterator types = convert.entrySet().iterator();
626             while(types.hasNext()){
627                 Map.Entry typeEntry = (Map.Entry) types.next();
628                 int nodeType = ((Integer JavaDoc)typeEntry.getKey()).intValue();
629                 Set nodes = (Set) typeEntry.getValue();
630                 MMObjectNode typedefNode;
631                 try {
632                     typedefNode = getNode(nodeType, true);
633                 } catch (Exception JavaDoc e) {
634                     log.error("Exception during conversion of nodelist to right types. Nodes (" + nodes + ") of current type " + nodeType + " will be skipped. Probably the storage is inconsistent. Message: " + e.getMessage());
635
636                     continue;
637                 }
638                 if(typedefNode == null) {
639                     // builder not known in typedef?
640
// skip this builder and process to next one..
641
// TODO: research: add incorrect node to node's cache?
642
log.error("Could not find typedef node #" + nodeType);
643                     continue;
644                 }
645                 MMObjectBuilder conversionBuilder = builder.getMMBase().getBuilder(typedefNode.getStringValue("name"));
646                 if(conversionBuilder == null) {
647                     // could not find the builder that was in typedef..
648
// maybe it is not active?
649
// TODO: research: add incorrect node's to node cache?
650
log.error("Could not find builder with name:" + typedefNode.getStringValue("name") + " refered by node #" + typedefNode.getNumber()+", is it active?");
651                     continue;
652                 }
653                 try {
654                     Iterator converted = conversionBuilder.getStorageConnector().getNodes(nodes).iterator();
655                     while(converted.hasNext()) {
656                         MMObjectNode current = (MMObjectNode) converted.next();
657                         convertedNodes.put(new Integer JavaDoc(current.getNumber()), current);
658                     }
659                 } catch (SearchQueryException sqe) {
660                     log.error(sqe.getMessage() + Logging.stackTrace(sqe));
661                     // no nodes
662
}
663             }
664
665             // insert all the corrected nodes that were found into the list..
666
for(int i = 0; i < results.size(); i++) {
667                 MMObjectNode current = (MMObjectNode) results.get(i);
668                 Integer JavaDoc number = new Integer JavaDoc(current.getNumber());
669                 if(convertedNodes.containsKey(number)) {
670                     // converting the node...
671
results.set(i, convertedNodes.get(number));
672                     convertedCount ++;
673                 }
674                 current = (MMObjectNode) results.get(i);
675                 if(current.getNumber() < 0) {
676                     // never happened to me, and never should!
677
throw new RuntimeException JavaDoc("invalid node found, node number was invalid:" + current.getNumber());
678                 }
679             }
680         } else if(convert.size() != 0) {
681             log.warn("we still need to convert " + convertCount + " of the " + results.size() + " nodes"
682                      + "(number of different types:"+ convert.size() +")");
683         }
684         if(log.isDebugEnabled()) {
685             log.debug("retrieved " + results.size() +
686                       " nodes, converted " + convertedCount +
687                       " of the " + convertCount +
688                       " invalid nodes(" + convert.size() +
689                       " types, " + cacheGetCount +
690                       " from cache, " + cachePutCount + " to cache)");
691         }
692     }
693
694     /**
695      * Creates search query that retrieves nodes matching a specified
696      * constraint.
697      *
698      * @param where The constraint, can be a SQL where-clause, a MMNODE
699      * expression, an altavista-formatted expression, empty or
700      * <code>null</code>.
701      * @return The query.
702      * @since MMBase-1.7
703      */

704     public NodeSearchQuery getSearchQuery(String JavaDoc where) {
705         NodeSearchQuery query;
706
707         if (where != null && where.startsWith("MMNODE ")) {
708             // MMNODE expression.
709
query = convertMMNodeSearch2Query(where);
710         } else {
711             query = new NodeSearchQuery(builder);
712             QueryConvertor.setConstraint(query, where);
713         }
714
715         return query;
716     }
717
718     /**
719      * Creates query based on an MMNODE expression.
720      *
721      * @deprecated MMNODE expressions are deprecated, scan only?
722      * @param expr The MMNODE expression.
723      * @return The query.
724      * @throws IllegalArgumentException when an invalid argument is supplied.
725      */

726     private NodeSearchQuery convertMMNodeSearch2Query(String JavaDoc expr) {
727         NodeSearchQuery query = new NodeSearchQuery(builder);
728         BasicCompositeConstraint constraints = new BasicCompositeConstraint(CompositeConstraint.LOGICAL_AND);
729         String JavaDoc logicalOperator = null;
730
731         // Strip leading string "MMNODE " from expression, parse
732
// fieldexpressions and logical operators.
733
// (legacy: eol characters '\n' and '\r' are interpreted as "AND NOT")
734
StringTokenizer tokenizer = new StringTokenizer(expr.substring(7), "+-\n\r", true);
735         while (tokenizer.hasMoreTokens()) {
736             String JavaDoc fieldExpression = tokenizer.nextToken();
737
738             // Remove prefix if present (example episodes.title==).
739
int pos = fieldExpression.indexOf('.');
740             if (pos != -1) {
741                 fieldExpression = fieldExpression.substring(pos + 1);
742             }
743
744             // Break up field expression in fieldname, comparison operator
745
// and value.
746
pos = fieldExpression.indexOf('=');
747             if (pos != -1 && fieldExpression.length() > pos + 2) {
748                 String JavaDoc fieldName = fieldExpression.substring(0, pos);
749                 char comparison = fieldExpression.charAt(pos + 1);
750                 String JavaDoc value = fieldExpression.substring(pos + 2);
751
752                 // Add corresponding constraint to constraints.
753
CoreField field = builder.getField(fieldName);
754                 if (field == null) {
755                     throw new IllegalArgumentException JavaDoc(
756                         "Invalid MMNODE expression: " + expr);
757                 }
758                 StepField stepField = query.getField(field);
759                 BasicConstraint constraint = parseFieldPart(stepField, comparison, value);
760                 constraints.addChild(constraint);
761
762                 // Set to inverse if preceded by a logical operator that is
763
// not equal to "+".
764
if (logicalOperator != null && !logicalOperator.equals("+")) {
765                     constraint.setInverse(true);
766                 }
767             } else {
768                 // Invalid expression.
769
throw new IllegalArgumentException JavaDoc(
770                     "Invalid MMNODE expression: " + expr);
771             }
772
773             // Read next logical operator.
774
if (tokenizer.hasMoreTokens()) {
775                 logicalOperator = tokenizer.nextToken();
776             }
777         }
778
779         List childs = constraints.getChilds();
780         if (childs.size() == 1) {
781             query.setConstraint((FieldValueConstraint) childs.get(0));
782         } else if (childs.size() > 1) {
783             query.setConstraint(constraints);
784         }
785         return query;
786     }
787
788     /**
789      * Creates a {@link org.mmbase.storage.search.FieldCompareConstraint
790      * FieldCompareConstraint}, based on parts of a field expression in a
791      * MMNODE expression.
792      *
793      * @deprecated MMNODE expressions are deprecated
794      * @param field The field
795      * @param comparison The second character of the comparison operator.
796      * @param strValue The value to compare with, represented as
797      * <code>String<code>.
798      * @return The constraint.
799      */

800     private BasicFieldValueConstraint parseFieldPart(StepField field, char comparison, String JavaDoc strValue) {
801
802         Object JavaDoc value = strValue;
803
804         // For numberical fields, convert string representation to Double.
805
if (field.getType() != Field.TYPE_STRING &&
806             field.getType() != Field.TYPE_XML &&
807             field.getType() != Field.TYPE_UNKNOWN) {
808                 // backwards comp fix. This is needed for the scan editors.
809
int length = strValue.length();
810                 if (strValue.charAt(0) == '*' && strValue.charAt(length - 1) == '*') {
811                     strValue = strValue.substring(1, length - 1);
812                 }
813                 value = Double.valueOf(strValue);
814         }
815
816         BasicFieldValueConstraint constraint = new BasicFieldValueConstraint(field, value);
817
818         switch (comparison) {
819         case '=':
820         case 'E':
821             // EQUAL (string field)
822
if (field.getType() == Field.TYPE_STRING ||
823                 field.getType() == Field.TYPE_XML) {
824                 // Strip first and last character of value, when
825
// equal to '*'.
826
String JavaDoc str = (String JavaDoc) value;
827                 int length = str.length();
828                 if (str.charAt(0) == '*' && str.charAt(length - 1) == '*') {
829                     value = str.substring(1, length - 1);
830                 }
831
832                 // Convert to LIKE comparison with wildchard characters
833
// before and after (legacy).
834
constraint.setValue('%' + (String JavaDoc) value + '%');
835                 constraint.setCaseSensitive(false);
836                 constraint.setOperator(FieldCompareConstraint.LIKE);
837
838                 // EQUAL (numerical field)
839
} else {
840                 constraint.setOperator(FieldCompareConstraint.EQUAL);
841             }
842             break;
843
844         case 'N':
845             constraint.setOperator(FieldCompareConstraint.NOT_EQUAL);
846             break;
847
848         case 'G':
849             constraint.setOperator(FieldCompareConstraint.GREATER);
850             break;
851
852         case 'g':
853             constraint.setOperator(FieldCompareConstraint.GREATER_EQUAL);
854             break;
855
856         case 'S':
857             constraint.setOperator(FieldCompareConstraint.LESS);
858             break;
859
860         case 's':
861             constraint.setOperator(FieldCompareConstraint.LESS_EQUAL);
862             break;
863
864         default:
865             throw new IllegalArgumentException JavaDoc("Invalid comparison character: '" + comparison + "'");
866         }
867         return constraint;
868     }
869
870
871
872 }
873
Popular Tags