001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */
019: package org.apache.openjpa.jdbc.meta.strats;
020:
021: import java.sql.*;
022: import java.util.*;
023:
024: import org.apache.openjpa.lib.util.*;
025: import org.apache.openjpa.meta.*;
026: import org.apache.openjpa.kernel.*;
027: import org.apache.openjpa.util.*;
028: import org.apache.openjpa.jdbc.meta.*;
029: import org.apache.openjpa.jdbc.kernel.*;
030: import org.apache.openjpa.jdbc.schema.*;
031: import org.apache.openjpa.jdbc.sql.*;
032:
033: /**
034: * <p>Mapping for a map whose keys and values are both relations to other
035: * persistent objects.</p>
036: *
037: * @author Abe White
038: * @since 0.4.0, 1.1.0
039: */
040: public class RelationRelationMapTableFieldStrategy extends
041: MapTableFieldStrategy {
042:
043: private static final Localizer _loc = Localizer
044: .forPackage(RelationRelationMapTableFieldStrategy.class);
045:
046: private String _keyRelationName = null;
047:
048: public Column[] getKeyColumns(ClassMapping cls) {
049: return field.getKeyMapping().getColumns();
050: }
051:
052: public Column[] getValueColumns(ClassMapping cls) {
053: return field.getElementMapping().getColumns();
054: }
055:
056: public void selectKey(Select sel, ClassMapping key,
057: OpenJPAStateManager sm, JDBCStore store,
058: JDBCFetchConfiguration fetch, Joins joins) {
059: sel.select(key, field.getKeyMapping().getSelectSubclasses(),
060: store, fetch, JDBCFetchConfiguration.EAGER_NONE, joins);
061: }
062:
063: public void selectValue(Select sel, ClassMapping val,
064: OpenJPAStateManager sm, JDBCStore store,
065: JDBCFetchConfiguration fetch, Joins joins) {
066: sel.select(val,
067: field.getElementMapping().getSelectSubclasses(), store,
068: fetch, JDBCFetchConfiguration.EAGER_NONE, joins);
069: }
070:
071: public Result[] getResults(final OpenJPAStateManager sm,
072: final JDBCStore store, final JDBCFetchConfiguration fetch,
073: final int eagerMode, final Joins[] resJoins, boolean lrs)
074: throws SQLException {
075: ValueMapping key = field.getKeyMapping();
076: final ClassMapping[] keys = key.getIndependentTypeMappings();
077: Union kunion = store.getSQLFactory().newUnion(keys.length);
078: if (fetch.getSubclassFetchMode(key.getTypeMapping()) != JDBCFetchConfiguration.EAGER_JOIN)
079: kunion.abortUnion();
080: kunion.setLRS(lrs);
081: kunion.select(new Union.Selector() {
082: public void select(Select sel, int idx) {
083: sel.whereForeignKey(field.getJoinForeignKey(), sm
084: .getObjectId(), field.getDefiningMapping(),
085: store);
086:
087: // order before select in case we're faking union with
088: // multiple selects; order vals used to merge results
089: Joins joins = joinKeyRelation(sel.newJoins(), keys[idx]);
090: sel.orderBy(field.getKeyMapping().getColumns(), true,
091: true);
092: sel.select(keys[idx], field.getKeyMapping()
093: .getSelectSubclasses(), store, fetch,
094: eagerMode, joins);
095:
096: //### cheat: result joins only care about the relation path;
097: //### thus we can use first mapping of union only
098: if (idx == 0)
099: resJoins[0] = joins;
100: }
101: });
102:
103: ValueMapping val = field.getElementMapping();
104: final ClassMapping[] vals = val.getIndependentTypeMappings();
105: Union vunion = store.getSQLFactory().newUnion(vals.length);
106: if (fetch.getSubclassFetchMode(val.getTypeMapping()) != JDBCFetchConfiguration.EAGER_JOIN)
107: vunion.abortUnion();
108: vunion.setLRS(lrs);
109: vunion.select(new Union.Selector() {
110: public void select(Select sel, int idx) {
111: sel.whereForeignKey(field.getJoinForeignKey(), sm
112: .getObjectId(), field.getDefiningMapping(),
113: store);
114:
115: // order before select in case we're faking union with
116: // multiple selects; order vals used to merge results
117: Joins joins = joinValueRelation(sel.newJoins(),
118: vals[idx]);
119: sel.orderBy(field.getKeyMapping().getColumns(), true,
120: true);
121: sel.select(vals[idx], field.getElementMapping()
122: .getSelectSubclasses(), store, fetch,
123: eagerMode, joins);
124:
125: //### cheat: result joins only care about the relation path;
126: //### thus we can use first mapping of union only
127: if (idx == 0)
128: resJoins[1] = joins;
129: }
130: });
131:
132: Result kres = null;
133: Result vres = null;
134: try {
135: kres = kunion.execute(store, fetch);
136: vres = vunion.execute(store, fetch);
137: return new Result[] { kres, vres };
138: } catch (SQLException se) {
139: if (kres != null)
140: kres.close();
141: if (vres != null)
142: vres.close();
143: throw se;
144: }
145: }
146:
147: public Object loadKey(OpenJPAStateManager sm, JDBCStore store,
148: JDBCFetchConfiguration fetch, Result res, Joins joins)
149: throws SQLException {
150: ClassMapping key = res.getBaseMapping();
151: if (key == null)
152: key = field.getKeyMapping().getIndependentTypeMappings()[0];
153: return res.load(key, store, fetch, joins);
154: }
155:
156: public Object loadValue(OpenJPAStateManager sm, JDBCStore store,
157: JDBCFetchConfiguration fetch, Result res, Joins joins)
158: throws SQLException {
159: ClassMapping val = res.getBaseMapping();
160: if (val == null)
161: val = field.getElementMapping()
162: .getIndependentTypeMappings()[0];
163: return res.load(val, store, fetch, joins);
164: }
165:
166: public Joins joinKeyRelation(Joins joins, ClassMapping key) {
167: ValueMapping vm = field.getKeyMapping();
168: return joins.joinRelation(_keyRelationName, vm
169: .getForeignKey(key), key, vm.getSelectSubclasses(),
170: false, false);
171: }
172:
173: public Joins joinValueRelation(Joins joins, ClassMapping val) {
174: ValueMapping vm = field.getElementMapping();
175: return joins.joinRelation(field.getName(), vm
176: .getForeignKey(val), val, vm.getSelectSubclasses(),
177: false, false);
178: }
179:
180: public void map(boolean adapt) {
181: super .map(adapt);
182:
183: ValueMapping key = field.getKeyMapping();
184: if (key.getTypeCode() != JavaTypes.PC || key.isEmbeddedPC())
185: throw new MetaDataException(_loc.get("not-relation", key));
186: ValueMapping val = field.getElementMapping();
187: if (val.getTypeCode() != JavaTypes.PC || val.isEmbeddedPC())
188: throw new MetaDataException(_loc.get("not-relation", val));
189: assertNotMappedBy();
190:
191: field.mapJoin(adapt, true);
192: mapTypeJoin(key, "key", adapt);
193: mapTypeJoin(val, "value", adapt);
194:
195: field.mapPrimaryKey(adapt);
196: }
197:
198: /**
199: * Map the given value's join to its persistent type.
200: */
201: private void mapTypeJoin(ValueMapping vm, String name, boolean adapt) {
202: if (vm.getTypeMapping().isMapped()) {
203: ValueMappingInfo vinfo = vm.getValueInfo();
204: ForeignKey fk = vinfo.getTypeJoin(vm, name, false, adapt);
205: vm.setForeignKey(fk);
206: vm.setColumnIO(vinfo.getColumnIO());
207: } else
208: RelationStrategies.mapRelationToUnmappedPC(vm, name, adapt);
209: vm.mapConstraints(name, adapt);
210: }
211:
212: public void initialize() {
213: _keyRelationName = field.getName() + ":key";
214: }
215:
216: public void insert(OpenJPAStateManager sm, JDBCStore store,
217: RowManager rm) throws SQLException {
218: insert(sm, rm, (Map) sm.fetchObject(field.getIndex()));
219: }
220:
221: private void insert(OpenJPAStateManager sm, RowManager rm, Map map)
222: throws SQLException {
223: if (map == null || map.isEmpty())
224: return;
225:
226: Row row = rm.getSecondaryRow(field.getTable(),
227: Row.ACTION_INSERT);
228: row.setForeignKey(field.getJoinForeignKey(), field
229: .getJoinColumnIO(), sm);
230:
231: ValueMapping key = field.getKeyMapping();
232: ValueMapping val = field.getElementMapping();
233: StoreContext ctx = sm.getContext();
234: OpenJPAStateManager keysm, valsm;
235: Map.Entry entry;
236: for (Iterator itr = map.entrySet().iterator(); itr.hasNext();) {
237: entry = (Map.Entry) itr.next();
238: keysm = RelationStrategies.getStateManager(entry.getKey(),
239: ctx);
240: valsm = RelationStrategies.getStateManager(
241: entry.getValue(), ctx);
242: key.setForeignKey(row, keysm);
243: val.setForeignKey(row, valsm);
244: rm.flushSecondaryRow(row);
245: }
246: }
247:
248: public void update(OpenJPAStateManager sm, JDBCStore store,
249: RowManager rm) throws SQLException {
250: Map map = (Map) sm.fetchObject(field.getIndex());
251: ChangeTracker ct = null;
252: if (map instanceof Proxy) {
253: Proxy proxy = (Proxy) map;
254: if (Proxies.isOwner(proxy, sm, field.getIndex()))
255: ct = proxy.getChangeTracker();
256: }
257:
258: // if no fine-grained change tracking then just delete and reinsert
259: if (ct == null || !ct.isTracking()) {
260: delete(sm, store, rm);
261: insert(sm, rm, map);
262: return;
263: }
264:
265: ValueMapping key = field.getKeyMapping();
266: ValueMapping val = field.getElementMapping();
267: StoreContext ctx = store.getContext();
268: OpenJPAStateManager keysm, valsm;
269:
270: // update the changes; note that we have to model changes as
271: // delete-then-insert if we have a foreign key action, because
272: // secondary row updates aren't part of the constraint graph
273: Collection change = ct.getChanged();
274: boolean canChange = val.getForeignKey().isLogical();
275: Object mkey;
276: if (canChange && !change.isEmpty()) {
277: Row changeRow = rm.getSecondaryRow(field.getTable(),
278: Row.ACTION_UPDATE);
279: changeRow.whereForeignKey(field.getJoinForeignKey(), sm);
280:
281: for (Iterator itr = change.iterator(); itr.hasNext();) {
282: mkey = itr.next();
283: keysm = RelationStrategies.getStateManager(mkey, ctx);
284: valsm = RelationStrategies.getStateManager(map
285: .get(mkey), ctx);
286: key.whereForeignKey(changeRow, keysm);
287: val.setForeignKey(changeRow, valsm);
288: rm.flushSecondaryRow(changeRow);
289: }
290: }
291:
292: // delete the removes
293: Collection rem = ct.getRemoved();
294: if (!rem.isEmpty() || (!canChange && !change.isEmpty())) {
295: Row delRow = rm.getSecondaryRow(field.getTable(),
296: Row.ACTION_DELETE);
297: delRow.whereForeignKey(field.getJoinForeignKey(), sm);
298:
299: for (Iterator itr = rem.iterator(); itr.hasNext();) {
300: keysm = RelationStrategies.getStateManager(itr.next(),
301: ctx);
302: key.whereForeignKey(delRow, keysm);
303: rm.flushSecondaryRow(delRow);
304: }
305: if (!canChange && !change.isEmpty()) {
306: for (Iterator itr = change.iterator(); itr.hasNext();) {
307: keysm = RelationStrategies.getStateManager(itr
308: .next(), ctx);
309: key.whereForeignKey(delRow, keysm);
310: rm.flushSecondaryRow(delRow);
311: }
312: }
313: }
314:
315: // insert the adds
316: Collection add = ct.getAdded();
317: if (!add.isEmpty() || (!canChange && !change.isEmpty())) {
318: Row addRow = rm.getSecondaryRow(field.getTable(),
319: Row.ACTION_INSERT);
320: addRow.setForeignKey(field.getJoinForeignKey(), field
321: .getJoinColumnIO(), sm);
322:
323: for (Iterator itr = add.iterator(); itr.hasNext();) {
324: mkey = itr.next();
325: keysm = RelationStrategies.getStateManager(mkey, ctx);
326: valsm = RelationStrategies.getStateManager(map
327: .get(mkey), ctx);
328: key.setForeignKey(addRow, keysm);
329: val.setForeignKey(addRow, valsm);
330: rm.flushSecondaryRow(addRow);
331: }
332: if (!canChange && !change.isEmpty()) {
333: for (Iterator itr = change.iterator(); itr.hasNext();) {
334: mkey = itr.next();
335: keysm = RelationStrategies.getStateManager(mkey,
336: ctx);
337: valsm = RelationStrategies.getStateManager(map
338: .get(mkey), ctx);
339: key.setForeignKey(addRow, keysm);
340: val.setForeignKey(addRow, valsm);
341: rm.flushSecondaryRow(addRow);
342: }
343: }
344: }
345: }
346:
347: public Joins joinRelation(Joins joins, boolean forceOuter,
348: boolean traverse) {
349: ValueMapping val = field.getElementMapping();
350: ClassMapping[] clss = val.getIndependentTypeMappings();
351: if (clss.length != 1) {
352: if (traverse)
353: throw RelationStrategies.unjoinable(val);
354: return joins;
355: }
356: if (forceOuter)
357: return joins.outerJoinRelation(field.getName(), val
358: .getForeignKey(clss[0]), clss[0], val
359: .getSelectSubclasses(), false, false);
360: return joins.joinRelation(field.getName(), val
361: .getForeignKey(clss[0]), clss[0], val
362: .getSelectSubclasses(), false, false);
363: }
364:
365: public Joins joinKeyRelation(Joins joins, boolean forceOuter,
366: boolean traverse) {
367: ValueMapping key = field.getKeyMapping();
368: ClassMapping[] clss = key.getIndependentTypeMappings();
369: if (clss.length != 1) {
370: if (traverse)
371: throw RelationStrategies.unjoinable(key);
372: return joins;
373: }
374: if (forceOuter)
375: return joins.outerJoinRelation(field.getName(), key
376: .getForeignKey(clss[0]), clss[0], key
377: .getSelectSubclasses(), false, false);
378: return joins.joinRelation(_keyRelationName, key
379: .getForeignKey(clss[0]), clss[0], key
380: .getSelectSubclasses(), false, false);
381: }
382:
383: public Object toDataStoreValue(Object val, JDBCStore store) {
384: return RelationStrategies.toDataStoreValue(field
385: .getElementMapping(), val, store);
386: }
387:
388: public Object toKeyDataStoreValue(Object val, JDBCStore store) {
389: return RelationStrategies.toDataStoreValue(field
390: .getKeyMapping(), val, store);
391: }
392: }
|