blob: 15416a48431bae1a07ea1443d97093acc73426e3 [file] [log] [blame]
/*
* 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.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.BitSet;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import org.apache.openjpa.enhance.PersistenceCapable;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.util.ProxyManager;
/**
* FieldManager type used to store information for rollback.
*
* @author Abe White
*/
public class SaveFieldManager
extends ClearFieldManager
implements Serializable {
private static final long serialVersionUID = 1L;
private final StateManagerImpl _sm;
private final BitSet _unloaded;
private BitSet _saved = null;
private int[] _copyField = null;
private transient PersistenceCapable _state = null;
// used to track field value during store/fetch cycle
private Object _field = null;
/**
* Constructor. Provide {@link StateManagerImpl} of instance to save.
*/
SaveFieldManager(StateManagerImpl sm, PersistenceCapable pc, BitSet dirty) {
_sm = sm;
_state = pc;
// if instance is new or transient all fields will be marked dirty even
// though they have their original values, so we can restore them;
// otherwise, we need to record already-dirty persistent fields as
// ones we won't be able to restore
FieldMetaData[] fields = _sm.getMetaData().getFields();
if (_sm.isNew() || !_sm.isPersistent() || dirty == null)
_unloaded = new BitSet(fields.length);
else {
_unloaded = (BitSet) dirty.clone();
for (int i = 0; i < fields.length; i++)
if (fields[i].getManagement() != FieldMetaData.MANAGE_PERSISTENT)
_unloaded.clear(i);
}
}
/**
* Return the persistence capable copy holding the rollback field values.
*/
public PersistenceCapable getState() {
return _state;
}
/**
* Return the currently-loaded fields that will be unloaded after rollback.
*/
public BitSet getUnloaded() {
return _unloaded;
}
/**
* Save the given field. If this method returns true, then you need
* to use this field manager to replace the given field in the instance
* returned by {@link #getState}.
*/
public boolean saveField(int field) {
// if not loaded we can't save orig value; mark as unloaded on rollback
if (_sm.getLoaded() != null && !_sm.getLoaded().get(field)) {
_unloaded.set(field);
return false;
}
// already saved?
if (_saved != null && _saved.get(field))
return false;
FieldMetaData fmd = _sm.getMetaData().getField(field);
boolean mutable = false;
switch (fmd.getDeclaredTypeCode()) {
case JavaTypes.DATE:
case JavaTypes.ARRAY:
case JavaTypes.COLLECTION:
case JavaTypes.MAP:
case JavaTypes.OBJECT:
mutable = true;
}
// if this is not an inverse field and the proper restore flag is
// not set, skip it
if (_sm.getBroker().getInverseManager() == null
|| fmd.getInverseMetaDatas().length == 0) {
// use sm's restore directive, not broker's
int restore = _sm.getBroker().getRestoreState();
if (restore == RestoreState.RESTORE_NONE
|| (mutable && restore == RestoreState.RESTORE_IMMUTABLE)) {
_unloaded.set(field);
return false;
}
}
// prepare to save the field
if (_state == null)
_state = _sm.getPersistenceCapable().pcNewInstance(_sm, true);
if (_saved == null)
_saved = new BitSet(_sm.getMetaData().getFields().length);
_saved.set(field);
// if mutable, return true to indicate that the field needs to be
// copied by providing and replacing it using this field manager
if (mutable)
return true;
// immutable fields can just be copied over
if (_copyField == null)
_copyField = new int[1];
_copyField[0] = field;
getState().pcCopyFields(_sm.getPersistenceCapable(), _copyField);
return false;
}
/**
* Restore the given field. If this method returns true, then you need
* to use this field manager to replace the given field in the state
* manager's instance.
*/
public boolean restoreField(int field) {
// if the given field needs to be unloaded, return true so that it gets
// replaced with a default value
if (_unloaded.get(field))
return true;
// if the field was not saved, it must not have gotten dirty; just
// return false so that the current value is kept
if (_saved == null || !_saved.get(field))
return false;
// copy the saved field over
if (_copyField == null)
_copyField = new int[1];
_copyField[0] = field;
_sm.getPersistenceCapable().pcCopyFields(getState(), _copyField);
return false;
}
/**
* Compare the given field.
* @return <code>true</code> if the field is the same in the current
* state and in the saved state; otherwise, <code>false</code>.
*/
public boolean isFieldEqual(int field, Object current) {
// if the field is not available, assume that it has changed.
if (_saved == null || !_saved.get(field))
return false;
if (!(getState().pcGetStateManager() instanceof StateManagerImpl))
return false;
StateManagerImpl sm = (StateManagerImpl) getState().pcGetStateManager();
SingleFieldManager single = new SingleFieldManager(sm, sm.getBroker());
sm.provideField(getState(), single, field);
Object old = single.fetchObjectField(field);
return current == old || current != null && current.equals(old);
}
@Override
public Object fetchObjectField(int field) {
// return the copied field during save, or a null value during restore
return _field;
}
@Override
public void storeObjectField(int field, Object curVal) {
// copy mutable fields
ProxyManager proxy = _sm.getBroker().getConfiguration().
getProxyManagerInstance();
FieldMetaData fmd = _sm.getMetaData().getField(field);
switch (fmd.getDeclaredTypeCode()) {
case JavaTypes.ARRAY:
_field = proxy.copyArray(curVal);
break;
case JavaTypes.COLLECTION:
_field = proxy.copyCollection((Collection) curVal);
break;
case JavaTypes.MAP:
_field = proxy.copyMap((Map) curVal);
break;
case JavaTypes.DATE:
_field = proxy.copyDate((Date) curVal);
break;
case JavaTypes.OBJECT:
_field = proxy.copyCustom(curVal);
if (_field == null)
_field = curVal;
break;
default:
_field = curVal;
}
// if we couldn't get a copy of the sco, act like it wasn't saved
if (curVal != null && _field == null) {
_unloaded.set(field);
_saved.clear(field);
}
}
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
_sm.writePC(oos, _state);
}
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject();
_state = _sm.readPC(ois);
}
}