KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectstyle > cayenne > map > EntityResolver


1 /* ====================================================================
2  *
3  * The ObjectStyle Group Software License, version 1.1
4  * ObjectStyle Group - http://objectstyle.org/
5  *
6  * Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
7  * of the software. All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  * notice, this list of conditions and the following disclaimer in
18  * the documentation and/or other materials provided with the
19  * distribution.
20  *
21  * 3. The end-user documentation included with the redistribution, if any,
22  * must include the following acknowlegement:
23  * "This product includes software developed by independent contributors
24  * and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
25  * Alternately, this acknowlegement may appear in the software itself,
26  * if and wherever such third-party acknowlegements normally appear.
27  *
28  * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
29  * or promote products derived from this software without prior written
30  * permission. For written permission, email
31  * "andrus at objectstyle dot org".
32  *
33  * 5. Products derived from this software may not be called "ObjectStyle"
34  * or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
35  * names without prior written permission.
36  *
37  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40  * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
41  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48  * SUCH DAMAGE.
49  * ====================================================================
50  *
51  * This software consists of voluntary contributions made by many
52  * individuals and hosted on ObjectStyle Group web site. For more
53  * information on the ObjectStyle Group, please see
54  * <http://objectstyle.org/>.
55  */

56 package org.objectstyle.cayenne.map;
57
58 import java.util.ArrayList JavaDoc;
59 import java.util.Collection JavaDoc;
60 import java.util.Collections JavaDoc;
61 import java.util.HashMap JavaDoc;
62 import java.util.Iterator JavaDoc;
63 import java.util.List JavaDoc;
64 import java.util.Map JavaDoc;
65
66 import org.apache.commons.collections.collection.CompositeCollection;
67 import org.apache.log4j.Logger;
68 import org.objectstyle.cayenne.CayenneRuntimeException;
69 import org.objectstyle.cayenne.DataObject;
70 import org.objectstyle.cayenne.ObjectId;
71 import org.objectstyle.cayenne.TempObjectId;
72 import org.objectstyle.cayenne.client.ClientEntityResolver;
73 import org.objectstyle.cayenne.conf.Configuration;
74 import org.objectstyle.cayenne.distribution.GlobalID;
75 import org.objectstyle.cayenne.query.ProcedureQuery;
76 import org.objectstyle.cayenne.query.Query;
77
78 /**
79  * Represents a virtual shared namespace for zero or more DataMaps. EntityResolver is used
80  * by Cayenne runtime and in addition to EntityNamespace interface methods implements
81  * other convenience lookups, resolving entities for Queries, Java classes, etc. DataMaps
82  * can be added or removed dynamically at runtime.
83  * <p>
84  * EntityResolver is thread-safe.
85  * </p>
86  *
87  * @since 1.1 In 1.1 EntityResolver was moved from the access package.
88  * @author Andrei Adamchik
89  */

90 public class EntityResolver implements MappingNamespace {
91
92     private static final Logger logObj = Logger.getLogger(EntityResolver.class);
93
94     protected boolean indexedByClass;
95     protected Map JavaDoc queryCache;
96     protected Map JavaDoc dbEntityCache;
97     protected Map JavaDoc objEntityCache;
98     protected Map JavaDoc procedureCache;
99     protected List JavaDoc maps;
100     protected List JavaDoc mapsRef;
101     protected Map JavaDoc entityInheritanceCache;
102
103     /**
104      * Creates new EntityResolver.
105      */

106     public EntityResolver() {
107         this.indexedByClass = true;
108         this.maps = new ArrayList JavaDoc();
109         this.mapsRef = Collections.unmodifiableList(maps);
110         this.queryCache = new HashMap JavaDoc();
111         this.dbEntityCache = new HashMap JavaDoc();
112         this.objEntityCache = new HashMap JavaDoc();
113         this.procedureCache = new HashMap JavaDoc();
114         this.entityInheritanceCache = new HashMap JavaDoc();
115     }
116
117     /**
118      * Creates new EntityResolver that indexes a collection of DataMaps.
119      */

120     public EntityResolver(Collection JavaDoc dataMaps) {
121         this();
122         this.maps.addAll(dataMaps); //Take a copy
123
this.constructCache();
124     }
125     
126     /**
127      * Converts ObjectId to GlobalID.
128      *
129      * @since 1.2
130      */

131     public GlobalID convertToGlobalID(ObjectId id) {
132         ObjEntity entity = lookupObjEntity(id.getObjectClass());
133         if (entity == null) {
134             throw new CayenneRuntimeException("Unmapped object class:"
135                     + id.getObjectClass().getName());
136         }
137
138         if (id instanceof TempObjectId) {
139             return new GlobalID(entity.getName(), ((TempObjectId) id).getKey());
140         }
141         else {
142             return new GlobalID(entity.getName(), id.getIdSnapshot());
143         }
144     }
145     
146     /**
147      * Converts GlobalID to ObjectId.
148      *
149      * @since 1.2
150      */

151     public ObjectId convertToObjectID(GlobalID id) {
152         ObjEntity entity = lookupObjEntity(id.getEntityName());
153
154         if (entity == null) {
155             throw new CayenneRuntimeException("Unknown entity:" + id.getEntityName());
156         }
157
158         Class JavaDoc objectClass = entity.getJavaClass(Thread
159                 .currentThread()
160                 .getContextClassLoader());
161         return (id.isTemporary())
162                 ? new TempObjectId(objectClass, id.getKey())
163                 : new ObjectId(objectClass, id.getObjectIdKeys());
164     }
165     
166     /**
167      * Returns ClientEntityResolver that maps client classes to entity names.
168      *
169      * @since 1.2
170      */

171     public ClientEntityResolver getClientEntityResolver() {
172         // TODO: cache client resolver
173
Map JavaDoc entityNameByClientClass = new HashMap JavaDoc();
174
175         Iterator JavaDoc it = getObjEntities().iterator();
176         while (it.hasNext()) {
177             ObjEntity entity = (ObjEntity) it.next();
178             String JavaDoc clientClassName = entity.getClientClassName();
179             if (clientClassName == null) {
180                 clientClassName = entity.getClassName();
181             }
182
183             entityNameByClientClass.put(clientClassName, entity.getName());
184         }
185
186         return new ClientEntityResolver(entityNameByClientClass);
187     }
188
189     /**
190      * Returns all DbEntities.
191      */

192     public Collection JavaDoc getDbEntities() {
193         CompositeCollection c = new CompositeCollection();
194         Iterator JavaDoc it = getDataMaps().iterator();
195         while (it.hasNext()) {
196             DataMap map = (DataMap) it.next();
197             c.addComposited(map.getDbEntities());
198         }
199
200         return c;
201     }
202
203     public Collection JavaDoc getObjEntities() {
204         CompositeCollection c = new CompositeCollection();
205         Iterator JavaDoc it = getDataMaps().iterator();
206         while (it.hasNext()) {
207             DataMap map = (DataMap) it.next();
208             c.addComposited(map.getObjEntities());
209         }
210
211         return c;
212     }
213
214     public Collection JavaDoc getProcedures() {
215         CompositeCollection c = new CompositeCollection();
216         Iterator JavaDoc it = getDataMaps().iterator();
217         while (it.hasNext()) {
218             DataMap map = (DataMap) it.next();
219             c.addComposited(map.getProcedures());
220         }
221
222         return c;
223     }
224
225     public Collection JavaDoc getQueries() {
226         CompositeCollection c = new CompositeCollection();
227         Iterator JavaDoc it = getDataMaps().iterator();
228         while (it.hasNext()) {
229             DataMap map = (DataMap) it.next();
230             c.addComposited(map.getQueries());
231         }
232
233         return c;
234     }
235
236     public DbEntity getDbEntity(String JavaDoc name) {
237         return _lookupDbEntity(name);
238     }
239
240     public ObjEntity getObjEntity(String JavaDoc name) {
241         return _lookupObjEntity(name);
242     }
243
244     public Procedure getProcedure(String JavaDoc name) {
245         return lookupProcedure(name);
246     }
247
248     public Query getQuery(String JavaDoc name) {
249         return lookupQuery(name);
250     }
251
252     public synchronized void addDataMap(DataMap map) {
253         if (!maps.contains(map)) {
254             maps.add(map);
255             map.setNamespace(this);
256             clearCache();
257         }
258     }
259
260     /**
261      * Removes all entity mappings from the cache. Cache can be rebuilt either explicitly
262      * by calling <code>constructCache</code>, or on demand by calling any of the
263      * <code>lookup...</code> methods.
264      */

265     public synchronized void clearCache() {
266         queryCache.clear();
267         dbEntityCache.clear();
268         objEntityCache.clear();
269         procedureCache.clear();
270         entityInheritanceCache.clear();
271     }
272
273     /**
274      * Creates caches of DbEntities by ObjEntity, DataObject class, and ObjEntity name
275      * using internal list of maps.
276      */

277     protected synchronized void constructCache() {
278         clearCache();
279
280         // rebuild index
281
Iterator JavaDoc mapIterator = maps.iterator();
282         while (mapIterator.hasNext()) {
283             DataMap map = (DataMap) mapIterator.next();
284
285             // index ObjEntities
286
Iterator JavaDoc objEntities = map.getObjEntities().iterator();
287             while (objEntities.hasNext()) {
288                 ObjEntity oe = (ObjEntity) objEntities.next();
289
290                 // index by name
291
objEntityCache.put(oe.getName(), oe);
292
293                 // index by class
294
String JavaDoc className = oe.getClassName();
295                 if (indexedByClass && className != null) {
296                     Class JavaDoc entityClass;
297                     try {
298                         entityClass = Configuration.getResourceLoader().loadClass(
299                                 className);
300                     }
301                     catch (ClassNotFoundException JavaDoc e) {
302                         // print a big warning and continue... DataMaps can contain all
303
// kinds of garbage...
304
logObj.warn("*** Class '"
305                                 + className
306                                 + "' not found in runtime. Ignoring.");
307                         continue;
308                     }
309
310                     if (objEntityCache.get(entityClass) != null) {
311                         throw new CayenneRuntimeException(getClass().getName()
312                                 + ": More than one ObjEntity ("
313                                 + oe.getName()
314                                 + " and "
315                                 + ((ObjEntity) objEntityCache.get(entityClass)).getName()
316                                 + ") uses the class "
317                                 + entityClass.getName());
318                     }
319
320                     objEntityCache.put(entityClass, oe);
321                     if (oe.getDbEntity() != null) {
322                         dbEntityCache.put(entityClass, oe.getDbEntity());
323                     }
324                 }
325             }
326
327             // index ObjEntity inheritance
328
objEntities = map.getObjEntities().iterator();
329             while (objEntities.hasNext()) {
330                 ObjEntity oe = (ObjEntity) objEntities.next();
331
332                 // build inheritance tree... include nodes that
333
// have no children to avoid uneeded cache rebuilding on lookup...
334
EntityInheritanceTree node = (EntityInheritanceTree) entityInheritanceCache
335                         .get(oe.getName());
336                 if (node == null) {
337                     node = new EntityInheritanceTree(oe);
338                     entityInheritanceCache.put(oe.getName(), node);
339                 }
340
341                 String JavaDoc superOEName = oe.getSuperEntityName();
342                 if (superOEName != null) {
343                     EntityInheritanceTree superNode = (EntityInheritanceTree) entityInheritanceCache
344                             .get(superOEName);
345
346                     if (superNode == null) {
347                         // do direct entity lookup to avoid recursive cache rebuild
348
ObjEntity superOE = (ObjEntity) objEntityCache.get(superOEName);
349                         if (superOE != null) {
350                             superNode = new EntityInheritanceTree(superOE);
351                             entityInheritanceCache.put(superOEName, superNode);
352                         }
353                         else {
354                             // bad mapping?
355
logObj.debug("Invalid superEntity '"
356                                     + superOEName
357                                     + "' for entity '"
358                                     + oe.getName()
359                                     + "'");
360                             continue;
361                         }
362                     }
363
364                     superNode.addChildNode(node);
365                 }
366             }
367
368             // index DbEntities
369
Iterator JavaDoc dbEntities = map.getDbEntities().iterator();
370             while (dbEntities.hasNext()) {
371                 DbEntity de = (DbEntity) dbEntities.next();
372                 dbEntityCache.put(de.getName(), de);
373             }
374
375             // index stored procedures
376
Iterator JavaDoc procedures = map.getProcedures().iterator();
377             while (procedures.hasNext()) {
378                 Procedure proc = (Procedure) procedures.next();
379                 procedureCache.put(proc.getName(), proc);
380             }
381
382             // index queries
383
Iterator JavaDoc queries = map.getQueries().iterator();
384             while (queries.hasNext()) {
385                 Query query = (Query) queries.next();
386                 String JavaDoc name = query.getName();
387                 Object JavaDoc existingQuery = queryCache.put(name, query);
388
389                 if (existingQuery != null && query != existingQuery) {
390                     throw new CayenneRuntimeException("More than one Query for name"
391                             + name);
392                 }
393             }
394         }
395     }
396
397     /**
398      * Returns a DataMap matching the name.
399      */

400     public synchronized DataMap getDataMap(String JavaDoc mapName) {
401         if (mapName == null) {
402             return null;
403         }
404
405         Iterator JavaDoc it = maps.iterator();
406         while (it.hasNext()) {
407             DataMap map = (DataMap) it.next();
408             if (mapName.equals(map.getName())) {
409                 return map;
410             }
411         }
412
413         return null;
414     }
415
416     public synchronized void setDataMaps(Collection JavaDoc maps) {
417         this.maps.clear();
418         this.maps.addAll(maps);
419         clearCache();
420     }
421
422     /**
423      * Returns an unmodifiable collection of DataMaps.
424      */

425     public Collection JavaDoc getDataMaps() {
426         return mapsRef;
427     }
428
429     /**
430      * Searches for DataMap that holds Query root object.
431      */

432     public synchronized DataMap lookupDataMap(Query q) {
433         if (q.getRoot() instanceof DataMap) {
434             return (DataMap) q.getRoot();
435         }
436
437         DbEntity entity = lookupDbEntity(q);
438         if (entity != null) {
439             return entity.getDataMap();
440         }
441
442         // try procedure
443
Procedure procedure = lookupProcedure(q);
444         return (procedure != null) ? procedure.getDataMap() : null;
445     }
446
447     /**
448      * Looks in the DataMap's that this object was created with for the DbEntity that
449      * services the specified class
450      *
451      * @return the required DbEntity, or null if none matches the specifier
452      */

453     public synchronized DbEntity lookupDbEntity(Class JavaDoc aClass) {
454         if (!indexedByClass) {
455             throw new CayenneRuntimeException("Class index is disabled.");
456         }
457         return this._lookupDbEntity(aClass);
458     }
459
460     /**
461      * Looks in the DataMap's that this object was created with for the DbEntity that
462      * services the specified data Object
463      *
464      * @return the required DbEntity, or null if none matches the specifier
465      */

466     public synchronized DbEntity lookupDbEntity(DataObject dataObject) {
467         return this._lookupDbEntity(dataObject.getClass());
468     }
469
470     /**
471      * Looks up the DbEntity for the given query by using the query's getRoot method and
472      * passing to lookupDbEntity
473      *
474      * @return the root DbEntity of the query
475      */

476     public synchronized DbEntity lookupDbEntity(Query q) {
477         Object JavaDoc root = q.getRoot();
478         if (root instanceof DbEntity) {
479             return (DbEntity) root;
480         }
481         else if (root instanceof Class JavaDoc) {
482             return this.lookupDbEntity((Class JavaDoc) root);
483         }
484         else if (root instanceof ObjEntity) {
485             return ((ObjEntity) root).getDbEntity();
486         }
487         else if (root instanceof String JavaDoc) {
488             ObjEntity objEntity = this.lookupObjEntity((String JavaDoc) root);
489             return (objEntity != null) ? objEntity.getDbEntity() : null;
490         }
491         else if (root instanceof DataObject) {
492             return this.lookupDbEntity((DataObject) root);
493         }
494         return null;
495     }
496
497     /**
498      * Returns EntityInheritanceTree representing inheritance hierarchy that starts with a
499      * given ObjEntity as root, and includes all its subentities. If ObjEntity has no
500      * known subentities, null is returned.
501      */

502     public EntityInheritanceTree lookupInheritanceTree(ObjEntity entity) {
503
504         EntityInheritanceTree tree = (EntityInheritanceTree) entityInheritanceCache
505                 .get(entity.getName());
506
507         if (tree == null) {
508             // since we keep inheritance trees for all entities, null means
509
// unknown entity...
510

511             // rebuild cache just in case some of the datamaps
512
// have changed and now contain the required information
513
constructCache();
514             tree = (EntityInheritanceTree) entityInheritanceCache.get(entity.getName());
515         }
516
517         // don't return "trivial" trees
518
return (tree == null || tree.getChildrenCount() == 0) ? null : tree;
519     }
520
521     /**
522      * Looks in the DataMap's that this object was created with for the ObjEntity that
523      * maps to the services the specified class
524      *
525      * @return the required ObjEntity or null if there is none that matches the specifier
526      */

527     public synchronized ObjEntity lookupObjEntity(Class JavaDoc aClass) {
528         if (!indexedByClass) {
529             throw new CayenneRuntimeException("Class index is disabled.");
530         }
531
532         return this._lookupObjEntity(aClass);
533     }
534
535     /**
536      * Looks in the DataMap's that this object was created with for the ObjEntity that
537      * services the specified data Object
538      *
539      * @return the required ObjEntity, or null if none matches the specifier
540      */

541     public synchronized ObjEntity lookupObjEntity(DataObject dataObject) {
542         return this._lookupObjEntity(dataObject.getClass());
543     }
544
545     /**
546      * Looks up the ObjEntity for the given query by using the query's getRoot method and
547      * passing to lookupObjEntity
548      *
549      * @return the root ObjEntity of the query
550      * @throws CayenneRuntimeException if the root of the query is a DbEntity (it is not
551      * reliably possible to map from a DbEntity to an ObjEntity as a DbEntity
552      * may be the source for multiple ObjEntities. It is not safe to rely on
553      * such behaviour).
554      */

555     public synchronized ObjEntity lookupObjEntity(Query q) {
556
557         // a special case of ProcedureQuery ...
558
// TODO: should really come up with some generic way of doing this...
559
// e.g. all queries may separate the notion of root from the notion
560
// of result type
561

562         Object JavaDoc root = (q instanceof ProcedureQuery) ? ((ProcedureQuery) q)
563                 .getResultClass(Configuration.getResourceLoader()) : q.getRoot();
564
565         if (root instanceof DbEntity) {
566             throw new CayenneRuntimeException(
567                     "Cannot safely resolve the ObjEntity for the query "
568                             + q
569                             + " because the root of the query is a DbEntity");
570         }
571         else if (root instanceof ObjEntity) {
572             return (ObjEntity) root;
573         }
574         else if (root instanceof Class JavaDoc) {
575             return this.lookupObjEntity((Class JavaDoc) root);
576         }
577         else if (root instanceof String JavaDoc) {
578             return this.lookupObjEntity((String JavaDoc) root);
579         }
580         else if (root instanceof DataObject) {
581             return this.lookupObjEntity((DataObject) root);
582         }
583
584         return null;
585     }
586
587     /**
588      * Looks in the DataMap's that this object was created with for the ObjEntity that
589      * maps to the services the class with the given name
590      *
591      * @return the required ObjEntity or null if there is none that matches the specifier
592      */

593     public synchronized ObjEntity lookupObjEntity(String JavaDoc entityName) {
594         return this._lookupObjEntity(entityName);
595     }
596
597     public Procedure lookupProcedure(Query q) {
598         Object JavaDoc root = q.getRoot();
599         if (root instanceof Procedure) {
600             return (Procedure) root;
601         }
602         else if (root instanceof String JavaDoc) {
603             return this.lookupProcedure((String JavaDoc) root);
604         }
605         return null;
606     }
607
608     public Procedure lookupProcedure(String JavaDoc procedureName) {
609
610         Procedure result = (Procedure) procedureCache.get(procedureName);
611         if (result == null) {
612             // reconstruct cache just in case some of the datamaps
613
// have changed and now contain the required information
614
constructCache();
615             result = (Procedure) procedureCache.get(procedureName);
616         }
617
618         return result;
619     }
620
621     /**
622      * Returns a named query or null if no query exists for a given name.
623      */

624     public synchronized Query lookupQuery(String JavaDoc name) {
625         Query result = (Query) queryCache.get(name);
626
627         if (result == null) {
628             // reconstruct cache just in case some of the datamaps
629
// have changed and now contain the required information
630
constructCache();
631             result = (Query) queryCache.get(name);
632         }
633         return result;
634     }
635
636     public synchronized void removeDataMap(DataMap map) {
637         if (maps.remove(map)) {
638             clearCache();
639         }
640     }
641
642     public boolean isIndexedByClass() {
643         return indexedByClass;
644     }
645
646     public void setIndexedByClass(boolean b) {
647         indexedByClass = b;
648     }
649
650     /**
651      * Internal usage only - provides the type-unsafe implementation which services the
652      * four typesafe public lookupDbEntity methods Looks in the DataMap's that this object
653      * was created with for the ObjEntity that maps to the specified object. Object may be
654      * a Entity name, ObjEntity, DataObject class (Class object for a class which
655      * implements the DataObject interface), or a DataObject instance itself
656      *
657      * @return the required DbEntity, or null if none matches the specifier
658      */

659     protected DbEntity _lookupDbEntity(Object JavaDoc object) {
660         if (object instanceof DbEntity) {
661             return (DbEntity) object;
662         }
663
664         DbEntity result = (DbEntity) dbEntityCache.get(object);
665         if (result == null) {
666             // reconstruct cache just in case some of the datamaps
667
// have changed and now contain the required information
668
constructCache();
669             result = (DbEntity) dbEntityCache.get(object);
670         }
671         return result;
672     }
673
674     /**
675      * Internal usage only - provides the type-unsafe implementation which services the
676      * three typesafe public lookupObjEntity methods Looks in the DataMap's that this
677      * object was created with for the ObjEntity that maps to the specified object. Object
678      * may be a Entity name, DataObject instance or DataObject class (Class object for a
679      * class which implements the DataObject interface)
680      *
681      * @return the required ObjEntity or null if there is none that matches the specifier
682      */

683     protected ObjEntity _lookupObjEntity(Object JavaDoc object) {
684         if (object instanceof ObjEntity) {
685             return (ObjEntity) object;
686         }
687
688         if (object instanceof DataObject) {
689             object = object.getClass();
690         }
691
692         ObjEntity result = (ObjEntity) objEntityCache.get(object);
693         if (result == null) {
694             // reconstruct cache just in case some of the datamaps
695
// have changed and now contain the required information
696
constructCache();
697             result = (ObjEntity) objEntityCache.get(object);
698         }
699         return result;
700     }
701 }
Popular Tags