| /* |
| * 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.io.IOException; |
| import java.io.ObjectOutput; |
| import java.io.Serializable; |
| import java.sql.Timestamp; |
| import java.util.Arrays; |
| import java.util.Calendar; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.TimeZone; |
| |
| import org.apache.openjpa.enhance.PersistenceCapable; |
| 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.meta.ValueStrategies; |
| import org.apache.openjpa.util.ChangeTracker; |
| import org.apache.openjpa.util.Exceptions; |
| import org.apache.openjpa.util.ImplHelper; |
| import org.apache.openjpa.util.InvalidStateException; |
| import org.apache.openjpa.util.LRSProxy; |
| import org.apache.openjpa.util.MapChangeTracker; |
| import org.apache.openjpa.util.ObjectId; |
| import org.apache.openjpa.util.Proxies; |
| import org.apache.openjpa.util.Proxy; |
| import org.apache.openjpa.util.ProxyManager; |
| import org.apache.openjpa.util.UserException; |
| |
| /** |
| * FieldManager type used to hold onto a single field value and then |
| * dispense it via the fetch methods. The manager can also perform actions |
| * on the held field. |
| */ |
| class SingleFieldManager extends TransferFieldManager implements Serializable { |
| |
| private static final long serialVersionUID = 1L; |
| |
| private static final Localizer _loc = Localizer.forPackage(SingleFieldManager.class); |
| |
| private final StateManagerImpl _sm; |
| private final BrokerImpl _broker; |
| private final boolean _checkDbOnCascadePersist; |
| |
| public SingleFieldManager(StateManagerImpl sm, BrokerImpl broker) { |
| _sm = sm; |
| _broker = broker; |
| _checkDbOnCascadePersist = |
| _broker.getConfiguration().getCompatibilityInstance().getCheckDatabaseForCascadePersistToDetachedEntity(); |
| } |
| |
| /** |
| * Proxy the held field if needed. Return true if the field needs to |
| * be replaced with the now-proxied instance. |
| */ |
| public boolean proxy(boolean reset, boolean replaceNull) { |
| FieldMetaData fmd = _sm.getMetaData().getField(field); |
| Proxy proxy = null; |
| boolean ret = false; |
| switch (fmd.getDeclaredTypeCode()) { |
| case JavaTypes.DATE: |
| if (objval == null) |
| return false; |
| proxy = checkProxy(fmd); |
| if (proxy == null) { |
| proxy = (Proxy) _sm.newFieldProxy(field); |
| ((Date) proxy).setTime(((Date) objval).getTime()); |
| if (proxy instanceof Timestamp && objval instanceof Timestamp) |
| ((Timestamp) proxy).setNanos(((Timestamp) objval).getNanos()); |
| ret = true; |
| } |
| break; |
| case JavaTypes.CALENDAR: |
| if (objval == null) |
| return false; |
| proxy = checkProxy(fmd); |
| if (proxy == null) { |
| proxy = (Proxy) _sm.newFieldProxy(field); |
| ((Calendar) proxy).setTime(((Calendar) objval).getTime()); |
| ret = true; |
| } else { |
| Object init = fmd.getInitializer(); |
| if (init != null && init instanceof TimeZone) { |
| ((Calendar) proxy).setTimeZone((TimeZone)init); |
| } |
| } |
| break; |
| case JavaTypes.COLLECTION: |
| if (objval == null && !replaceNull) |
| return false; |
| proxy = checkProxy(fmd); |
| if (proxy == null) { |
| proxy = (Proxy) _sm.newFieldProxy(field); |
| if (objval != null) |
| ((Collection) proxy).addAll((Collection) objval); |
| ret = true; |
| } |
| break; |
| case JavaTypes.MAP: |
| if (objval == null && !replaceNull) |
| return false; |
| proxy = checkProxy(fmd); |
| if (proxy == null) { |
| proxy = (Proxy) _sm.newFieldProxy(field); |
| if (objval != null) |
| ((Map) proxy).putAll((Map) objval); |
| ret = true; |
| } |
| break; |
| case JavaTypes.OBJECT: |
| if (objval == null) |
| return false; |
| proxy = checkProxy(fmd); |
| if (proxy == null) { |
| proxy = getProxyManager().newCustomProxy(objval, |
| _sm.getBroker().getConfiguration(). |
| getCompatibilityInstance().getAutoOff()); |
| ret = proxy != null; |
| } |
| break; |
| } |
| |
| if (proxy != null) { |
| proxy.setOwner(_sm, field); |
| ChangeTracker tracker = proxy.getChangeTracker(); |
| if (reset && tracker != null) { |
| if (fmd.getDeclaredTypeCode() == JavaTypes.MAP) { |
| // track values if key is derived from value, else keys |
| boolean keys = fmd.getKey().getValueMappedBy() == null; |
| ((MapChangeTracker) tracker).setTrackKeys(keys); |
| } |
| tracker.startTracking(); |
| } |
| objval = proxy; |
| } |
| return ret; |
| } |
| |
| /** |
| * If the current field is a usable proxy and it should be a proxy, return it; else return null. |
| * |
| * This method will skim out Calendar instances that were proxied before we knew if they need to be proxied. |
| */ |
| private Proxy checkProxy(FieldMetaData fmd) { |
| if (!(objval instanceof Proxy)) |
| return null; |
| |
| Proxy proxy = (Proxy) objval; |
| if (proxy.getOwner() == null || Proxies.isOwner(proxy, _sm, field)) { |
| if (fmd.getProxyType().isAssignableFrom(proxy.getClass()) |
| || (fmd.isLRS() && (objval instanceof LRSProxy))) { |
| return proxy; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Unproxies the current field if needed. |
| */ |
| public void unproxy() { |
| if (objval == null) |
| return; |
| |
| FieldMetaData fmd = _sm.getMetaData().getField(field); |
| switch (fmd.getDeclaredTypeCode()) { |
| case JavaTypes.COLLECTION: |
| case JavaTypes.MAP: |
| case JavaTypes.DATE: |
| case JavaTypes.OBJECT: |
| if (objval instanceof Proxy) { |
| Proxy proxy = (Proxy) objval; |
| proxy.setOwner(null, -1); |
| if (proxy.getChangeTracker() != null) |
| proxy.getChangeTracker().stopTracking(); |
| } |
| } |
| } |
| |
| /** |
| * Release the currently embedded field (make it transient). |
| */ |
| public void releaseEmbedded() { |
| if (objval == null) |
| return; |
| |
| FieldMetaData fmd = _sm.getMetaData().getField(field); |
| switch (fmd.getDeclaredTypeCode()) { |
| case JavaTypes.PC: |
| if (fmd.isEmbeddedPC()) |
| releaseEmbedded(fmd, objval); |
| break; |
| case JavaTypes.ARRAY: |
| if (fmd.getElement().isEmbeddedPC()) |
| releaseEmbedded(fmd.getElement(), (Object[]) objval); |
| break; |
| case JavaTypes.COLLECTION: |
| if (fmd.getElement().isEmbeddedPC()) |
| releaseEmbedded(fmd.getElement(), (Collection) objval); |
| break; |
| case JavaTypes.MAP: |
| if (fmd.getKey().isEmbeddedPC()) |
| releaseEmbedded(fmd.getKey(), ((Map) objval).keySet()); |
| if (fmd.getElement().isEmbeddedPC()) |
| releaseEmbedded(fmd.getElement(), ((Map) objval).values()); |
| break; |
| } |
| } |
| |
| /** |
| * Release the given embedded objects. |
| */ |
| private void releaseEmbedded(ValueMetaData vmd, Object[] objs) { |
| for (int i = 0; i < objs.length; i++) |
| releaseEmbedded(vmd, objs[i]); |
| } |
| |
| /** |
| * Release the given embedded objects. |
| */ |
| private void releaseEmbedded(ValueMetaData vmd, Collection objs) { |
| for (Iterator itr = objs.iterator(); itr.hasNext();) |
| releaseEmbedded(vmd, itr.next()); |
| } |
| |
| /** |
| * Release the given embedd object. |
| */ |
| private void releaseEmbedded(ValueMetaData vmd, Object obj) { |
| if (obj == null) |
| return; |
| |
| StateManagerImpl sm = _broker.getStateManagerImpl(obj, false); |
| if (sm != null && sm.getOwner() == _sm |
| && sm.getOwnerIndex() == vmd.getFieldMetaData().getIndex()) |
| sm.release(true); |
| } |
| |
| /** |
| * Persist the stored field safely, preventing infinite recursion using |
| * the given set of already-persisted objects. This method is only called |
| * for fields that we know have cascade-immediate settings. |
| */ |
| public void persist(OpCallbacks call) { |
| if (objval == null) |
| return; |
| |
| FieldMetaData fmd = _sm.getMetaData().getField(field); |
| switch (fmd.getDeclaredTypeCode()) { |
| case JavaTypes.PC: |
| case JavaTypes.PC_UNTYPED: |
| if (!_broker.isDetachedNew() && _broker.isDetached(objval, _checkDbOnCascadePersist)) |
| return; // allow but ignore |
| _broker.persist(objval, true, call); |
| break; |
| case JavaTypes.ARRAY: |
| _broker.persistAll(Arrays.asList((Object[]) objval), true, |
| call); |
| break; |
| case JavaTypes.COLLECTION: |
| _broker.persistAll((Collection) objval, true, call); |
| break; |
| case JavaTypes.MAP: |
| if (fmd.getKey().getCascadePersist() |
| == ValueMetaData.CASCADE_IMMEDIATE) |
| _broker.persistAll(((Map) objval).keySet(), true, call); |
| if (fmd.getElement().getCascadePersist() |
| == ValueMetaData.CASCADE_IMMEDIATE) |
| _broker.persistAll(((Map) objval).values(), true, call); |
| break; |
| } |
| } |
| |
| /** |
| * Delete and/or dereference field values. |
| */ |
| public void delete(OpCallbacks call) { |
| delete(true, call); |
| } |
| |
| /** |
| * Dereference field values. |
| */ |
| public void dereferenceDependent() { |
| delete(false, null, true); |
| } |
| |
| private void delete(boolean immediate, OpCallbacks call) { |
| delete(immediate, call, false); |
| } |
| |
| /** |
| * Delete or dereference the stored field as necessary. |
| */ |
| private void delete(boolean immediate, OpCallbacks call, boolean deref) { |
| if (objval == null) |
| return; |
| |
| FieldMetaData fmd = _sm.getMetaData().getField(field); |
| if (fmd.getCascadeDelete() != ValueMetaData.CASCADE_NONE) { |
| // immediate cascade works on field value; dependent deref |
| // works on external value |
| if ((immediate || fmd.isEmbeddedPC()) |
| && fmd.getCascadeDelete() == ValueMetaData.CASCADE_IMMEDIATE) { |
| if (fmd.isEmbeddedPC() && deref) { |
| StateManagerImpl sm = _broker.getStateManagerImpl(objval, false); |
| if (sm != null) |
| dereferenceEmbedDependent(sm); |
| } |
| delete(fmd, objval, call); |
| } |
| else if (fmd.getCascadeDelete() == ValueMetaData.CASCADE_AUTO) |
| dereferenceDependent(fmd.getExternalValue(objval, _broker)); |
| return; |
| } |
| |
| Object external = null; |
| ValueMetaData vmd = fmd.getKey(); |
| if ((immediate || vmd.isEmbeddedPC()) |
| && vmd.getCascadeDelete() == ValueMetaData.CASCADE_IMMEDIATE) |
| delete(vmd, ((Map) objval).keySet(), call); |
| else if (vmd.getCascadeDelete() == ValueMetaData.CASCADE_AUTO) { |
| external = fmd.getExternalValue(objval, _broker); |
| if (external == null) |
| return; |
| dereferenceDependent(((Map) external).keySet()); |
| } |
| |
| vmd = fmd.getElement(); |
| if ((immediate || vmd.isEmbeddedPC()) |
| && vmd.getCascadeDelete() == ValueMetaData.CASCADE_IMMEDIATE) { |
| switch (fmd.getDeclaredTypeCode()) { |
| case JavaTypes.COLLECTION: |
| delete(vmd, (Collection) objval, call); |
| break; |
| case JavaTypes.ARRAY: |
| delete(vmd, (Object[]) objval, call); |
| break; |
| case JavaTypes.MAP: |
| delete(vmd, ((Map) objval).values(), call); |
| break; |
| } |
| } else if (vmd.getCascadeDelete() == ValueMetaData.CASCADE_AUTO) { |
| if (external == null) { |
| external = fmd.getExternalValue(objval, _broker); |
| if (external == null) |
| return; |
| } |
| |
| switch (fmd.getTypeCode()) { |
| case JavaTypes.COLLECTION: |
| dereferenceDependent((Collection) external); |
| break; |
| case JavaTypes.ARRAY: |
| dereferenceDependent((Object[]) external); |
| break; |
| case JavaTypes.MAP: |
| dereferenceDependent(((Map) external).values()); |
| break; |
| } |
| } |
| } |
| |
| /** |
| * Delete the objects in the given value. |
| */ |
| private void delete(ValueMetaData vmd, Object[] objs, OpCallbacks call) { |
| for (int i = 0; i < objs.length; i++) |
| delete(vmd, objs[i], call); |
| } |
| |
| /** |
| * Delete the objects embedded in the given value. |
| */ |
| private void delete(ValueMetaData vmd, Collection objs, OpCallbacks call) { |
| for (Iterator itr = objs.iterator(); itr.hasNext();) |
| delete(vmd, itr.next(), call); |
| } |
| |
| /** |
| * Delete an object embedded in the given value. |
| */ |
| void delete(ValueMetaData vmd, Object obj, OpCallbacks call) { |
| if (obj == null) |
| return; |
| |
| // delete if unknowned or this isn't an embedded field or if owned by us |
| StateManagerImpl sm = _broker.getStateManagerImpl(obj, false); |
| if (sm != null && (sm.getOwner() == null || !vmd.isEmbeddedPC() |
| || (sm.getOwner() == _sm |
| && sm.getOwnerIndex() == vmd.getFieldMetaData().getIndex()))) |
| _broker.delete(sm.getManagedInstance(), sm, call); |
| } |
| |
| /** |
| * Dereference all valid persistent objects in the given collection. |
| */ |
| private void dereferenceDependent(Object[] objs) { |
| for (int i = 0; i < objs.length; i++) |
| dereferenceDependent(objs[i]); |
| } |
| |
| /** |
| * Dereference all valid persistent objects in the given collection. |
| */ |
| private void dereferenceDependent(Collection objs) { |
| for (Iterator itr = objs.iterator(); itr.hasNext();) |
| dereferenceDependent(itr.next()); |
| } |
| |
| /** |
| * Dereference the given object. |
| */ |
| void dereferenceDependent(Object obj) { |
| if (obj == null) |
| return; |
| StateManagerImpl sm = _broker.getStateManagerImpl(obj, false); |
| if (sm != null) |
| sm.setDereferencedDependent(true, true); |
| } |
| |
| void dereferenceEmbedDependent(StateManagerImpl sm) { |
| sm.setDereferencedEmbedDependent(true); |
| } |
| |
| /** |
| * Recursively invoke the broker to gather cascade-refresh objects in |
| * the current field into the given set. This method is only called |
| * for fields that we know have cascade-refresh settings. |
| */ |
| public void gatherCascadeRefresh(OpCallbacks call) { |
| if (objval == null) |
| return; |
| |
| FieldMetaData fmd = _sm.getMetaData().getField(field); |
| switch (fmd.getDeclaredTypeCode()) { |
| case JavaTypes.PC: |
| case JavaTypes.PC_UNTYPED: |
| _broker.gatherCascadeRefresh(objval, call); |
| break; |
| case JavaTypes.ARRAY: |
| gatherCascadeRefresh((Object[]) objval, call); |
| break; |
| case JavaTypes.COLLECTION: |
| gatherCascadeRefresh((Collection) objval, call); |
| break; |
| case JavaTypes.MAP: |
| if (fmd.getKey().getCascadeRefresh() |
| == ValueMetaData.CASCADE_IMMEDIATE) |
| gatherCascadeRefresh(((Map) objval).keySet(), call); |
| if (fmd.getElement().getCascadeRefresh() |
| == ValueMetaData.CASCADE_IMMEDIATE) |
| gatherCascadeRefresh(((Map) objval).values(), call); |
| break; |
| } |
| } |
| |
| /** |
| * Gather each element. |
| */ |
| private void gatherCascadeRefresh(Object[] arr, OpCallbacks call) { |
| for (int i = 0; i < arr.length; i++) |
| _broker.gatherCascadeRefresh(arr[i], call); |
| } |
| |
| /** |
| * Gather each element. |
| */ |
| private void gatherCascadeRefresh(Collection coll, OpCallbacks call) { |
| for (Iterator itr = coll.iterator(); itr.hasNext();) |
| _broker.gatherCascadeRefresh(itr.next(), call); |
| } |
| |
| /** |
| * Perform pre-flush tasks on the current field. This includes checking |
| * for nulls, persisting pcs, embedding embedded fields, and ref'ing |
| * pc fields. Return true if the field needs to be replaced with the |
| * new value. |
| */ |
| public boolean preFlush(boolean logical, OpCallbacks call) { |
| // only care about object fields |
| FieldMetaData fmd = _sm.getMetaData().getField(field); |
| if (fmd.getDeclaredTypeCode() < JavaTypes.OBJECT) |
| return false; |
| |
| // manage inverses |
| InverseManager manager = _broker.getInverseManager(); |
| if (manager != null) |
| manager.correctRelations(_sm, fmd, objval); |
| |
| // perform pers-by-reach and dependent refs |
| return preFlush(fmd, logical, call); |
| } |
| |
| /** |
| * Return true if the last-provided field has a default value. |
| */ |
| public boolean isDefaultValue() { |
| return dblval == 0 && longval == 0 |
| && (objval == null || "".equals(objval)); |
| } |
| |
| /** |
| * Write the stored field or its default value to the given stream. |
| */ |
| public void serialize(ObjectOutput out, boolean def) |
| throws IOException { |
| FieldMetaData fmd = _sm.getMetaData().getField(field); |
| switch (fmd.getDeclaredTypeCode()) { |
| case JavaTypes.BOOLEAN: |
| out.writeBoolean(!def && longval == 1); |
| break; |
| case JavaTypes.BYTE: |
| out.writeByte((def) ? (byte) 0 : (byte) longval); |
| break; |
| case JavaTypes.CHAR: |
| out.writeChar((def) ? (char) 0 : (char) longval); |
| break; |
| case JavaTypes.DOUBLE: |
| out.writeDouble((def) ? 0D : dblval); |
| break; |
| case JavaTypes.FLOAT: |
| out.writeFloat((def) ? 0F : (float) dblval); |
| break; |
| case JavaTypes.INT: |
| out.writeInt((def) ? 0 : (int) longval); |
| break; |
| case JavaTypes.LONG: |
| out.writeLong((def) ? 0L : longval); |
| break; |
| case JavaTypes.SHORT: |
| out.writeShort((def) ? (short) 0 : (short) longval); |
| break; |
| default: |
| out.writeObject((def) ? null : objval); |
| } |
| } |
| |
| /** |
| * Helper method to perform pre flush actions on the current object. |
| */ |
| private boolean preFlush(FieldMetaData fmd, boolean logical, OpCallbacks call) { |
| // check for illegal nulls |
| if (objval == null) { |
| // If we have an AUTOASSIGN strategy that means that we have a field that is GenerationType.IDENTITY so |
| // skip checking to see if the value is null as it will get assigned later in flush processing. |
| if (fmd.getValueStrategy() != ValueStrategies.AUTOASSIGN) { |
| if (fmd.getNullValue() == FieldMetaData.NULL_EXCEPTION || fmd.getDeclaredTypeCode() == JavaTypes.OID) |
| throw new InvalidStateException(_loc.get("null-value", fmd.getName(), _sm.getManagedInstance())) |
| .setFatal(true); |
| } |
| return false; |
| } |
| |
| // nothing else to do for non-persistent |
| if (fmd.getManagement() != FieldMetaData.MANAGE_PERSISTENT) |
| return false; |
| |
| // don't allow managed objectid field value |
| if (fmd.getDeclaredTypeCode() == JavaTypes.OID) { |
| _sm.assertNotManagedObjectId(objval); |
| if (_sm.getObjectId() != null |
| && !objval.equals(((ObjectId) _sm.getObjectId()).getId())) |
| throw new InvalidStateException(_loc.get("changed-oid", |
| _sm.getObjectId(), objval, |
| Exceptions.toString(_sm.getManagedInstance()))). |
| setFatal(true); |
| } |
| |
| // check for pcs in field value |
| if (preFlush(fmd, fmd.getDeclaredTypeCode(), |
| fmd.getKey().getDeclaredTypeCode(), |
| fmd.getElement().getDeclaredTypeCode(), false, logical, call)) |
| return true; |
| |
| // also check for pcs in externalized values |
| if (fmd.isExternalized()) |
| preFlush(fmd, fmd.getTypeCode(), fmd.getKey().getTypeCode(), |
| fmd.getElement().getTypeCode(), true, logical, call); |
| return false; |
| } |
| |
| /** |
| * Make new objects persistent and ref other objects so referenced |
| * dependent objects won't be deleted. |
| */ |
| private boolean preFlush(FieldMetaData fmd, int type, int keyType, |
| int elemType, boolean external, boolean logical, OpCallbacks call) { |
| Object val = objval; |
| if (val == null) |
| return false; |
| |
| boolean copy = false; |
| switch (type) { |
| case JavaTypes.PC: |
| if (fmd.isEmbeddedPC()) { |
| objval = embed(fmd, val); |
| copy = true; |
| } else { |
| if (external) |
| val = fmd.getExternalValue(val, _broker); |
| if (val != null) |
| preFlushPC(fmd, val, logical, call); |
| } |
| break; |
| case JavaTypes.PC_UNTYPED: |
| if (external) |
| val = fmd.getExternalValue(val, _broker); |
| if (val != null) |
| preFlushPC(fmd, val, logical, call); |
| break; |
| case JavaTypes.ARRAY: |
| if (fmd.getElement().isEmbeddedPC()) |
| embed(fmd.getElement(), (Object[]) val); |
| else if (elemType == JavaTypes.PC |
| || elemType == JavaTypes.PC_UNTYPED) { |
| if (external) |
| val = fmd.getExternalValue(val, _broker); |
| if (val != null) |
| preFlushPCs(fmd.getElement(), (Object[]) val, logical, |
| call); |
| } |
| break; |
| case JavaTypes.COLLECTION: |
| if (fmd.getElement().isEmbeddedPC()) { |
| objval = embed(fmd.getElement(), (Collection) val); |
| copy = true; |
| } else if (elemType == JavaTypes.PC |
| || elemType == JavaTypes.PC_UNTYPED) { |
| boolean flushed = false; |
| if (external) |
| val = fmd.getExternalValue(val, _broker); |
| else if (val instanceof Proxy) { |
| // shortcut change trackers; also ensures we don't |
| // iterate lrs fields |
| ChangeTracker ct = ((Proxy) val).getChangeTracker(); |
| if (ct != null && ct.isTracking()) { |
| preFlushPCs(fmd.getElement(), ct.getAdded(), |
| logical, call); |
| preFlushPCs(fmd.getElement(), ct.getChanged(), |
| logical, call); |
| flushed = true; |
| } |
| } |
| if (!flushed && val != null) |
| preFlushPCs(fmd.getElement(), (Collection) val, logical, |
| call); |
| } |
| break; |
| case JavaTypes.MAP: |
| boolean keyEmbed = fmd.getKey().isEmbeddedPC(); |
| boolean valEmbed = fmd.getElement().isEmbeddedPC(); |
| if (keyEmbed || valEmbed) { |
| objval = embed(fmd, (Map) val, keyEmbed, valEmbed); |
| copy = keyEmbed; |
| } |
| |
| if (!keyEmbed && (keyType == JavaTypes.PC |
| || keyType == JavaTypes.PC_UNTYPED)) { |
| boolean flushed = false; |
| if (external) { |
| val = fmd.getExternalValue(val, _broker); |
| external = false; |
| } else if (val instanceof Proxy) { |
| // shortcut change trackers; also ensures we don't |
| // iterate lrs fields |
| MapChangeTracker ct = (MapChangeTracker) ((Proxy) val). |
| getChangeTracker(); |
| if (ct != null && ct.isTracking() && ct.getTrackKeys()) |
| { |
| preFlushPCs(fmd.getKey(), ct.getAdded(), logical, |
| call); |
| preFlushPCs(fmd.getKey(), ct.getChanged(), logical, |
| call); |
| flushed = true; |
| } |
| } |
| if (!flushed && val != null) |
| preFlushPCs(fmd.getKey(), ((Map) val).keySet(), logical, |
| call); |
| } |
| |
| if (!valEmbed && (elemType == JavaTypes.PC |
| || elemType == JavaTypes.PC_UNTYPED)) { |
| boolean flushed = false; |
| if (external) |
| val = fmd.getExternalValue(val, _broker); |
| else if (val instanceof Proxy) { |
| // shortcut change trackers; also ensures we don't |
| // iterate lrs fields |
| MapChangeTracker ct = (MapChangeTracker) ((Proxy) val). |
| getChangeTracker(); |
| if (ct != null && ct.isTracking()) { |
| if (ct.getTrackKeys()) { |
| preFlushPCs(fmd.getElement(), ct.getAdded(), |
| (Map) val, logical, call); |
| preFlushPCs(fmd.getElement(), ct.getChanged(), |
| (Map) val, logical, call); |
| } else { |
| preFlushPCs(fmd.getElement(), ct.getAdded(), |
| logical, call); |
| preFlushPCs(fmd.getElement(), ct.getChanged(), |
| logical, call); |
| } |
| flushed = true; |
| } |
| } |
| if (!flushed && val != null) |
| preFlushPCs(fmd.getElement(), ((Map) val).values(), |
| logical, call); |
| } |
| break; |
| } |
| return copy; |
| } |
| |
| /** |
| * Make new objects persistent and ref all valid persistent objects for |
| * the given keys. |
| */ |
| private void preFlushPCs(ValueMetaData vmd, Collection keys, Map map, |
| boolean logical, OpCallbacks call) { |
| for (Iterator itr = keys.iterator(); itr.hasNext();) |
| preFlushPC(vmd, map.get(itr.next()), logical, call); |
| } |
| |
| /** |
| * Make new objects persistent and ref all valid persistent objects in |
| * the given array. |
| */ |
| private void preFlushPCs(ValueMetaData vmd, Object[] objs, |
| boolean logical, OpCallbacks call) { |
| for (int i = 0; i < objs.length; i++) |
| preFlushPC(vmd, objs[i], logical, call); |
| } |
| |
| /** |
| * Make new objects persistent and ref all valid persistent objects in |
| * the given collection. |
| */ |
| private void preFlushPCs(ValueMetaData vmd, Collection objs, |
| boolean logical, OpCallbacks call) { |
| for (Iterator itr = objs.iterator(); itr.hasNext();) |
| preFlushPC(vmd, itr.next(), logical, call); |
| } |
| |
| /** |
| * Perform pre flush operations on the given object. |
| */ |
| private void preFlushPC(ValueMetaData vmd, Object obj, boolean logical, OpCallbacks call) { |
| if (obj == null) |
| return; |
| |
| OpenJPAStateManager sm; |
| |
| if (vmd.getCascadePersist() == ValueMetaData.CASCADE_NONE) { |
| if (!_broker.isDetachedNew() && _broker.isDetached(obj, _checkDbOnCascadePersist)) { |
| return; // allow but ignore |
| } |
| |
| sm = _broker.getStateManager(obj); |
| if (sm == null || !sm.isPersistent()) { |
| if (((StoreContext)_broker).getAllowReferenceToSiblingContext() |
| && ImplHelper.isManageable(obj) |
| && ((PersistenceCapable)obj).pcGetStateManager() != null) { |
| return; |
| } else { |
| throw new InvalidStateException(_loc.get("cant-cascade-persist", |
| vmd.toString(), Exceptions.toString(obj), |
| sm == null ? " unmanaged" : sm.getPCState().getClass().getSimpleName())) |
| .setFailedObject(obj); |
| } |
| } |
| } else { |
| if (vmd.getCascadePersist() == ValueMetaData.CASCADE_IMMEDIATE) { |
| if (!_broker.isDetachedNew() && _broker.isDetached(obj, _checkDbOnCascadePersist)) { |
| return; // allow but ignore |
| } |
| } |
| sm = _broker.getStateManager(obj); |
| if (sm == null || !sm.isProvisional()) { |
| sm = _broker.persist(obj, null, true, call); |
| // ensure generated IDs get assigned properly |
| if (!logical) |
| ((StateManagerImpl)sm).assignObjectId(false, true); |
| |
| // Call preFetch on this and any related persistent fields. |
| // This will ensure IDs get assigned to those that need them. |
| if (_broker.isFlushing()) { |
| ((StateManagerImpl)sm).preFlush(logical, call); |
| } |
| } |
| } |
| |
| if (sm != null) { |
| // if deleted and not managed inverse, die |
| if (sm.isDeleted() && (_broker.getInverseManager() == null |
| || vmd.getFieldMetaData().getInverseMetaDatas().length == 0)) |
| throw new UserException(_loc.get("ref-to-deleted", |
| Exceptions.toString(obj), vmd, |
| Exceptions.toString(_sm.getManagedInstance()))). |
| setFailedObject(obj); |
| |
| StateManagerImpl smimpl = (StateManagerImpl) sm; |
| smimpl.nonprovisional(logical, call); |
| smimpl.setDereferencedDependent(false, true); |
| } |
| } |
| |
| /** |
| * Make all elements of the given array embedded. |
| */ |
| private void embed(ValueMetaData vmd, Object[] arr) { |
| for (int i = 0; i < arr.length; i++) |
| arr[i] = embed(vmd, arr[i]); |
| } |
| |
| /** |
| * Create a copy of the given collection containing embedded elements. |
| */ |
| private Collection embed(ValueMetaData vmd, Collection orig) { |
| // we have to copy to get a collection of the right type and size, |
| // though we immediately clear it |
| if (orig == null) |
| throw new UserException(_loc.get("not-copyable", |
| vmd.getFieldMetaData())); |
| Collection coll = null; |
| try { |
| coll = getProxyManager().copyCollection(orig); |
| } catch (Exception e) { |
| coll = (Collection) _sm.newFieldProxy(vmd.getFieldMetaData().getIndex()); |
| } |
| coll.clear(); |
| for (Iterator itr = orig.iterator(); itr.hasNext();) |
| coll.add(embed(vmd, itr.next())); |
| return coll; |
| } |
| |
| /** |
| * Embed the elements of the given map. |
| */ |
| private Map embed(FieldMetaData fmd, Map orig, boolean keyEmbed, |
| boolean valEmbed) { |
| Map map; |
| Map.Entry entry; |
| |
| // if we have to replace keys, we need to copy the map; otherwise |
| // we can mutate the values directly |
| if (keyEmbed) { |
| if (orig == null) |
| throw new UserException(_loc.get("not-copyable", fmd)); |
| // we have to copy to get a collection of the right type and size, |
| // though we immediately clear it |
| try { |
| map = getProxyManager().copyMap(orig); |
| } catch (Exception e) { |
| map = (Map) _sm.newFieldProxy(fmd.getIndex()); |
| } |
| map.clear(); |
| Object key, val; |
| for (Iterator itr = orig.entrySet().iterator(); itr.hasNext();) { |
| entry = (Map.Entry) itr.next(); |
| key = embed(fmd.getKey(), entry.getKey()); |
| val = entry.getValue(); |
| if (valEmbed) |
| val = embed(fmd.getElement(), val); |
| map.put(key, val); |
| } |
| } else { |
| map = orig; |
| for (Iterator itr = map.entrySet().iterator(); itr.hasNext();) { |
| entry = (Map.Entry) itr.next(); |
| entry.setValue(embed(fmd.getElement(), |
| entry.getValue())); |
| } |
| } |
| return map; |
| } |
| |
| /** |
| * Make the given object embedded. |
| */ |
| private Object embed(ValueMetaData vmd, Object obj) { |
| if (obj == null) |
| return null; |
| return _broker.embed(obj, null, _sm, vmd).getManagedInstance(); |
| } |
| |
| /** |
| * Return the proxy manager. |
| */ |
| private ProxyManager getProxyManager () |
| { |
| return _broker.getConfiguration ().getProxyManagerInstance (); |
| } |
| } |