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.SQLException;
022: import java.util.Collection;
023: import java.util.Iterator;
024:
025: import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
026: import org.apache.openjpa.jdbc.kernel.JDBCStore;
027: import org.apache.openjpa.jdbc.meta.ClassMapping;
028: import org.apache.openjpa.jdbc.meta.FieldMapping;
029: import org.apache.openjpa.jdbc.meta.FieldMappingInfo;
030: import org.apache.openjpa.jdbc.meta.ValueMapping;
031: import org.apache.openjpa.jdbc.meta.ValueMappingInfo;
032: import org.apache.openjpa.jdbc.schema.Column;
033: import org.apache.openjpa.jdbc.schema.ColumnIO;
034: import org.apache.openjpa.jdbc.schema.ForeignKey;
035: import org.apache.openjpa.jdbc.sql.Joins;
036: import org.apache.openjpa.jdbc.sql.Result;
037: import org.apache.openjpa.jdbc.sql.Row;
038: import org.apache.openjpa.jdbc.sql.RowManager;
039: import org.apache.openjpa.jdbc.sql.Select;
040: import org.apache.openjpa.kernel.OpenJPAStateManager;
041: import org.apache.openjpa.kernel.StoreContext;
042: import org.apache.openjpa.lib.log.Log;
043: import org.apache.openjpa.lib.util.Localizer;
044: import org.apache.openjpa.meta.JavaTypes;
045: import org.apache.openjpa.util.ChangeTracker;
046: import org.apache.openjpa.util.InternalException;
047: import org.apache.openjpa.util.MetaDataException;
048: import org.apache.openjpa.util.Proxies;
049: import org.apache.openjpa.util.Proxy;
050:
051: /**
052: * Maps a relation to a set of other objects using an inverse
053: * foreign key in the related object table.
054: *
055: * @author Abe White
056: */
057: public abstract class RelationToManyInverseKeyFieldStrategy extends
058: StoreCollectionFieldStrategy {
059:
060: private static final Localizer _loc = Localizer
061: .forPackage(RelationToManyInverseKeyFieldStrategy.class);
062:
063: private boolean _orderInsert = false;
064: private boolean _orderUpdate = false;
065:
066: protected ClassMapping[] getIndependentElementMappings(
067: boolean traverse) {
068: return field.getElementMapping().getIndependentTypeMappings();
069: }
070:
071: protected ForeignKey getJoinForeignKey(ClassMapping elem) {
072: return field.getElementMapping().getForeignKey(elem);
073: }
074:
075: protected void selectElement(Select sel, ClassMapping elem,
076: JDBCStore store, JDBCFetchConfiguration fetch,
077: int eagerMode, Joins joins) {
078: sel.select(elem, field.getElementMapping()
079: .getSelectSubclasses(), store, fetch, eagerMode, joins);
080: }
081:
082: protected Object loadElement(OpenJPAStateManager sm,
083: JDBCStore store, JDBCFetchConfiguration fetch, Result res,
084: Joins joins) throws SQLException {
085: ClassMapping elem = res.getBaseMapping();
086: if (elem == null)
087: elem = field.getElementMapping()
088: .getIndependentTypeMappings()[0];
089: return res.load(elem, store, fetch, joins);
090: }
091:
092: protected Joins join(Joins joins, ClassMapping elem) {
093: ValueMapping vm = field.getElementMapping();
094: ForeignKey fk = vm.getForeignKey(elem);
095: ClassMapping owner = field.getDefiningMapping();
096: while (fk.getPrimaryKeyTable() != owner.getTable()) {
097: joins = owner.joinSuperclass(joins, false);
098: owner = owner.getJoinablePCSuperclassMapping();
099: if (owner == null)
100: throw new InternalException();
101: }
102: return joins.joinRelation(field.getName(), fk, elem, vm
103: .getSelectSubclasses(), true, true);
104: }
105:
106: protected Joins joinElementRelation(Joins joins, ClassMapping elem) {
107: return joinRelation(joins, false, false);
108: }
109:
110: public void map(boolean adapt) {
111: field.getValueInfo().assertNoSchemaComponents(field, !adapt);
112: field.getKeyMapping().getValueInfo().assertNoSchemaComponents(
113: field.getKey(), !adapt);
114:
115: ValueMapping elem = field.getElementMapping();
116: if (elem.getTypeCode() != JavaTypes.PC || elem.isEmbeddedPC()
117: || !elem.getTypeMapping().isMapped())
118: throw new MetaDataException(_loc.get("not-elem-relation",
119: field));
120:
121: // check for named inverse
122: FieldMapping mapped = field.getMappedByMapping();
123: FieldMappingInfo finfo = field.getMappingInfo();
124: ValueMappingInfo vinfo = elem.getValueInfo();
125: boolean criteria = vinfo.getUseClassCriteria();
126: if (mapped != null) {
127: mapped.resolve(mapped.MODE_META | mapped.MODE_MAPPING);
128: if (!(mapped.getStrategy() instanceof RelationFieldStrategy))
129: throw new MetaDataException(_loc.get(
130: "not-inv-relation", field, mapped));
131: vinfo.assertNoSchemaComponents(elem, !adapt);
132: elem.setForeignKey(mapped.getForeignKey(field
133: .getDefiningMapping()));
134: elem.setColumns(mapped.getDefiningMapping()
135: .getPrimaryKeyColumns());
136: elem.setJoinDirection(ValueMapping.JOIN_EXPECTED_INVERSE);
137: elem.setUseClassCriteria(criteria);
138:
139: field.setOrderColumn(finfo.getOrderColumn(field, mapped
140: .getForeignKey().getTable(), adapt));
141: field.setOrderColumnIO(finfo.getColumnIO());
142: return;
143: }
144:
145: // map inverse foreign key in related table
146: ForeignKey fk = vinfo.getInverseTypeJoin(elem, field.getName(),
147: adapt);
148: elem.setForeignKey(fk);
149: elem.setColumnIO(vinfo.getColumnIO());
150: elem.setColumns(elem.getTypeMapping().getPrimaryKeyColumns());
151: elem.setJoinDirection(ValueMapping.JOIN_EXPECTED_INVERSE);
152: elem.setUseClassCriteria(criteria);
153: elem.mapConstraints(field.getName(), adapt);
154:
155: field.setOrderColumn(finfo.getOrderColumn(field, fk.getTable(),
156: adapt));
157: field.setOrderColumnIO(finfo.getColumnIO());
158: }
159:
160: public void initialize() {
161: Column order = field.getOrderColumn();
162: _orderInsert = field.getOrderColumnIO().isInsertable(order,
163: false);
164: _orderUpdate = field.getOrderColumnIO().isUpdatable(order,
165: false);
166:
167: ValueMapping elem = field.getElementMapping();
168: Log log = field.getRepository().getLog();
169: if (field.getMappedBy() == null && elem.getUseClassCriteria()
170: && log.isWarnEnabled()) {
171: ForeignKey fk = elem.getForeignKey();
172: if (elem.getColumnIO().isAnyUpdatable(fk, false))
173: log.warn(_loc.get("class-crit-owner", field));
174: }
175: }
176:
177: public void insert(OpenJPAStateManager sm, JDBCStore store,
178: RowManager rm) throws SQLException {
179: if (field.getMappedBy() == null || _orderInsert || _orderUpdate)
180: insert(sm, rm, sm.fetchObject(field.getIndex()));
181: }
182:
183: private void insert(OpenJPAStateManager sm, RowManager rm,
184: Object vals) throws SQLException {
185: if (field.getMappedBy() != null && !_orderInsert
186: && !_orderUpdate)
187: return;
188: Collection coll = toCollection(vals);
189: if (coll == null || coll.isEmpty())
190: return;
191:
192: ClassMapping rel = field.getElementMapping().getTypeMapping();
193: int idx = 0;
194: for (Iterator itr = coll.iterator(); itr.hasNext(); idx++)
195: updateInverse(sm.getContext(), itr.next(), rel, rm, sm, idx);
196: }
197:
198: public void update(OpenJPAStateManager sm, JDBCStore store,
199: RowManager rm) throws SQLException {
200: if (field.getMappedBy() != null && !_orderInsert
201: && !_orderUpdate)
202: return;
203:
204: Object obj = sm.fetchObject(field.getIndex());
205: ChangeTracker ct = null;
206: if (obj instanceof Proxy) {
207: Proxy proxy = (Proxy) obj;
208: if (Proxies.isOwner(proxy, sm, field.getIndex()))
209: ct = proxy.getChangeTracker();
210: }
211:
212: // if no fine-grained change tracking then just delete and reinsert
213: if (ct == null || !ct.isTracking()) {
214: delete(sm, store, rm);
215: insert(sm, rm, obj);
216: return;
217: }
218:
219: // null inverse columns for deletes and update them with our oid for
220: // inserts
221: ClassMapping rel = field.getElementMapping().getTypeMapping();
222: StoreContext ctx = store.getContext();
223: if (field.getMappedBy() == null) {
224: Collection rem = ct.getRemoved();
225: for (Iterator itr = rem.iterator(); itr.hasNext();)
226: updateInverse(ctx, itr.next(), rel, rm, null, 0);
227: }
228:
229: Collection add = ct.getAdded();
230: int seq = ct.getNextSequence();
231: for (Iterator itr = add.iterator(); itr.hasNext(); seq++)
232: updateInverse(ctx, itr.next(), rel, rm, sm, seq);
233: if (field.getOrderColumn() != null)
234: ct.setNextSequence(seq);
235: }
236:
237: public void delete(OpenJPAStateManager sm, JDBCStore store,
238: RowManager rm) throws SQLException {
239: if (field.getMappedBy() != null)
240: return;
241:
242: // if nullable, null any existing inverse columns that refer to this obj
243: ValueMapping elem = field.getElementMapping();
244: ColumnIO io = elem.getColumnIO();
245: ForeignKey fk = elem.getForeignKey();
246: if (!elem.getUseClassCriteria() && io.isAnyUpdatable(fk, true)) {
247: assertInversable();
248: Row row = rm.getAllRows(fk.getTable(), Row.ACTION_UPDATE);
249: row.setForeignKey(fk, io, null);
250: row.whereForeignKey(fk, sm);
251: rm.flushAllRows(row);
252: return;
253: }
254:
255: if (!sm.getLoaded().get(field.getIndex()))
256: return;
257:
258: // update fk on each field value row
259: ClassMapping rel = field.getElementMapping().getTypeMapping();
260: StoreContext ctx = store.getContext();
261: Collection objs = toCollection(sm.fetchObject(field.getIndex()));
262: if (objs != null && !objs.isEmpty())
263: for (Iterator itr = objs.iterator(); itr.hasNext();)
264: updateInverse(ctx, itr.next(), rel, rm, sm, 0);
265: }
266:
267: /**
268: * This method updates the inverse columns of a 1-M related object
269: * with the given oid.
270: */
271: private void updateInverse(StoreContext ctx, Object inverse,
272: ClassMapping rel, RowManager rm, OpenJPAStateManager sm,
273: int idx) throws SQLException {
274: OpenJPAStateManager invsm = RelationStrategies.getStateManager(
275: inverse, ctx);
276: if (invsm == null)
277: return;
278:
279: ValueMapping elem = field.getElementMapping();
280: ForeignKey fk = elem.getForeignKey();
281: ColumnIO io = elem.getColumnIO();
282: Column order = field.getOrderColumn();
283:
284: int action;
285: boolean writeable;
286: boolean orderWriteable;
287: if (invsm.isNew() && !invsm.isFlushed()) {
288: // no need to null inverse columns of new instance
289: if (sm == null || sm.isDeleted())
290: return;
291: writeable = io.isAnyInsertable(fk, false);
292: orderWriteable = _orderInsert;
293: action = Row.ACTION_INSERT;
294: } else if (invsm.isDeleted()) {
295: // no need to null inverse columns of deleted instance
296: if (invsm.isFlushed() || sm == null || !sm.isDeleted())
297: return;
298: writeable = true;
299: orderWriteable = false;
300: action = Row.ACTION_DELETE;
301: } else {
302: if (sm != null && sm.isDeleted())
303: sm = null;
304: writeable = io.isAnyUpdatable(fk, sm == null);
305: orderWriteable = field.getOrderColumnIO().isUpdatable(
306: order, sm == null);
307: action = Row.ACTION_UPDATE;
308: }
309: if (!writeable && !orderWriteable)
310: return;
311:
312: assertInversable();
313:
314: // if this is an update, this might be the only mod to the row, so
315: // make sure the where condition is set
316: Row row = rm.getRow(fk.getTable(), action, invsm, true);
317: if (action == Row.ACTION_UPDATE)
318: row.wherePrimaryKey(invsm);
319:
320: // update the inverse pointer with our oid value
321: if (writeable)
322: row.setForeignKey(fk, io, sm);
323: if (orderWriteable)
324: row.setInt(order, idx);
325: }
326:
327: public Object toDataStoreValue(Object val, JDBCStore store) {
328: ClassMapping cm = field.getElementMapping().getTypeMapping();
329: return cm.toDataStoreValue(val, cm.getPrimaryKeyColumns(),
330: store);
331: }
332:
333: public Joins join(Joins joins, boolean forceOuter) {
334: ValueMapping elem = field.getElementMapping();
335: ClassMapping[] clss = elem.getIndependentTypeMappings();
336: if (clss.length != 1)
337: throw RelationStrategies.unjoinable(elem);
338: if (forceOuter)
339: return joins.outerJoinRelation(field.getName(), elem
340: .getForeignKey(clss[0]), clss[0], elem
341: .getSelectSubclasses(), true, true);
342: return joins.joinRelation(field.getName(), elem
343: .getForeignKey(clss[0]), clss[0], elem
344: .getSelectSubclasses(), true, true);
345: }
346:
347: private void assertInversable() {
348: ValueMapping elem = field.getElementMapping();
349: if (elem.getIndependentTypeMappings().length != 1)
350: throw RelationStrategies.uninversable(elem);
351: }
352: }
|