blob: ae6ae81528d44e276034efe0e00d352ca09d4358 [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.lang.reflect.Array;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.apache.openjpa.event.OrphanedKeyAction;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.meta.ValueMetaData;
import org.apache.openjpa.util.ChangeTracker;
import org.apache.openjpa.util.Proxy;
/**
* Abstract base class which implements core PCData behavior.
*
* @author Patrick Linskey
* @author Abe White
*/
public abstract class AbstractPCData
implements PCData {
private static final long serialVersionUID = 1L;
public static final Object NULL = new Object();
private static final Object[] EMPTY_ARRAY = new Object[0];
/**
* Return the loaded field mask.
*/
public abstract BitSet getLoaded();
/**
* Create a new pcdata for holding the state of an embedded instance.
*/
public abstract AbstractPCData newEmbeddedPCData(OpenJPAStateManager sm);
@Override
public boolean isLoaded(int field) {
return getLoaded().get(field);
}
/**
* Transform the given data value into its field value.
*/
protected Object toField(OpenJPAStateManager sm, FieldMetaData fmd,
Object data, FetchConfiguration fetch, Object context) {
if (data == null)
return null;
switch (fmd.getDeclaredTypeCode()) {
case JavaTypes.COLLECTION:
ProxyDataList c = (ProxyDataList) data;
Collection c2 = (Collection) sm.newFieldProxy(fmd.getIndex());
c2 = toNestedFields(sm, fmd.getElement(), (Collection) data,
fetch, context);
if (c2 instanceof Proxy) {
ChangeTracker ct = ((Proxy) c2).getChangeTracker();
if (ct != null)
ct.setNextSequence(c.nextSequence);
}
return c2;
case JavaTypes.MAP:
Map m = (Map) data;
Map m2 = (Map) sm.newFieldProxy(fmd.getIndex());
Collection keys = new ArrayList (m.size());
Collection values = new ArrayList(m.size());
for (Map.Entry e : (Iterable<Map.Entry>) m.entrySet()) {
keys.add(e.getKey());
values.add(e.getValue());
}
Object[] keyArray = toNestedFields(sm, fmd.getKey(),
keys, fetch, context).toArray();
Object[] valueArray = toNestedFields(sm, fmd.getElement(),
values, fetch, context).toArray();
for (int idx = 0; idx < keyArray.length; idx++)
m2.put(keyArray[idx], valueArray[idx]);
return m2;
case JavaTypes.ARRAY:
int length = Array.getLength(data);
Object a = Array.newInstance(fmd.getElement().getDeclaredType(),
length);
if (length == 0)
return a;
if (isImmutableType(fmd.getElement())) {
System.arraycopy(data, 0, a, 0, length);
} else {
for (int i = 0; i < length; i++) {
Array.set(a, i, toNestedField(sm, fmd.getElement(),
Array.get(data, i), fetch, context));
}
}
return a;
default:
return toNestedField(sm, fmd, data, fetch, context);
}
}
/**
* Transform the given data value to its field value. The data value
* may be a key, value, or element of a map or collection.
*/
protected Object toNestedField(OpenJPAStateManager sm, ValueMetaData vmd,
Object data, FetchConfiguration fetch, Object context) {
if (data == null)
return null;
switch (vmd.getDeclaredTypeCode()) {
case JavaTypes.DATE:
return ((Date) data).clone();
case JavaTypes.LOCALE:
return (Locale) data;
case JavaTypes.PC:
if (vmd.isEmbedded())
return toEmbeddedField(sm, vmd, data, fetch, context);
// no break
case JavaTypes.PC_UNTYPED:
Object ret =
toRelationField(sm, vmd, data, fetch, context);
if (ret != null)
return ret;
OrphanedKeyAction action = sm.getContext().getConfiguration().
getOrphanedKeyActionInstance();
return action.orphan(data, sm, vmd);
default:
return data;
}
}
/**
* Transform the given data value to its field value. The data value
* may be a key, value, or element of a map or collection.
*/
protected Collection toNestedFields(OpenJPAStateManager sm,
ValueMetaData vmd, Collection data, FetchConfiguration fetch,
Object context) {
if (data == null)
return null;
Collection ret = new ArrayList(data.size());
switch (vmd.getDeclaredTypeCode()) {
case JavaTypes.DATE:
for (Object item : data) {
ret.add(((Date) item).clone());
}
return ret;
case JavaTypes.LOCALE:
for (Object value : data) {
ret.add((Locale) value);
}
return ret;
case JavaTypes.PC:
if (vmd.isEmbedded()) {
for (Object datum : data) {
ret.add(toEmbeddedField(sm, vmd, datum, fetch
, context));
}
return ret;
}
// no break
case JavaTypes.PC_UNTYPED:
Object[] r = toRelationFields(sm, data, fetch);
if (r != null) {
for (Object o : r)
if (o != null)
ret.add(o);
else {
ret.add(sm.getContext().getConfiguration().
getOrphanedKeyActionInstance().
orphan(data, sm, vmd));
}
return ret;
}
default:
return data;
}
}
/**
* Transform the given data into a relation field value. Default
* implementation assumes the data is an oid.
*/
protected Object toRelationField(OpenJPAStateManager sm, ValueMetaData vmd,
Object data, FetchConfiguration fetch, Object context) {
return sm.getContext().find(data, fetch, null, null, 0);
}
/**
* Transform the given data into relation field values. Default
* implementation assumes the data is an oid.
*/
protected Object[] toRelationFields(OpenJPAStateManager sm,
Object data, FetchConfiguration fetch) {
return sm.getContext().findAll((Collection) data, fetch, null, null, 0);
}
/**
* Transform the given data into an embedded PC field value. Default
* implementation assumes the data is an {@link AbstractPCData}.
*/
protected Object toEmbeddedField(OpenJPAStateManager sm, ValueMetaData vmd,
Object data, FetchConfiguration fetch, Object context) {
AbstractPCData pcdata = (AbstractPCData) data;
OpenJPAStateManager embedded = sm.getContext().embed(null,
pcdata.getId(), sm, vmd);
pcdata.load(embedded, (BitSet) pcdata.getLoaded().clone(),
fetch, context);
return embedded.getManagedInstance();
}
/**
* Transform the given field value to a data value for caching. Return
* {@link #NULL} if unable to cache.
*/
protected Object toData(FieldMetaData fmd, Object val, StoreContext ctx) {
if (val == null)
return null;
switch (fmd.getDeclaredTypeCode()) {
case JavaTypes.COLLECTION:
Collection c = (Collection) val;
if (c.isEmpty())
return ProxyDataList.EMPTY_LIST;
ProxyDataList c2 = null;
int size;
for (Object value : c) {
val = toNestedData(fmd.getElement(), value, ctx);
if (val == NULL)
return NULL;
if (c2 == null) {
size = c.size();
c2 = new ProxyDataList(size);
if (c instanceof Proxy) {
ChangeTracker ct = ((Proxy) c).getChangeTracker();
if (ct != null)
c2.nextSequence = ct.getNextSequence();
}
else
c2.nextSequence = size;
}
c2.add(val);
}
return c2;
case JavaTypes.MAP:
Map m = (Map) val;
if (m.isEmpty())
return Collections.EMPTY_MAP;
Map m2 = null;
Map.Entry e;
Object val2;
for (Object o : m.entrySet()) {
e = (Map.Entry) o;
val = toNestedData(fmd.getKey(), e.getKey(), ctx);
if (val == NULL)
return NULL;
val2 = toNestedData(fmd.getElement(), e.getValue(), ctx);
if (val2 == NULL)
return NULL;
if (m2 == null)
m2 = new HashMap(m.size());
m2.put(val, val2);
}
return m2;
case JavaTypes.ARRAY:
int length = Array.getLength(val);
if (length == 0)
return EMPTY_ARRAY;
Object a;
if (isImmutableType(fmd.getElement())) {
a = Array.newInstance(fmd.getElement().getDeclaredType(),
length);
System.arraycopy(val, 0, a, 0, length);
} else {
Object[] data = new Object[length];
for (int i = 0; i < length; i++) {
data[i] = toNestedData(fmd.getElement(),
Array.get(val, i), ctx);
}
a = data;
}
return a;
default:
return toNestedData(fmd, val, ctx);
}
}
/**
* Return whether the declared type of the given value is immutable.
*/
private boolean isImmutableType(ValueMetaData element) {
switch (element.getDeclaredTypeCode()) {
case JavaTypes.BOOLEAN:
case JavaTypes.BYTE:
case JavaTypes.CHAR:
case JavaTypes.DOUBLE:
case JavaTypes.FLOAT:
case JavaTypes.INT:
case JavaTypes.LONG:
case JavaTypes.SHORT:
case JavaTypes.STRING:
case JavaTypes.NUMBER:
case JavaTypes.BOOLEAN_OBJ:
case JavaTypes.BYTE_OBJ:
case JavaTypes.CHAR_OBJ:
case JavaTypes.DOUBLE_OBJ:
case JavaTypes.FLOAT_OBJ:
case JavaTypes.INT_OBJ:
case JavaTypes.LONG_OBJ:
case JavaTypes.SHORT_OBJ:
case JavaTypes.BIGDECIMAL:
case JavaTypes.BIGINTEGER:
return true;
default:
return false;
}
}
/**
* Transform the given nested value to a cachable value. Return
* {@link #NULL} if the value cannot be cached.
*/
protected Object toNestedData(ValueMetaData vmd, Object val,
StoreContext ctx) {
if (val == null)
return null;
switch (vmd.getDeclaredTypeCode()) {
case JavaTypes.PC:
if (vmd.isEmbedded())
return toEmbeddedData(val, ctx);
// no break
case JavaTypes.PC_UNTYPED:
return toRelationData(val, ctx);
case JavaTypes.DATE:
if (val instanceof Proxy)
return ((Proxy) val).copy(val);
else
return ((Date) val).clone();
case JavaTypes.LOCALE:
return (Locale) val;
case JavaTypes.OBJECT:
if (val instanceof Proxy)
return ((Proxy) val).copy(val);
else
return val;
default:
return val;
}
}
/**
* Return the value to cache for the given object. Caches its oid by
* default.
*/
protected Object toRelationData(Object val, StoreContext ctx) {
return ctx.getObjectId(val);
}
/**
* Return the value to cache for the given embedded PC. Caches a
* {@link PCData} from {@link #newEmbeddedPCData} by default.
*/
protected Object toEmbeddedData(Object val, StoreContext ctx) {
if (ctx == null)
return NULL;
OpenJPAStateManager sm = ctx.getStateManager(val);
if (sm == null)
return NULL;
// have to cache all data, so make sure it's all loaded
// ### prevent loading of things that aren't cached (lobs, lrs, etc)
ctx.retrieve(val, false, null);
PCData pcdata = newEmbeddedPCData(sm);
pcdata.store(sm);
return pcdata;
}
/**
* Tracks proxy data along with list elements.
*/
private static class ProxyDataList
extends ArrayList {
private static final long serialVersionUID = 1L;
public static final ProxyDataList EMPTY_LIST = new ProxyDataList(0);
public int nextSequence = 0;
public ProxyDataList(int size) {
super (size);
}
}
}