| /* |
| * 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.lang.reflect.Field; |
| import java.util.Collection; |
| import java.util.Map; |
| |
| import org.apache.openjpa.enhance.PersistenceCapable; |
| import org.apache.openjpa.enhance.Reflection; |
| import org.apache.openjpa.enhance.StateManager; |
| import org.apache.openjpa.event.LifecycleEvent; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.meta.ClassMetaData; |
| 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.ApplicationIds; |
| import org.apache.openjpa.util.ImplHelper; |
| import org.apache.openjpa.util.ObjectNotFoundException; |
| import org.apache.openjpa.util.OptimisticException; |
| |
| /** |
| * Handles attaching instances using version and primary key fields. |
| * |
| * @author Steve Kim |
| */ |
| class VersionAttachStrategy |
| extends AttachStrategy |
| implements DetachState { |
| |
| private static final Localizer _loc = Localizer.forPackage |
| (VersionAttachStrategy.class); |
| |
| @Override |
| protected Object getDetachedObjectId(AttachManager manager, |
| Object toAttach) { |
| Broker broker = manager.getBroker(); |
| ClassMetaData meta = broker.getConfiguration(). |
| getMetaDataRepositoryInstance().getMetaData( |
| ImplHelper.getManagedInstance(toAttach).getClass(), |
| broker.getClassLoader(), true); |
| return ApplicationIds.create(ImplHelper.toPersistenceCapable(toAttach, |
| broker.getConfiguration()), |
| meta); |
| } |
| |
| @Override |
| protected void provideField(Object toAttach, StateManagerImpl sm, |
| int field) { |
| sm.provideField(ImplHelper.toPersistenceCapable(toAttach, |
| sm.getContext().getConfiguration()), this, field); |
| } |
| |
| @Override |
| public Object attach(AttachManager manager, Object toAttach, |
| ClassMetaData meta, PersistenceCapable into, OpenJPAStateManager owner, |
| ValueMetaData ownerMeta, boolean explicit) { |
| BrokerImpl broker = manager.getBroker(); |
| PersistenceCapable pc = ImplHelper.toPersistenceCapable(toAttach, |
| meta.getRepository().getConfiguration()); |
| |
| boolean embedded = ownerMeta != null && ownerMeta.isEmbeddedPC(); |
| |
| // OJ-2405: If toAttach has a StateManagerImpl, then it is important to check if it |
| // is being managed by different broker. If it is, then it should not be |
| // considered "new". |
| boolean isNew = !broker.isDetached(pc) && !isManagedByAnotherPCtx(pc, broker); |
| Object version = null; |
| StateManagerImpl sm; |
| |
| // if the state manager for the embedded instance is null, then |
| // it should be treated as a new instance (since the |
| // newly persisted owner may create a new embedded instance |
| // in the constructor); fixed bug #1075. |
| // also, if the user has attached a detached obj from somewhere |
| // else in the graph to an embedded field that was previously null, |
| // copy into a new embedded instance |
| if (embedded && (isNew || into == null |
| || broker.getStateManager(into) == null)) { |
| if (into == null) |
| into = pc.pcNewInstance(null, false); |
| sm = (StateManagerImpl) broker.embed(into, null, owner, ownerMeta); |
| into = sm.getPersistenceCapable(); |
| } else if (isNew) { |
| Object oid = null; |
| if (!isPrimaryKeysGenerated(meta)) |
| oid = ApplicationIds.create(pc, meta); |
| |
| sm = persist(manager, pc, meta, oid, explicit); |
| into = sm.getPersistenceCapable(); |
| } else if (!embedded && into == null) { |
| Object id = getDetachedObjectId(manager, toAttach); |
| if (id != null) |
| into = |
| ImplHelper.toPersistenceCapable(broker.find(id, true, null), |
| broker.getConfiguration()); |
| if (into == null) |
| throw new OptimisticException(_loc.get("attach-version-del", |
| ImplHelper.getManagedInstance(pc).getClass(), id, version)) |
| .setFailedObject(toAttach); |
| |
| sm = manager.assertManaged(into); |
| if (meta.getDescribedType() |
| != sm.getMetaData().getDescribedType()) { |
| throw new ObjectNotFoundException(_loc.get |
| ("attach-wrongclass", id, toAttach.getClass(), |
| sm.getMetaData().getDescribedType())). |
| setFailedObject(toAttach); |
| } |
| } else |
| sm = manager.assertManaged(into); |
| |
| // mark that we attached the instance *before* we |
| // fill in values to avoid endless recursion |
| manager.setAttachedCopy(toAttach, into); |
| |
| // if persisting in place, just attach field values |
| if (pc == into) { |
| attachFieldsInPlace(manager, sm); |
| return into; |
| } |
| |
| if (isNew) { |
| broker.fireLifecycleEvent(toAttach, null, meta, |
| LifecycleEvent.BEFORE_PERSIST); |
| } else { |
| // invoke any preAttach on the detached instance |
| manager.fireBeforeAttach(toAttach, meta); |
| } |
| |
| // assign the detached pc the same state manager as the object we're |
| // copying into during the attach process |
| StateManager smBefore = pc.pcGetStateManager(); |
| pc.pcReplaceStateManager(sm); |
| int detach = (isNew) ? DETACH_ALL : broker.getDetachState(); |
| FetchConfiguration fetch = broker.getFetchConfiguration(); |
| try { |
| FieldMetaData[] fmds = sm.getMetaData().getFields(); |
| for (FieldMetaData fmd : fmds) { |
| switch (detach) { |
| case DETACH_ALL: |
| attachField(manager, toAttach, sm, fmd, true); |
| break; |
| case DETACH_FETCH_GROUPS: |
| if (fetch.requiresFetch(fmd) |
| != FetchConfiguration.FETCH_NONE) |
| attachField(manager, toAttach, sm, fmd, true); |
| break; |
| case DETACH_LOADED: |
| attachField(manager, toAttach, sm, fmd, false); |
| break; |
| } |
| } |
| } finally { |
| pc.pcReplaceStateManager(smBefore); |
| } |
| if (!embedded && !isNew) |
| compareVersion(sm, pc); |
| return ImplHelper.getManagedInstance(into); |
| } |
| |
| /** |
| * Make sure the version information is correct in the detached object. |
| */ |
| private void compareVersion(StateManagerImpl sm, PersistenceCapable pc) { |
| Object version = pc.pcGetVersion(); |
| // In the event that the version field is a primitive and it is the types default value, we can't differentiate |
| // between a value that was set to be the default, and one that defaulted to that value. |
| if (version != null |
| && JavaTypes.isPrimitiveDefault(version, sm.getMetaData().getVersionField().getTypeCode())) { |
| Field pcVersionInitField = null; |
| try { |
| pcVersionInitField = pc.getClass().getDeclaredField("pcVersionInit"); |
| Object pcField = Reflection.get(pc, pcVersionInitField); |
| if (pcField != null) { |
| boolean bool = (Boolean) pcField; |
| if (!bool) { |
| // If this field if false, that means that the pcGetVersion returned a default value rather than |
| // and actual value. |
| version = null; |
| } |
| } |
| } catch (Exception e) { |
| // Perhaps this is an Entity that was enhanced before the pcVersionInit field was added. |
| } |
| } |
| if (version == null) { |
| return; |
| } |
| // don't need to load unloaded fields since its implicitly |
| // a single field value |
| StoreManager store = sm.getBroker().getStoreManager(); |
| switch (store.compareVersion(sm, version, sm.getVersion())) { |
| case StoreManager.VERSION_LATER: |
| // we have a later version: set it into the object. |
| // lock validation will occur at commit time |
| sm.setVersion(version); |
| break; |
| case StoreManager.VERSION_EARLIER: |
| case StoreManager.VERSION_DIFFERENT: |
| sm.setVersion(version); |
| throw new OptimisticException(sm.getManagedInstance()); |
| case StoreManager.VERSION_SAME: |
| // no action required |
| break; |
| } |
| } |
| |
| /** |
| * Attach the fields of an in-place persisted instance. |
| */ |
| private void attachFieldsInPlace(AttachManager manager, |
| StateManagerImpl sm) { |
| FieldMetaData[] fmds = sm.getMetaData().getFields(); |
| for (int i = 0; i < fmds.length; i++) { |
| if (fmds[i].getManagement() != FieldMetaData.MANAGE_PERSISTENT) |
| continue; |
| |
| Object cur, attached; |
| switch (fmds[i].getDeclaredTypeCode()) { |
| case JavaTypes.PC: |
| case JavaTypes.PC_UNTYPED: |
| cur = sm.fetchObjectField(i); |
| attached = attachInPlace(manager, sm, fmds[i], cur); |
| break; |
| case JavaTypes.ARRAY: |
| if (!fmds[i].getElement().isDeclaredTypePC()) |
| continue; |
| cur = sm.fetchObjectField(i); |
| attached = |
| attachInPlace(manager, sm, fmds[i], (Object[]) cur); |
| break; |
| case JavaTypes.COLLECTION: |
| if (!fmds[i].getElement().isDeclaredTypePC()) |
| continue; |
| cur = sm.fetchObjectField(i); |
| attached = attachInPlace(manager, sm, fmds[i], |
| (Collection) cur); |
| break; |
| case JavaTypes.MAP: |
| if (!fmds[i].getElement().isDeclaredTypePC() |
| && !fmds[i].getKey().isDeclaredTypePC()) |
| continue; |
| cur = sm.fetchObjectField(i); |
| attached = attachInPlace(manager, sm, fmds[i], (Map) cur); |
| break; |
| default: |
| continue; |
| } |
| |
| if (cur != attached) |
| sm.settingObjectField(sm.getPersistenceCapable(), i, |
| cur, attached, StateManager.SET_REMOTE); |
| } |
| } |
| |
| /** |
| * Attach the given pc. |
| */ |
| private Object attachInPlace(AttachManager manager, StateManagerImpl sm, |
| ValueMetaData vmd, Object pc) { |
| if (pc == null) |
| return null; |
| Object attached = manager.getAttachedCopy(pc); |
| if (attached != null) |
| return attached; |
| |
| OpenJPAStateManager into = manager.getBroker().getStateManager(pc); |
| PersistenceCapable intoPC = (into == null) ? null |
| : into.getPersistenceCapable(); |
| if (vmd.isEmbedded()) |
| return manager.attach(pc, intoPC, sm, vmd, false); |
| return manager.attach(pc, intoPC, null, null, false); |
| } |
| |
| /** |
| * Attach the given array. |
| */ |
| private Object[] attachInPlace(AttachManager manager, StateManagerImpl sm, |
| FieldMetaData fmd, Object[] arr) { |
| if (arr == null) |
| return null; |
| |
| for (int i = 0; i < arr.length; i++) |
| arr[i] = attachInPlace(manager, sm, fmd.getElement(), arr[i]); |
| return arr; |
| } |
| |
| /** |
| * Attach the given collection. |
| */ |
| private Collection attachInPlace(AttachManager manager, |
| StateManagerImpl sm, FieldMetaData fmd, Collection coll) { |
| if (coll == null || coll.isEmpty()) |
| return coll; |
| |
| // copy if elements embedded or contains detached, which will mean |
| // we'll have to copy the existing elements |
| Collection copy = null; |
| if (fmd.getElement().isEmbedded()) |
| copy = (Collection) sm.newFieldProxy(fmd.getIndex()); |
| else { |
| for (Object o : coll) { |
| if (manager.getBroker().isDetached(o)) { |
| copy = (Collection) sm.newFieldProxy(fmd.getIndex()); |
| break; |
| } |
| } |
| } |
| |
| Object attached; |
| for (Object o : coll) { |
| attached = attachInPlace(manager, sm, fmd.getElement(), |
| o); |
| if (copy != null) |
| copy.add(attached); |
| } |
| return (copy == null) ? coll : copy; |
| } |
| |
| /** |
| * Attach the given map. |
| */ |
| private Map attachInPlace(AttachManager manager, StateManagerImpl sm, |
| FieldMetaData fmd, Map map) { |
| if (map == null || map.isEmpty()) |
| return map; |
| |
| Map copy = null; |
| Map.Entry entry; |
| boolean keyPC = fmd.getKey().isDeclaredTypePC(); |
| boolean valPC = fmd.getElement().isDeclaredTypePC(); |
| |
| // copy if embedded pcs or detached pcs, which will require us to |
| // copy elements |
| if (fmd.getKey().isEmbeddedPC() || fmd.getElement().isEmbeddedPC()) |
| copy = (Map) sm.newFieldProxy(fmd.getIndex()); |
| else { |
| for (Object o : map.entrySet()) { |
| entry = (Map.Entry) o; |
| if ((keyPC && manager.getBroker().isDetached(entry.getKey())) |
| || (valPC && manager.getBroker().isDetached |
| (entry.getValue()))) { |
| copy = (Map) sm.newFieldProxy(fmd.getIndex()); |
| break; |
| } |
| } |
| } |
| |
| Object key, val; |
| for (Object o : map.entrySet()) { |
| entry = (Map.Entry) o; |
| key = entry.getKey(); |
| if (keyPC) |
| key = attachInPlace(manager, sm, fmd.getKey(), key); |
| val = entry.getValue(); |
| if (valPC) |
| val = attachInPlace(manager, sm, fmd.getElement(), val); |
| if (copy != null) |
| copy.put(key, val); |
| } |
| return (copy == null) ? map : copy; |
| } |
| |
| /** |
| * Find a PersistenceCapable instance of an Object if it exists in the |
| * database. If the object is null or can't be found in the database. |
| * |
| * @param pc An object which will be attached into the current context. The |
| * object may or may not correspond to a row in the database. |
| * |
| * @return If the object is null or can't be found in the database this |
| * method returns null. Otherwise a PersistenceCapable representation of the |
| * object is returned. |
| */ |
| protected PersistenceCapable findFromDatabase(AttachManager manager, |
| Object pc) { |
| Object oid = manager.getBroker().newObjectId(pc.getClass(), |
| manager.getDetachedObjectId(pc)); |
| |
| if (oid != null) { |
| return ImplHelper.toPersistenceCapable( |
| manager.getBroker().find(oid, true, null), |
| manager.getBroker().getConfiguration()); |
| } else { |
| return null; |
| } |
| } |
| |
| private boolean isPrimaryKeysGenerated(ClassMetaData meta) { |
| FieldMetaData[] pks = meta.getPrimaryKeyFields(); |
| for (FieldMetaData pk : pks) { |
| if (pk.getValueStrategy() != ValueStrategies.NONE) |
| return true; |
| } |
| return false; |
| } |
| |
| private static boolean isManagedByAnotherPCtx(PersistenceCapable pc, BrokerImpl broker) { |
| StateManager sm = pc.pcGetStateManager(); |
| if (sm != null && sm instanceof StateManagerImpl) { |
| StateManagerImpl smi = (StateManagerImpl) sm; |
| Broker associatedBroker = smi.getBroker(); |
| |
| if (broker != associatedBroker) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| } |