KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > module > core > ClusterBuilder


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.module.core;
11
12 import java.util.*;
13 import org.mmbase.module.corebuilders.*;
14 import org.mmbase.core.CoreField;
15 import org.mmbase.bridge.Field;
16 import org.mmbase.core.util.Fields;
17 import org.mmbase.util.functions.*;
18 import org.mmbase.datatypes.*;
19 import org.mmbase.storage.search.*;
20 import org.mmbase.storage.search.implementation.*;
21 import org.mmbase.storage.search.legacy.ConstraintParser;
22 import org.mmbase.util.QueryConvertor;
23 import org.mmbase.util.logging.*;
24
25
26 /**
27  * The builder for {@link ClusterNode clusternodes}.
28  * <p>
29  * Provides these methods to retrieve clusternodes:
30  * <ul>
31  * <li>{@link #getClusterNodes(SearchQuery)}
32  * to retrieve clusternodes using a <code>SearchQuery</code> (recommended).
33  * <li>{@link #getMultiLevelSearchQuery(List,List,String,List,String,List,List,int)}
34  * as a convenience method to create a <code>SearchQuery</code>
35  * <li>{@link #searchMultiLevelVector(List,List,String,List,String,List,List,int)}
36  * to retrieve clusternodes using a constraint string.
37  * </ul>
38  * <p>
39  * Individual nodes in a 'cluster' node can be retrieved by calling the node's
40  * {@link MMObjectNode#getNodeValue(String) getNodeValue()} method, using
41  * the builder name (or step alias) as argument.
42  *
43  * @todo XXXX. This 'builder' is actually singleton (only one instance is created). It does
44  * therefore not support getFields, so this is more or less hacked in bridge. Perhaps in 'core' a
45  * similar approach as now in birdge must be taken, so no ClusterBuilder, but only Virtual builders,
46  * one for every query result.
47  *
48  *
49  * @author Rico Jansen
50  * @author Pierre van Rooden
51  * @author Rob van Maris
52  * @version $Id: ClusterBuilder.java,v 1.85 2006/01/06 16:34:16 michiel Exp $
53  * @see ClusterNode
54  */

55 public class ClusterBuilder extends VirtualBuilder {
56
57     /**
58      * Search for all valid relations.
59      * When searching relations, return both relations from source to deastination and from destination to source,
60      * provided there is an allowed relation in that directon.
61      * @deprecated use {@link RelationStep#DIRECTIONS_BOTH}
62      * In future versions of MMBase (1.8 and up) this will be the default value
63      */

64     public static final int SEARCH_BOTH = RelationStep.DIRECTIONS_BOTH;
65
66     /**
67      * Search for destinations,
68      * When searching relations, return only relations from source to deastination.
69      * @deprecated use {@link RelationStep#DIRECTIONS_DESTINATION}
70      */

71     public static final int SEARCH_DESTINATION = RelationStep.DIRECTIONS_DESTINATION;
72
73     /**
74      * Seach for sources.
75      * When searching a multilevel, return only relations from destination to source, provided directionality allows
76      * @deprecated use {@link RelationStep#DIRECTIONS_SOURCE}
77      */

78     public static final int SEARCH_SOURCE = RelationStep.DIRECTIONS_SOURCE;
79
80     /**
81      * Search for all relations. When searching a multilevel, return both relations from source to
82      * deastination and from destination to source. Allowed relations are not checked - ALL
83      * relations are used. This makes more inefficient queries, but it is not really wrong.
84      * @deprecated use {@link RelationStep#DIRECTIONS_ALL}
85      */

86     public static final int SEARCH_ALL = RelationStep.DIRECTIONS_ALL;
87
88     /**
89      * Search for either destination or source.
90      * When searching a multilevel, return either relations from source to destination OR from destination to source.
91      * The returned set is decided through the typerel tabel. However, if both directions ARE somehow supported, the
92      * system only returns source to destination relations.
93      * This is the default value (for compatibility purposes).
94      * @deprecated use {@link RelationStep#DIRECTIONS_EITHER}.
95      * In future versions of MMBase (1.8 and up) the default value will be
96      * {@link RelationStep#DIRECTIONS_BOTH}
97      */

98     public static final int SEARCH_EITHER = RelationStep.DIRECTIONS_EITHER;
99
100     // logging variable
101
private static final Logger log= Logging.getLoggerInstance(ClusterBuilder.class);
102
103     /**
104      * Creates <code>ClusterBuilder</code> instance.
105      * Must be called from the MMBase class.
106      * @param m the MMbase cloud creating the node
107      * @scope package
108      */

109     public ClusterBuilder(MMBase m) {
110         super(m, "clusternodes");
111     }
112
113     /**
114      * Translates a string to a search direction constant.
115      *
116      * @since MMBase-1.6
117      */

118     public static int getSearchDir(String JavaDoc search) {
119         if (search == null) {
120             return RelationStep.DIRECTIONS_EITHER;
121         }
122         return org.mmbase.bridge.util.Queries.getRelationStepDirection(search);
123     }
124
125     /**
126      * Translates a search direction constant to a string.
127      *
128      * @since MMBase-1.6
129      */

130     public static String JavaDoc getSearchDirString(int search) {
131         if (search == RelationStep.DIRECTIONS_DESTINATION) {
132             return "DESTINATION";
133         } else if (search == RelationStep.DIRECTIONS_SOURCE) {
134             return "SOURCE";
135         } else if (search == RelationStep.DIRECTIONS_BOTH) {
136             return "BOTH";
137         } else if (search == RelationStep.DIRECTIONS_ALL) {
138             return "ALL";
139         } else {
140             return "EITHER";
141         }
142     }
143
144     /**
145      * Get a new node, using this builder as its parent.
146      * The new node is a cluster node.
147      * Unlike most other nodes, a cluster node does not have a number,
148      * owner, or otype fields.
149      * @param owner The administrator creating the new node (ignored).
150      * @return A newly initialized <code>VirtualNode</code>.
151      */

152     public MMObjectNode getNewNode(String JavaDoc owner) {
153         throw new UnsupportedOperationException JavaDoc("One cannot create new ClusterNodes");
154     }
155
156     /**
157      * What should a GUI display for this node.
158      * This version displays the contents of the 'name' field(s) that were retrieved.
159      * XXX: should be changed to something better
160      * @param node The node to display
161      * @return the display of the node as a <code>String</code>
162      */

163     public String JavaDoc getGUIIndicator(MMObjectNode node) {
164         // Return "name"-field when available.
165
String JavaDoc s = node.getStringValue("name");
166         if (s != null) {
167             return s;
168         }
169
170         // Else "name"-fields of contained nodes.
171
StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
172         for (Iterator i= node.getValues().entrySet().iterator(); i.hasNext();) {
173             Map.Entry entry = (Map.Entry)i.next();
174             String JavaDoc key = (String JavaDoc) entry.getKey();
175             if (key.endsWith(".name")) {
176                 if (s.length() != 0) {
177                     sb.append(", ");
178                 }
179                 sb.append(entry.getValue());
180             }
181         }
182         if (sb.length() > 15) {
183             return sb.substring(0, 12) + "...";
184         } else {
185             return sb.toString();
186         }
187     }
188
189     /**
190      * What should a GUI display for this node/field combo.
191      * For a multilevel node, the builder tries to determine
192      * the original builder of a field, and invoke the method using
193      * that builder.
194      *
195      * @param node The node to display
196      * @param pars Parameters, see {@link MMObjectBuilder#GUI_PARAMETERS}
197      * @return the display of the node's field as a <code>String</code>, null if not specified
198      */

199     protected String JavaDoc getGUIIndicator(MMObjectNode node, Parameters pars) {
200
201         if (node == null) throw new RuntimeException JavaDoc("Tried to get GUIIndicator for " + pars + " with NULL node");
202             
203         ClusterNode clusterNode = (ClusterNode) node;
204
205         String JavaDoc field = pars.getString(Parameter.FIELD);
206         if (field == null) {
207             return super.getGUIIndicator(node, pars);
208         } else {
209             int pos = field.indexOf('.');
210             if (pos != -1) {
211                 String JavaDoc bulName = getTrueTableName(field.substring(0, pos));
212                 MMObjectNode n = clusterNode.getRealNode(bulName);
213                 if (n != null) {
214                     MMObjectBuilder bul= n.getBuilder();
215                     if (bul != null) {
216                         // what are we trying here?
217
String JavaDoc fieldName = field.substring(pos + 1);
218                         Parameters newPars = new Parameters(pars.getDefinition(), pars);
219                         newPars.set(Parameter.FIELD, fieldName);
220                         newPars.set("stringvalue", null);
221                         org.mmbase.bridge.Node bnode = (org.mmbase.bridge.Node) pars.get(Parameter.NODE);
222                         if (bnode != null) {
223                             newPars.set(Parameter.NODE, bnode.getNodeValue(bulName));
224                         }
225                         newPars.set(Parameter.CORENODE, n);
226                         return (String JavaDoc) bul.guiFunction.getFunctionValue(newPars);
227                     }
228                 }
229             }
230             return super.getGUIIndicator(node, pars);
231         }
232     }
233
234     /**
235      * Determines the builder part of a specified field.
236      * @param fieldName the name of the field
237      * @return the name of the field's builder
238      */

239     public String JavaDoc getBuilderNameFromField(String JavaDoc fieldName) {
240         int pos = fieldName.indexOf(".");
241         if (pos != -1) {
242             String JavaDoc bulName = fieldName.substring(0, pos);
243             return getTrueTableName(bulName);
244         }
245         return "";
246     }
247
248     /**
249      * Determines the fieldname part of a specified field (without the builder name).
250      * @param fieldname the name of the field
251      * @return the name of the field without its builder
252      */

253     public static String JavaDoc getFieldNameFromField(String JavaDoc fieldname) {
254         int pos= fieldname.indexOf(".");
255         if (pos != -1) {
256             fieldname = fieldname.substring(pos + 1);
257         }
258         return fieldname;
259     }
260
261     /**
262      * Return a field.
263      * @param fieldName the requested field's name
264      * @return the field
265      */

266     public FieldDefs getField(String JavaDoc fieldName) {
267         String JavaDoc builderName = getBuilderNameFromField(fieldName);
268         if (builderName.length() > 0) {
269             MMObjectBuilder bul = mmb.getBuilder(builderName);
270             if (bul == null) {
271                 throw new RuntimeException JavaDoc("No builder with name '" + builderName + "' found");
272             }
273             return bul.getField(getFieldNameFromField(fieldName));
274         } else {
275             //
276
MMObjectBuilder bul = mmb.getBuilder(getTrueTableName(fieldName));
277             if (bul != null) {
278                 return new FieldDefs(fieldName, FieldDefs.TYPE_NODE, -1, FieldDefs.STATE_VIRTUAL, org.mmbase.datatypes.DataTypes.getDataType("node"));
279             }
280         }
281         return null;
282     }
283                           
284     public List getFields(int order) {
285         throw new UnsupportedOperationException JavaDoc("Cluster-nodes can have any field.");
286     }
287     public Collection getFields() {
288         throw new UnsupportedOperationException JavaDoc("Cluster-nodes can have any field.");
289     }
290                    
291     /**
292      * @since MMBase-1.8
293      */

294     public Map getFields(MMObjectNode node) {
295         Map ret = new HashMap();
296         Iterator i = node.getValues().keySet().iterator();
297         DataType nodeType = DataTypes.getDataType("node");
298         while (i.hasNext()) {
299             String JavaDoc name = (String JavaDoc) i.next();
300             int pos = name.indexOf(".");
301             if (pos != -1) {
302                 String JavaDoc builderName = name.substring(0, pos);
303                 if (! ret.containsKey(builderName)) {
304                     CoreField fd = Fields.createField(builderName, Field.TYPE_NODE, Field.TYPE_UNKNOWN, Field.STATE_VIRTUAL, nodeType);
305                     ret.put(builderName, fd);
306                 }
307             }
308             ret.put(name, getField(name));
309         }
310         return ret;
311     }
312
313     /**
314      * Same as {@link #searchMultiLevelVector(List,List,String,List,String,List,List,int)
315      * searchMultiLevelVector(snodes, fields, pdistinct, tables, where, orderVec, direction, RelationStep.DIRECTIONS_EITHER)},
316      * where <code>snodes</code> contains just the number specified by <code>snode</code>.
317      *
318      * @see #searchMultiLevelVector(List, List, String, List, String, List, List, List)
319      */

320    public Vector searchMultiLevelVector(
321         int snode,
322         Vector fields,
323         String JavaDoc pdistinct,
324         Vector tables,
325         String JavaDoc where,
326         Vector orderVec,
327         Vector direction) {
328         Vector v= new Vector();
329         v.addElement("" + snode);
330         return searchMultiLevelVector(v, fields, pdistinct, tables, where, orderVec, direction, RelationStep.DIRECTIONS_EITHER);
331     }
332
333     /**
334      * Same as {@link #searchMultiLevelVector(List,List,String,List,String,List,List,int)
335               * searchMultiLevelVector(snodes, fields, pdistinct, tables, where, orderVec, direction, RelationStep.DIRECTIONS_EITHER)}.
336      *
337      * @see #searchMultiLevelVector(List,List,String,List,String,List,List,int)
338      */

339     public Vector searchMultiLevelVector(
340         Vector snodes,
341         Vector fields,
342         String JavaDoc pdistinct,
343         Vector tables,
344         String JavaDoc where,
345         Vector orderVec,
346         Vector direction) {
347         return searchMultiLevelVector(snodes, fields, pdistinct, tables, where, orderVec, direction, RelationStep.DIRECTIONS_EITHER);
348     }
349
350     /**
351      * Return all the objects that match the searchkeys.
352      * The constraint must be in one of the formats specified by {@link
353      * org.mmbase.util.QueryConvertor#setConstraint(BasicSearchQuery,String)
354      * QueryConvertor#setConstraint()}.
355      *
356      * @param snodes The numbers of the nodes to start the search with. These have to be present in the first table
357      * listed in the tables parameter.
358      * @param fields The fieldnames to return. This should include the name of the builder. Fieldnames without a builder prefix are ignored.
359      * Fieldnames are accessible in the nodes returned in the same format (i.e. with manager indication) as they are specified in this parameter.
360      * Examples: 'people.lastname'
361      * @param pdistinct 'YES' indicates the records returned need to be distinct. Any other value indicates double values can be returned.
362      * @param tables The builder chain. A list containing builder names.
363      * The search is formed by following the relations between successive builders in the list. It is possible to explicitly supply
364      * a relation builder by placing the name of the builder between two builders to search.
365      * Example: company,people or typedef,authrel,people.
366      * @param where The constraint, must be in one of the formats specified by {@link
367      * org.mmbase.util.QueryConvertor#setConstraint(BasicSearchQuery,String)
368      * QueryConvertor#setConstraint()}.
369      * E.g. "WHERE news.title LIKE '%MMBase%' AND news.title > 100"
370      * @param sortFields the fieldnames on which you want to sort.
371      * @param directions A list of values containing, for each field in the order parameter, a value indicating whether the sort is
372      * ascending (<code>UP</code>) or descending (<code>DOWN</code>). If less values are syupplied then there are fields in order,
373      * the first value in the list is used for the remaining fields. Default value is <code>'UP'</code>.
374      * @param searchDir Specifies in which direction relations are to be
375      * followed, this must be one of the values defined by this class.
376      * @return a <code>Vector</code> containing all matching nodes
377      * @deprecated use {@link #searchMultiLevelVector(List snodes, List fields, String pdistinct, List tables, String where,
378      * List orderVec, List directions, List searchDirs)}
379      */

380     public Vector searchMultiLevelVector(List snodes, List fields, String JavaDoc pdistinct, List tables, String JavaDoc where, List sortFields,
381             List directions, int searchDir) {
382         List searchDirs = new ArrayList();
383         searchDirs.add(new Integer JavaDoc(searchDir));
384         return searchMultiLevelVector(snodes, fields, pdistinct, tables, where, sortFields, directions, searchDirs);
385     }
386
387     /**
388      * Return all the objects that match the searchkeys.
389      * The constraint must be in one of the formats specified by {@link
390      * org.mmbase.util.QueryConvertor#setConstraint(BasicSearchQuery,String)
391      * QueryConvertor#setConstraint()}.
392      *
393      * @param snodes The numbers of the nodes to start the search with. These have to be present in the first table
394      * listed in the tables parameter.
395      * @param fields The fieldnames to return. This should include the name of the builder. Fieldnames without a builder prefix are ignored.
396      * Fieldnames are accessible in the nodes returned in the same format (i.e. with manager indication) as they are specified in this parameter.
397      * Examples: 'people.lastname'
398      * @param pdistinct 'YES' indicates the records returned need to be distinct. Any other value indicates double values can be returned.
399      * @param tables The builder chain. A list containing builder names.
400      * The search is formed by following the relations between successive builders in the list. It is possible to explicitly supply
401      * a relation builder by placing the name of the builder between two builders to search.
402      * Example: company,people or typedef,authrel,people.
403      * @param where The constraint, must be in one of the formats specified by {@link
404      * org.mmbase.util.QueryConvertor#setConstraint(BasicSearchQuery,String)
405      * QueryConvertor#setConstraint()}.
406      * E.g. "WHERE news.title LIKE '%MMBase%' AND news.title > 100"
407      * @param sortFields the fieldnames on which you want to sort.
408      * @param directions A list of values containing, for each field in the order parameter, a value indicating whether the sort is
409      * ascending (<code>UP</code>) or descending (<code>DOWN</code>). If less values are syupplied then there are fields in order,
410      * the first value in the list is used for the remaining fields. Default value is <code>'UP'</code>.
411      * @param searchDirs Specifies in which direction relations are to be followed. You can specify a direction for each
412      * relation in the path. If you specify less directions than there are relations, the last specified direction is used
413      * for the remaining relations. If you specify an empty list the default direction is BOTH.
414      * @return a <code>Vector</code> containing all matching nodes
415      */

416     public Vector searchMultiLevelVector(List snodes, List fields, String JavaDoc pdistinct, List tables, String JavaDoc where, List sortFields,
417         List directions, List searchDirs) {
418         // Try to handle using the SearchQuery framework.
419
try {
420             SearchQuery query = getMultiLevelSearchQuery(snodes, fields, pdistinct, tables, where, sortFields, directions, searchDirs);
421             List clusterNodes = getClusterNodes(query);
422             return new Vector(clusterNodes);
423         } catch (Exception JavaDoc e) {
424             log.error(e + Logging.stackTrace(e));
425             return null;
426         }
427     }
428
429     /**
430      * Executes query, returns results as {@link ClusterNode clusternodes} or MMObjectNodes if the
431      * query is a Node-query.
432      *
433      * @param query The query.
434      * @return The clusternodes.
435      * @throws org.mmbase.storage.search.SearchQueryException
436      * When an exception occurred while retrieving the results.
437      * @since MMBase-1.7
438      * @see org.mmbase.storage.search.SearchQueryHandler#getNodes
439      */

440     public List getClusterNodes(SearchQuery query) throws SearchQueryException {
441
442         // TODO (later): implement maximum set by maxNodesFromQuery?
443
// Execute query, return results.
444

445         return mmb.getSearchQueryHandler().getNodes(query, this);
446
447     }
448
449     /**
450      * Returns the name part of a tablename.
451      * The name part is the table name minus the numeric digit appended
452      * to a name (if appliable).
453      * @param table name of the original table
454      * @return A <code>String</code> containing the table name
455      */

456     private String JavaDoc getTableName(String JavaDoc table) {
457         int end = table.length() ;
458         if (end == 0) throw new IllegalArgumentException JavaDoc("Table name too short '" + table + "'");
459         while (Character.isDigit(table.charAt(end -1))) --end;
460         return table.substring(0, end );
461     }
462
463     /**
464      * Returns the name part of a tablename, and convert it to a buidler name.
465      * This will catch specifying a rolename in stead of a builder name when using relations.
466      * @param table name of the original table
467      * @return A <code>String</code> containing the table name
468      */

469     private String JavaDoc getTrueTableName(String JavaDoc table) {
470         String JavaDoc tab = getTableName(table);
471         int rnumber = mmb.getRelDef().getNumberByName(tab);
472         if (rnumber != -1) {
473             return mmb.getRelDef().getBuilderName(new Integer JavaDoc(rnumber));
474         } else {
475             return tab;
476         }
477     }
478
479     /**
480      * Get text from a blob field.
481      * The text is cut if it is to long.
482      * @param fieldname name of the field
483      * @param number number of the object in the table
484      * @return a <code>String</code> containing the contents of a field as text
485      */

486     public String JavaDoc getShortedText(String JavaDoc fieldname, int number) {
487         String JavaDoc buildername= getBuilderNameFromField(fieldname);
488         if (buildername.length() > 0) {
489             MMObjectBuilder bul= mmb.getMMObject(buildername);
490             return bul.getShortedText(getFieldNameFromField(fieldname), bul.getNode(number));
491         }
492         return null;
493     }
494
495     /**
496      * Get binary data of a database blob field.
497      * The data is cut if it is to long.
498      * @param fieldname name of the field
499      * @param number number of the object in the table
500      * @return an array of <code>byte</code> containing the contents of a field as text
501      */

502     public byte[] getShortedByte(String JavaDoc fieldname, int number) {
503         String JavaDoc buildername= getBuilderNameFromField(fieldname);
504         if (buildername.length() > 0) {
505             MMObjectBuilder bul= mmb.getMMObject(buildername);
506             return bul.getShortedByte(getFieldNameFromField(fieldname), bul.getNode(number));
507         }
508         return null;
509     }
510
511     /**
512      * Creates search query that selects all the objects that match the
513      * searchkeys.
514      * The constraint must be in one of the formats specified by {@link
515      * org.mmbase.util.QueryConvertor#setConstraint(BasicSearchQuery,String)
516      * QueryConvertor#setConstraint()}.
517      *
518      * @param snodes <code>null</code> or a list of numbers
519      * of nodes to start the search with.
520      * These have to be present in the first table listed in the
521      * tables parameter.
522      * @param fields List of fieldnames to return.
523      * These should be formatted as <em>stepalias.field</em>,
524      * e.g. 'people.lastname'
525      * @param pdistinct 'YES' if the records returned need to be
526      * distinct (ignoring case).
527      * Any other value indicates double values can be returned.
528      * @param tables The builder chain, a list containing builder names.
529      * The search is formed by following the relations between
530      * successive builders in the list.
531      * It is possible to explicitly supply a relation builder by
532      * placing the name of the builder between two builders to search.
533      * Example: company,people or typedef,authrel,people.
534      * @param where The constraint, must be in one of the formats specified by {@link
535      * org.mmbase.util.QueryConvertor#setConstraint(BasicSearchQuery,String)
536      * QueryConvertor#setConstraint()}.
537      * E.g. "WHERE news.title LIKE '%MMBase%' AND news.title > 100"
538      * @param sortFields <code>null</code> or a list of fieldnames on which you want to sort.
539      * @param directions <code>null</code> or a list of values containing, for each field in the
540      * <code>sortFields</code> parameter, a value indicating whether the sort is
541      * ascending (<code>UP</code>) or descending (<code>DOWN</code>).
542      * If less values are supplied then there are fields in order,
543      * the first value in the list is used for the remaining fields.
544      * Default value is <code>'UP'</code>.
545      * @param searchDir Specifies in which direction relations are to be
546      * followed, this must be one of the values defined by this class.
547      * @deprecated use {@link #getMultiLevelSearchQuery(List snodes, List fields, String pdistinct, List tables, String where,
548      * List orderVec, List directions, int searchDir)}
549      * @return the resulting search query.
550      * @since MMBase-1.7
551      */

552     public BasicSearchQuery getMultiLevelSearchQuery(List snodes, List fields, String JavaDoc pdistinct, List tables, String JavaDoc where,
553             List sortFields, List directions, int searchDir) {
554         List searchDirs = new ArrayList();
555         searchDirs.add(new Integer JavaDoc(searchDir));
556         return getMultiLevelSearchQuery(snodes, fields, pdistinct, tables, where, sortFields, directions, searchDirs);
557     }
558
559     /**
560      * Creates search query that selects all the objects that match the
561      * searchkeys.
562      * The constraint must be in one of the formats specified by {@link
563      * org.mmbase.util.QueryConvertor#setConstraint(BasicSearchQuery,String)
564      * QueryConvertor#setConstraint()}.
565      *
566      * @param snodes <code>null</code> or a list of numbers
567      * of nodes to start the search with.
568      * These have to be present in the first table listed in the
569      * tables parameter.
570      * @param fields List of fieldnames to return.
571      * These should be formatted as <em>stepalias.field</em>,
572      * e.g. 'people.lastname'
573      * @param pdistinct 'YES' if the records returned need to be
574      * distinct (ignoring case).
575      * Any other value indicates double values can be returned.
576      * @param tables The builder chain, a list containing builder names.
577      * The search is formed by following the relations between
578      * successive builders in the list.
579      * It is possible to explicitly supply a relation builder by
580      * placing the name of the builder between two builders to search.
581      * Example: company,people or typedef,authrel,people.
582      * @param where The constraint, must be in one of the formats specified by {@link
583      * org.mmbase.util.QueryConvertor#setConstraint(BasicSearchQuery,String)
584      * QueryConvertor#setConstraint()}.
585      * E.g. "WHERE news.title LIKE '%MMBase%' AND news.title > 100"
586      * @param sortFields <code>null</code> or a list of fieldnames on which you want to sort.
587      * @param directions <code>null</code> or a list of values containing, for each field in the
588      * <code>sortFields</code> parameter, a value indicating whether the sort is
589      * ascending (<code>UP</code>) or descending (<code>DOWN</code>).
590      * If less values are supplied then there are fields in order,
591      * the first value in the list is used for the remaining fields.
592      * Default value is <code>'UP'</code>.
593      * @param searchDirs Specifies in which direction relations are to be
594      * followed, this must be one of the values defined by this class.
595      * @return the resulting search query.
596      * @since MMBase-1.7
597      */

598     public BasicSearchQuery getMultiLevelSearchQuery(List snodes, List fields, String JavaDoc pdistinct, List tables, String JavaDoc where,
599             List sortFields, List directions, List searchDirs) {
600
601         // Create the query.
602
BasicSearchQuery query= new BasicSearchQuery();
603
604         // Set the distinct property.
605
boolean distinct= pdistinct != null && pdistinct.equalsIgnoreCase("YES");
606         query.setDistinct(distinct);
607
608         // Get ALL tables (including missing reltables)
609
Map roles= new HashMap();
610         Map fieldsByAlias= new HashMap();
611         Map stepsByAlias= addSteps(query, tables, roles, !distinct, fieldsByAlias);
612
613         // Add fields.
614
Iterator iFields= fields.iterator();
615         while (iFields.hasNext()) {
616             String JavaDoc field = (String JavaDoc) iFields.next();
617             addFields(query, field, stepsByAlias, fieldsByAlias);
618         }
619
620         // Add sortorders.
621
addSortOrders(query, sortFields, directions, fieldsByAlias);
622
623         // Supporting more then 1 source node or no source node at all
624
// Note that node number -1 is seen as no source node
625
if (snodes != null && snodes.size() > 0) {
626             Integer JavaDoc nodeNumber= new Integer JavaDoc(-1);
627
628             // Copy list, so the original list is not affected.
629
snodes= new ArrayList(snodes);
630
631             // Go trough the whole list of strings (each representing
632
// either a nodenumber or an alias), convert all to Integer objects.
633
// from last to first,,... since we want snode to be the one that
634
// contains the first..
635
for (int i= snodes.size() - 1; i >= 0; i--) {
636                 String JavaDoc str= (String JavaDoc)snodes.get(i);
637                 try {
638                     nodeNumber= new Integer JavaDoc(str);
639                 } catch (NumberFormatException JavaDoc e) {
640                     // maybe it was not an integer, hmm lets look in OAlias
641
// table then
642
nodeNumber= new Integer JavaDoc(mmb.getOAlias().getNumber(str));
643                     if (nodeNumber.intValue() < 0) {
644                         nodeNumber= new Integer JavaDoc(0);
645                     }
646                 }
647                 snodes.set(i, nodeNumber);
648             }
649
650             BasicStep nodesStep= getNodesStep(query.getSteps(), nodeNumber.intValue());
651
652             if (nodesStep == null) {
653                 // specified a node which is not of the type of one of the steps.
654
// take as default the 'first' step (which will make the result empty, compatible with 1.6, bug #6440).
655
nodesStep = (BasicStep) query.getSteps().get(0);
656             }
657
658             Iterator iNodeNumbers= snodes.iterator();
659             while (iNodeNumbers.hasNext()) {
660                 Integer JavaDoc number= (Integer JavaDoc)iNodeNumbers.next();
661                 nodesStep.addNode(number.intValue());
662             }
663         }
664
665         addRelationDirections(query, searchDirs, roles);
666
667         // Add constraints.
668
// QueryConverter supports the old formats for backward compatibility.
669
QueryConvertor.setConstraint(query, where);
670
671         return query;
672     }
673
674     /**
675      * Creates a full chain of steps, adds these to the specified query.
676      * This includes adding necessary relation tables when not explicitly
677      * specified, and generating unique table aliases where necessary.
678      * Optionally adds "number"-fields for all tables in the original chain.
679      *
680      * @param query The searchquery.
681      * @param tables The original chain of tables.
682      * @param roles Map of tablenames mapped to <code>Integer</code> values,
683      * representing the nodenumber of a corresponing RelDef node.
684      * This method adds entries for table aliases that specify a role,
685      * e.g. "related" or "related2".
686      * @param includeAllReference Indicates if the "number"-fields must
687      * included in the query for all tables in the original chain.
688      * @param fieldsByAlias Map, mapping aliases (fieldname prefixed by table
689      * alias) to the stepfields in the query. An entry is added for
690      * each stepfield added to the query.
691      * @return Map, maps original table names to steps.
692      * @since MMBase-1.7
693      */

694     // package access!
695
Map addSteps(BasicSearchQuery query, List tables, Map roles, boolean includeAllReference, Map fieldsByAlias) {
696
697         Map stepsByAlias= new HashMap(); // Maps original table names to steps.
698
Set tableAliases= new HashSet(); // All table aliases that are in use.
699

700         Iterator iTables= tables.iterator();
701         if (iTables.hasNext()) {
702             // First table.
703
String JavaDoc tableName= (String JavaDoc)iTables.next();
704             MMObjectBuilder bul= getBuilder(tableName, roles);
705             String JavaDoc tableAlias= getUniqueTableAlias(tableName, tableAliases, tables);
706             BasicStep step= query.addStep(bul);
707             step.setAlias(tableAlias);
708             stepsByAlias.put(tableName, step);
709             if (includeAllReference) {
710                 // Add number field.
711
addField(query, step, "number", fieldsByAlias);
712             }
713         }
714         while (iTables.hasNext()) {
715             String JavaDoc tableName2 = (String JavaDoc)iTables.next();
716             MMObjectBuilder bul2 = getBuilder(tableName2, roles);
717             BasicRelationStep relation;
718             BasicStep step2;
719             String JavaDoc tableName;
720             if (bul2 instanceof InsRel) {
721                 // Explicit relation step.
722
tableName = tableName2;
723                 InsRel bul = (InsRel)bul2;
724                 tableName2 = (String JavaDoc)iTables.next();
725                 bul2 = getBuilder(tableName2, roles);
726                 relation = query.addRelationStep(bul, bul2);
727                 step2 = (BasicStep)relation.getNext();
728
729                 // MM: setting aliases used to be _inside_ the includeAllReference-if.
730
// but I don't see how that would make sense. Trying a while like this.
731
relation.setAlias(tableName);
732                 step2.setAlias(tableName2);
733                 if (includeAllReference) {
734                     // Add number fields.
735
addField(query, relation, "number", fieldsByAlias);
736                     addField(query, step2, "number", fieldsByAlias);
737                 }
738                 if (log.isDebugEnabled()) {
739                     log.debug("Created a relation step " + relation + " (explicit)" + roles);
740                 }
741             } else {
742                 // Not a relation, relation step is implicit.
743
tableName = "insrel";
744                 InsRel bul = mmb.getInsRel();
745                 relation = query.addRelationStep(bul, bul2);
746                 step2 = (BasicStep)relation.getNext();
747                 step2.setAlias(tableName2); //see above
748
if (includeAllReference) {
749                     // Add number field.
750
addField(query, step2, "number", fieldsByAlias);
751                 }
752                 if (log.isDebugEnabled()) {
753                     log.debug("Created a relation step " + relation + " (implicit)");
754                 }
755             }
756             String JavaDoc tableAlias = getUniqueTableAlias(tableName, tableAliases, tables);
757             String JavaDoc tableAlias2 = getUniqueTableAlias(tableName2, tableAliases, tables);
758             if (! tableName.equals(tableAlias)) {
759                 roles.put(tableAlias, roles.get(tableName));
760             }
761             relation.setAlias(tableAlias);
762             step2.setAlias(tableAlias2);
763             stepsByAlias.put(tableAlias, relation);
764             stepsByAlias.put(tableAlias2, step2);
765         }
766         return stepsByAlias;
767     }
768
769     /**
770      * Gets builder corresponding to the specified table alias.
771      * This amounts to removing the optionally appended digit from the table
772      * alias, and interpreting the result as either a tablename or a relation
773      * role.
774      *
775      * @param tableAlias The table alias.
776      * Must be tablename or relation role, optionally appended
777      * with a digit, e.g. images, images3, related and related4.
778      * @param roles Map of tablenames mapped to <code>Integer</code> values,
779      * representing the nodenumber of a corresponing RelDef node.
780      * This method adds entries for table aliases that specify a role,
781      * e.g. "related" or "related2".
782      * @return The builder.
783      * @since MMBase-1.7
784      */

785     // package access!
786
MMObjectBuilder getBuilder(String JavaDoc tableAlias, Map roles) {
787         String JavaDoc tableName= getTableName(tableAlias);
788         // check builder - should throw exception if builder doesn't exist ?
789
MMObjectBuilder bul= null;
790         try {
791             bul= mmb.getBuilder(tableName);
792         } catch (BuilderConfigurationException e) {}
793
794         if (bul == null) {
795             // check if it is a role name. if so, use the builder of the
796
// rolename and store a filter on rnumber.
797
int rnumber= mmb.getRelDef().getNumberByName(tableName);
798             if (rnumber == -1) {
799                 String JavaDoc msg= "Specified builder " + tableName + " does not exist.";
800                 log.error(msg);
801                 throw new IllegalArgumentException JavaDoc(msg);
802             } else {
803                 bul = mmb.getRelDef().getBuilder(rnumber); // relation builder
804
roles.put(tableAlias, new Integer JavaDoc(rnumber));
805             }
806         } else if (bul instanceof InsRel) {
807             int rnumber= mmb.getRelDef().getNumberByName(tableName);
808             if (rnumber != -1) {
809                 roles.put(tableAlias, new Integer JavaDoc(rnumber));
810             }
811         }
812         if (log.isDebugEnabled()) {
813             log.debug("Resolved table alias \"" + tableAlias + "\" to builder \"" + bul.getTableName() + "\"");
814         }
815         return bul;
816     }
817
818     /**
819      * Returns unique table alias, must be tablename/rolename, optionally
820      * appended with a digit.
821      * Tests the provided table alias for uniqueness, generates alternative
822      * table alias if the provided alias is already in use.
823      *
824      * @param tableAlias The table alias.
825      * @param tableAliases The table aliases that are already in use. The
826      * resulting table alias is added to this collection.
827      * @param originalAliases The originally supplied aliases - generated
828      * aliases should not match any of these.
829      * @return The resulting table alias.
830      * @since MMBase-1.7
831      */

832     // package access!
833
String JavaDoc getUniqueTableAlias(String JavaDoc tableAlias, Set tableAliases, Collection originalAliases) {
834
835         // If provided alias is not unique, try alternatives,
836
// skipping alternatives that are already in originalAliases.
837
if (tableAliases.contains(tableAlias)) {
838             tableName= getTableName(tableAlias);
839
840             tableAlias= tableName;
841             char ch= '0';
842             while (originalAliases.contains(tableAlias) || tableAliases.contains(tableAlias)) {
843                 // Can't create more than 11 aliases for same tablename.
844
if (ch > '9') {
845                     throw new IndexOutOfBoundsException JavaDoc("Failed to create unique table alias, because there "
846                                                         + "are already 11 aliases for this tablename: \"" + tableName + "\"");
847                 }
848                 tableAlias = tableName + ch;
849                 ch++;
850             }
851         }
852
853         // Unique table alias: add to collection, return as result.
854
tableAliases.add(tableAlias);
855         return tableAlias;
856     }
857
858     /**
859      * Retrieves fieldnames from an expression, and adds these to a search
860      * query.
861      * The expression may be either a fieldname or a a functionname with a
862      * (commaseparated) parameterlist between parenthesis
863      * (parameters being expressions themselves).
864      * <p>
865      * Fieldnames must be formatted as <em>stepalias.field</em>.
866      *
867      * @param query The query.
868      * @param expression The expression.
869      * @param stepsByAlias Map, mapping step aliases to the steps in the query.
870      * @param fieldsByAlias Map, mapping field aliases (fieldname prefixed by
871      * table alias) to the stepfields in the query.
872      * An entry is added for each stepfield added to the query.
873      * @since MMBase-1.7
874      */

875     // package access!
876
void addFields(BasicSearchQuery query, String JavaDoc expression, Map stepsByAlias, Map fieldsByAlias) {
877
878         // TODO RvM: stripping functions is this (still) necessary?.
879
// Strip function(s).
880
int pos1= expression.indexOf('(');
881         int pos2= expression.indexOf(')');
882         if (pos1 != -1 ^ pos2 != -1) {
883             // Parenthesis do not match.
884
throw new IllegalArgumentException JavaDoc("Parenthesis do not match in expression: \"" + expression + "\"");
885         } else if (pos1 != -1) {
886             // Function parameter list containing subexpression(s).
887
String JavaDoc parameters= expression.substring(pos1 + 1, pos2);
888             Iterator iParameters= getFunctionParameters(parameters).iterator();
889             while (iParameters.hasNext()) {
890                 String JavaDoc parameter= (String JavaDoc)iParameters.next();
891                 addFields(query, parameter, stepsByAlias, fieldsByAlias);
892             }
893         } else if (!Character.isDigit(expression.charAt(0))) {
894             int pos= expression.indexOf('.');
895             if (pos < 1 || pos == (expression.length() - 1)) {
896                 throw new IllegalArgumentException JavaDoc("Invalid fieldname: \"" + expression + "\"");
897             }
898             int bracketOffset = (expression.startsWith("[") && expression.endsWith("]")) ? 1 : 0;
899             String JavaDoc stepAlias= expression.substring(0 + bracketOffset, pos);
900             String JavaDoc fieldName= expression.substring(pos + 1 - bracketOffset);
901
902             BasicStep step = (BasicStep)stepsByAlias.get(stepAlias);
903             if (step == null) {
904                 throw new IllegalArgumentException JavaDoc("Invalid step alias: \"" + stepAlias + "\" in fields list");
905             }
906             addField(query, step, fieldName, fieldsByAlias);
907         }
908     }
909
910     /**
911      * Adds field to a search query, unless it is already added.
912      *
913      * @param query The query.
914      * @param step The non-null step corresponding to the field.
915      * @param fieldName The fieldname.
916      * @param fieldsByAlias Map, mapping field aliases (fieldname prefixed by
917      * table alias) to the stepfields in the query.
918      * An entry is added for each stepfield added to the query.
919      * @since MMBase-1.7
920      */

921     private void addField(BasicSearchQuery query, BasicStep step, String JavaDoc fieldName, Map fieldsByAlias) {
922
923         // Fieldalias = stepalias.fieldname.
924
// This value is used to store the field in fieldsByAlias.
925
// The actual alias of the field is not set.
926
String JavaDoc fieldAlias= step.getAlias() + "." + fieldName;
927         if (fieldsByAlias.containsKey(fieldAlias)) {
928             // Added already.
929
return;
930         }
931
932         MMObjectBuilder builder= mmb.getBuilder(step.getTableName());
933         CoreField fieldDefs= builder.getField(fieldName);
934         if (fieldDefs == null) {
935             throw new IllegalArgumentException JavaDoc("Not a known field of builder " + step.getTableName() + ": \"" + fieldName + "\"");
936         }
937
938         // Add the stepfield.
939
BasicStepField stepField= query.addField(step, fieldDefs);
940         fieldsByAlias.put(fieldAlias, stepField);
941     }
942
943     /**
944      * Adds sorting orders to a search query.
945      *
946      * @param query The query.
947      * @param fieldNames The fieldnames prefixed by the table aliases.
948      * @param directions The corresponding sorting directions ("UP"/"DOWN").
949      * @param fieldsByAlias Map, mapping field aliases (fieldname prefixed by
950      * table alias) to the stepfields in the query.
951      * @since MMBase-1.7
952      */

953     // package visibility!
954
void addSortOrders(BasicSearchQuery query, List fieldNames, List directions, Map fieldsByAlias) {
955
956         // Test if fieldnames are specified.
957
if (fieldNames == null || fieldNames.size() == 0) {
958             return;
959         }
960
961         int defaultSortOrder= SortOrder.ORDER_ASCENDING;
962         if (directions != null && directions.size() != 0) {
963             if (((String JavaDoc)directions.get(0)).trim().equalsIgnoreCase("DOWN")) {
964                 defaultSortOrder= SortOrder.ORDER_DESCENDING;
965             }
966         }
967
968         Iterator iFieldNames= fieldNames.iterator();
969         Iterator iDirections= directions.iterator();
970         while (iFieldNames.hasNext()) {
971             String JavaDoc fieldName= (String JavaDoc)iFieldNames.next();
972             StepField field= (BasicStepField)fieldsByAlias.get(fieldName);
973             if (field == null) {
974                 // Field has not been added.
975
field= ConstraintParser.getField(fieldName, query.getSteps());
976             }
977             if (field == null) {
978                 throw new IllegalArgumentException JavaDoc("Invalid fieldname: \"" + fieldName + "\"");
979             }
980
981             // Add sort order.
982
BasicSortOrder sortOrder= query.addSortOrder(field); // ascending
983

984             // Change direction if needed.
985
if (iDirections.hasNext()) {
986                 String JavaDoc direction= (String JavaDoc)iDirections.next();
987                 if (direction.trim().equalsIgnoreCase("DOWN")) {
988                     sortOrder.setDirection(SortOrder.ORDER_DESCENDING);
989                 } else if (!direction.trim().equalsIgnoreCase("UP")) {
990                     throw new IllegalArgumentException JavaDoc("Parameter directions contains an invalid value ("+direction+"), should be UP or DOWN.");
991                 }
992
993             } else {
994                 sortOrder.setDirection(defaultSortOrder);
995             }
996         }
997     }
998
999     /**
1000     * Gets first step from list, that corresponds to the builder
1001     * of a specified node - or one of its parentbuilders.
1002     *
1003     * @param steps The steps.
1004     * @param nodeNumber The number identifying the node.
1005     * @return The step, or <code>null</code> when not found.
1006     * @since MMBase-1.7
1007     */

1008    // package visibility!
1009
BasicStep getNodesStep(List steps, int nodeNumber) {
1010        if (nodeNumber < 0) {
1011            return null;
1012        }
1013
1014        MMObjectNode node= getNode(nodeNumber);
1015        if (node == null) {
1016            return null;
1017        }
1018
1019        MMObjectBuilder builder = node.parent;
1020        BasicStep result = null;
1021        do {
1022            // Find step corresponding to builder.
1023
Iterator iSteps= steps.iterator();
1024            while (iSteps.hasNext() && result == null) {
1025                BasicStep step= (BasicStep)iSteps.next();
1026                if (step.getTableName().equals(builder.tableName)) { // should inheritance not be considered?
1027
// Found.
1028
result = step;
1029                }
1030            }
1031            // Not found, then try again with parentbuilder.
1032
builder = builder.getParentBuilder();
1033        } while (builder != null && result == null);
1034
1035        /*
1036          if (result == null) {
1037          throw new RuntimeException("Node '" + nodeNumber + "' not of one of the types " + steps);
1038          }
1039        */

1040
1041        return result;
1042    }
1043
1044
1045    /**
1046     * Adds relation directions.
1047     *
1048     * @param query The search query.
1049     * @param searchDirs Specifies in which direction relations are to be followed. You can specify a direction for each
1050     * relation in the path. If you specify less directions than there are relations, the last specified direction is used
1051     * for the remaining relations. If you specify an empty list the default direction is BOTH.
1052     * @param roles Map of tablenames mapped to <code>Integer</code> values,
1053     * representing the nodenumber of the corresponing RelDef node.
1054     * @since MMBase-1.7
1055     */

1056    // package visibility!
1057
void addRelationDirections(BasicSearchQuery query, List searchDirs, Map roles) {
1058
1059        Iterator iSteps = query.getSteps().iterator();
1060        Iterator iSearchDirs = searchDirs.iterator();
1061        int searchDir = RelationStep.DIRECTIONS_BOTH;
1062
1063        if (! iSteps.hasNext()) return; // nothing to be done.
1064
BasicStep sourceStep = (BasicStep)iSteps.next();
1065        BasicStep destinationStep = null;
1066
1067        while (iSteps.hasNext()) {
1068            if (destinationStep != null) {
1069                sourceStep = destinationStep;
1070            }
1071            BasicRelationStep relationStep= (BasicRelationStep)iSteps.next();
1072            destinationStep= (BasicStep)iSteps.next();
1073            if (iSearchDirs.hasNext()) searchDir = ((Integer JavaDoc)iSearchDirs.next()).intValue();
1074
1075            // Determine typedef number of the source-type.
1076
int sourceType = sourceStep.getBuilder().getObjectType();
1077
1078            // Determine reldef number of the role.
1079
Integer JavaDoc role = (Integer JavaDoc) roles.get(relationStep.getAlias());
1080
1081            // Determine the typedef number of the destination-type.
1082
int destinationType = destinationStep.getBuilder().getObjectType();
1083
1084            int roleInt;
1085            if (role != null) {
1086                roleInt = role.intValue();
1087                relationStep.setRole(role);
1088            } else {
1089                roleInt = -1;
1090            }
1091
1092            if (!mmb.getTypeRel().optimizeRelationStep(relationStep, sourceType, destinationType, roleInt, searchDir)) {
1093                if (searchDir != RelationStep.DIRECTIONS_SOURCE && searchDir != RelationStep.DIRECTIONS_DESTINATION) {
1094                    log.warn("No relation defined between " + sourceStep.getTableName() + " and " + destinationStep.getTableName() + " using " + relationStep + " with direction(s) " + getSearchDirString(searchDir) + ". Searching in 'destination' direction now, but perhaps the query should be fixed, because this should always result nothing.");
1095                } else {
1096                    log.warn("No relation defined between " + sourceStep.getTableName() + " and " + destinationStep.getTableName() + " using " + relationStep + " with direction(s) " + getSearchDirString(searchDir) + ". Trying anyway, but perhaps the query should be fixed, because this should always result nothing.");
1097                }
1098                log.warn(Logging.applicationStacktrace());
1099            }
1100
1101        }
1102    }
1103
1104}
1105
Popular Tags