| /* |
| * 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.kernel; |
| |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.openjpa.conf.OpenJPAConfiguration; |
| import org.apache.openjpa.datacache.DataCache; |
| import org.apache.openjpa.datacache.DataCacheManager; |
| import org.apache.openjpa.lib.conf.Configurable; |
| import org.apache.openjpa.lib.conf.Configuration; |
| import org.apache.openjpa.lib.log.Log; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.meta.FieldMetaData; |
| import org.apache.openjpa.meta.JavaTypes; |
| import org.apache.openjpa.meta.ValueMetaData; |
| import org.apache.openjpa.util.InvalidStateException; |
| |
| /** |
| * Class which manages inverse relations before flushing |
| * to the datastore. Ensures that inverse fields are set. |
| * Currently limited to managing PC and Collection-type relations. |
| * |
| * @author Steve Kim |
| */ |
| public class InverseManager implements Configurable { |
| |
| private static final Localizer _loc = Localizer.forPackage(InverseManager.class); |
| |
| protected static final Object NONE = new Object(); |
| |
| protected DataCacheManager _mgr; |
| |
| /** |
| * Constant representing the {@link #ACTION_MANAGE} action |
| */ |
| public static final int ACTION_MANAGE = 0; |
| |
| /** |
| * Constant representing the {@link #ACTION_WARN} action |
| */ |
| public static final int ACTION_WARN = 1; |
| |
| /** |
| * Constant representing the {@link #ACTION_EXCEPTION} action |
| */ |
| public static final int ACTION_EXCEPTION = 2; |
| |
| private boolean _manageLRS = false; |
| private int _action = ACTION_MANAGE; |
| private Log _log; |
| |
| /** |
| * Return whether to manage LRS fields. |
| */ |
| public boolean getManageLRS() { |
| return _manageLRS; |
| } |
| |
| /** |
| * Set whether to false LRS relations. Defaults to false. |
| */ |
| public void setManageLRS(boolean manage) { |
| _manageLRS = manage; |
| } |
| |
| /** |
| * Return the action constant to use during relationship checking. |
| * Defaults to {@link #ACTION_MANAGE}. |
| */ |
| public int getAction() { |
| return _action; |
| } |
| |
| /** |
| * Set the action constant to use during relationship checking. |
| * Defaults to {@link #ACTION_MANAGE}. |
| */ |
| public void setAction(int action) { |
| _action = action; |
| } |
| |
| /** |
| * Set the action string to use during relationship checking. |
| * Options include <code>manage, exception, warn</code>. |
| * This method is primarily for string-based automated configuration. |
| */ |
| public void setAction(String action) { |
| if ("exception".equals(action)) |
| _action = ACTION_EXCEPTION; |
| else if ("warn".equals(action)) |
| _action = ACTION_WARN; |
| else if ("manage".equals(action)) |
| _action = ACTION_MANAGE; |
| else |
| throw new IllegalArgumentException(action); |
| } |
| |
| @Override |
| public void startConfiguration() { |
| } |
| |
| @Override |
| public void endConfiguration() { |
| } |
| |
| @Override |
| public void setConfiguration(Configuration conf) { |
| _log = conf.getLog(OpenJPAConfiguration.LOG_RUNTIME); |
| _mgr = ((OpenJPAConfiguration)conf).getDataCacheManagerInstance(); |
| } |
| |
| /** |
| * Correct relations from the given dirty field to inverse instances. |
| * Field <code>fmd</code> of the instance managed by <code>sm</code> has |
| * value <code>value</code>. Ensure that all inverses relations from |
| * <code>value</code> are consistent with this. |
| */ |
| public void correctRelations(OpenJPAStateManager sm, FieldMetaData fmd, |
| Object value) { |
| if (fmd.getDeclaredTypeCode() != JavaTypes.PC && |
| ((fmd.getDeclaredTypeCode() != JavaTypes.COLLECTION && |
| fmd.getDeclaredTypeCode() != JavaTypes.MAP) || |
| fmd.getElement().getDeclaredTypeCode() != JavaTypes.PC)) |
| return; |
| |
| // ignore LRS fields |
| if (!getManageLRS() && fmd.isLRS()) |
| return; |
| |
| FieldMetaData[] inverses = fmd.getInverseMetaDatas(); |
| if (inverses.length == 0) |
| return; |
| |
| // clear any restorable relations |
| clearInverseRelations(sm, fmd, inverses, value); |
| |
| if (value != null) { |
| StoreContext ctx = sm.getContext(); |
| switch (fmd.getDeclaredTypeCode()) { |
| case JavaTypes.PC: |
| createInverseRelations(ctx, sm.getManagedInstance(), |
| value, fmd, inverses); |
| break; |
| case JavaTypes.COLLECTION: |
| for (Iterator itr = ((Collection) value).iterator(); |
| itr.hasNext();) |
| createInverseRelations(ctx, sm.getManagedInstance(), |
| itr.next(), fmd, inverses); |
| break; |
| } |
| } |
| } |
| |
| /** |
| * Create the inverse relations for all the given inverse fields. |
| * A relation exists from <code>fromRef</code> to <code>toRef</code>; this |
| * method creates the inverses. |
| */ |
| protected void createInverseRelations(StoreContext ctx, |
| Object fromRef, Object toRef, FieldMetaData fmd, |
| FieldMetaData[] inverses) { |
| OpenJPAStateManager other = ctx.getStateManager(toRef); |
| if (other == null || other.isDeleted()) |
| return; |
| |
| boolean owned; |
| for (int i = 0; i < inverses.length; i++) { |
| if (!getManageLRS() && inverses[i].isLRS()) |
| continue; |
| |
| // if this is the owned side of the relation and has not yet been |
| // loaded, no point in setting it now, cause it'll have the correct |
| // value the next time it is loaded after the flush |
| owned = fmd == inverses[i].getMappedByMetaData() |
| && _action == ACTION_MANAGE |
| && !isLoaded(other, inverses[i].getIndex()); |
| |
| switch (inverses[i].getDeclaredTypeCode()) { |
| case JavaTypes.PC: |
| if (!owned || inverses[i].getCascadeDelete() |
| == ValueMetaData.CASCADE_AUTO) |
| storeField(other, inverses[i], NONE, fromRef); |
| break; |
| case JavaTypes.COLLECTION: |
| if (!owned || inverses[i].getElement().getCascadeDelete() |
| == ValueMetaData.CASCADE_AUTO) |
| addToCollection(other, inverses[i], fromRef); |
| break; |
| } |
| } |
| } |
| |
| /** |
| * Return whether the given field is loaded for the given instance. |
| */ |
| private boolean isLoaded(OpenJPAStateManager sm, int field) { |
| if (sm.getLoaded().get(field)) |
| return true; |
| |
| // if the field isn't loaded in the state manager, it still might be |
| // loaded in the data cache, in which case we still have to correct |
| // it to keep the cache in sync |
| DataCache cache = _mgr.selectCache(sm); |
| if (cache == null) |
| return false; |
| |
| // can't retrieve an embedded object directly, so always assume the |
| // field is loaded and needs to be corrected |
| if (sm.isEmbedded()) |
| return true; |
| |
| PCData pc = cache.get(sm.getObjectId()); |
| if (pc == null) |
| return false; |
| return pc.isLoaded(field); |
| } |
| |
| /** |
| * Remove all relations between the initial value of <code>fmd</code> for |
| * the instance managed by <code>sm</code> and its inverses. Relations |
| * shared with <code>newValue</code> can be left intact. |
| */ |
| protected void clearInverseRelations(OpenJPAStateManager sm, |
| FieldMetaData fmd, FieldMetaData[] inverses, Object newValue) { |
| // don't bother clearing unflushed new instances |
| if (sm.isNew() && !sm.getFlushed().get(fmd.getIndex())) |
| return; |
| if (fmd.getDeclaredTypeCode() == JavaTypes.PC) { |
| Object initial = sm.fetchInitialField(fmd.getIndex()); |
| clearInverseRelations(sm, initial, fmd, inverses); |
| } else { |
| Object obj = sm.fetchInitialField(fmd.getIndex()); |
| Collection initial = null; |
| if (obj instanceof Collection) |
| initial = (Collection) obj; |
| else if (obj instanceof Map) |
| initial = ((Map)obj).values(); |
| |
| if (initial == null) |
| return; |
| |
| // clear all relations not also in the new value |
| Collection coll = null; |
| if (newValue instanceof Collection) |
| coll = (Collection) newValue; |
| else if (newValue instanceof Map) |
| coll = ((Map)newValue).values(); |
| Object elem; |
| for (Iterator itr = initial.iterator(); itr.hasNext();) { |
| elem = itr.next(); |
| if (coll == null || !coll.contains(elem)) |
| clearInverseRelations(sm, elem, fmd, inverses); |
| } |
| } |
| } |
| |
| /** |
| * Clear all inverse the relations from <code>val</code> to the instance |
| * managed by <code>sm</code>. |
| */ |
| protected void clearInverseRelations(OpenJPAStateManager sm, Object val, |
| FieldMetaData fmd, FieldMetaData[] inverses) { |
| if (val == null) |
| return; |
| OpenJPAStateManager other = sm.getContext().getStateManager(val); |
| if (other == null || other.isDeleted()) |
| return; |
| |
| boolean owned; |
| for (int i = 0; i < inverses.length; i++) { |
| if (!getManageLRS() && inverses[i].isLRS()) |
| continue; |
| |
| // if this is the owned side of the relation and has not yet been |
| // loaded, no point in setting it now, cause it'll have the correct |
| // value the next time it is loaded after the flush |
| owned = fmd == inverses[i].getMappedByMetaData() |
| && _action == ACTION_MANAGE |
| && !isLoaded(other, inverses[i].getIndex()); |
| |
| switch (inverses[i].getDeclaredTypeCode()) { |
| case JavaTypes.PC: |
| if (!owned || inverses[i].getCascadeDelete() |
| == ValueMetaData.CASCADE_AUTO) |
| storeNull(other, inverses[i], sm.getManagedInstance()); |
| break; |
| case JavaTypes.COLLECTION: |
| if (!owned || inverses[i].getElement().getCascadeDelete() |
| == ValueMetaData.CASCADE_AUTO) |
| removeFromCollection(other, inverses[i], |
| sm.getManagedInstance()); |
| break; |
| } |
| } |
| } |
| |
| /** |
| * Store null value at the given field. Verify that the given compare |
| * value is the value being nulled. Pass NONE for no comparison. |
| */ |
| protected void storeNull(OpenJPAStateManager sm, FieldMetaData fmd, |
| Object compare) { |
| storeField(sm, fmd, compare, null); |
| } |
| |
| /** |
| * Store a given value at the given field. Compare the given |
| * argument if not NONE. |
| */ |
| protected void storeField(OpenJPAStateManager sm, FieldMetaData fmd, |
| Object compare, Object val) { |
| Object oldValue = sm.fetchObjectField(fmd.getIndex()); |
| if (oldValue == val) |
| return; |
| if (compare != NONE && oldValue != compare) |
| return; |
| |
| switch (_action) { |
| case ACTION_MANAGE: |
| sm.settingObjectField(sm.getPersistenceCapable(), |
| fmd.getIndex(), |
| oldValue, val, OpenJPAStateManager.SET_USER); |
| break; |
| case ACTION_WARN: |
| warnConsistency(sm, fmd); |
| break; |
| case ACTION_EXCEPTION: |
| throwException(sm, fmd); |
| default: |
| throw new IllegalStateException(); |
| } |
| } |
| |
| /** |
| * Remove the given instance from the collection. |
| */ |
| protected void removeFromCollection(OpenJPAStateManager sm, |
| FieldMetaData fmd, |
| Object val) { |
| Collection coll = (Collection) sm.fetchObjectField(fmd.getIndex()); |
| if (coll != null) { |
| switch (_action) { |
| case ACTION_MANAGE: |
| remove: |
| for (int i = 0; coll.remove(val); i++) |
| if (i == 0 && coll instanceof Set) |
| break remove; |
| break; |
| case ACTION_WARN: |
| if (coll.contains(val)) |
| warnConsistency(sm, fmd); |
| break; |
| case ACTION_EXCEPTION: |
| if (coll.contains(val)) |
| throwException(sm, fmd); |
| break; |
| default: |
| throw new IllegalStateException(); |
| } |
| } |
| } |
| |
| /** |
| * Add the given value to the collection at the selected field. |
| */ |
| protected void addToCollection(OpenJPAStateManager sm, FieldMetaData fmd, |
| Object val) { |
| Collection coll = (Collection) sm.fetchObjectField(fmd.getIndex()); |
| if (coll == null) { |
| coll = (Collection) sm.newFieldProxy(fmd.getIndex()); |
| sm.storeObjectField(fmd.getIndex(), coll); |
| } |
| if (!coll.contains(val)) { |
| switch (_action) { |
| case ACTION_MANAGE: |
| coll.add(val); |
| break; |
| case ACTION_WARN: |
| warnConsistency(sm, fmd); |
| break; |
| case ACTION_EXCEPTION: |
| throwException(sm, fmd); |
| default: |
| throw new IllegalStateException(); |
| } |
| } |
| } |
| |
| /** |
| * Log an inconsistency warning |
| */ |
| protected void warnConsistency(OpenJPAStateManager sm, FieldMetaData fmd) { |
| if (_log.isWarnEnabled()) |
| _log.warn(_loc.get("inverse-consistency", fmd, sm.getId(), |
| sm.getContext())); |
| } |
| |
| /** |
| * Throw an inconsistency exception |
| */ |
| protected void throwException(OpenJPAStateManager sm, FieldMetaData fmd) { |
| throw new InvalidStateException(_loc.get("inverse-consistency", |
| fmd, sm.getId(), sm.getContext())).setFailedObject |
| (sm.getManagedInstance()).setFatal(true); |
| } |
| } |