0001: /*
0002:
0003: This software is OSI Certified Open Source Software.
0004: OSI Certified is a certification mark of the Open Source Initiative.
0005:
0006: The license (Mozilla version 1.0) can be read at the MMBase site.
0007: See http://www.MMBase.org/license
0008:
0009: */
0010:
0011: package org.mmbase.bridge.util;
0012:
0013: import java.util.*;
0014:
0015: import org.mmbase.bridge.*;
0016: import org.mmbase.bridge.implementation.BasicQuery;
0017: import org.mmbase.module.core.ClusterBuilder;
0018: import org.mmbase.module.core.MMBase;
0019: import org.mmbase.storage.search.*;
0020: import org.mmbase.storage.search.legacy.ConstraintParser;
0021: import org.mmbase.util.*;
0022: import org.mmbase.util.logging.*;
0023:
0024: /**
0025: * This class contains various utility methods for manipulating and creating query objects.
0026: * Most essential methods are available on the Query object itself, but too specific or legacy-ish
0027: * methods are put here.
0028: *
0029: * @author Michiel Meeuwissen
0030: * @version $Id: Queries.java,v 1.96 2008/02/29 11:00:05 michiel Exp $
0031: * @see org.mmbase.bridge.Query
0032: * @since MMBase-1.7
0033: */
0034: abstract public class Queries {
0035:
0036: public static final int OPERATOR_BETWEEN = -1; // not a FieldCompareConstraint (numeric)
0037: public static final int OPERATOR_IN = 10000; // not a FieldCompareConstraint (non numeric)
0038: public static final int OPERATOR_NULL = 10001; // FieldIsNullConstraint
0039:
0040: private static final Logger log = Logging
0041: .getLoggerInstance(Queries.class);
0042:
0043: /**
0044: * Translates a string to a search direction constant. If the string is <code>null</code> then
0045: * 'BOTH' is returned.
0046: * @param search string representation of the searchdir constant
0047: * @return Searchdir constant as in {@link RelationStep}
0048: * @see ClusterBuilder#getSearchDir The same function, only with another return value if String is <code>null</code>
0049: */
0050: public static int getRelationStepDirection(String search) {
0051: if (search == null) {
0052: return RelationStep.DIRECTIONS_BOTH;
0053: }
0054: search = search.toUpperCase();
0055: if ("DESTINATION".equals(search)) {
0056: return RelationStep.DIRECTIONS_DESTINATION;
0057: } else if ("SOURCE".equals(search)) {
0058: return RelationStep.DIRECTIONS_SOURCE;
0059: } else if ("BOTH".equals(search)) {
0060: return RelationStep.DIRECTIONS_BOTH;
0061: } else if ("ALL".equals(search)) {
0062: return RelationStep.DIRECTIONS_ALL;
0063: } else if ("EITHER".equals(search)) {
0064: return RelationStep.DIRECTIONS_EITHER;
0065: } else {
0066: throw new BridgeException(
0067: "'"
0068: + search
0069: + "' cannot be converted to a relation-step direction constant");
0070: }
0071: }
0072:
0073: /**
0074: * Creates a Query object using arguments for {@link Cloud#getList(String, String, String, String, String, String, String, boolean)}
0075: * (this function is of course implemented using this utility). This is useful to convert (legacy) code which uses
0076: * getList, but you want to use new Query features without rewriting the complete thing.
0077: *
0078: * It can also be simply handy to specify things as Strings.
0079: *
0080: * @param cloud
0081: * @param startNodes
0082: * @param nodePath
0083: * @param fields
0084: * @param constraints
0085: * @param orderby
0086: * @param directions
0087: * @param searchDir
0088: * @param distinct
0089: * @return New query object
0090: * @todo Should this method be part of Cloud itself?
0091: */
0092: public static Query createQuery(Cloud cloud, String startNodes,
0093: String nodePath, String fields, String constraints,
0094: String orderby, String directions, String searchDir,
0095: boolean distinct) {
0096:
0097: // the bridge test case say that you may also specifiy empty string (why?)
0098: if ("".equals(startNodes) || "-1".equals(startNodes)) {
0099: startNodes = null;
0100: }
0101: if ("".equals(fields)) {
0102: fields = null;
0103: }
0104: if ("".equals(constraints)) {
0105: constraints = null;
0106: }
0107: if ("".equals(searchDir)) {
0108: searchDir = null;
0109: }
0110: if ("".equals(orderby)) {
0111: orderby = null;
0112: }
0113:
0114: if ("".equals(directions)) {
0115: directions = null;
0116: }
0117: // check invalid search command
0118: Encode encoder = new Encode("ESCAPE_SINGLE_QUOTE");
0119: // if(startNodes != null) startNodes = encoder.encode(startNodes);
0120: // if(nodePath != null) nodePath = encoder.encode(nodePath);
0121: // if(fields != null) fields = encoder.encode(fields);
0122: if (orderby != null) {
0123: orderby = encoder.encode(orderby);
0124: }
0125: if (directions != null) {
0126: directions = encoder.encode(directions);
0127: }
0128: if (searchDir != null) {
0129: searchDir = encoder.encode(searchDir);
0130: }
0131: if (constraints != null) {
0132: constraints = ConstraintParser
0133: .convertClauseToDBS(constraints);
0134: if (!ConstraintParser.validConstraints(constraints)) {
0135: throw new BridgeException("invalid constraints:"
0136: + constraints);
0137: }
0138: if (!constraints.substring(0, 5).equalsIgnoreCase("WHERE")) {
0139: /// WHERE is used in org.mmbase.util.QueryConvertor
0140: constraints = "WHERE " + constraints;
0141: }
0142: }
0143:
0144: // create query object
0145: //TODO: remove this code... classes under org.mmbase.bridge.util must not use the core
0146:
0147: // getMultilevelSearchQuery must perhaps move to a utility container org.mmbase.storage.search.Queries or so.
0148:
0149: ClusterBuilder clusterBuilder = MMBase.getMMBase()
0150: .getClusterBuilder();
0151: int search = -1;
0152: if (searchDir != null) {
0153: search = ClusterBuilder.getSearchDir(searchDir);
0154: }
0155:
0156: List<String> snodes = StringSplitter.split(startNodes);
0157: List<String> tables = StringSplitter.split(nodePath);
0158: List<String> f = StringSplitter.split(fields);
0159: List<String> orderVec = StringSplitter.split(orderby);
0160: List<String> d = StringSplitter.split(directions);
0161: try {
0162: // pitty that we can't use cloud.createQuery for this.
0163: // but all essential methods are on ClusterBuilder
0164: // XXX need casting here, something's wrong!!!
0165: Query query = new BasicQuery(cloud, clusterBuilder
0166: .getMultiLevelSearchQuery(snodes, f,
0167: distinct ? "YES" : "NO", tables,
0168: constraints, orderVec, d, search));
0169: return query;
0170: } catch (IllegalArgumentException iae) {
0171: throw new BridgeException(iae.getMessage()
0172: + ". (arguments: startNodes='" + startNodes
0173: + "', path='" + nodePath + "', fields='" + fields
0174: + "', constraints='" + constraints + "' orderby='"
0175: + orderby + "', directions='" + directions
0176: + "', searchdir='" + searchDir + "')", iae);
0177: }
0178: }
0179:
0180: /**
0181: * Adds a 'legacy' constraint to the query, i.e. constraint(s) represented
0182: * by a string. Alreading existing constraints remain ('AND' is used).
0183: *
0184: * @param query query to add constraint to
0185: * @param constraints string representation of constraints
0186: * @return The new constraint, or null if nothing changed added.
0187: */
0188: public static Constraint addConstraints(Query query,
0189: String constraints) {
0190: if (constraints == null || constraints.equals("")) {
0191: return null;
0192: }
0193:
0194: // (Try to) parse constraints string to Constraint object.
0195: Constraint newConstraint = new ConstraintParser(query)
0196: .toConstraint(constraints);
0197: addConstraint(query, newConstraint);
0198: return newConstraint;
0199: }
0200:
0201: /**
0202: * Adds a Constraint to the already present constraint (with AND).
0203: * @param query query to add the constraint to
0204: * @param newConstraint constraint to add
0205: * @return The new constraint.
0206: */
0207: public static Constraint addConstraint(Query query,
0208: Constraint newConstraint) {
0209: if (newConstraint == null) {
0210: return null;
0211: }
0212:
0213: Constraint constraint = query.getConstraint();
0214:
0215: if (constraint != null) {
0216: log.debug("compositing constraint");
0217: Constraint compConstraint = query.createConstraint(
0218: constraint, CompositeConstraint.LOGICAL_AND,
0219: newConstraint);
0220: query.setConstraint(compConstraint);
0221: } else {
0222: query.setConstraint(newConstraint);
0223: }
0224: return newConstraint;
0225: }
0226:
0227: /**
0228: * Creates a operator constant for use by createConstraint
0229: * @param s String representation of operator
0230: * @return FieldCompareConstraint operator constant
0231: * @see #createConstraint(Query, String, int, Object)
0232: * @see #createConstraint(Query, String, int, Object, Object, boolean)
0233: */
0234: public static int getOperator(String s) {
0235: String op = s.toUpperCase();
0236: // first: determine operator:
0237: if (op.equals("<") || op.equals("LESS") || op.equals("LT")) {
0238: return FieldCompareConstraint.LESS;
0239: } else if (op.equals("<=") || op.equals("LESS_EQUAL")
0240: || op.equals("LE")) {
0241: return FieldCompareConstraint.LESS_EQUAL;
0242: } else if (op.equals("=") || op.equals("EQUAL")
0243: || op.equals("") || op.equals("EQ")) {
0244: return FieldCompareConstraint.EQUAL;
0245: } else if (op.equals("!=") || op.equals("NOT_EQUAL")
0246: || op.equals("NE")) {
0247: return FieldCompareConstraint.NOT_EQUAL;
0248: } else if (op.equals(">") || op.equals("GREATER")
0249: || op.equals("GT")) {
0250: return FieldCompareConstraint.GREATER;
0251: } else if (op.equals(">=") || op.equals("GREATER_EQUAL")
0252: || op.equals("GE")) {
0253: return FieldCompareConstraint.GREATER_EQUAL;
0254: } else if (op.equals("LIKE")) {
0255: return FieldCompareConstraint.LIKE;
0256: } else if (op.equals("BETWEEN")) {
0257: return OPERATOR_BETWEEN;
0258: } else if (op.equals("IN")) {
0259: return OPERATOR_IN;
0260: } else if (op.equals("NULL")) {
0261: return OPERATOR_NULL;
0262: //} else if (op.equals("~") || op.equals("REGEXP")) {
0263: // return FieldCompareConstraint.REGEXP;
0264: } else {
0265: throw new BridgeException(
0266: "Unknown Field Compare Operator '" + op + "'");
0267: }
0268: }
0269:
0270: /**
0271: * Creates a part constant for use by createConstraint
0272: * @param s String representation of a datetime part
0273: * @return FieldValueDateConstraint part constant
0274: * @see #createConstraint(Query, String, int, Object, Object, boolean, int)
0275: */
0276: public static int getDateTimePart(String s) {
0277: String sPart = s.toUpperCase();
0278: if (sPart.equals("")) {
0279: return -1;
0280: } else if (sPart.equals("CENTURY")) {
0281: return FieldValueDateConstraint.CENTURY;
0282: } else if (sPart.equals("YEAR")) {
0283: return FieldValueDateConstraint.YEAR;
0284: } else if (sPart.equals("QUARTER")) {
0285: return FieldValueDateConstraint.QUARTER;
0286: } else if (sPart.equals("MONTH")) {
0287: return FieldValueDateConstraint.MONTH;
0288: } else if (sPart.equals("WEEK")) {
0289: return FieldValueDateConstraint.WEEK;
0290: } else if (sPart.equals("DAYOFYEAR")) {
0291: return FieldValueDateConstraint.DAY_OF_YEAR;
0292: } else if (sPart.equals("DAY") || sPart.equals("DAYOFMONTH")) {
0293: return FieldValueDateConstraint.DAY_OF_MONTH;
0294: } else if (sPart.equals("DAYOFWEEK")) {
0295: return FieldValueDateConstraint.DAY_OF_WEEK;
0296: } else if (sPart.equals("HOUR")) {
0297: return FieldValueDateConstraint.HOUR;
0298: } else if (sPart.equals("MINUTE")) {
0299: return FieldValueDateConstraint.MINUTE;
0300: } else if (sPart.equals("SECOND")) {
0301: return FieldValueDateConstraint.SECOND;
0302: } else if (sPart.equals("MILLISECOND")) {
0303: return FieldValueDateConstraint.MILLISECOND;
0304: } else {
0305: throw new BridgeException("Unknown datetime part '" + sPart
0306: + "'");
0307: }
0308: }
0309:
0310: /**
0311: * Used in implementation of createConstraint
0312: * @param stringValue string representation of a number
0313: * @return Number object
0314: * @throws BridgeException when failed to convert the string
0315: */
0316: protected static Number getNumberValue(String stringValue)
0317: throws BridgeException {
0318: if (stringValue == null)
0319: return null;
0320: try {
0321: return Integer.valueOf(stringValue);
0322: } catch (NumberFormatException e) {
0323: try {
0324: return Double.valueOf(stringValue);
0325: } catch (NumberFormatException e2) {
0326: if (stringValue.equalsIgnoreCase("true")) {
0327: return Integer.valueOf(1);
0328: } else if (stringValue.equalsIgnoreCase("false")) {
0329: return Integer.valueOf(0);
0330: }
0331: throw new BridgeException(
0332: "Operator requires number value ('"
0333: + stringValue + "' is not)");
0334: }
0335: }
0336: }
0337:
0338: /**
0339: * Used in implementation of createConstraint
0340: * @param fieldType Field Type constant (@link Field)
0341: * @param operator Compare operator
0342: * @param value value to convert
0343: * @return new Compare value
0344: */
0345: protected static Object getCompareValue(int fieldType,
0346: int operator, Object value) {
0347: return getCompareValue(fieldType, operator, value, -1, null);
0348: }
0349:
0350: /**
0351: * Used in implementation of createConstraint
0352: * @param fieldType Field Type constant (@link Field)
0353: * @param operator Compare operator
0354: * @param value value to convert
0355: * @param cloud The cloud may be used to pass locale sensitive properties which may be needed for comparisions (locales, timezones)
0356: * @return new Compare value
0357: * @since MMBase-1.8.2
0358: */
0359: protected static Object getCompareValue(int fieldType,
0360: int operator, Object value, int datePart, Cloud cloud) {
0361: if (operator == OPERATOR_IN) {
0362: SortedSet<Object> set;
0363: if (value instanceof SortedSet) {
0364: set = (SortedSet<Object>) value;
0365: } else if (value instanceof NodeList) {
0366: set = new TreeSet<Object>();
0367: for (Node node : ((NodeList) value)) {
0368: set.add(getCompareValue(fieldType,
0369: FieldCompareConstraint.EQUAL, node
0370: .getNumber()));
0371: }
0372: } else if (value instanceof Collection) {
0373: set = new TreeSet<Object>();
0374: for (Object o : ((Collection) value)) {
0375: set.add(getCompareValue(fieldType,
0376: FieldCompareConstraint.EQUAL, o));
0377: }
0378: } else {
0379: set = new TreeSet<Object>();
0380: if (!(value == null || value.equals(""))) {
0381: set.add(getCompareValue(fieldType,
0382: FieldCompareConstraint.EQUAL, value));
0383: }
0384: }
0385: return set;
0386: }
0387: switch (fieldType) {
0388: case Field.TYPE_STRING:
0389: return value == null ? null : Casting.toString(value);
0390: case Field.TYPE_INTEGER:
0391: case Field.TYPE_FLOAT:
0392: case Field.TYPE_LONG:
0393: case Field.TYPE_DOUBLE:
0394: case Field.TYPE_NODE:
0395: if (value instanceof Number) {
0396: return value;
0397: } else {
0398: return getNumberValue(value == null ? null : Casting
0399: .toString(value));
0400: }
0401: case Field.TYPE_DATETIME:
0402: //TimeZone tz = cloud == null ? null : (TimeZone) cloud.getProperty("org.mmbase.timezone");
0403: if (datePart > -1) {
0404: return Casting.toInteger(value);
0405: } else {
0406: return Casting.toDate(value);
0407: }
0408: case Field.TYPE_BOOLEAN:
0409: return Casting.toBoolean(value) ? Boolean.TRUE
0410: : Boolean.FALSE;
0411: default:
0412: return value;
0413: }
0414: }
0415:
0416: /**
0417: * Defaulting version of {@link #createConstraint(Query, String, int, Object, Object, boolean, int)}.
0418: * Casesensitivity defaults to false, value2 to null (so 'BETWEEN' cannot be used), datePart set to -1 (so no date part comparison)
0419: * @param query The query to create the constraint for
0420: * @param fieldName The field to create the constraint on (as a string, so it can include the step), e.g. 'news.number'
0421: * @param operator The operator to use. This constant can be produces from a string using {@link #getOperator(String)}.
0422: * @param value The value to compare with, which must be of the right type. If field is number it might also be an alias.
0423: * @return The new constraint, or <code>null</code> it by chance the specified arguments did not lead to a new actual constraint (e.g. if value is an empty set)
0424: */
0425: public static Constraint createConstraint(Query query,
0426: String fieldName, int operator, Object value) {
0427: return createConstraint(query, fieldName, operator, value,
0428: null, false, -1);
0429: }
0430:
0431: /**
0432: * Defaulting version of {@link #createConstraint(Query, String, int, Object, Object, boolean, int)}.
0433: * DatePart set to -1 (so no date part comparison)
0434: * @param query The query to create the constraint for
0435: * @param fieldName The field to create the constraint on (as a string, so it can include the step), e.g. 'news.number'
0436: * @param operator The operator to use. This constant can be produces from a string using {@link #getOperator(String)}.
0437: * @param value The value to compare with, which must be of the right type. If field is number it might also be an alias.
0438: * @param value2 The other value (only relevant if operator is BETWEEN, the only terniary operator)
0439: * @param caseSensitive Whether it should happen case sensitively (not relevant for number fields)
0440: * @return The new constraint, or <code>null</code> it by chance the specified arguments did not lead to a new actual constraint (e.g. if value is an empty set)
0441: */
0442: public static Constraint createConstraint(Query query,
0443: String fieldName, int operator, Object value,
0444: Object value2, boolean caseSensitive) {
0445: return createConstraint(query, fieldName, operator, value,
0446: value2, caseSensitive, -1);
0447: }
0448:
0449: /**
0450: * Creates a constraint smartly, depending on the type of the field, the value is cast to the
0451: * right type, and the right type of constraint is created.
0452: * This is used in taglib implementation, but could be useful more generally.
0453: *
0454: * @param query The query to create the constraint for
0455: * @param fieldName The field to create the constraint on (as a string, so it can include the step), e.g. 'news.number'
0456: * @param operator The operator to use. This constant can be produces from a string using {@link #getOperator(String)}.
0457: * @param value The value to compare with, which must be of the right type. If field is number it might also be an alias.
0458: * @param value2 The other value (only relevant if operator is BETWEEN, the only terniary operator)
0459: * @param caseSensitive Whether it should happen case sensitively (not relevant for number fields)
0460: * @param datePart The part of a DATETIME value that is to be checked
0461: * @return The new constraint, or <code>null</code> it by chance the specified arguments did not lead to a new actual constraint (e.g. if value is an empty set)
0462: */
0463: public static Constraint createConstraint(final Query query,
0464: final String fieldName, final int operator,
0465: final Object originalValue, final Object value2,
0466: final boolean caseSensitive, final int datePart) {
0467:
0468: Object value = originalValue;
0469: StepField stepField = query.createStepField(fieldName);
0470: if (stepField == null) {
0471: throw new BridgeException(
0472: "Could not create stepfield with '" + fieldName
0473: + "'");
0474: }
0475:
0476: Cloud cloud = query.getCloud();
0477: FieldConstraint newConstraint;
0478:
0479: if (value instanceof StepField) {
0480: newConstraint = query.createConstraint(stepField, operator,
0481: (StepField) value);
0482: } else if (operator == OPERATOR_NULL || value == null) {
0483: newConstraint = query.createConstraint(stepField);
0484: } else {
0485: Field field = cloud.getNodeManager(
0486: stepField.getStep().getTableName()).getField(
0487: stepField.getFieldName());
0488: int fieldType = field.getType();
0489:
0490: if (fieldName.equals("number")
0491: || fieldType == Field.TYPE_NODE) {
0492: if (value instanceof String) { // it might be an alias!
0493: if (cloud.hasNode((String) value)) {
0494: Node node = cloud.getNode((String) value);
0495: value = Integer.valueOf(node.getNumber());
0496: } else {
0497: value = -1; // non existing node number. Integer.parseInt((String) value);
0498: }
0499: } else if (value instanceof Collection) { // or even more aliases!
0500: Collection col = (Collection) value;
0501: value = new ArrayList();
0502: List<Object> list = (List<Object>) value;
0503: for (Object v : col) {
0504: if (v instanceof Number) {
0505: list.add(v);
0506: } else {
0507: String s = Casting.toString(v);
0508: if (cloud.hasNode(s)) {
0509: Node node = cloud.getNode(s);
0510: list.add(node.getNumber());
0511: } else {
0512: list.add(-1);
0513: }
0514:
0515: }
0516: }
0517:
0518: }
0519: }
0520: if (operator != OPERATOR_IN) { // should the elements of the collection then not be cast?
0521:
0522: if (fieldType == Field.TYPE_XML) {
0523: // XML's are treated as String in the query-handler so, let's anticipate that here...
0524: // a bit of a hack, perhaps we need something like a 'searchCast' or so.
0525: value = Casting.toString(value);
0526: } else {
0527: Object castedValue = field.getDataType().cast(
0528: value, null, field);
0529: if (castedValue == null && value != null
0530: && fieldType == Field.TYPE_NODE) {
0531: // non existing node-number, like e.g. -1 are csated to null,
0532: // but that is incorrect when e..g the operator is GREATER
0533: castedValue = Casting.toInteger(value);
0534: }
0535: value = castedValue;
0536:
0537: }
0538: }
0539:
0540: Object compareValue = getCompareValue(fieldType, operator,
0541: value, datePart, cloud);
0542:
0543: if (log.isDebugEnabled()) {
0544: log.debug(" " + originalValue + " -> " + value + " -> "
0545: + compareValue);
0546: }
0547:
0548: if (operator > 0 && operator < OPERATOR_IN) {
0549: if (fieldType == Field.TYPE_DATETIME && datePart > -1) {
0550: newConstraint = query.createConstraint(stepField,
0551: operator, compareValue, datePart);
0552: } else {
0553: if (operator == FieldCompareConstraint.EQUAL
0554: && compareValue == null) {
0555: newConstraint = query
0556: .createConstraint(stepField);
0557: } else {
0558: newConstraint = query.createConstraint(
0559: stepField, operator, compareValue);
0560: }
0561: }
0562: } else {
0563: if (fieldType == Field.TYPE_DATETIME && datePart > -1) {
0564: throw new RuntimeException(
0565: "Cannot apply IN or BETWEEN to a partial date field");
0566: }
0567: switch (operator) {
0568: case OPERATOR_BETWEEN:
0569: Object compareValue2 = getCompareValue(fieldType,
0570: operator, value2);
0571: newConstraint = query.createConstraint(stepField,
0572: compareValue, compareValue2);
0573: break;
0574: case OPERATOR_IN:
0575: newConstraint = query.createConstraint(stepField,
0576: (SortedSet) compareValue);
0577: break;
0578: default:
0579: throw new RuntimeException(
0580: "Unknown value for operation " + operator);
0581: }
0582: }
0583: }
0584: query.setCaseSensitive(newConstraint, caseSensitive);
0585: return newConstraint;
0586:
0587: }
0588:
0589: /**
0590: * Takes a Constraint of a query, and takes al constraints on 'sourceStep' of it, and copies
0591: * those Constraints to the given step of the receiving query.
0592: *
0593: * Constraints on different steps then the given 'sourceStep' are ignored. CompositeConstraints
0594: * cause recursion and would work too (but same limitation are valid for the childs).
0595: *
0596: * @param c The constrain to be copied (for example the result of sourceQuery.getConstraint()).
0597: * @param sourceStep The step in the 'source' query.
0598: * @param query The receiving query
0599: * @param step The step of the receiving query which must 'receive' the sort orders.
0600: * @since MMBase-1.7.1
0601: * @see org.mmbase.storage.search.implementation.BasicSearchQuery#copyConstraint Functions are similar
0602: * @throws IllegalArgumentException If the given constraint is not compatible with the given step.
0603: * @throws UnsupportedOperationException If CompareFieldsConstraints or LegacyConstraints are encountered.
0604: * @return The new constraint or null
0605: */
0606: public static Constraint copyConstraint(Constraint c,
0607: Step sourceStep, Query query, Step step) {
0608: if (c == null)
0609: return null;
0610:
0611: if (c instanceof CompositeConstraint) {
0612: CompositeConstraint constraint = (CompositeConstraint) c;
0613: List<Constraint> constraints = new ArrayList<Constraint>();
0614: for (Constraint child : constraint.getChilds()) {
0615: Constraint cons = copyConstraint(child, sourceStep,
0616: query, step);
0617: if (cons != null)
0618: constraints.add(cons);
0619: }
0620: int size = constraints.size();
0621: if (size == 0)
0622: return null;
0623: if (size == 1)
0624: return constraints.get(0);
0625: Iterator<Constraint> i = constraints.iterator();
0626: int op = constraint.getLogicalOperator();
0627: Constraint newConstraint = query.createConstraint(i.next(),
0628: op, i.next());
0629: while (i.hasNext()) {
0630: newConstraint = query.createConstraint(newConstraint,
0631: op, i.next());
0632: }
0633: query.setInverse(newConstraint, constraint.isInverse());
0634: return newConstraint;
0635: } else if (c instanceof CompareFieldsConstraint) {
0636: throw new UnsupportedOperationException(
0637: "Cannot copy comparison between fields"); // at least not from different steps
0638: }
0639:
0640: FieldConstraint fieldConstraint = (FieldConstraint) c;
0641: if (!fieldConstraint.getField().getStep().equals(sourceStep))
0642: return null; // constraint is not for the request step, so don't copy.
0643:
0644: StepField field = query.createStepField(step, fieldConstraint
0645: .getField().getFieldName());
0646:
0647: FieldConstraint newConstraint;
0648: if (c instanceof FieldValueConstraint) {
0649: newConstraint = query.createConstraint(field,
0650: ((FieldValueConstraint) c).getOperator(),
0651: ((FieldValueConstraint) c).getValue());
0652: } else if (c instanceof FieldNullConstraint) {
0653: newConstraint = query.createConstraint(field);
0654: } else if (c instanceof FieldValueBetweenConstraint) {
0655: FieldValueBetweenConstraint constraint = (FieldValueBetweenConstraint) c;
0656: try {
0657: newConstraint = query.createConstraint(field,
0658: constraint.getLowerLimit(), constraint
0659: .getUpperLimit());
0660: } catch (NumberFormatException e) {
0661: newConstraint = query.createConstraint(field,
0662: constraint.getLowerLimit(), constraint
0663: .getUpperLimit());
0664: }
0665: } else if (c instanceof FieldValueInConstraint) {
0666: FieldValueInConstraint constraint = (FieldValueInConstraint) c;
0667:
0668: // sigh
0669: SortedSet<Object> set = new TreeSet<Object>();
0670: int type = field.getType();
0671: for (Object value : constraint.getValues()) {
0672: switch (type) {
0673: case Field.TYPE_INTEGER:
0674: case Field.TYPE_LONG:
0675: case Field.TYPE_NODE:
0676: value = Long.valueOf(Casting.toLong(value));
0677: break;
0678: case Field.TYPE_FLOAT:
0679: case Field.TYPE_DOUBLE:
0680: value = Double.valueOf(Casting.toDouble(value));
0681: break;
0682: case Field.TYPE_DATETIME:
0683: value = new Date((long) 1000
0684: * Integer.parseInt("" + value));
0685: break;
0686: default:
0687: log.debug("Unknown type " + type);
0688: break;
0689: }
0690: set.add(value);
0691: }
0692: newConstraint = query.createConstraint(field, set);
0693: } else if (c instanceof LegacyConstraint) {
0694: throw new UnsupportedOperationException(
0695: "Cannot copy legacy constraint to other step");
0696: } else {
0697: throw new RuntimeException("Could not copy constraint " + c);
0698: }
0699: query.setInverse(newConstraint, fieldConstraint.isInverse());
0700: query.setCaseSensitive(newConstraint, fieldConstraint
0701: .isCaseSensitive());
0702: return newConstraint;
0703:
0704: }
0705:
0706: /**
0707: * Copies SortOrders to a given step of another query. SortOrders which do not sort the given
0708: * 'sourceStep' are ignored.
0709: * @param sortOrders A list of SortOrders (for example the result of sourceQuery.getSortOrders()).
0710: * @param sourceStep The step in the 'source' query.
0711: * @param query The receiving query
0712: * @param step The step of the receiving query which must 'receive' the sort orders.
0713: * @since MMBase-1.7.1
0714:
0715: */
0716: public static void copySortOrders(List<SortOrder> sortOrders,
0717: Step sourceStep, Query query, Step step) {
0718: for (SortOrder sortOrder : sortOrders) {
0719: StepField sourceField = sortOrder.getField();
0720: if (!sourceField.getStep().equals(sourceStep))
0721: continue; // for another step
0722: if (sortOrder instanceof DateSortOrder) {
0723: query.addSortOrder(query.createStepField(step,
0724: sourceField.getFieldName()), sortOrder
0725: .getDirection(), sortOrder.isCaseSensitive(),
0726: ((DateSortOrder) sortOrder).getPart());
0727: } else {
0728: query.addSortOrder(query.createStepField(step,
0729: sourceField.getFieldName()), sortOrder
0730: .getDirection(), sortOrder.isCaseSensitive());
0731: }
0732: }
0733: }
0734:
0735: /**
0736: * Converts a String to a SortOrder constant
0737: * @param dir string representation of direction of sortorder
0738: * @return SortOrder constant
0739: * @since MMBase-1.7.1
0740: */
0741: public static int getSortOrder(String dir) {
0742: dir = dir.toUpperCase();
0743: if (dir.equals("")) {
0744: return SortOrder.ORDER_ASCENDING;
0745: } else if (dir.equals("DOWN")) {
0746: return SortOrder.ORDER_DESCENDING;
0747: } else if (dir.equals("UP")) {
0748: return SortOrder.ORDER_ASCENDING;
0749: } else if (dir.equals("ASCENDING")) {
0750: return SortOrder.ORDER_ASCENDING;
0751: } else if (dir.equals("DESCENDING")) {
0752: return SortOrder.ORDER_DESCENDING;
0753: } else {
0754: throw new BridgeException("Unknown sort-order '" + dir
0755: + "'");
0756: }
0757: }
0758:
0759: /**
0760: * Adds sort orders to the query, using two strings. Like in 'getList' of Cloud. Several tag-attributes need this.
0761: * @param query query to add the sortorders to
0762: * @param sorted string with comma-separated fields
0763: * @param directions string with comma-separated directions
0764: *
0765: * @todo implement for normal query.
0766: * @return The new sort orders
0767: */
0768: public static List<SortOrder> addSortOrders(Query query,
0769: String sorted, String directions) {
0770: // following code was copied from MMObjectBuilder.setSearchQuery (bit ugly)
0771: if (sorted == null) {
0772: return query.getSortOrders().subList(0, 0);
0773: }
0774: if (directions == null) {
0775: directions = "";
0776: }
0777: List<SortOrder> list = query.getSortOrders();
0778: int initialSize = list.size();
0779:
0780: StringTokenizer sortedTokenizer = new StringTokenizer(sorted,
0781: ",");
0782: StringTokenizer directionsTokenizer = new StringTokenizer(
0783: directions, ",");
0784:
0785: while (sortedTokenizer.hasMoreTokens()) {
0786: String fieldName = sortedTokenizer.nextToken().trim();
0787: int dot = fieldName.indexOf('.');
0788:
0789: StepField sf;
0790: if (dot == -1 && query instanceof NodeQuery) {
0791: NodeManager nodeManager = ((NodeQuery) query)
0792: .getNodeManager();
0793: sf = ((NodeQuery) query).getStepField(nodeManager
0794: .getField(fieldName));
0795: } else {
0796: sf = query.createStepField(fieldName);
0797: }
0798:
0799: int dir = SortOrder.ORDER_ASCENDING;
0800: if (directionsTokenizer.hasMoreTokens()) {
0801: String direction = directionsTokenizer.nextToken()
0802: .trim();
0803: dir = getSortOrder(direction);
0804: }
0805: query.addSortOrder(sf, dir);
0806: }
0807:
0808: return list.subList(initialSize, list.size());
0809: }
0810:
0811: /**
0812: * Returns substring of given string without the leading digits (used in 'paths')
0813: * @param complete string with leading digits
0814: * @return string with digits removed
0815: */
0816: public static String removeDigits(String complete) {
0817: int end = complete.length() - 1;
0818: while (Character.isDigit(complete.charAt(end))) {
0819: --end;
0820: }
0821: return complete.substring(0, end + 1);
0822: }
0823:
0824: /**
0825: * Adds path of steps to an existing query. The query may contain steps already. Per step also
0826: * the 'search direction' may be specified.
0827: * @param query extend this query
0828: * @param path create steps from this path
0829: * @param searchDirs add steps with these relation directions
0830: * @return The new steps.
0831: */
0832: public static List<Step> addPath(Query query, String path,
0833: String searchDirs) {
0834: if (path == null || path.equals("")) {
0835: return query.getSteps().subList(0, 0);
0836: }
0837: if (searchDirs == null) {
0838: searchDirs = "";
0839: }
0840:
0841: List<Step> list = query.getSteps();
0842: int initialSize = list.size();
0843:
0844: StringTokenizer pathTokenizer = new StringTokenizer(path, ",");
0845: StringTokenizer searchDirsTokenizer = new StringTokenizer(
0846: searchDirs, ",");
0847:
0848: Cloud cloud = query.getCloud();
0849:
0850: if (query.getSteps().size() == 0) { // if no steps yet, first step must be added with addStep
0851: String completeFirstToken = pathTokenizer.nextToken()
0852: .trim();
0853: String firstToken = removeDigits(completeFirstToken);
0854: //if (cloud.hasRole(firstToken)) {
0855: // you cannot start with a role.., should we throw exception?
0856: // naa, the following code will throw exception that node type does not exist.
0857: //}
0858: Step step = query.addStep(cloud.getNodeManager(firstToken));
0859: if (!firstToken.equals(completeFirstToken)) {
0860: query.setAlias(step, completeFirstToken);
0861: }
0862: }
0863:
0864: String searchDir = null; // outside the loop, so defaulting to previous searchDir
0865: while (pathTokenizer.hasMoreTokens()) {
0866: String completeToken = pathTokenizer.nextToken().trim();
0867: String token = removeDigits(completeToken);
0868:
0869: if (searchDirsTokenizer.hasMoreTokens()) {
0870: searchDir = searchDirsTokenizer.nextToken();
0871: }
0872:
0873: if (cloud.hasRole(token) && pathTokenizer.hasMoreTokens()) {
0874: if (cloud.hasNodeManager(token)) {
0875: // Ambigious path element '" + token + "', is both a role and a nodemanager
0876: // This is pretty common though. E.g. 'posrel'.
0877: }
0878: String nodeManagerAlias = pathTokenizer.nextToken()
0879: .trim();
0880: String nodeManagerName = removeDigits(nodeManagerAlias);
0881: NodeManager nodeManager = cloud
0882: .getNodeManager(nodeManagerName);
0883: RelationStep relationStep = query.addRelationStep(
0884: nodeManager, token, searchDir);
0885:
0886: /// make it possible to postfix with numbers manually
0887: if (!cloud.hasRole(completeToken)) {
0888: query.setAlias(relationStep, completeToken);
0889: }
0890: if (!nodeManagerName.equals(nodeManagerAlias)) {
0891: Step next = relationStep.getNext();
0892: query.setAlias(next, nodeManagerAlias);
0893: }
0894: } else {
0895: NodeManager nodeManager = cloud.getNodeManager(token);
0896: RelationStep step = query.addRelationStep(nodeManager,
0897: null /* role */, searchDir);
0898: if (!completeToken.equals(nodeManager.getName())) {
0899: Step next = step.getNext();
0900: query.setAlias(next, completeToken);
0901: }
0902: }
0903: }
0904: if (searchDirsTokenizer.hasMoreTokens()) {
0905: throw new BridgeException("Too many search directions ("
0906: + path + "/" + searchDirs + ")");
0907: }
0908: return list.subList(initialSize, list.size());
0909: }
0910:
0911: /**
0912: * Adds a number of fields. Fields is represented as a comma separated string.
0913: * @param query The query where the fields should be added to
0914: * @param fields a comma separated string of fields
0915: * @return The new stepfields
0916: */
0917: public static List<StepField> addFields(Query query, String fields) {
0918: List<StepField> result = new ArrayList<StepField>();
0919: if (fields == null || fields.equals("")) {
0920: return result;
0921: }
0922:
0923: for (String fieldName : StringSplitter.split(fields)) {
0924: result.add(query.addField(fieldName));
0925: }
0926: return result;
0927:
0928: }
0929:
0930: /**
0931: * Add startNodes to the first step with the correct type to the given query. The nodes are identified
0932: * by a String, which could be prefixed with a step-alias, if you want to add the nodes to
0933: * another then this found step.
0934: *
0935: * Furthermore may the nodes by identified by their alias, if they have one.
0936: * @param query query to add the startnodes
0937: * @param startNodes start nodes
0938: *
0939: * @see org.mmbase.module.core.ClusterBuilder#getMultiLevelSearchQuery(List, List, String, List, String, List, List, int)
0940: * (this is essentially a 'bridge' version of the startnodes part)
0941: */
0942: public static void addStartNodes(Query query, String startNodes) {
0943: if (startNodes == null || "".equals(startNodes)
0944: || "-1".equals(startNodes)) {
0945: return;
0946: }
0947:
0948: Step firstStep = null; // the 'default' step to which nodes are added. It is the first step which corresponds with the type of the first node.
0949:
0950: for (String nodeAlias : StringSplitter.split(startNodes)) {
0951: // can be a string, prefixed with the step alias.
0952: Step step; // the step to which the node must be added (defaults to 'firstStep').
0953: String nodeNumber; // a node number or perhaps also a node alias.
0954: {
0955: int dot = nodeAlias.indexOf('.'); // this feature is not in core. It should be considered experimental
0956: if (dot == -1) {
0957: step = firstStep;
0958: nodeNumber = nodeAlias;
0959: } else {
0960: step = query.getStep(nodeAlias.substring(0, dot));
0961: nodeNumber = nodeAlias.substring(dot + 1);
0962: }
0963: }
0964:
0965: if (firstStep == null) { // firstStep not yet determined, do that now.
0966: Node node;
0967: try {
0968: node = query.getCloud().getNode(nodeNumber);
0969: } catch (NotFoundException nfe) { // alias with dot?
0970: node = query.getCloud().getNode(nodeAlias);
0971: }
0972: NodeManager nodeManager = node.getNodeManager();
0973: for (Step queryStep : query.getSteps()) {
0974: NodeManager queryNodeManager = query.getCloud()
0975: .getNodeManager(queryStep.getTableName());
0976: if (queryNodeManager.equals(nodeManager)
0977: || queryNodeManager.getDescendants()
0978: .contains(nodeManager)) {
0979: // considering inheritance. ClusterBuilder is not doing that, but I think it is a bug.
0980: firstStep = queryStep;
0981: break;
0982: }
0983: }
0984: if (firstStep == null) {
0985: // odd..
0986: // See also org.mmbase.module.core.ClusterBuilder#getMultiLevelSearchQuery
0987: // specified a node which is not of the type of one of the steps.
0988: // take as default the 'first' step (which will make the result empty, compatible with 1.6, bug #6440).
0989: firstStep = query.getSteps().get(0);
0990: }
0991: }
0992:
0993: if (step == null) {
0994: step = firstStep;
0995: }
0996:
0997: try {
0998: try {
0999: query.addNode(step, Integer.parseInt(nodeNumber));
1000: } catch (NumberFormatException nfe) {
1001: query.addNode(step, query.getCloud().getNode(
1002: nodeNumber)); // node was specified by alias.
1003: }
1004: } catch (NotFoundException nnfe) {
1005: query
1006: .addNode(step, query.getCloud().getNode(
1007: nodeAlias)); // perhas an alias containing a dot?
1008: }
1009: }
1010: }
1011:
1012: /**
1013: * Takes the query, and does a count with the same constraints (so ignoring 'offset' and 'max')
1014: * @param query query as base for the count
1015: * @return number of results
1016: */
1017: public static int count(Query query) {
1018: Cloud cloud = query.getCloud();
1019: Query count = query.aggregatingClone();
1020: int type = query.isDistinct() ? AggregatedField.AGGREGATION_TYPE_COUNT_DISTINCT
1021: : AggregatedField.AGGREGATION_TYPE_COUNT;
1022:
1023: String resultName;
1024: if (query instanceof NodeQuery) {
1025: // all fields are present of the node-step, so, we could use the number field simply.
1026: resultName = "number";
1027: NodeQuery nq = (NodeQuery) query;
1028: count.addAggregatedField(nq.getNodeStep(), nq
1029: .getNodeManager().getField(resultName), type);
1030: } else {
1031: List<StepField> fields = query.getFields();
1032: if (fields.size() == 0) { // for non-distinct queries always the number fields would be available
1033: throw new IllegalArgumentException(
1034: "Cannot count queries with less than one field: "
1035: + query);
1036: }
1037:
1038: if (query.isDistinct() && fields.size() > 1) {
1039: // aha hmm. Well, we also find it ok if all fields are of one step, and 'number' is present
1040: resultName = null;
1041: Step step = null;
1042: for (StepField sf : fields) {
1043: if (step == null) {
1044: step = sf.getStep();
1045: } else {
1046: if (!step.equals(sf.getStep())) {
1047: throw new UnsupportedOperationException(
1048: "Cannot count distinct queries with fields of more than one step. Current fields: "
1049: + fields);
1050: }
1051: }
1052: if (sf.getFieldName().equals("number")) {
1053: resultName = sf.getFieldName();
1054: }
1055: }
1056: if (resultName == null) {
1057: throw new UnsupportedOperationException(
1058: "Cannot count distinct queries with more than one field if 'number' field is missing. Current fields: "
1059: + fields);
1060: }
1061: count
1062: .addAggregatedField(step, cloud.getNodeManager(
1063: step.getTableName()).getField(
1064: resultName), type);
1065: } else {
1066: // simply take this one field
1067: StepField sf = fields.get(0);
1068: Step step = sf.getStep();
1069: resultName = sf.getFieldName();
1070: count
1071: .addAggregatedField(step, cloud.getNodeManager(
1072: step.getTableName()).getField(
1073: resultName), type);
1074: }
1075: }
1076: NodeList r = cloud.getList(count);
1077: if (r.size() != 1)
1078: throw new RuntimeException("Count query " + query
1079: + " did not give one result but " + r);
1080: Node result = r.getNode(0);
1081: return result.getIntValue(resultName);
1082: }
1083:
1084: /**
1085: * @since MMBase-1.8
1086: */
1087: protected static Object aggregate(Query query, StepField field,
1088: int type) {
1089: Cloud cloud = query.getCloud();
1090: Query aggregate = query.aggregatingClone();
1091: String resultName = field.getFieldName();
1092: Step step = field.getStep();
1093: aggregate.addAggregatedField(step, cloud.getNodeManager(
1094: step.getTableName()).getField(resultName), type);
1095: NodeList r = cloud.getList(aggregate);
1096: if (r.size() != 1)
1097: throw new RuntimeException("Aggregated query " + query
1098: + " did not give one result but " + r);
1099: Node result = r.getNode(0);
1100: return result.getValue(resultName);
1101: }
1102:
1103: /**
1104: * @since MMBase-1.8
1105: */
1106: public static Object min(Query query, StepField field) {
1107: return aggregate(query, field,
1108: AggregatedField.AGGREGATION_TYPE_MIN);
1109: }
1110:
1111: /**
1112: * @since MMBase-1.8
1113: */
1114: public static Object max(Query query, StepField field) {
1115: return aggregate(query, field,
1116: AggregatedField.AGGREGATION_TYPE_MAX);
1117: }
1118:
1119: /**
1120: * Searches a list of Steps for a step with a certain name. (alias or tableName)
1121: * @param steps steps to search through
1122: * @param stepAlias alias to search for
1123: * @return The Step if found, otherwise null
1124: * @throws ClassCastException if list does not contain only Steps
1125: */
1126: public static Step searchStep(List<Step> steps, String stepAlias) {
1127: if (log.isDebugEnabled()) {
1128: log.debug("Searching '" + stepAlias + "' in " + steps);
1129: }
1130: // first try aliases
1131: for (Step step : steps) {
1132: if (stepAlias.equals(step.getAlias())) {
1133: return step;
1134: }
1135: }
1136: // if no aliases found, try table names
1137: for (Step step : steps) {
1138: if (stepAlias.equals(step.getTableName())) {
1139: return step;
1140: }
1141: }
1142: return null;
1143: }
1144:
1145: /**
1146: * Returns the NodeQuery returning the given Node. This query itself is not very useful, because
1147: * you already have its result (the node), but it is convenient as a base query for many other
1148: * goals.
1149: *
1150: * If the node is uncommited, it cannot be queried, and the node query returning all nodes from
1151: * the currect type will be returned.
1152: *
1153: * @param node Node to create the query from
1154: * @return A new NodeQuery object
1155: */
1156: public static NodeQuery createNodeQuery(Node node) {
1157: NodeManager nm = node.getNodeManager();
1158: NodeQuery query = node.getCloud().createNodeQuery(); // use the version which can accept more steps
1159: Step step = query.addStep(nm);
1160: query.setNodeStep(step);
1161: if (!node.isNew()) {
1162: query.addNode(step, node);
1163: }
1164: return query;
1165: }
1166:
1167: /**
1168: * Returns a query to find the nodes related to the given node.
1169: * @param node start node
1170: * @param otherNodeManager node manager on the other side of the relation
1171: * @param role role of the relation
1172: * @param direction direction of the relation
1173: * @return A new NodeQuery object
1174: */
1175: public static NodeQuery createRelatedNodesQuery(Node node,
1176: NodeManager otherNodeManager, String role, String direction) {
1177: NodeQuery query = createNodeQuery(node);
1178: if (otherNodeManager == null)
1179: otherNodeManager = node.getCloud().getNodeManager("object");
1180: RelationStep step = query.addRelationStep(otherNodeManager,
1181: role, direction);
1182: query.setNodeStep(step.getNext());
1183: return query;
1184: }
1185:
1186: /**
1187: * Returns a query to find the relations nodes of the given node.
1188: * @param node start node
1189: * @param otherNodeManager node manager on the other side of the relation
1190: * @param role role of the relation
1191: * @param direction direction of the relation
1192: * @return A new NodeQuery object
1193: */
1194: public static NodeQuery createRelationNodesQuery(Node node,
1195: NodeManager otherNodeManager, String role, String direction) {
1196: NodeQuery query = createNodeQuery(node);
1197: if (otherNodeManager == null)
1198: otherNodeManager = node.getCloud().getNodeManager("object");
1199: RelationStep step = query.addRelationStep(otherNodeManager,
1200: role, direction);
1201: query.setNodeStep(step);
1202: return query;
1203: }
1204:
1205: /**
1206: * Returns a query to find the relations nodes between two given nodes.
1207: *
1208: * To test <em>whether</em> to nodes are related you can use e.g.:
1209: * <code>
1210: * if (Queries.count(Queries.createRelationNodesQuery(node1, node2, "posrel", null)) > 0) {
1211: * ..
1212: * }
1213: * </code>
1214: * @param node start node
1215: * @param otherNode node on the other side of the relation
1216: * @param role role of the relation
1217: * @param direction direction of the relation
1218: * @return A new NodeQuery object
1219: * @since MMBase-1.8
1220: */
1221: public static NodeQuery createRelationNodesQuery(Node node,
1222: Node otherNode, String role, String direction) {
1223: NodeQuery query = createNodeQuery(node);
1224: NodeManager otherNodeManager = otherNode.getNodeManager();
1225: RelationStep step = query.addRelationStep(otherNodeManager,
1226: role, direction);
1227: Step nextStep = step.getNext();
1228: query.addNode(nextStep, otherNode.getNumber());
1229: query.setNodeStep(step);
1230: return query;
1231: }
1232:
1233: /**
1234: * Queries a list of cluster nodes, using a {@link org.mmbase.bridge.NodeQuery} (so al fields of
1235: * one step are available), plus some fields of the relation step. The actual node can be got
1236: * from the node cache by doing a {@link org.mmbase.bridge.Node#getNodeValue} with the {@link
1237: * org.mmbase.bridge.NodeList#NODESTEP_PROPERTY} property. The fields of the relation can be got by
1238: * prefixing their names by the role and a dot (as normal in multilevel results).
1239: * @param node start node
1240: * @param otherNodeManager node manager on the other side of the relation
1241: * @param role role of the relation
1242: * @param direction direction of the relation
1243: * @param relationFields Comma separated string of fields which must be queried from the relation step
1244: * @param sortOrders Comma separated string of fields of sortorders, or the empty string or <code>null</code>
1245: * So, this methods is targeted at the use of 'posrel' and similar fields, because sorting on other fields isn't possible right now.
1246: * @since MMBase-1.8
1247: * @todo EXPERIMENTAL
1248: */
1249: public static NodeList getRelatedNodes(Node node,
1250: NodeManager otherNodeManager, String role,
1251: String direction, String relationFields, String sortOrders) {
1252: NodeQuery q = Queries.createRelatedNodesQuery(node,
1253: otherNodeManager, role, direction);
1254: addRelationFields(q, role, relationFields, sortOrders);
1255: return q.getCloud().getList(q);
1256: }
1257:
1258: /**
1259: * @since MMBase-1.8
1260: */
1261: public static NodeQuery addRelationFields(NodeQuery q, String role,
1262: String relationFields, String sortOrders) {
1263: List<String> list = StringSplitter.split(relationFields);
1264: List<String> orders = StringSplitter.split(sortOrders);
1265: Iterator<String> j = orders.iterator();
1266: for (String fieldName : list) {
1267: StepField sf = q.addField(role + "." + fieldName);
1268: if (j.hasNext()) {
1269: String so = j.next();
1270: q.addSortOrder(sf, getSortOrder(so));
1271: }
1272: }
1273: return q;
1274: }
1275:
1276: /**
1277: * Add a sortorder (DESCENDING) on al the'number' fields of the query, on which there is not yet a
1278: * sortorder. This ensures that the query result is ordered uniquely.
1279: * @param q query to change
1280: * @return The changed Query
1281: */
1282: public static Query sortUniquely(final Query q) {
1283: List<Step> steps = null;
1284:
1285: // remove the ones which are already sorted
1286: for (SortOrder sortOrder : q.getSortOrders()) {
1287: if (sortOrder.getField().getFieldName().equals("number")) {
1288: Step step = sortOrder.getField().getStep();
1289: if (steps == null) {
1290: // instantiate new ArrayList only if really necessary
1291: steps = new ArrayList<Step>(q.getSteps());
1292: }
1293: steps.remove(step);
1294: }
1295: }
1296: if (steps == null) {
1297: steps = q.getSteps();
1298: }
1299: // add sort order on the remaining ones:
1300: for (Step step : steps) {
1301: StepField sf = q.createStepField(step, "number");
1302: if (sf == null) {
1303: throw new RuntimeException(
1304: "Create stepfield for 'number' field returned null!");
1305: }
1306: q.addSortOrder(sf, SortOrder.ORDER_DESCENDING);
1307: }
1308: return q;
1309: }
1310:
1311: /**
1312: * Make sure all sorted fields are queried
1313: * @since MMBase-1.8
1314: */
1315: public static Query addSortedFields(Query q) {
1316: List<StepField> fields = q.getFields();
1317: for (SortOrder order : q.getSortOrders()) {
1318: StepField field = order.getField();
1319: Step s = field.getStep();
1320: StepField sf = q.createStepField(s, q.getCloud()
1321: .getNodeManager(s.getTableName()).getField(
1322: field.getFieldName()));
1323: if (!fields.contains(sf)) {
1324: q.addField(s, q.getCloud().getNodeManager(
1325: s.getTableName())
1326: .getField(field.getFieldName()));
1327: }
1328: }
1329: return q;
1330: }
1331:
1332: /**
1333: * Obtains a value for the field of a sortorder from a given node.
1334: * Used to set constraints based on sortorder.
1335: * @since MMBase-1.8
1336: */
1337: public static Object getSortOrderFieldValue(Node node,
1338: SortOrder sortOrder) {
1339: String fieldName = sortOrder.getField().getFieldName();
1340: if (node == null)
1341: throw new IllegalArgumentException("No node given");
1342: Object value = node.getValue(fieldName);
1343: if (value == null) {
1344: Step step = sortOrder.getField().getStep();
1345: String pref = step.getAlias();
1346: if (pref == null) {
1347: pref = step.getTableName();
1348: }
1349: value = node.getValue(pref + "." + fieldName);
1350:
1351: }
1352: if (value instanceof Node) {
1353: value = ((Node) value).getNumber();
1354: }
1355: return value;
1356: }
1357:
1358: /**
1359: * Compare tho nodes, with a SortOrder. This determins where a certain node is smaller or bigger than a certain other node, with respect to some SortOrder.
1360: * This is used by {@link #compare(Node, Node, List)}
1361: *
1362: * If node2 is only 'longer' then node1, but otherwise equal, then it is bigger.
1363: *
1364: * @since MMBase-1.8
1365: */
1366: public static int compare(Node node1, Node node2,
1367: SortOrder sortOrder) {
1368: return compare(getSortOrderFieldValue(node1, sortOrder),
1369: getSortOrderFieldValue(node2, sortOrder), sortOrder);
1370:
1371: }
1372:
1373: /**
1374: * @since MMBase-1.8
1375: */
1376: public static int compare(Object value, Object value2,
1377: SortOrder sortOrder) {
1378: int result;
1379: // compare values - if they differ, detemrine whether
1380: // they are bigger or smaller and return the result
1381: // remaining fields are not of interest ionce a difference is found
1382: if (value == null) {
1383: if (value2 != null) {
1384: return 1;
1385: } else {
1386: result = 0;
1387: }
1388: } else if (value2 == null) {
1389: return -1;
1390: } else {
1391: // compare the results
1392: try {
1393: result = ((Comparable<Object>) value).compareTo(value2);
1394: } catch (ClassCastException cce) {
1395: // This should not occur, and indicates very odd values are being sorted on (i.e. byte arrays).
1396: // warn and ignore this sortorder
1397: log.warn("Cannot compare values " + value + " and "
1398: + value2 + " in sortorder field "
1399: + sortOrder.getField().getFieldName()
1400: + " in step "
1401: + sortOrder.getField().getStep().getAlias());
1402: result = 0;
1403: }
1404: }
1405: // if the order of this field is descending,
1406: // then the result of the comparison is the reverse (the node is 'greater' if the value is 'less' )
1407: if (sortOrder.getDirection() == SortOrder.ORDER_DESCENDING) {
1408: result = -result;
1409: }
1410: return result;
1411: }
1412:
1413: /**
1414: * Does a field-by-field compare of two Node objects, on the fields used to order the nodes.
1415: * This is used to determine whether a node comes after or before another, in a certain query result.
1416: *
1417: * @return -1 if node1 is smaller than node 2, 0 if both nodes are equals, and +1 is node 1 is greater than node 2.
1418: * @since MMBase-1.8
1419: */
1420: public static int compare(Node node1, Node node2,
1421: List<SortOrder> sortOrders) {
1422: if (node1 == null)
1423: return -1;
1424: if (node2 == null)
1425: return +1;
1426: int result = 0;
1427: Iterator<SortOrder> i = sortOrders.iterator();
1428: while (result == 0 && i.hasNext()) {
1429: SortOrder order = i.next();
1430: result = compare(node1, node2, order);
1431: }
1432: // if all fields match - return 0 as if equal
1433: return result;
1434: }
1435:
1436: public static void main(String[] argv) {
1437: System.out
1438: .println(ConstraintParser
1439: .convertClauseToDBS("(([cpsettings.status]='[A]' OR [cpsettings.status]='I') AND [users.account] != '') and (lower([users.account]) LIKE '%t[est%' OR lower([users.email]) LIKE '%te]st%' OR lower([users.firstname]) LIKE '%t[e]st%' OR lower([users.lastname]) LIKE '%]test%')"));
1440: }
1441:
1442: }
|