001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.storage.search.implementation;
011:
012: import java.util.*;
013: import org.mmbase.bridge.Field;
014: import org.mmbase.bridge.NodeManager;
015: import org.mmbase.module.core.*;
016: import org.mmbase.module.corebuilders.*;
017: import org.mmbase.cache.CachePolicy;
018: import org.mmbase.core.CoreField;
019: import org.mmbase.storage.search.*;
020: import org.mmbase.util.logging.*;
021:
022: /**
023: * Basic implementation.
024: *
025: * @author Rob van Maris
026: * @version $Id: BasicSearchQuery.java,v 1.45 2008/02/22 12:28:19 michiel Exp $
027: * @since MMBase-1.7
028: */
029: public class BasicSearchQuery implements SearchQuery, Cloneable {
030: private static final Logger log = Logging
031: .getLoggerInstance(BasicSearchQuery.class);
032:
033: /**
034: * Distinct property.
035: */
036: private boolean distinct = false;
037:
038: /** MaxNumber property. */
039: private int maxNumber = SearchQuery.DEFAULT_MAX_NUMBER;
040:
041: /** Offset property. */
042: private int offset = SearchQuery.DEFAULT_OFFSET;
043:
044: private List<Step> steps = new ArrayList<Step>();
045: protected List<StepField> fields = new ArrayList<StepField>();
046: private List<SortOrder> sortOrders = new ArrayList<SortOrder>();
047:
048: /** Constraint.. */
049: private Constraint constraint = null;
050:
051: /** Aggragating property. */
052: private boolean aggregating = false;
053:
054: /** Two variables to speed up hashCode() by caching the result */
055: private boolean hasChangedHashcode = true;
056: private int savedHashcode = -1;
057:
058: /**
059: * Whether this Query is cacheable.
060: */
061: private CachePolicy cachePolicy = CachePolicy.ALWAYS;
062:
063: /**
064: * Constructor.
065: *
066: * @param aggregating True for an aggregating query, false otherwise.
067: */
068: public BasicSearchQuery(boolean aggregating) {
069: this .aggregating = aggregating;
070: hasChangedHashcode = true;
071: }
072:
073: /**
074: * Constructor, constructs non-aggragating query.
075: */
076: public BasicSearchQuery() {
077: this (false);
078: }
079:
080: public final static int COPY_NORMAL = 0;
081: public final static int COPY_AGGREGATING = 1;
082: public final static int COPY_WITHOUTFIELDS = 2;
083:
084: /**
085: * A deep copy, but sets also aggregating, and clear fields if aggregating is true then.
086: */
087:
088: public BasicSearchQuery(SearchQuery q, int copyMethod) {
089: distinct = q.isDistinct();
090: copySteps(q);
091: Constraint c = q.getConstraint();
092: if (c != null) {
093: setConstraint(copyConstraint(q, c));
094: }
095: switch (copyMethod) {
096: case COPY_NORMAL:
097: copyFields(q);
098: case COPY_WITHOUTFIELDS:
099: copySortOrders(q);
100: maxNumber = q.getMaxNumber();
101: offset = q.getOffset();
102: aggregating = false;
103: break;
104: case COPY_AGGREGATING:
105: aggregating = true;
106: break;
107: default:
108: log.debug("Unknown copy method " + copyMethod);
109: break;
110: }
111: hasChangedHashcode = true;
112: }
113:
114: /**
115: * A deep copy. Needed if you want to do multiple queries (and change the query between them).
116: * Used by bridge.Query#clone (so it will be decided that that is not needed, ths can be removed too)
117: * @see org.mmbase.bridge.Query#clone
118: */
119: public BasicSearchQuery(SearchQuery q) {
120: this (q, COPY_NORMAL);
121: }
122:
123: public Object clone() {
124: try {
125: BasicSearchQuery clone = (BasicSearchQuery) super .clone();
126: clone.copySteps(this );
127: clone.copyFields(this );
128: clone.copySortOrders(this );
129: Constraint c = getConstraint();
130: if (c != null) {
131: clone.setConstraint(copyConstraint(this , c));
132: }
133: return clone;
134: } catch (CloneNotSupportedException e) {
135: // cannot happen
136: throw new InternalError(e.toString());
137: }
138: }
139:
140: protected void copySteps(SearchQuery q) {
141: MMBase mmb = MMBase.getMMBase();
142: steps = new ArrayList<Step>();
143: Iterator<Step> i = q.getSteps().iterator();
144: while (i.hasNext()) {
145: Step step = i.next();
146: if (step instanceof RelationStep) {
147: RelationStep relationStep = (RelationStep) step;
148: MMObjectBuilder dest = mmb.getBuilder(relationStep
149: .getNext().getTableName());
150: InsRel insrel = (InsRel) mmb.getBuilder(relationStep
151: .getTableName());
152: BasicRelationStep newRelationStep = addRelationStep(
153: insrel, dest);
154: newRelationStep.setDirectionality(relationStep
155: .getDirectionality());
156: newRelationStep.setCheckedDirectionality(relationStep
157: .getCheckedDirectionality());
158: newRelationStep.setRole(relationStep.getRole());
159: newRelationStep.setAlias(relationStep.getAlias());
160: Iterator<Integer> j = relationStep.getNodes()
161: .iterator();
162: while (j.hasNext()) {
163: newRelationStep.addNode(j.next().intValue());
164: }
165: BasicStep next = (BasicStep) relationStep.getNext();
166: BasicStep newNext = (BasicStep) newRelationStep
167: .getNext();
168: newNext.setAlias(next.getAlias());
169: j = next.getNodes().iterator();
170: while (j.hasNext()) {
171: newNext.addNode(j.next().intValue());
172: }
173: i.next(); // dealt with that already
174:
175: } else {
176: BasicStep newStep = addStep(mmb.getBuilder(step
177: .getTableName()));
178: newStep.setAlias(step.getAlias());
179: Iterator<Integer> j = step.getNodes().iterator();
180: while (j.hasNext()) {
181: newStep.addNode(j.next().intValue());
182: }
183: }
184: }
185: //log.info("copied steps " + q.getSteps() + " became " + steps);
186: hasChangedHashcode = true;
187: }
188:
189: protected void copyFields(SearchQuery q) {
190: fields = new ArrayList<StepField>();
191: MMBase mmb = MMBase.getMMBase();
192: for (StepField field : q.getFields()) {
193: Step step = field.getStep();
194: MMObjectBuilder bul = mmb.getBuilder(step.getTableName());
195: int j = q.getSteps().indexOf(step);
196: if (j == -1) {
197: throw new RuntimeException("Step " + step
198: + " could not be found in " + q.getSteps());
199: }
200: Step newStep = steps.get(j);
201: BasicStepField newField = addField(newStep, bul
202: .getField(field.getFieldName()));
203: newField.setAlias(field.getAlias());
204: }
205: hasChangedHashcode = true;
206: //log.info("copied fields " + q.getFields() + " became " + fields);
207: }
208:
209: protected void copySortOrders(SearchQuery q) {
210: sortOrders = new ArrayList<SortOrder>();
211: MMBase mmb = MMBase.getMMBase();
212: for (SortOrder sortOrder : q.getSortOrders()) {
213: StepField field = sortOrder.getField();
214: int j = q.getFields().indexOf(field);
215: StepField newField;
216: if (j == -1 || j >= fields.size()) { // not sorting on field of field list.
217: Step step = field.getStep();
218: MMObjectBuilder bul = mmb.getBuilder(step
219: .getTableName());
220: newField = new BasicStepField(field.getStep(), bul
221: .getField(field.getFieldName()));
222: } else {
223: newField = fields.get(j);
224: }
225: BasicSortOrder newSortOrder = addSortOrder(newField);
226: newSortOrder.setDirection(sortOrder.getDirection());
227: }
228: hasChangedHashcode = true;
229: }
230:
231: /**
232: * Creates a new StepField like f for query q.
233: */
234: protected static StepField createNewStepField(SearchQuery q,
235: StepField f) {
236: Step fstep = f.getStep();
237: // find existing step.
238: List<Step> steps = q.getSteps();
239: Step step = steps.get(steps.indexOf(fstep));
240: MMObjectBuilder bul = MMBase.getMMBase().getBuilder(
241: step.getTableName());
242: CoreField field = bul.getField(f.getFieldName());
243: if (field == null) {
244: throw new IllegalStateException("Did not find field "
245: + f.getFieldName() + " in builder "
246: + step.getTableName() + " " + bul.getFields());
247: }
248: return new BasicStepField(step, field);
249: }
250:
251: /**
252: * Used by copy-constructor. Constraints have to be done recursively.
253: */
254: protected static Constraint copyConstraint(SearchQuery q,
255: Constraint c) {
256: if (c instanceof CompositeConstraint) {
257: CompositeConstraint constraint = (CompositeConstraint) c;
258: BasicCompositeConstraint newConstraint = new BasicCompositeConstraint(
259: constraint.getLogicalOperator());
260: for (Constraint cons : constraint.getChilds()) {
261: newConstraint.addChild(copyConstraint(q, cons));
262: }
263: newConstraint.setInverse(constraint.isInverse());
264: return newConstraint;
265: } else if (c instanceof CompareFieldsConstraint) {
266: CompareFieldsConstraint constraint = (CompareFieldsConstraint) c;
267: BasicCompareFieldsConstraint newConstraint = new BasicCompareFieldsConstraint(
268: createNewStepField(q, constraint.getField()),
269: createNewStepField(q, constraint.getField2()));
270: newConstraint.setOperator(constraint.getOperator());
271: newConstraint.setInverse(constraint.isInverse());
272: newConstraint
273: .setCaseSensitive(constraint.isCaseSensitive());
274: return newConstraint;
275: } else if (c instanceof FieldValueDateConstraint) {
276: FieldValueDateConstraint constraint = (FieldValueDateConstraint) c;
277: Object value = constraint.getValue();
278: BasicFieldValueDateConstraint newConstraint = new BasicFieldValueDateConstraint(
279: createNewStepField(q, constraint.getField()),
280: value, constraint.getPart());
281: newConstraint.setOperator(constraint.getOperator());
282: newConstraint.setInverse(constraint.isInverse());
283: newConstraint
284: .setCaseSensitive(constraint.isCaseSensitive());
285: return newConstraint;
286: } else if (c instanceof FieldValueConstraint) {
287: FieldValueConstraint constraint = (FieldValueConstraint) c;
288: Object value = constraint.getValue();
289: BasicFieldValueConstraint newConstraint = new BasicFieldValueConstraint(
290: createNewStepField(q, constraint.getField()), value);
291: newConstraint.setOperator(constraint.getOperator());
292: newConstraint.setInverse(constraint.isInverse());
293: newConstraint
294: .setCaseSensitive(constraint.isCaseSensitive());
295: return newConstraint;
296: } else if (c instanceof FieldNullConstraint) {
297: FieldNullConstraint constraint = (FieldNullConstraint) c;
298: BasicFieldNullConstraint newConstraint = new BasicFieldNullConstraint(
299: createNewStepField(q, constraint.getField()));
300: newConstraint.setInverse(constraint.isInverse());
301: newConstraint
302: .setCaseSensitive(constraint.isCaseSensitive());
303: return newConstraint;
304: } else if (c instanceof FieldValueBetweenConstraint) {
305: FieldValueBetweenConstraint constraint = (FieldValueBetweenConstraint) c;
306: BasicFieldValueBetweenConstraint newConstraint;
307: try {
308: newConstraint = new BasicFieldValueBetweenConstraint(
309: createNewStepField(q, constraint.getField()),
310: constraint.getLowerLimit(), constraint
311: .getUpperLimit());
312: } catch (NumberFormatException e) {
313: newConstraint = new BasicFieldValueBetweenConstraint(
314: createNewStepField(q, constraint.getField()),
315: constraint.getLowerLimit(), constraint
316: .getUpperLimit());
317: }
318: newConstraint.setInverse(constraint.isInverse());
319: newConstraint
320: .setCaseSensitive(constraint.isCaseSensitive());
321: return newConstraint;
322: } else if (c instanceof FieldValueInConstraint) {
323: FieldValueInConstraint constraint = (FieldValueInConstraint) c;
324: BasicFieldValueInConstraint newConstraint = new BasicFieldValueInConstraint(
325: createNewStepField(q, constraint.getField()));
326:
327: Iterator<Object> k = constraint.getValues().iterator();
328: while (k.hasNext()) {
329: Object value = k.next();
330: newConstraint.addValue(value);
331: }
332: newConstraint.setInverse(constraint.isInverse());
333: newConstraint
334: .setCaseSensitive(constraint.isCaseSensitive());
335: return newConstraint;
336: } else if (c instanceof LegacyConstraint) {
337: LegacyConstraint constraint = (LegacyConstraint) c;
338: BasicLegacyConstraint newConstraint = new BasicLegacyConstraint(
339: constraint.getConstraint());
340: return newConstraint;
341: }
342: throw new RuntimeException("Could not copy constraint " + c);
343: }
344:
345: /**
346: * Sets distinct.
347: *
348: * @param distinct The distinct value.
349: * @return This <code>BasicSearchQuery</code> instance.
350: */
351: public BasicSearchQuery setDistinct(boolean distinct) {
352: this .distinct = distinct;
353: hasChangedHashcode = true;
354: return this ;
355: }
356:
357: /**
358: * Sets maxNumber.
359: *
360: * @param maxNumber The maxNumber value.
361: * @return This <code>BasicSearchQuery</code> instance.
362: * @throws IllegalArgumentException when an invalid argument is supplied.
363: */
364: public BasicSearchQuery setMaxNumber(int maxNumber) {
365: if (maxNumber < -1) {
366: throw new IllegalArgumentException(
367: "Invalid maxNumber value: " + maxNumber);
368: }
369: this .maxNumber = maxNumber;
370: hasChangedHashcode = true;
371: return this ;
372: }
373:
374: /**
375: * Sets offset.
376: *
377: * @param offset The offset value.
378: * @return This <code>BasicSearchQuery</code> instance.
379: * @throws IllegalArgumentException when an invalid argument is supplied.
380: */
381: public BasicSearchQuery setOffset(int offset) {
382: if (offset < 0) {
383: throw new IllegalArgumentException("Invalid offset value: "
384: + offset);
385: }
386: this .offset = offset;
387: hasChangedHashcode = true;
388: return this ;
389: }
390:
391: /**
392: * Adds new step to this SearchQuery.
393: *
394: * @param builder The builder associated with the step.
395: * @return The new step.
396: * @throws IllegalArgumentException when an invalid argument is supplied.
397: */
398: public BasicStep addStep(MMObjectBuilder builder) {
399: BasicStep step = new BasicStep(builder);
400: steps.add(step);
401: hasChangedHashcode = true;
402: return step;
403: }
404:
405: /**
406: * Adds new relationstep to this SearchQuery.
407: * This adds the next step as well, it can be retrieved by calling <code>
408: * {@link org.mmbase.storage.search.RelationStep#getNext getNext()}
409: * </code> on the relationstep, and cast to {@link BasicStep BasicStep}.
410: *
411: * @param builder The builder associated with the relation step.
412: * @param nextBuilder The builder associated with the next step.
413: * @return The new relationstep.
414: * @throws IllegalArgumentException when an invalid argument is supplied.
415: * @throws IllegalStateException when there is no previous step.
416: */
417: public BasicRelationStep addRelationStep(InsRel builder,
418: MMObjectBuilder nextBuilder) {
419: int nrOfSteps = steps.size();
420: if (nrOfSteps == 0) {
421: throw new IllegalStateException("No previous step.");
422: }
423: BasicStep previous = (BasicStep) steps.get(nrOfSteps - 1);
424: BasicStep next = new BasicStep(nextBuilder);
425: BasicRelationStep relationStep = new BasicRelationStep(builder,
426: previous, next);
427: steps.add(relationStep);
428: steps.add(next);
429: hasChangedHashcode = true;
430: return relationStep;
431: }
432:
433: /**
434: * Adds new field to this SearchQuery.
435: *
436: * @param step The associated step.
437: * @param fieldDefs The associated fieldDefs.
438: * @return The new field.
439: * @throws IllegalArgumentException when an invalid argument is supplied.
440: * @throws UnsupportedOperationException when called
441: * on an aggregating query.
442: */
443: public BasicStepField addField(Step step, CoreField fieldDefs) {
444: if (aggregating) {
445: throw new UnsupportedOperationException(
446: "Adding non-aggregated field to aggregating query.");
447: }
448: BasicStepField field = new BasicStepField(step, fieldDefs);
449: assert !fields.contains(field) : "" + field
450: + " is already one of " + fields;
451: fields.add(field);
452: hasChangedHashcode = true;
453: return field;
454: }
455:
456: /**
457: * @since MMBase-1.8.2
458: */
459: public BasicStepField addFieldUnlessPresent(Step step,
460: CoreField fieldDefs) {
461: if (aggregating) {
462: throw new UnsupportedOperationException(
463: "Adding non-aggregated field to aggregating query.");
464: }
465: BasicStepField field = new BasicStepField(step, fieldDefs);
466: int i = fields.indexOf(field);
467: if (i == -1) {
468: fields.add(field);
469: hasChangedHashcode = true;
470: } else {
471: field = (BasicStepField) fields.get(i);
472: }
473: return field;
474: }
475:
476: // only sensible for NodeSearchQuery
477: protected void mapField(CoreField field, BasicStepField stepField) {
478:
479: }
480:
481: // MM
482: /**
483: * Add all fields of given step
484: */
485: public void addFields(Step step) {
486: MMBase mmb = MMBase.getMMBase();
487: MMObjectBuilder builder = mmb.getBuilder(step.getTableName());
488: // http://www.mmbase.org/jira/browse/MMB-1435,
489: // Using fields with "ORDER_CREATE" only returns fields actually in storage, and also in the
490: // right order, which is import for microsoft JDBC.
491: if (builder != null) {
492: for (CoreField field : builder
493: .getFields(NodeManager.ORDER_CREATE)) {
494: if (field.inStorage()) {
495: BasicStepField stepField = addField(step, field);
496: mapField(field, stepField);
497: }
498: }
499: } else {
500: // this can e.g. happen during shut-down of mmbase
501: if (mmb.getState()) {
502: throw new RuntimeException(
503: "Step is describing non-existing builder "
504: + step.getTableName());
505: } else {
506: log.debug("Step is describing non-existing builder "
507: + step.getTableName());
508: }
509: }
510: hasChangedHashcode = true;
511: }
512:
513: public void removeFields() {
514: fields.clear();
515: hasChangedHashcode = true;
516: }
517:
518: /**
519: * Adds new aggregated field to this SearchQuery.
520: *
521: * @param step The associated step.
522: * @param field The associated Field.
523: * @param aggregationType The aggregation type.
524: * @return The new field.
525: * @throws IllegalArgumentException when an invalid argument is supplied.
526: * @throws UnsupportedOperationException when called
527: * on an non-aggregating query.
528: */
529: public BasicAggregatedField addAggregatedField(Step step,
530: CoreField field, int aggregationType) {
531: if (!aggregating) {
532: throw new UnsupportedOperationException(
533: "Adding aggregated field to non-aggregating query.");
534: }
535: BasicAggregatedField stepField = new BasicAggregatedField(step,
536: field, aggregationType);
537: fields.add(stepField);
538: hasChangedHashcode = true;
539: return stepField;
540: }
541:
542: /**
543: * Creates sortorder for this SearchQuery.
544: *
545: * @param field The associated stepfield.
546: * @return The new sortOrder
547: * @throws IllegalArgumentException when an invalid argument is supplied.
548: */
549: public BasicSortOrder addSortOrder(StepField field) {
550: if (field == null)
551: throw new IllegalArgumentException();
552: BasicSortOrder sortOrder;
553: if (field.getType() == Field.TYPE_DATETIME) {
554: sortOrder = new BasicDateSortOrder(field);
555: } else {
556: sortOrder = new BasicSortOrder(field);
557: }
558: sortOrders.add(sortOrder);
559: hasChangedHashcode = true;
560: return sortOrder;
561: }
562:
563: /**
564: * Sets constraint.
565: *
566: * @param constraint The constraint.
567: * @throws IllegalArgumentException when an invalid argument is supplied.
568: */
569: public void setConstraint(Constraint constraint) {
570: this .constraint = constraint;
571: hasChangedHashcode = true;
572: }
573:
574: // javadoc is inherited
575: public boolean isDistinct() {
576: return distinct;
577: }
578:
579: // javadoc is inherited
580: public boolean isAggregating() {
581: return aggregating;
582: }
583:
584: // javadoc is inherited
585: public List<SortOrder> getSortOrders() {
586: // return as unmodifiable list
587: return Collections.unmodifiableList(sortOrders);
588: }
589:
590: // javadoc is inherited
591: public List<Step> getSteps() {
592: // return as unmodifiable list
593: return Collections.unmodifiableList(steps);
594: }
595:
596: // javadoc is inherited
597: public List<StepField> getFields() {
598: // return as unmodifiable list
599: return Collections.unmodifiableList(fields);
600: }
601:
602: // javadoc is inherited
603: public Constraint getConstraint() {
604: return constraint;
605: }
606:
607: // javadoc is inherited
608: public int getMaxNumber() {
609: return maxNumber;
610: }
611:
612: //javadoc is inherited
613: public int getOffset() {
614: return offset;
615: }
616:
617: public CachePolicy getCachePolicy() {
618: return cachePolicy;
619: }
620:
621: public void setCachePolicy(CachePolicy policy) {
622: this .cachePolicy = policy;
623: }
624:
625: // javadoc is inherited
626: public boolean equals(Object obj) {
627: if (obj == this ) {
628: return true;
629: }
630: if (obj instanceof SearchQuery) {
631: SearchQuery query = (SearchQuery) obj;
632: return distinct == query.isDistinct()
633: && maxNumber == query.getMaxNumber()
634: && offset == query.getOffset()
635: && steps.equals(query.getSteps())
636: && fields.equals(query.getFields())
637: && sortOrders.equals(query.getSortOrders())
638: && (constraint == null ? query.getConstraint() == null
639: : constraint.equals(query.getConstraint()));
640: } else {
641: return false;
642: }
643: }
644:
645: // javadoc is inherited
646: public int hashCode() {
647: if (hasChangedHashcode) {
648: savedHashcode = (distinct ? 0 : 101) + maxNumber * 17
649: + offset * 19 + 23 * steps.hashCode() + 29
650: * fields.hashCode() + 31 * sortOrders.hashCode()
651: + 37
652: * (constraint == null ? 0 : constraint.hashCode());
653: hasChangedHashcode = false;
654: }
655: return savedHashcode;
656: }
657:
658: // javadoc is inherited
659: public String toString() {
660: StringBuilder sb = new StringBuilder("SearchQuery(distinct:")
661: .append(isDistinct()).append(", steps:" + getSteps())
662: .append(", fields:").append(getFields()).append(
663: ", constraint:").append(getConstraint())
664: .append(", sortorders:").append(getSortOrders())
665: .append(", max:").append(getMaxNumber()).append(
666: ", offset:").append(getOffset()).append(")");
667: return sb.toString();
668: }
669:
670: }
|