Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/objectstyle/cayenne/access/util/QueryUtils.java


1   /* ====================================================================
2    * 
3    * The ObjectStyle Group Software License, Version 1.0 
4    *
5    * Copyright (c) 2002-2003 The ObjectStyle Group 
6    * and individual authors of the software.  All rights reserved.
7    *
8    * Redistribution and use in source and binary forms, with or without
9    * modification, are permitted provided that the following conditions
10   * are met:
11   *
12   * 1. Redistributions of source code must retain the above copyright
13   *    notice, this list of conditions and the following disclaimer. 
14   *
15   * 2. Redistributions in binary form must reproduce the above copyright
16   *    notice, this list of conditions and the following disclaimer in
17   *    the documentation and/or other materials provided with the
18   *    distribution.
19   *
20   * 3. The end-user documentation included with the redistribution, if
21   *    any, must include the following acknowlegement:  
22   *       "This product includes software developed by the 
23   *        ObjectStyle Group (http://objectstyle.org/)."
24   *    Alternately, this acknowlegement may appear in the software itself,
25   *    if and wherever such third-party acknowlegements normally appear.
26   *
27   * 4. The names "ObjectStyle Group" and "Cayenne" 
28   *    must not be used to endorse or promote products derived
29   *    from this software without prior written permission. For written 
30   *    permission, please contact andrus@objectstyle.org.
31   *
32   * 5. Products derived from this software may not be called "ObjectStyle"
33   *    nor may "ObjectStyle" appear in their names without prior written
34   *    permission of the ObjectStyle Group.
35   *
36   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39   * DISCLAIMED.  IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
40   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47   * SUCH DAMAGE.
48   * ====================================================================
49   *
50   * This software consists of voluntary contributions made by many
51   * individuals on behalf of the ObjectStyle Group.  For more
52   * information on the ObjectStyle Group, please see
53   * <http://objectstyle.org/>.
54   *
55   */
56  package org.objectstyle.cayenne.access.util;
57  
58  import java.util.HashMap;
59  import java.util.Iterator;
60  import java.util.List;
61  import java.util.ListIterator;
62  import java.util.Map;
63  
64  import org.apache.log4j.Logger;
65  import org.objectstyle.cayenne.CayenneRuntimeException;
66  import org.objectstyle.cayenne.DataObject;
67  import org.objectstyle.cayenne.FlattenedObjectId;
68  import org.objectstyle.cayenne.ObjectId;
69  import org.objectstyle.cayenne.access.QueryEngine;
70  import org.objectstyle.cayenne.exp.Expression;
71  import org.objectstyle.cayenne.exp.ExpressionException;
72  import org.objectstyle.cayenne.exp.ExpressionFactory;
73  import org.objectstyle.cayenne.exp.ExpressionTraversal;
74  import org.objectstyle.cayenne.exp.TraversalHelper;
75  import org.objectstyle.cayenne.map.DbRelationship;
76  import org.objectstyle.cayenne.map.Entity;
77  import org.objectstyle.cayenne.map.ObjAttribute;
78  import org.objectstyle.cayenne.map.ObjEntity;
79  import org.objectstyle.cayenne.map.ObjRelationship;
80  import org.objectstyle.cayenne.map.Relationship;
81  import org.objectstyle.cayenne.query.DeleteQuery;
82  import org.objectstyle.cayenne.query.InsertQuery;
83  import org.objectstyle.cayenne.query.PrefetchSelectQuery;
84  import org.objectstyle.cayenne.query.SelectQuery;
85  import org.objectstyle.cayenne.query.UpdateQuery;
86  
87  /**
88   * Implements helper methods that perform different query-related operations.
89   * <i>May be deprecated in the future, after its functionality is moved to the 
90   * places where it is used now.</i>
91   * 
92   * @author Andrei Adamchik
93   *
94   */
95  public class QueryUtils {
96    private static Logger logObj = Logger.getLogger(QueryUtils.class);
97    
98    private static Map putModifiedAttribute(
99      Map aMap,
100     String name,
101     Object value) {
102     if (aMap == null) {
103       aMap = new HashMap();
104     }
105     aMap.put(name, value);
106     return aMap;
107   }
108 
109   /**
110    * Returns a map of the properties of dataObject which have actually changed
111    * compared to the objects commited snapshot.  Actual change is determined
112    * by using equals() (true implies no change).
113    * Will return null if there are no changes
114    * 
115    * @param dataObject dataObject that may have changes
116    */
117   public static Map updatedProperties(DataObject dataObject) {
118     Map result = null;
119     //Lazily created to avoid creating too many unnecessary objects
120 
121     Map committedSnapshot = dataObject.getCommittedSnapshot();
122     Map currentSnapshot = dataObject.getCurrentSnapshot();
123 
124     Iterator it = currentSnapshot.keySet().iterator();
125     while (it.hasNext()) {
126       String attrName = (String) it.next();
127       Object newValue = currentSnapshot.get(attrName);
128 
129       // if snapshot exists, compare old values and new values,
130       // only add attribute to the update clause if the value has changed
131       if (committedSnapshot != null) {
132         Object oldValue = committedSnapshot.get(attrName);
133         if (oldValue != null && !oldValue.equals(newValue)) {
134           result = putModifiedAttribute(result, attrName, newValue);
135         } else if (oldValue == null && newValue != null) {
136           result = putModifiedAttribute(result, attrName, newValue);
137         }
138       }
139       // if no snapshot exists, just add the fresh value to update clause
140       else {
141         result = putModifiedAttribute(result, attrName, newValue);
142       }
143     }
144 
145     // original snapshot can have extra keys that are missing in the
146     // current snapshot; process those
147     if (committedSnapshot != null) {
148       Iterator origit = committedSnapshot.keySet().iterator();
149       while (origit.hasNext()) {
150         String attrName = (String) origit.next();
151         if (currentSnapshot.containsKey(attrName))
152           continue;
153 
154         Object oldValue = committedSnapshot.get(attrName);
155         if (oldValue == null)
156           continue;
157         result = putModifiedAttribute(result, attrName, null);
158       }
159     }
160     return result; //Might be null if nothing was actually changed
161   }
162 
163   /** Returns an update query for the DataObject that can be used to commit
164    *  object state changes to the database. If no changes are found, null is returned.
165    *
166    *  @param dataObject data object that potentially can have changes that need
167    *  to be synchronized with the database.
168    */
169   public static UpdateQuery updateQuery(DataObject dataObject) {
170     UpdateQuery upd = new UpdateQuery();
171 
172     ObjectId id = dataObject.getObjectId();
173     upd.setRoot(dataObject.getClass());
174     Map modifiedProperties = updatedProperties(dataObject);
175     if ((modifiedProperties != null) && (modifiedProperties.size() > 0)) {
176       Iterator keyIterator = modifiedProperties.keySet().iterator();
177       while (keyIterator.hasNext()) {
178         String key = (String) keyIterator.next();
179         upd.addUpdAttribute(key, modifiedProperties.get(key));
180       }
181       // set qualifier
182       upd.setQualifier(
183         ExpressionFactory.matchAllDbExp(
184           id.getIdSnapshot(),
185           Expression.EQUAL_TO));
186       return upd;
187     }
188 
189     return null;
190   }
191 
192   /** Generates a delete query for a specified data object */
193   public static DeleteQuery deleteQuery(DataObject dataObject) {
194     DeleteQuery del = new DeleteQuery();
195     ObjectId id = dataObject.getObjectId();
196     del.setRoot(dataObject.getClass());
197     del.setQualifier(
198       ExpressionFactory.matchAllDbExp(
199         id.getIdSnapshot(),
200         Expression.EQUAL_TO));
201     return del;
202   }
203 
204   /** Generates an insert query for a specified data object.
205    *
206    *  @param dataObject new data object that need to be inserted to the database.
207    *  @param permId permanent object id that will be assigned to this data object after
208    *  it is committed to the database.
209    */
210   public static InsertQuery insertQuery(
211     Map objectSnapshot,
212     ObjectId permId) {
213     InsertQuery ins = new InsertQuery();
214     ins.setRoot(permId.getObjClass());
215     ins.setObjectSnapshot(objectSnapshot);
216     ins.setObjectId(permId);
217     return ins;
218   }
219 
220   public static SelectQuery selectObjectForFlattenedObjectId(
221     QueryEngine e,
222     FlattenedObjectId oid) {
223     //Create a selectquery using the relationship name
224     // and source id snapshot of the FlattenedObjectId
225     SelectQuery sel = new SelectQuery();
226     sel.setRoot(oid.getObjClass());
227 
228     DataObject sourceObject = oid.getSourceObject();
229     Expression sourceExpression =
230       ExpressionFactory.matchAllDbExp(
231         sourceObject.getObjectId().getIdSnapshot(),
232         Expression.EQUAL_TO);
233     ObjEntity ent = e.getEntityResolver().lookupObjEntity(sourceObject);
234 
235     sel.setQualifier(
236       transformQualifier(
237         ent,
238         sourceExpression,
239         oid.getRelationshipName()));
240 
241     return sel;
242   }
243   /** 
244    * Creates and returns a select query that can be used to 
245    * fetch an object given an ObjectId.
246    */
247   public static SelectQuery selectObjectForId(ObjectId oid) {
248     if (oid instanceof FlattenedObjectId) {
249       throw new CayenneRuntimeException(
250         "Please use "
251           + "selectObjectForFlattenedObjectId(QueryEngine, FlattenedObjectId)"
252           + " to create a select query for a FlattenedObjectId");
253     }
254     SelectQuery sel = new SelectQuery();
255     sel.setRoot(oid.getObjClass());
256 
257     sel.setQualifier(
258       ExpressionFactory.matchAllDbExp(
259         oid.getIdSnapshot(),
260         Expression.EQUAL_TO));
261     return sel;
262   }
263 
264   /** 
265    * Creates and returns a select query that can be used to 
266    * fetch a list of objects given a list of ObjectIds.
267    * All ObjectIds must belong to the same entity.
268    */
269   public static SelectQuery selectQueryForIds(List oids) {
270     if (oids == null || oids.size() == 0) {
271       throw new IllegalArgumentException("List must contain at least one ObjectId");
272     }
273 
274     SelectQuery sel = new SelectQuery();
275     sel.setRoot(((ObjectId) oids.get(0)).getObjClass());
276 
277     Iterator it = oids.iterator();
278 
279     ObjectId firstId = (ObjectId) it.next();
280     Expression exp =
281       ExpressionFactory.matchAllDbExp(
282         firstId.getIdSnapshot(),
283         Expression.EQUAL_TO);
284 
285     while (it.hasNext()) {
286       ObjectId anId = (ObjectId) it.next();
287       exp =
288         exp.orExp(
289           ExpressionFactory.matchAllDbExp(
290             anId.getIdSnapshot(),
291             Expression.EQUAL_TO));
292     }
293 
294     sel.setQualifier(exp);
295     return sel;
296   }
297 
298   /** 
299    * Creates and returns SelectQuery for a given SelectQuery and
300    * relationship prefetching path.
301    */
302   public static PrefetchSelectQuery selectPrefetchPath(
303     QueryEngine e,
304     SelectQuery q,
305     String prefetchPath) {
306     ObjEntity ent = e.getEntityResolver().lookupObjEntity(q);
307     PrefetchSelectQuery newQ = new PrefetchSelectQuery();
308 
309     newQ.setPrefetchPath(prefetchPath);
310     newQ.setRootQuery(q);
311     Expression exp =
312       ExpressionFactory.unaryExp(Expression.OBJ_PATH, prefetchPath);
313     Iterator it = ent.resolvePathComponents(exp);
314     Relationship r = null;
315     int relCount = 0;
316     while (it.hasNext()) {
317       r = (Relationship) it.next();
318       relCount++;
319     }
320 
321     if (r != null) {
322       ObjRelationship objR = (ObjRelationship) r;
323       newQ.setRoot(objR.getTargetEntity());
324       newQ.setQualifier(
325         transformQualifier(ent, q.getQualifier(), prefetchPath));
326       if ((relCount == 1) && objR.isToMany() && !objR.isFlattened()) {
327         //A one step toMany relationship needs the special handling
328         newQ.setSingleStepToManyRelationship(objR);
329       }
330       return newQ;
331     } else {
332       // TODO: what else could we do here?
333       return null;
334     }
335   }
336 
337   /**
338    * Translates qualifier applicable for one ObjEntity into a
339    * qualifier for a related ObjEntity.
340    * @param ent the entity to which the original qualifier (<code>qual</code>) 
341    * applies
342    * @param qual the qualifier on <code>ent</code>
343    * @param relPath a relationship path from <code>ent</code> to some target entity
344    * @return Expression which, when applied to the target entity of relPath, 
345    * will give the union of the objects that would be obtained by following 
346    * relPath from all of the objects in <code>ent</code> that match <code>qual</code>
347    */
348   public static Expression transformQualifier(
349     ObjEntity ent,
350     Expression qual,
351     String relPath) {
352     if (qual == null) {
353       return null;
354     }
355 
356     ExpressionTranslator trans = new ExpressionTranslator(ent, relPath);
357     ExpressionTraversal parser = new ExpressionTraversal();
358     parser.setHandler(trans);
359     parser.traverseExpression(qual);
360 
361     return trans.getPeer(qual);
362   }
363 
364   /**
365    * Generates a SelectQuery that can be used to fetch 
366    * relationship destination objects given a source object
367    * of a to-many relationship. 
368    */
369   public static SelectQuery selectRelationshipObjects(
370     QueryEngine e,
371     DataObject source,
372     String relName) {
373 
374     ObjEntity ent = e.getEntityResolver().lookupObjEntity(source);
375     ObjRelationship rel = (ObjRelationship) ent.getRelationship(relName);
376     ObjEntity destEnt = (ObjEntity) rel.getTargetEntity();
377 
378     List dbRels = rel.getDbRelationships();
379 
380     // sanity check
381     if (dbRels == null || dbRels.size() == 0) {
382       throw new CayenneRuntimeException(
383         "ObjRelationship '" + rel.getName() + "' is unmapped.");
384     }
385 
386     // build a reverse DB path
387     // ...while reverse ObjRelationship may be absent,
388     // reverse DB must always be there...
389     StringBuffer buf = new StringBuffer();
390     ListIterator it = dbRels.listIterator(dbRels.size());
391     while (it.hasPrevious()) {
392       if (buf.length() > 0) {
393         buf.append(".");
394       }
395       DbRelationship dbRel = (DbRelationship) it.previous();
396       DbRelationship reverse = dbRel.getReverseRelationship();
397 
398       // another sanity check
399       if (reverse == null) {
400         throw new CayenneRuntimeException(
401           "DbRelationship '"
402             + dbRel.getName()
403             + "' has no reverse relationship");
404       }
405 
406       buf.append(reverse.getName());
407     }
408 
409     SelectQuery sel = new SelectQuery(destEnt);
410     sel.setQualifier(
411       ExpressionFactory.binaryDbPathExp(
412         Expression.EQUAL_TO,
413         buf.toString(),
414         source));
415 
416     return sel;
417   }
418 
419   static final class ExpressionTranslator extends TraversalHelper {
420     protected Map expMap = new HashMap();
421     protected Map expFill = new HashMap();
422     protected ObjEntity ent;
423     protected String relationshipPath;
424     protected String relationshipDbPath;
425     protected String prependObjPath;
426     protected String prependDbPath;
427 
428     public ExpressionTranslator(ObjEntity e, String relPath) {
429       this.ent = e;
430       this.relationshipPath = relPath;
431       this.relationshipDbPath = forwardDbPath(e, relPath);
432       this.prependObjPath = reversePath(e, relPath);
433       this.prependDbPath = reverseDbPath(e, relPath);
434     }
435 
436     public Expression getPeer(Expression orig) {
437       return (Expression) expMap.get(orig);
438     }
439 
440     private int getOperandIndex(Expression orig) {
441       Integer indObj = (Integer) expFill.get(orig);
442       int ind = (indObj != null) ? indObj.intValue() + 1 : 0;
443       expFill.put(orig, new Integer(ind));
444 
445       return ind;
446     }
447 
448     /**
449      * For a relationship path from source to target, builds a reverse path 
450      * from target to source.
451      */
452     public String reversePath(ObjEntity e, String relPath) {
453       Expression exp =
454         ExpressionFactory.unaryExp(Expression.OBJ_PATH, relPath);
455       Iterator it = e.resolvePathComponents(exp);
456       StringBuffer buf = new StringBuffer();
457       boolean hasRels = false;
458 
459       while (it.hasNext()) {
460         ObjRelationship rel = (ObjRelationship) it.next();
461         ObjRelationship reverse = rel.getReverseRelationship();
462         if (reverse == null) {
463           //Couldn't create reverse obj path because of a missing reverse
464           // relationship
465           return null;
466         }
467         if (hasRels) {
468           buf.insert(0, Entity.PATH_SEPARATOR);
469         }
470 
471         buf.insert(0, reverse.getName());
472         hasRels = true;
473       }
474 
475       return buf.toString();
476     }
477 
478     public String forwardDbPath(ObjEntity e, String relPath) {
479       Expression exp =
480         ExpressionFactory.unaryExp(Expression.OBJ_PATH, relPath);
481       Iterator it = e.resolvePathComponents(exp);
482       StringBuffer buf = new StringBuffer();
483 
484       while (it.hasNext()) {
485         ObjRelationship rel = (ObjRelationship) it.next();
486         Iterator dbRels = rel.getDbRelationships().iterator();
487         while (dbRels.hasNext()) {
488           DbRelationship r = (DbRelationship) dbRels.next();
489           if (buf.length() > 0) {
490             buf.append(Entity.PATH_SEPARATOR);
491           }
492 
493           buf.append(r.getName());
494         }
495       }
496 
497       return buf.toString();
498     }
499 
500     /**
501      * For a relationship path from source to target, builds a reverse path 
502      * from target to source.
503      */
504     public String reverseDbPath(ObjEntity e, String relPath) {
505       Expression exp =
506         ExpressionFactory.unaryExp(Expression.DB_PATH, relPath);
507       Iterator it = e.resolvePathComponents(exp);
508       StringBuffer buf = new StringBuffer();
509       boolean hasRels = false;
510 
511       while (it.hasNext()) {
512         ObjRelationship rel = (ObjRelationship) it.next();
513 
514         Iterator dbRels = rel.getDbRelationships().iterator();
515         while (dbRels.hasNext()) {
516           DbRelationship dbRel = (DbRelationship) dbRels.next();
517           DbRelationship reverse = dbRel.getReverseRelationship();
518 
519           if (hasRels) {
520             buf.insert(0, Entity.PATH_SEPARATOR);
521           }
522 
523           buf.insert(0, reverse.getName());
524           hasRels = true;
525         }
526       }
527 
528       return buf.toString();
529     }
530 
531     /** 
532      * Creates expression of the same type and same operands 
533      * as the original expression. Operands of the new expression
534      * are set to null.
535      */
536     public Expression createExpressionOfType(Expression e)
537       throws ExpressionException {
538       try {
539         Expression exp = (Expression) e.getClass().newInstance();
540         exp.setType(e.getType());
541         return exp;
542       } catch (Exception ex) {
543         throw new ExpressionException(
544           "Error instantiating expression.",
545           ex);
546       }
547     }
548 
549     private void processOperand(Object operand, Expression parentNode) {
550       // attach operand to parent at index
551       if (parentNode != null) {
552         int ind = getOperandIndex(parentNode);
553         Expression parentPeer = getPeer(parentNode);
554         // operands of object expression need translation
555         if (parentPeer.getType() == Expression.OBJ_PATH) {
556           if (prependObjPath == null) {
557             //Change to a db path type expression,
558             // because there is no ObjPath
559             parentPeer.setType(Expression.DB_PATH);
560 
561             Iterator it = ent.resolvePathComponents(parentNode);
562             Object lastComponent = null;
563             while (it.hasNext()) {
564               lastComponent = it.next();
565             }
566             if (lastComponent instanceof ObjAttribute) {
567               ObjAttribute objAttr = (ObjAttribute) lastComponent;
568               operand =
569                 processPath(
570                   objAttr.getDbAttribute().getName(),
571                   relationshipDbPath,
572                   prependDbPath);
573             } else {
574               //Not sure what to do... just hope that this
575               // will work I guess?
576               operand =
577                 processPath(
578                   (String) operand,
579                   relationshipDbPath,
580                   prependDbPath);
581 
582             }
583 
584           } else {
585             //There is a reverse obj path.. use it
586             operand =
587               processPath(
588                 (String) operand,
589                 relationshipPath,
590                 prependObjPath);
591           }
592         } else if (parentPeer.getType() == Expression.DB_PATH) {
593           operand =
594             processPath(
595               (String) operand,
596               relationshipDbPath,
597               prependDbPath);
598         }
599         parentPeer.setOperand(ind, operand);
600       }
601     }
602 
603     private String processPath(
604       String path,
605       String toPrefix,
606       String fromPrefix) {
607       if (path.equals(toPrefix)) {
608         // 1. Path ends with prefetch entity - match PK
609         throw new CayenneRuntimeException(
610           "Prefetching with path ending on "
611             + "prefetch entity is not supported yet.");
612       } else if (path.startsWith(toPrefix + Entity.PATH_SEPARATOR)) {
613         // 2. Path starts with prefetch entity - strip it.
614         return path.substring(
615           (toPrefix + Entity.PATH_SEPARATOR).length());
616       } else {
617         // 3. Path has nothing to do with prefetch entity - prepend rel from prefetch
618         return fromPrefix + Entity.PATH_SEPARATOR + path;
619       }
620     }
621 
622     private void processNode(Expression node, Expression parentNode) {
623       Expression e = createExpressionOfType(node);
624       expMap.put(node, e);
625       processOperand(e, parentNode);
626     }
627 
628     public void startUnaryNode(Expression node, Expression parentNode) {
629       processNode(node, parentNode);
630     }
631 
632     public void startBinaryNode(Expression node, Expression parentNode) {
633       processNode(node, parentNode);
634     }
635 
636     public void startTernaryNode(Expression node, Expression parentNode) {
637       processNode(node, parentNode);
638     }
639 
640     public void objectNode(Object leaf, Expression parentNode) {
641       processOperand(leaf, parentNode);
642     }
643 
644     public void startListNode(Expression node, Expression parentNode) {
645       processNode(node, parentNode);
646     }
647 
648   }
649 }