Source code: org/objectstyle/cayenne/access/util/PrimaryKeyHelper.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
57 package org.objectstyle.cayenne.access.util;
58
59 import java.util.ArrayList;
60 import java.util.Collection;
61 import java.util.Comparator;
62 import java.util.HashMap;
63 import java.util.Iterator;
64 import java.util.List;
65 import java.util.Map;
66
67 import org.apache.commons.collections.ComparatorUtils;
68 import org.objectstyle.ashwood.graph.CollectionFactory;
69 import org.objectstyle.ashwood.graph.Digraph;
70 import org.objectstyle.ashwood.graph.GraphUtils;
71 import org.objectstyle.ashwood.graph.IndegreeTopologicalSort;
72 import org.objectstyle.ashwood.graph.MapDigraph;
73 import org.objectstyle.ashwood.graph.StrongConnection;
74 import org.objectstyle.cayenne.CayenneException;
75 import org.objectstyle.cayenne.CayenneRuntimeException;
76 import org.objectstyle.cayenne.DataObject;
77 import org.objectstyle.cayenne.ObjectId;
78 import org.objectstyle.cayenne.TempObjectId;
79 import org.objectstyle.cayenne.access.DataNode;
80 import org.objectstyle.cayenne.access.QueryEngine;
81 import org.objectstyle.cayenne.dba.PkGenerator;
82 import org.objectstyle.cayenne.map.DataMap;
83 import org.objectstyle.cayenne.map.DbAttribute;
84 import org.objectstyle.cayenne.map.DbEntity;
85 import org.objectstyle.cayenne.map.DbRelationship;
86 import org.objectstyle.cayenne.map.ObjAttribute;
87 import org.objectstyle.cayenne.map.ObjEntity;
88 import org.objectstyle.cayenne.map.ObjRelationship;
89
90 /**
91 * PrimaryKeyHelper resolves primary key dependencies for entities
92 * related to the supported query engine via topological sorting. It is
93 * directly based on ASHWOOD. In addition it provides means for primary key
94 * generation relying on DbAdapter in this.
95 *
96 * @author Andriy Shapochka
97 */
98
99 public class PrimaryKeyHelper {
100 private Map indexedDbEntities;
101 private QueryEngine queryEngine;
102 private DbEntityComparator dbEntityComparator;
103 private ObjEntityComparator objEntityComparator;
104
105 public PrimaryKeyHelper(QueryEngine queryEngine) {
106 this.queryEngine = queryEngine;
107 init();
108 dbEntityComparator = new DbEntityComparator();
109 objEntityComparator = new ObjEntityComparator();
110 }
111
112 public void reset() {
113 init();
114 }
115
116 public Comparator getDbEntityComparator() {
117 return dbEntityComparator;
118 }
119
120 public Comparator getObjEntityComparator() {
121 return objEntityComparator;
122 }
123
124 public void createPermIdsForObjEntity(
125 ObjEntity objEntity,
126 List dataObjects)
127 throws CayenneException {
128
129 if (dataObjects.isEmpty()) {
130 return;
131 }
132
133 TempObjectId tempId =
134 (TempObjectId) ((DataObject) dataObjects.get(0)).getObjectId();
135
136 DbEntity dbEntity = objEntity.getDbEntity();
137 DataNode owner = queryEngine.dataNodeForObjEntity(objEntity);
138 if (owner == null) {
139 throw new CayenneRuntimeException("No suitable DataNode to handle primary key generation.");
140 }
141
142 PkGenerator pkGenerator = owner.getAdapter().getPkGenerator();
143 List pkAttributes = dbEntity.getPrimaryKey();
144
145 HashMap idMap = null;
146 boolean pkFromMaster = true;
147 for (Iterator i = dataObjects.iterator(); i.hasNext();) {
148 idMap = new HashMap(idMap != null ? idMap.size() : 1);
149 DataObject object = (DataObject) i.next();
150 ObjectId id = object.getObjectId();
151 if (!(id instanceof TempObjectId)) {
152 continue; //with next loop
153 //If the id is not a temp, then it must be permanent. Do nothing else
154 }
155
156 tempId = (TempObjectId) id;
157 if (tempId.getPermId() != null) {
158 continue;
159 //An id already exists... nothing further required (definitely do not create another)
160 }
161
162 // first get values delivered via relationships
163 if (pkFromMaster)
164 pkFromMaster =
165 appendPkFromMasterRelationships(
166 idMap,
167 object,
168 objEntity,
169 dbEntity);
170
171 boolean autoPkDone = false;
172 Iterator it = pkAttributes.iterator();
173 while (it.hasNext()) {
174 DbAttribute dbAttr = (DbAttribute) it.next();
175 String dbAttrName = dbAttr.getName();
176 if (idMap.containsKey(dbAttrName)) {
177 continue;
178 }
179
180 ObjAttribute objAttr =
181 objEntity.getAttributeForDbAttribute(dbAttr);
182 if (objAttr != null) {
183 idMap.put(
184 dbAttrName,
185 object.readPropertyDirectly(objAttr.getName()));
186 continue;
187 }
188
189 if (autoPkDone) {
190 throw new CayenneException("Primary Key autogeneration only works for a single attribute.");
191 }
192
193 // finally, use database generation mechanism
194 try {
195 Object pkValue =
196 pkGenerator.generatePkForDbEntity(owner, dbEntity);
197 idMap.put(dbAttrName, pkValue);
198 autoPkDone = true;
199 } catch (Exception ex) {
200 throw new CayenneException(
201 "Error generating PK: " + ex.getMessage(),
202 ex);
203 }
204 }
205
206 // create permanent ObjectId and attach it to the temporary id
207 tempId.setPermId(new ObjectId(tempId.getObjClass(), idMap));
208 }
209 }
210
211 private boolean appendPkFromMasterRelationships(
212 Map map,
213 DataObject dataObject,
214 ObjEntity objEntity,
215 DbEntity dbEntity)
216 throws CayenneException {
217 boolean useful = false;
218 Iterator it = dbEntity.getRelationshipMap().values().iterator();
219 while (it.hasNext()) {
220 DbRelationship dbRel = (DbRelationship) it.next();
221 if (!dbRel.isToMasterPK())
222 continue;
223
224 ObjRelationship rel =
225 objEntity.getRelationshipForDbRelationship(dbRel);
226 if (rel == null)
227 continue;
228
229 DataObject targetDo =
230 (DataObject) dataObject.readPropertyDirectly(rel.getName());
231
232 if (targetDo == null)
233 throw new CayenneException("Null master object, can't create primary key for: "
234 + dataObject.getClass() + "." + dbRel.getName());
235
236 ObjectId targetKey = targetDo.getObjectId();
237 Map idMap = targetKey.getIdSnapshot();
238 if (idMap == null)
239 throw new CayenneException(
240 noMasterPkMsg(
241 objEntity.getName(),
242 targetKey.getObjClass().toString(),
243 dbRel.getName()));
244 map.putAll(dbRel.srcFkSnapshotWithTargetSnapshot(idMap));
245 useful = true;
246 }
247 return useful;
248 }
249
250 private String noMasterPkMsg(String src, String dst, String rel) {
251 StringBuffer msg =
252 new StringBuffer("Can't create primary key, master object has no PK snapshot.");
253 msg
254 .append("\nrelationship name: ")
255 .append(rel)
256 .append(", src object: ")
257 .append(src)
258 .append(", target obj: ")
259 .append(dst);
260 return msg.toString();
261 }
262
263 private List collectAllDbEntities() {
264 List entities = new ArrayList(32);
265 for (Iterator i = queryEngine.getDataMaps().iterator(); i.hasNext();) {
266 entities.addAll(((DataMap)i.next()).getDbEntities());
267 }
268 return entities;
269 }
270
271 private void init() {
272 List dbEntitiesToResolve = collectAllDbEntities();
273 Digraph pkDependencyGraph = new MapDigraph(MapDigraph.HASHMAP_FACTORY);
274 indexedDbEntities = new HashMap(dbEntitiesToResolve.size());
275 for (Iterator i = dbEntitiesToResolve.iterator(); i.hasNext();) {
276 DbEntity origin = (DbEntity) i.next();
277 for (Iterator j = origin.getRelationships().iterator(); j.hasNext();) {
278 DbRelationship relation = (DbRelationship) j.next();
279 if (relation.isToDependentPK()) {
280 DbEntity dst = (DbEntity) relation.getTargetEntity();
281 if (origin.equals(dst)) {
282 continue;
283 }
284 pkDependencyGraph.putArc(origin, dst, Boolean.TRUE);
285 }
286 }
287 }
288 int index = 0;
289 for (Iterator i = dbEntitiesToResolve.iterator(); i.hasNext();) {
290 DbEntity entity = (DbEntity) i.next();
291 if (!pkDependencyGraph.containsVertex(entity)) {
292 indexedDbEntities.put(entity, new Integer(index++));
293 }
294 }
295 boolean acyclic = GraphUtils.isAcyclic(pkDependencyGraph);
296 if (acyclic) {
297 IndegreeTopologicalSort sorter =
298 new IndegreeTopologicalSort(pkDependencyGraph);
299 while (sorter.hasNext())
300 indexedDbEntities.put(sorter.next(), new Integer(index++));
301 } else {
302 StrongConnection contractor =
303 new StrongConnection(
304 pkDependencyGraph,
305 CollectionFactory.ARRAYLIST_FACTORY);
306 Digraph contractedDigraph =
307 new MapDigraph(MapDigraph.HASHMAP_FACTORY);
308 contractor.contract(
309 contractedDigraph,
310 CollectionFactory.ARRAYLIST_FACTORY);
311 IndegreeTopologicalSort sorter =
312 new IndegreeTopologicalSort(contractedDigraph);
313 while (sorter.hasNext()) {
314 Collection component = (Collection) sorter.next();
315 for (Iterator i = component.iterator(); i.hasNext();)
316 indexedDbEntities.put(i.next(), new Integer(index++));
317 }
318 }
319 }
320
321 private class DbEntityComparator implements Comparator {
322 public int compare(Object o1, Object o2) {
323 if (o1.equals(o2)) {
324 return 0;
325 }
326 Integer index1 = (Integer) indexedDbEntities.get(o1);
327 Integer index2 = (Integer) indexedDbEntities.get(o2);
328 return ComparatorUtils.NATURAL_COMPARATOR.compare(index1, index2);
329 }
330 }
331
332 private class ObjEntityComparator implements Comparator {
333 public int compare(Object o1, Object o2) {
334 if (o1.equals(o2)) {
335 return 0;
336 }
337 DbEntity e1 = ((ObjEntity) o1).getDbEntity();
338 DbEntity e2 = ((ObjEntity) o2).getDbEntity();
339 return dbEntityComparator.compare(e1, e2);
340 }
341 }
342 }