blob: 6c96ffb2c44fb1d10773b077c1939067812ab2fb [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.xmlstore;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.openjpa.event.OrphanedKeyAction;
import org.apache.openjpa.kernel.FetchConfiguration;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.kernel.StoreContext;
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.util.Proxy;
import org.apache.openjpa.util.UnsupportedException;
import serp.util.Numbers;
/**
* In-memory form of data in datastore backing a single persistent object.
*/
public final class ObjectData
implements Cloneable {
private Object _oid;
private Object[] _data;
private Long _version;
private ClassMetaData _meta;
/**
* Create the object without underlying data. Just pass in type specific
* metadata and the oid.
*/
public ObjectData(Object oid, ClassMetaData meta) {
_oid = oid;
_meta = meta;
_data = new Object[meta.getFields().length];
}
/**
* Getter for oid.
*/
public Object getId() {
return _oid;
}
/**
* Get the data for the field with the given index.
*/
public Object getField(int num) {
return _data[num];
}
/**
* Set the data for the field with the given index.
*/
public void setField(int num, Object val) {
_data[num] = val;
}
/**
* Set the version number of the object.
*/
public void setVersion(Long version) {
_version = version;
}
/**
* Get the version number of the object.
*/
public Long getVersion() {
return _version;
}
/**
* Get the metadata associated with the type of persistent object for
* which this data applies.
*/
public ClassMetaData getMetaData() {
return _meta;
}
/**
* Load the data and version information for this object into the
* given state manager. Only fields in the given fetch configuration are
* loaded.
*/
public void load(OpenJPAStateManager sm, FetchConfiguration fetch) {
if (sm.getVersion() == null)
sm.setVersion(_version);
FieldMetaData[] fmds = _meta.getFields();
for (int i = 0; i < fmds.length; i++)
if (!sm.getLoaded().get(i) && fetch.requiresFetch(fmds[i])
!= FetchConfiguration.FETCH_NONE)
sm.store(i, toLoadable(sm, fmds[i], _data[i], fetch));
}
/**
* Load the data and version information for this object into the
* given state manager. Only fields in the given bit set will be loaded.
*/
public void load(OpenJPAStateManager sm, BitSet fields,
FetchConfiguration fetch) {
if (sm.getVersion() == null)
sm.setVersion(_version);
FieldMetaData[] fmds = _meta.getFields();
for (int i = 0; i < fmds.length; i++)
if (fields.get(i))
sm.store(i, toLoadable(sm, fmds[i], _data[i], fetch));
}
/**
* Convert the stored value <code>val</code> into a value for loading
* into a state manager.
*/
private static Object toLoadable(OpenJPAStateManager sm,
FieldMetaData fmd, Object val, FetchConfiguration fetch) {
if (val == null)
return null;
Collection c;
switch (fmd.getTypeCode()) {
case JavaTypes.COLLECTION:
// the stored value must be a collection
c = (Collection) val;
// the state manager will create a proxy collection of the needed
// type depending on the declared type of the user's field; the
// proxy will perform dirty tracking, etc
Collection c2 = (Collection) sm.newFieldProxy(fmd.getIndex());
// populate the proxy collection with our stored data, converting
// it to the right type from its stored form
for (Iterator itr = c.iterator(); itr.hasNext();)
c2.add(toNestedLoadable(sm, fmd.getElement(), itr.next(),
fetch));
return c2;
case JavaTypes.ARRAY:
// the stored value must be a collection; we put arrays into
// collections for storage
c = (Collection) val;
// create a new array of the right type; unlike collections in
// the case above, arrays cannot be proxied
Object a = Array.newInstance(fmd.getElement().getType(),
c.size());
// populate the array with our stored data, converting it to the
// right type from its stored form
int idx = 0;
for (Iterator itr = c.iterator(); itr.hasNext(); idx++)
Array.set(a, idx, toNestedLoadable(sm, fmd.getElement(),
itr.next(), fetch));
return a;
case JavaTypes.MAP:
// the stored value must be a map
Map m = (Map) val;
// the state manager will create a proxy map of the needed
// type depending on the declared type of the user's field; the
// proxy will perform dirty tracking, etc
Map m2 = (Map) sm.newFieldProxy(fmd.getIndex());
// populate the proxy map with our stored data, converting
// it to the right type from its stored form
for (Iterator itr = m.entrySet().iterator(); itr.hasNext();) {
Map.Entry e = (Map.Entry) itr.next();
m2.put(toNestedLoadable(sm, fmd.getKey(), e.getKey(),fetch),
toNestedLoadable(sm, fmd.getElement(), e.getValue(),
fetch));
}
return m2;
default:
// just convert the stored value into its loadable equivalent.
return toNestedLoadable(sm, fmd, val, fetch);
}
}
/**
* Convert the given stored value <code>val</code> to a value for loading
* into a state manager. The value <code>val</code> must be a singular
* value; it cannot be a container.
*/
private static Object toNestedLoadable(OpenJPAStateManager sm,
ValueMetaData vmd, Object val, FetchConfiguration fetch) {
if (val == null)
return null;
switch (vmd.getTypeCode()) {
// clone the date to prevent direct modification of our stored value
case JavaTypes.DATE:
return ((Date) val).clone();
case JavaTypes.PC:
case JavaTypes.PC_UNTYPED:
// for relations to other persistent objects, we store the related
// object's oid -- convert it back into a persistent instance
StoreContext ctx = sm.getContext();
Object pc = ctx.find(val, fetch, null, null, 0);
if (pc != null)
return pc;
OrphanedKeyAction action = ctx.getConfiguration().
getOrphanedKeyActionInstance();
return action.orphan(val, sm, vmd);
default:
return val;
}
}
/**
* Store the data and version information for this object from the
* given state manager. Only dirty fields will be stored.
*/
public void store(OpenJPAStateManager sm) {
_version = (Long) sm.getVersion();
// if the version has not been set in the state manager (only true
// when the object is new), set the version number to 0
if (_version == null)
_version = Numbers.valueOf(0L);
// run through each persistent field in the state manager and store it
FieldMetaData[] fmds = _meta.getFields();
for (int i = 0; i < fmds.length; i++) {
if (sm.getDirty().get(i)
&& fmds[i].getManagement() == fmds[i].MANAGE_PERSISTENT)
_data[i] = toStorable(fmds[i], sm.fetch(i), sm.getContext());
}
}
/**
* Convert the given field value <code>val</code> to a form we can store.
*/
private static Object toStorable(FieldMetaData fmd, Object val,
StoreContext ctx) {
if (val == null)
return null;
Collection c;
switch (fmd.getTypeCode()) {
case JavaTypes.COLLECTION:
c = (Collection) val;
// create a collection to copy the elements into for storage, and
// populate it with converted element values
Collection c2 = new ArrayList();
for (Iterator itr = c.iterator(); itr.hasNext();)
c2.add(toNestedStorable(fmd.getElement(), itr.next(), ctx));
return c2;
case JavaTypes.ARRAY:
// create a collection to copy the elements into for storage, and
// populate it with converted element values
c = new ArrayList();
for (int i = 0, len = Array.getLength(val); i < len; i++)
c.add(toNestedStorable(fmd.getElement(), Array.get(val, i),
ctx));
return c;
case JavaTypes.MAP:
Map m = (Map) val;
// create a map to copy the entries into for storage, and
// populate it with converted entry values
Map m2 = new HashMap();
for (Iterator itr = m.entrySet().iterator(); itr.hasNext();) {
Map.Entry e = (Map.Entry) itr.next();
m2.put(toNestedStorable(fmd.getKey(), e.getKey(), ctx),
toNestedStorable(fmd.getElement(), e.getValue(), ctx));
}
return m2;
default:
// just convert the loaded value into its storable equivalent
return toNestedStorable(fmd, val, ctx);
}
}
/**
* Convert the given loaded value <code>val</code> to a value for storing.
* The value <code>val</code> must be a singular value; it cannot be a
* container.
*/
private static Object toNestedStorable(ValueMetaData vmd, Object val,
StoreContext ctx) {
if (val == null)
return null;
switch (vmd.getTypeCode()) {
case JavaTypes.DATE:
// if the date is a proxy (since Dates are second class
// objects (SCOs) they can be proxied for dirty tracking,
// etc) then copy the value out of it for storage
if (val instanceof Proxy)
return ((Proxy) val).copy(val);
return ((Date) val).clone();
case JavaTypes.PC:
case JavaTypes.PC_UNTYPED:
return ctx.getObjectId(val);
case JavaTypes.COLLECTION:
case JavaTypes.ARRAY:
case JavaTypes.MAP:
// nested relation types (e.g. collections of collections)
// are not currently supported
throw new UnsupportedException("This store does not support "
+ "nested containers (e.g. collections of collections).");
default:
return val;
}
}
/**
* Clone this data.
*/
public Object clone() {
ObjectData data = new ObjectData(_oid, _meta);
data.setVersion(_version);
// copy each field
FieldMetaData[] fmds = _meta.getFields();
for (int i = 0; i < fmds.length; i++) {
Object val = _data[i];
if (val == null) {
data.setField(i, null);
continue;
}
switch (fmds[i].getTypeCode()) {
case JavaTypes.COLLECTION:
case JavaTypes.ARRAY:
data.setField(i, new ArrayList((Collection) val));
break;
case JavaTypes.MAP:
data.setField(i, new HashMap((Map) val));
break;
default:
data.setField(i, val);
}
}
return data;
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("Class: (" + _meta.getDescribedType().getName() + ")\n");
buf.append("Object Id: (" + _oid + ")\n");
buf.append("Version: (" + _version + ")\n");
FieldMetaData[] fmds = _meta.getFields();
for (int i = 0; i < fmds.length; i++) {
buf.append(" Field: (" + i + ")\n");
buf.append(" Name: (" + fmds[i].getName() + ")\n");
buf.append(" Value: (" + _data[i] + ")\n");
}
return buf.toString ();
}
}