| /* |
| * 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); |
| } |
| } |
| } |