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.kernel;
020:
021: import java.util.Collection;
022: import java.util.Iterator;
023: import java.util.Set;
024:
025: import org.apache.openjpa.conf.OpenJPAConfiguration;
026: import org.apache.openjpa.datacache.DataCache;
027: import org.apache.openjpa.lib.conf.Configurable;
028: import org.apache.openjpa.lib.conf.Configuration;
029: import org.apache.openjpa.lib.log.Log;
030: import org.apache.openjpa.lib.util.Localizer;
031: import org.apache.openjpa.meta.FieldMetaData;
032: import org.apache.openjpa.meta.JavaTypes;
033: import org.apache.openjpa.meta.ValueMetaData;
034: import org.apache.openjpa.util.InvalidStateException;
035:
036: /**
037: * Class which manages inverse relations before flushing
038: * to the datastore. Ensures that inverse fields are set.
039: * Currently limited to managing PC and Collection-type relations.
040: *
041: * @author Steve Kim
042: */
043: public class InverseManager implements Configurable {
044:
045: private static final Localizer _loc = Localizer
046: .forPackage(InverseManager.class);
047:
048: protected static final Object NONE = new Object();
049:
050: /**
051: * Constant representing the {@link #ACTION_MANAGE} action
052: */
053: public static final int ACTION_MANAGE = 0;
054:
055: /**
056: * Constant representing the {@link #ACTION_WARN} action
057: */
058: public static final int ACTION_WARN = 1;
059:
060: /**
061: * Constant representing the {@link #ACTION_EXCEPTION} action
062: */
063: public static final int ACTION_EXCEPTION = 2;
064:
065: private boolean _manageLRS = false;
066: private int _action = ACTION_MANAGE;
067: private Log _log;
068:
069: /**
070: * Return whether to manage LRS fields.
071: */
072: public boolean getManageLRS() {
073: return _manageLRS;
074: }
075:
076: /**
077: * Set whether to false LRS relations. Defaults to false.
078: */
079: public void setManageLRS(boolean manage) {
080: _manageLRS = manage;
081: }
082:
083: /**
084: * Return the action constant to use during relationship checking.
085: * Defaults to {@link #ACTION_MANAGE}.
086: */
087: public int getAction() {
088: return _action;
089: }
090:
091: /**
092: * Set the action constant to use during relationship checking.
093: * Defaults to {@link #ACTION_MANAGE}.
094: */
095: public void setAction(int action) {
096: _action = action;
097: }
098:
099: /**
100: * Set the action string to use during relationship checking.
101: * Options include <code>manage, exception, warn</code>.
102: * This method is primarily for string-based automated configuration.
103: */
104: public void setAction(String action) {
105: if ("exception".equals(action))
106: _action = ACTION_EXCEPTION;
107: else if ("warn".equals(action))
108: _action = ACTION_WARN;
109: else if ("manage".equals(action))
110: _action = ACTION_MANAGE;
111: else
112: throw new IllegalArgumentException(action);
113: }
114:
115: public void startConfiguration() {
116: }
117:
118: public void endConfiguration() {
119: }
120:
121: public void setConfiguration(Configuration conf) {
122: _log = conf.getLog(OpenJPAConfiguration.LOG_RUNTIME);
123: }
124:
125: /**
126: * Correct relations from the given dirty field to inverse instances.
127: * Field <code>fmd</code> of the instance managed by <code>sm</code> has
128: * value <code>value</code>. Ensure that all inverses relations from
129: * <code>value</code> are consistent with this.
130: */
131: public void correctRelations(OpenJPAStateManager sm,
132: FieldMetaData fmd, Object value) {
133: if (fmd.getDeclaredTypeCode() != JavaTypes.PC
134: && (fmd.getDeclaredTypeCode() != JavaTypes.COLLECTION || fmd
135: .getElement().getDeclaredTypeCode() != JavaTypes.PC))
136: return;
137:
138: // ignore LRS fields
139: if (!getManageLRS() && fmd.isLRS())
140: return;
141:
142: FieldMetaData[] inverses = fmd.getInverseMetaDatas();
143: if (inverses.length == 0)
144: return;
145:
146: // clear any restorable relations
147: clearInverseRelations(sm, fmd, inverses, value);
148:
149: if (value != null) {
150: StoreContext ctx = sm.getContext();
151: switch (fmd.getDeclaredTypeCode()) {
152: case JavaTypes.PC:
153: createInverseRelations(ctx, sm.getManagedInstance(),
154: value, fmd, inverses);
155: break;
156: case JavaTypes.COLLECTION:
157: for (Iterator itr = ((Collection) value).iterator(); itr
158: .hasNext();)
159: createInverseRelations(ctx,
160: sm.getManagedInstance(), itr.next(), fmd,
161: inverses);
162: break;
163: }
164: }
165: }
166:
167: /**
168: * Create the inverse relations for all the given inverse fields.
169: * A relation exists from <code>fromRef</code> to <code>toRef</code>; this
170: * method creates the inverses.
171: */
172: protected void createInverseRelations(StoreContext ctx,
173: Object fromRef, Object toRef, FieldMetaData fmd,
174: FieldMetaData[] inverses) {
175: OpenJPAStateManager other = ctx.getStateManager(toRef);
176: if (other == null || other.isDeleted())
177: return;
178:
179: boolean owned;
180: for (int i = 0; i < inverses.length; i++) {
181: if (!getManageLRS() && inverses[i].isLRS())
182: continue;
183:
184: // if this is the owned side of the relation and has not yet been
185: // loaded, no point in setting it now, cause it'll have the correct
186: // value the next time it is loaded after the flush
187: owned = fmd == inverses[i].getMappedByMetaData()
188: && _action == ACTION_MANAGE
189: && !isLoaded(other, inverses[i].getIndex());
190:
191: switch (inverses[i].getDeclaredTypeCode()) {
192: case JavaTypes.PC:
193: if (!owned
194: || inverses[i].getCascadeDelete() == ValueMetaData.CASCADE_AUTO)
195: storeField(other, inverses[i], NONE, fromRef);
196: break;
197: case JavaTypes.COLLECTION:
198: if (!owned
199: || inverses[i].getElement().getCascadeDelete() == ValueMetaData.CASCADE_AUTO)
200: addToCollection(other, inverses[i], fromRef);
201: break;
202: }
203: }
204: }
205:
206: /**
207: * Return whether the given field is loaded for the given instance.
208: */
209: private boolean isLoaded(OpenJPAStateManager sm, int field) {
210: if (sm.getLoaded().get(field))
211: return true;
212:
213: // if the field isn't loaded in the state manager, it still might be
214: // loaded in the data cache, in which case we still have to correct
215: // it to keep the cache in sync
216: DataCache cache = sm.getMetaData().getDataCache();
217: if (cache == null)
218: return false;
219:
220: // can't retrieve an embedded object directly, so always assume the
221: // field is loaded and needs to be corrected
222: if (sm.isEmbedded())
223: return true;
224:
225: PCData pc = cache.get(sm.getObjectId());
226: if (pc == null)
227: return false;
228: return pc.isLoaded(field);
229: }
230:
231: /**
232: * Remove all relations between the initial value of <code>fmd</code> for
233: * the instance managed by <code>sm</code> and its inverses. Relations
234: * shared with <code>newValue</code> can be left intact.
235: */
236: protected void clearInverseRelations(OpenJPAStateManager sm,
237: FieldMetaData fmd, FieldMetaData[] inverses, Object newValue) {
238: // don't bother clearing unflushed new instances
239: if (sm.isNew() && !sm.getFlushed().get(fmd.getIndex()))
240: return;
241: if (fmd.getDeclaredTypeCode() == JavaTypes.PC) {
242: Object initial = sm.fetchInitialField(fmd.getIndex());
243: clearInverseRelations(sm, initial, fmd, inverses);
244: } else {
245: Collection initial = (Collection) sm.fetchInitialField(fmd
246: .getIndex());
247: if (initial == null)
248: return;
249:
250: // clear all relations not also in the new value
251: Collection coll = (Collection) newValue;
252: Object elem;
253: for (Iterator itr = initial.iterator(); itr.hasNext();) {
254: elem = itr.next();
255: if (coll == null || !coll.contains(elem))
256: clearInverseRelations(sm, elem, fmd, inverses);
257: }
258: }
259: }
260:
261: /**
262: * Clear all inverse the relations from <code>val</code> to the instance
263: * managed by <code>sm</code>.
264: */
265: protected void clearInverseRelations(OpenJPAStateManager sm,
266: Object val, FieldMetaData fmd, FieldMetaData[] inverses) {
267: if (val == null)
268: return;
269: OpenJPAStateManager other = sm.getContext()
270: .getStateManager(val);
271: if (other == null || other.isDeleted())
272: return;
273:
274: boolean owned;
275: for (int i = 0; i < inverses.length; i++) {
276: if (!getManageLRS() && inverses[i].isLRS())
277: continue;
278:
279: // if this is the owned side of the relation and has not yet been
280: // loaded, no point in setting it now, cause it'll have the correct
281: // value the next time it is loaded after the flush
282: owned = fmd == inverses[i].getMappedByMetaData()
283: && _action == ACTION_MANAGE
284: && !isLoaded(other, inverses[i].getIndex());
285:
286: switch (inverses[i].getDeclaredTypeCode()) {
287: case JavaTypes.PC:
288: if (!owned
289: || inverses[i].getCascadeDelete() == ValueMetaData.CASCADE_AUTO)
290: storeNull(other, inverses[i], sm
291: .getManagedInstance());
292: break;
293: case JavaTypes.COLLECTION:
294: if (!owned
295: || inverses[i].getElement().getCascadeDelete() == ValueMetaData.CASCADE_AUTO)
296: removeFromCollection(other, inverses[i], sm
297: .getManagedInstance());
298: break;
299: }
300: }
301: }
302:
303: /**
304: * Store null value at the given field. Verify that the given compare
305: * value is the value being nulled. Pass NONE for no comparison.
306: */
307: protected void storeNull(OpenJPAStateManager sm, FieldMetaData fmd,
308: Object compare) {
309: storeField(sm, fmd, compare, null);
310: }
311:
312: /**
313: * Store a given value at the given field. Compare the given
314: * argument if not NONE.
315: */
316: protected void storeField(OpenJPAStateManager sm,
317: FieldMetaData fmd, Object compare, Object val) {
318: Object oldValue = sm.fetchObjectField(fmd.getIndex());
319: if (oldValue == val)
320: return;
321: if (compare != NONE && oldValue != compare)
322: return;
323:
324: switch (_action) {
325: case ACTION_MANAGE:
326: sm.settingObjectField(sm.getPersistenceCapable(), fmd
327: .getIndex(), oldValue, val,
328: OpenJPAStateManager.SET_USER);
329: break;
330: case ACTION_WARN:
331: warnConsistency(sm, fmd);
332: break;
333: case ACTION_EXCEPTION:
334: throwException(sm, fmd);
335: default:
336: throw new IllegalStateException();
337: }
338: }
339:
340: /**
341: * Remove the given instance from the collection.
342: */
343: protected void removeFromCollection(OpenJPAStateManager sm,
344: FieldMetaData fmd, Object val) {
345: Collection coll = (Collection) sm.fetchObjectField(fmd
346: .getIndex());
347: if (coll != null) {
348: switch (_action) {
349: case ACTION_MANAGE:
350: remove: for (int i = 0; coll.remove(val); i++)
351: if (i == 0 && coll instanceof Set)
352: break remove;
353: break;
354: case ACTION_WARN:
355: if (coll.contains(val))
356: warnConsistency(sm, fmd);
357: break;
358: case ACTION_EXCEPTION:
359: if (coll.contains(val))
360: throwException(sm, fmd);
361: break;
362: default:
363: throw new IllegalStateException();
364: }
365: }
366: }
367:
368: /**
369: * Add the given value to the collection at the selected field.
370: */
371: protected void addToCollection(OpenJPAStateManager sm,
372: FieldMetaData fmd, Object val) {
373: Collection coll = (Collection) sm.fetchObjectField(fmd
374: .getIndex());
375: if (coll == null) {
376: coll = (Collection) sm.newFieldProxy(fmd.getIndex());
377: sm.storeObjectField(fmd.getIndex(), coll);
378: }
379: if (!coll.contains(val)) {
380: switch (_action) {
381: case ACTION_MANAGE:
382: coll.add(val);
383: break;
384: case ACTION_WARN:
385: warnConsistency(sm, fmd);
386: break;
387: case ACTION_EXCEPTION:
388: throwException(sm, fmd);
389: default:
390: throw new IllegalStateException();
391: }
392: }
393: }
394:
395: /**
396: * Log an inconsistency warning
397: */
398: protected void warnConsistency(OpenJPAStateManager sm,
399: FieldMetaData fmd) {
400: if (_log.isWarnEnabled())
401: _log.warn(_loc.get("inverse-consistency", fmd, sm.getId(),
402: sm.getContext()));
403: }
404:
405: /**
406: * Throw an inconsistency exception
407: */
408: protected void throwException(OpenJPAStateManager sm,
409: FieldMetaData fmd) {
410: throw new InvalidStateException(_loc.get("inverse-consistency",
411: fmd, sm.getId(), sm.getContext())).setFailedObject(
412: sm.getManagedInstance()).setFatal(true);
413: }
414: }
|