/* | |
* Licensed to the Apache Software Foundation (ASF) under one | |
* or more contributor license agreements. See the NOTICE file | |
* distributed with this work for additional information | |
* regarding copyright ownership. The ASF licenses this file | |
* to you under the Apache License, Version 2.0 (the | |
* "License"); you may not use this file except in compliance | |
* with the License. You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, | |
* software distributed under the License is distributed on an | |
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
* KIND, either express or implied. See the License for the | |
* specific language governing permissions and limitations | |
* under the License. | |
*/ | |
package org.apache.openjpa.jdbc.meta.strats; | |
import java.sql.*; | |
import java.util.*; | |
import org.apache.openjpa.lib.util.*; | |
import org.apache.openjpa.meta.*; | |
import org.apache.openjpa.kernel.*; | |
import org.apache.openjpa.util.*; | |
import org.apache.openjpa.jdbc.meta.*; | |
import org.apache.openjpa.jdbc.kernel.*; | |
import org.apache.openjpa.jdbc.schema.*; | |
import org.apache.openjpa.jdbc.sql.*; | |
/** | |
* <p>Mapping for a map whose keys and values are both relations to other | |
* persistent objects.</p> | |
* | |
* @author Abe White | |
* @since 0.4.0, 1.1.0 | |
*/ | |
public class RelationRelationMapTableFieldStrategy | |
extends MapTableFieldStrategy { | |
private static final Localizer _loc = Localizer.forPackage | |
(RelationRelationMapTableFieldStrategy.class); | |
private String _keyRelationName = null; | |
public Column[] getKeyColumns(ClassMapping cls) { | |
return field.getKeyMapping().getColumns(); | |
} | |
public Column[] getValueColumns(ClassMapping cls) { | |
return field.getElementMapping().getColumns(); | |
} | |
public void selectKey(Select sel, ClassMapping key, OpenJPAStateManager sm, | |
JDBCStore store, JDBCFetchConfiguration fetch, Joins joins) { | |
sel.select(key, field.getKeyMapping().getSelectSubclasses(), | |
store, fetch, JDBCFetchConfiguration.EAGER_NONE, joins); | |
} | |
public void selectValue(Select sel, ClassMapping val, | |
OpenJPAStateManager sm, JDBCStore store, JDBCFetchConfiguration fetch, | |
Joins joins) { | |
sel.select(val, field.getElementMapping().getSelectSubclasses(), | |
store, fetch, JDBCFetchConfiguration.EAGER_NONE, joins); | |
} | |
public Result[] getResults(final OpenJPAStateManager sm, | |
final JDBCStore store, final JDBCFetchConfiguration fetch, | |
final int eagerMode, final Joins[] resJoins, boolean lrs) | |
throws SQLException { | |
ValueMapping key = field.getKeyMapping(); | |
final ClassMapping[] keys = key.getIndependentTypeMappings(); | |
Union kunion = store.getSQLFactory().newUnion(keys.length); | |
if (fetch.getSubclassFetchMode(key.getTypeMapping()) | |
!= JDBCFetchConfiguration.EAGER_JOIN) | |
kunion.abortUnion(); | |
kunion.setLRS(lrs); | |
kunion.select(new Union.Selector() { | |
public void select(Select sel, int idx) { | |
sel.whereForeignKey(field.getJoinForeignKey(), | |
sm.getObjectId(), field.getDefiningMapping(), store); | |
// order before select in case we're faking union with | |
// multiple selects; order vals used to merge results | |
Joins joins = joinKeyRelation(sel.newJoins(), keys[idx]); | |
sel.orderBy(field.getKeyMapping().getColumns(), true, true); | |
sel.select(keys[idx], field.getKeyMapping(). | |
getSelectSubclasses(), store, fetch, eagerMode, joins); | |
//### cheat: result joins only care about the relation path; | |
//### thus we can use first mapping of union only | |
if (idx == 0) | |
resJoins[0] = joins; | |
} | |
}); | |
ValueMapping val = field.getElementMapping(); | |
final ClassMapping[] vals = val.getIndependentTypeMappings(); | |
Union vunion = store.getSQLFactory().newUnion(vals.length); | |
if (fetch.getSubclassFetchMode(val.getTypeMapping()) | |
!= JDBCFetchConfiguration.EAGER_JOIN) | |
vunion.abortUnion(); | |
vunion.setLRS(lrs); | |
vunion.select(new Union.Selector() { | |
public void select(Select sel, int idx) { | |
sel.whereForeignKey(field.getJoinForeignKey(), | |
sm.getObjectId(), field.getDefiningMapping(), store); | |
// order before select in case we're faking union with | |
// multiple selects; order vals used to merge results | |
Joins joins = joinValueRelation(sel.newJoins(), vals[idx]); | |
sel.orderBy(field.getKeyMapping().getColumns(), true, true); | |
sel.select(vals[idx], field.getElementMapping(). | |
getSelectSubclasses(), store, fetch, eagerMode, joins); | |
//### cheat: result joins only care about the relation path; | |
//### thus we can use first mapping of union only | |
if (idx == 0) | |
resJoins[1] = joins; | |
} | |
}); | |
Result kres = null; | |
Result vres = null; | |
try { | |
kres = kunion.execute(store, fetch); | |
vres = vunion.execute(store, fetch); | |
return new Result[]{ kres, vres }; | |
} catch (SQLException se) { | |
if (kres != null) | |
kres.close(); | |
if (vres != null) | |
vres.close(); | |
throw se; | |
} | |
} | |
public Object loadKey(OpenJPAStateManager sm, JDBCStore store, | |
JDBCFetchConfiguration fetch, Result res, Joins joins) | |
throws SQLException { | |
ClassMapping key = res.getBaseMapping(); | |
if (key == null) | |
key = field.getKeyMapping().getIndependentTypeMappings()[0]; | |
return res.load(key, store, fetch, joins); | |
} | |
public Object loadValue(OpenJPAStateManager sm, JDBCStore store, | |
JDBCFetchConfiguration fetch, Result res, Joins joins) | |
throws SQLException { | |
ClassMapping val = res.getBaseMapping(); | |
if (val == null) | |
val = field.getElementMapping().getIndependentTypeMappings()[0]; | |
return res.load(val, store, fetch, joins); | |
} | |
public Joins joinKeyRelation(Joins joins, ClassMapping key) { | |
ValueMapping vm = field.getKeyMapping(); | |
return joins.joinRelation(_keyRelationName, vm.getForeignKey(key), key, | |
vm.getSelectSubclasses(), false, false); | |
} | |
public Joins joinValueRelation(Joins joins, ClassMapping val) { | |
ValueMapping vm = field.getElementMapping(); | |
return joins.joinRelation(field.getName(), vm.getForeignKey(val), val, | |
vm.getSelectSubclasses(), false, false); | |
} | |
public void map(boolean adapt) { | |
super.map(adapt); | |
ValueMapping key = field.getKeyMapping(); | |
if (key.getTypeCode() != JavaTypes.PC || key.isEmbeddedPC()) | |
throw new MetaDataException(_loc.get("not-relation", key)); | |
ValueMapping val = field.getElementMapping(); | |
if (val.getTypeCode() != JavaTypes.PC || val.isEmbeddedPC()) | |
throw new MetaDataException(_loc.get("not-relation", val)); | |
assertNotMappedBy(); | |
field.mapJoin(adapt, true); | |
mapTypeJoin(key, "key", adapt); | |
mapTypeJoin(val, "value", adapt); | |
field.mapPrimaryKey(adapt); | |
} | |
/** | |
* Map the given value's join to its persistent type. | |
*/ | |
private void mapTypeJoin(ValueMapping vm, String name, boolean adapt) { | |
if (vm.getTypeMapping().isMapped()) { | |
ValueMappingInfo vinfo = vm.getValueInfo(); | |
ForeignKey fk = vinfo.getTypeJoin(vm, name, false, adapt); | |
vm.setForeignKey(fk); | |
vm.setColumnIO(vinfo.getColumnIO()); | |
} else | |
RelationStrategies.mapRelationToUnmappedPC(vm, name, adapt); | |
vm.mapConstraints(name, adapt); | |
} | |
public void initialize() { | |
_keyRelationName = field.getName() + ":key"; | |
} | |
public void insert(OpenJPAStateManager sm, JDBCStore store, RowManager rm) | |
throws SQLException { | |
insert(sm, rm, (Map) sm.fetchObject(field.getIndex())); | |
} | |
private void insert(OpenJPAStateManager sm, RowManager rm, Map map) | |
throws SQLException { | |
if (map == null || map.isEmpty()) | |
return; | |
Row row = rm.getSecondaryRow(field.getTable(), Row.ACTION_INSERT); | |
row.setForeignKey(field.getJoinForeignKey(), field.getJoinColumnIO(), | |
sm); | |
ValueMapping key = field.getKeyMapping(); | |
ValueMapping val = field.getElementMapping(); | |
StoreContext ctx = sm.getContext(); | |
OpenJPAStateManager keysm, valsm; | |
Map.Entry entry; | |
for (Iterator itr = map.entrySet().iterator(); itr.hasNext();) { | |
entry = (Map.Entry) itr.next(); | |
keysm = RelationStrategies.getStateManager(entry.getKey(), ctx); | |
valsm = RelationStrategies.getStateManager(entry.getValue(), ctx); | |
key.setForeignKey(row, keysm); | |
val.setForeignKey(row, valsm); | |
rm.flushSecondaryRow(row); | |
} | |
} | |
public void update(OpenJPAStateManager sm, JDBCStore store, RowManager rm) | |
throws SQLException { | |
Map map = (Map) sm.fetchObject(field.getIndex()); | |
ChangeTracker ct = null; | |
if (map instanceof Proxy) { | |
Proxy proxy = (Proxy) map; | |
if (Proxies.isOwner(proxy, sm, field.getIndex())) | |
ct = proxy.getChangeTracker(); | |
} | |
// if no fine-grained change tracking then just delete and reinsert | |
if (ct == null || !ct.isTracking()) { | |
delete(sm, store, rm); | |
insert(sm, rm, map); | |
return; | |
} | |
ValueMapping key = field.getKeyMapping(); | |
ValueMapping val = field.getElementMapping(); | |
StoreContext ctx = store.getContext(); | |
OpenJPAStateManager keysm, valsm; | |
// update the changes; note that we have to model changes as | |
// delete-then-insert if we have a foreign key action, because | |
// secondary row updates aren't part of the constraint graph | |
Collection change = ct.getChanged(); | |
boolean canChange = val.getForeignKey().isLogical(); | |
Object mkey; | |
if (canChange && !change.isEmpty()) { | |
Row changeRow = rm.getSecondaryRow(field.getTable(), | |
Row.ACTION_UPDATE); | |
changeRow.whereForeignKey(field.getJoinForeignKey(), sm); | |
for (Iterator itr = change.iterator(); itr.hasNext();) { | |
mkey = itr.next(); | |
keysm = RelationStrategies.getStateManager(mkey, ctx); | |
valsm = RelationStrategies.getStateManager(map.get(mkey), ctx); | |
key.whereForeignKey(changeRow, keysm); | |
val.setForeignKey(changeRow, valsm); | |
rm.flushSecondaryRow(changeRow); | |
} | |
} | |
// delete the removes | |
Collection rem = ct.getRemoved(); | |
if (!rem.isEmpty() || (!canChange && !change.isEmpty())) { | |
Row delRow = rm.getSecondaryRow(field.getTable(), | |
Row.ACTION_DELETE); | |
delRow.whereForeignKey(field.getJoinForeignKey(), sm); | |
for (Iterator itr = rem.iterator(); itr.hasNext();) { | |
keysm = RelationStrategies.getStateManager(itr.next(), ctx); | |
key.whereForeignKey(delRow, keysm); | |
rm.flushSecondaryRow(delRow); | |
} | |
if (!canChange && !change.isEmpty()) { | |
for (Iterator itr = change.iterator(); itr.hasNext();) { | |
keysm = RelationStrategies.getStateManager(itr.next(), | |
ctx); | |
key.whereForeignKey(delRow, keysm); | |
rm.flushSecondaryRow(delRow); | |
} | |
} | |
} | |
// insert the adds | |
Collection add = ct.getAdded(); | |
if (!add.isEmpty() || (!canChange && !change.isEmpty())) { | |
Row addRow = rm.getSecondaryRow(field.getTable(), | |
Row.ACTION_INSERT); | |
addRow.setForeignKey(field.getJoinForeignKey(), | |
field.getJoinColumnIO(), sm); | |
for (Iterator itr = add.iterator(); itr.hasNext();) { | |
mkey = itr.next(); | |
keysm = RelationStrategies.getStateManager(mkey, ctx); | |
valsm = RelationStrategies.getStateManager(map.get(mkey), ctx); | |
key.setForeignKey(addRow, keysm); | |
val.setForeignKey(addRow, valsm); | |
rm.flushSecondaryRow(addRow); | |
} | |
if (!canChange && !change.isEmpty()) { | |
for (Iterator itr = change.iterator(); itr.hasNext();) { | |
mkey = itr.next(); | |
keysm = RelationStrategies.getStateManager(mkey, ctx); | |
valsm = RelationStrategies.getStateManager(map.get(mkey), | |
ctx); | |
key.setForeignKey(addRow, keysm); | |
val.setForeignKey(addRow, valsm); | |
rm.flushSecondaryRow(addRow); | |
} | |
} | |
} | |
} | |
public Joins joinRelation(Joins joins, boolean forceOuter, | |
boolean traverse) { | |
ValueMapping val = field.getElementMapping(); | |
ClassMapping[] clss = val.getIndependentTypeMappings(); | |
if (clss.length != 1) { | |
if (traverse) | |
throw RelationStrategies.unjoinable(val); | |
return joins; | |
} | |
if (forceOuter) | |
return joins.outerJoinRelation(field.getName(), | |
val.getForeignKey(clss[0]), clss[0], val.getSelectSubclasses(), | |
false, false); | |
return joins.joinRelation(field.getName(), | |
val.getForeignKey(clss[0]), clss[0], val.getSelectSubclasses(), | |
false, false); | |
} | |
public Joins joinKeyRelation(Joins joins, boolean forceOuter, | |
boolean traverse) { | |
ValueMapping key = field.getKeyMapping(); | |
ClassMapping[] clss = key.getIndependentTypeMappings(); | |
if (clss.length != 1) { | |
if (traverse) | |
throw RelationStrategies.unjoinable(key); | |
return joins; | |
} | |
if (forceOuter) | |
return joins.outerJoinRelation(field.getName(), | |
key.getForeignKey(clss[0]), clss[0], key.getSelectSubclasses(), | |
false, false); | |
return joins.joinRelation(_keyRelationName, | |
key.getForeignKey(clss[0]), clss[0], key.getSelectSubclasses(), | |
false, false); | |
} | |
public Object toDataStoreValue(Object val, JDBCStore store) { | |
return RelationStrategies.toDataStoreValue(field.getElementMapping(), | |
val, store); | |
} | |
public Object toKeyDataStoreValue(Object val, JDBCStore store) { | |
return RelationStrategies.toDataStoreValue(field.getKeyMapping(), | |
val, store); | |
} | |
} |