/**********************************************************************
Copyright (c) 2005 Andy Jefferson and others. All rights reserved.
Licensed 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.
Contributors:
...
**********************************************************************/
package org.datanucleus.store.rdbms.mapping.java;
import java.lang.reflect.Array;
import java.util.List;
import org.datanucleus.ExecutionContext;
import org.datanucleus.exceptions.ReachableObjectNotCascadedException;
import org.datanucleus.metadata.MetaDataUtils;
import org.datanucleus.metadata.RelationType;
import org.datanucleus.state.ObjectProvider;
import org.datanucleus.store.rdbms.mapping.MappingCallbacks;
import org.datanucleus.store.types.SCO;
import org.datanucleus.store.types.scostore.ArrayStore;
import org.datanucleus.store.types.wrappers.backed.BackedSCO;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.NucleusLogger;
/**
* Mapping for an array.
*/
public class ArrayMapping extends AbstractContainerMapping implements MappingCallbacks
{
/**
* Accessor for the Java type represented here.
* @return The java type
*/
public Class getJavaType()
{
if (mmd != null)
{
return mmd.getType();
}
return null;
}
/**
* Convenience method to return if the array is stored in the owning table as a column.
* Overrides the superclass since arrays can be stored in a single column also when the no join is
* specified and the array is of a primitive/wrapper type.
* @return Whether it is stored in a single column in the main table.
*/
protected boolean containerIsStoredInSingleColumn()
{
if (super.containerIsStoredInSingleColumn())
{
return true;
}
if (mmd != null && mmd.hasArray() && mmd.getJoinMetaData() == null && MetaDataUtils.getInstance().arrayStorableAsByteArrayInSingleColumn(mmd))
{
return true;
}
return false;
}
// ---------------------- Implementation of MappingCallbacks ----------------------------------
public void insertPostProcessing(ObjectProvider op)
{
}
/**
* Method to be called after the insert of the owner class element.
* @param ownerOP ObjectProvider of the owner
**/
public void postInsert(ObjectProvider ownerOP)
{
ExecutionContext ec = ownerOP.getExecutionContext();
Object value = ownerOP.provideField(getAbsoluteFieldNumber());
if (value == null)
{
return;
}
if (containerIsStoredInSingleColumn())
{
if (mmd.getArray().elementIsPersistent())
{
// Make sure all persistable elements have ObjectProviders
Object[] arrElements = (Object[])value;
for (Object elem : arrElements)
{
if (elem != null)
{
ObjectProvider elemOP = ec.findObjectProvider(elem);
if (elemOP == null || ec.getApiAdapter().getExecutionContext(elem) == null)
{
elemOP = ec.getNucleusContext().getObjectProviderFactory().newForEmbedded(ec, elem, false, ownerOP, mmd.getAbsoluteFieldNumber());
}
}
}
}
return;
}
int arrayLength = Array.getLength(value);
boolean persistentElements = (mmd.getRelationType(ec.getClassLoaderResolver()) != RelationType.NONE);
boolean needsAttaching = false;
if (persistentElements)
{
Object[] array = (Object[])value;
if (!mmd.isCascadePersist())
{
// Check that all elements are persistent before continuing and throw exception if necessary
if (NucleusLogger.PERSISTENCE.isDebugEnabled())
{
NucleusLogger.PERSISTENCE.debug(Localiser.msg("007006", mmd.getFullFieldName()));
}
for (int i=0;i<arrayLength;i++)
{
if (!ec.getApiAdapter().isDetached(array[i]) && !ec.getApiAdapter().isPersistent(array[i]))
{
// Element is not persistent so throw exception
throw new ReachableObjectNotCascadedException(mmd.getFullFieldName(), array[i]);
}
}
}
else
{
// Reachability
if (NucleusLogger.PERSISTENCE.isDebugEnabled())
{
NucleusLogger.PERSISTENCE.debug(Localiser.msg("007007", mmd.getFullFieldName()));
}
}
for (int i=0;i<arrayLength;i++)
{
if (ownerOP.getExecutionContext().getApiAdapter().isDetached(array[i]))
{
needsAttaching = true;
break;
}
}
}
if (needsAttaching)
{
// Create a wrapper and attach the elements (and add the others)
SCO collWrapper = replaceFieldWithWrapper(ownerOP, null);
if (arrayLength > 0)
{
collWrapper.attachCopy(value);
// The attach will have put entries in the operationQueue if using optimistic, so flush them
ownerOP.getExecutionContext().flushOperationsForBackingStore(((BackedSCO)collWrapper).getBackingStore(), ownerOP);
}
}
else
{
if (arrayLength > 0)
{
// Add the elements direct to the datastore
((ArrayStore) storeMgr.getBackingStoreForField(ownerOP.getExecutionContext().getClassLoaderResolver(),mmd, null)).set(ownerOP, value);
}
}
}
/**
* Method to be called after any fetch of the owner class element.
* @param op ObjectProvider of the owner
*/
public void postFetch(ObjectProvider op)
{
if (containerIsStoredInSingleColumn())
{
// Do nothing when stored in a single column since we are handled in the main request
return;
}
List elements = ((ArrayStore) storeMgr.getBackingStoreForField(op.getExecutionContext().getClassLoaderResolver(),mmd,null)).getArray(op);
if (elements != null)
{
boolean primitiveArray = mmd.getType().getComponentType().isPrimitive();
Object array = Array.newInstance(mmd.getType().getComponentType(), elements.size());
for (int i=0;i<elements.size();i++)
{
Object element = elements.get(i);
if (primitiveArray)
{
// Handle the conversion back to the primitive
if (element instanceof Boolean)
{
Array.setBoolean(array, i, ((Boolean)element).booleanValue());
}
else if (element instanceof Byte)
{
Array.setByte(array, i, ((Byte)element).byteValue());
}
else if (element instanceof Character)
{
Array.setChar(array, i, ((Character)element).charValue());
}
else if (element instanceof Double)
{
Array.setDouble(array, i, ((Double)element).doubleValue());
}
else if (element instanceof Float)
{
Array.setFloat(array, i, ((Float)element).floatValue());
}
else if (element instanceof Integer)
{
Array.setInt(array, i, ((Integer)element).intValue());
}
else if (element instanceof Long)
{
Array.setLong(array, i, ((Long)element).longValue());
}
else if (element instanceof Short)
{
Array.setShort(array, i, ((Short)element).shortValue());
}
}
else
{
Array.set(array, i, element);
}
}
if (elements.size() == 0)
{
op.replaceFieldMakeDirty(getAbsoluteFieldNumber(), null);
}
else
{
op.replaceFieldMakeDirty(getAbsoluteFieldNumber(), array);
}
}
else
{
op.replaceFieldMakeDirty(getAbsoluteFieldNumber(), null);
}
}
/**
* Method to be called after any update of the owner class element.
* This method could be called in two situations
* <ul>
* <li>Update an array field of an object by replacing the array with a new array,
* so UpdateRequest is called, which calls here</li>
* <li>Persist a new object, and it needed to wait til the element was inserted so
* goes into dirty state and then flush() triggers UpdateRequest, which comes here</li>
* </ul>
* @param ownerOP ObjectProvider of the owner
*/
public void postUpdate(ObjectProvider ownerOP)
{
ExecutionContext ec = ownerOP.getExecutionContext();
Object value = ownerOP.provideField(getAbsoluteFieldNumber());
if (containerIsStoredInSingleColumn())
{
if (value != null)
{
if (mmd.getArray().elementIsPersistent())
{
// Make sure all persistable elements have ObjectProviders
Object[] arrElements = (Object[])value;
for (Object elem : arrElements)
{
if (elem != null)
{
ObjectProvider elemOP = ec.findObjectProvider(elem);
if (elemOP == null || ec.getApiAdapter().getExecutionContext(elem) == null)
{
elemOP = ec.getNucleusContext().getObjectProviderFactory().newForEmbedded(ec, elem, false, ownerOP, mmd.getAbsoluteFieldNumber());
}
}
}
}
}
return;
}
if (value == null)
{
// array is now null so remove any elements in the array
((ArrayStore) storeMgr.getBackingStoreForField(ec.getClassLoaderResolver(),mmd,null)).clear(ownerOP);
return;
}
if (!mmd.isCascadeUpdate())
{
// User doesn't want to update by reachability
if (NucleusLogger.PERSISTENCE.isDebugEnabled())
{
NucleusLogger.PERSISTENCE.debug(Localiser.msg("007008", mmd.getFullFieldName()));
}
return;
}
if (NucleusLogger.PERSISTENCE.isDebugEnabled())
{
NucleusLogger.PERSISTENCE.debug(Localiser.msg("007009", mmd.getFullFieldName()));
}
// Update the datastore
// TODO Do this more efficiently, removing elements no longer present, and adding new ones
ArrayStore backingStore = (ArrayStore) storeMgr.getBackingStoreForField(ec.getClassLoaderResolver(), mmd, null);
backingStore.clear(ownerOP);
backingStore.set(ownerOP, value);
}
/**
* Method to be called before any delete of the owner class element, if the field in the owner is dependent
* @param ownerOP ObjectProvider of the owner
*/
public void preDelete(ObjectProvider ownerOP)
{
if (containerIsStoredInSingleColumn())
{
// Do nothing when stored in a single column since we are handled in the main request
return;
}
// makes sure field is loaded
ownerOP.isLoaded(getAbsoluteFieldNumber());
Object value = ownerOP.provideField(getAbsoluteFieldNumber());
if (value == null)
{
return;
}
// Clear the array via its backing store
ArrayStore backingStore = (ArrayStore) storeMgr.getBackingStoreForField(ownerOP.getExecutionContext().getClassLoaderResolver(), mmd, null);
backingStore.clear(ownerOP);
}
}