/**********************************************************************
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.mapped.mapping;
import java.lang.reflect.Array;
import java.util.List;
import org.datanucleus.metadata.MetaDataUtils;
import org.datanucleus.store.ObjectProvider;
import org.datanucleus.store.exceptions.ReachableObjectNotCascadedException;
import org.datanucleus.store.scostore.ArrayStore;
import org.datanucleus.store.types.sco.SCOUtils;
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 StateManager of the owner
**/
public void postInsert(ObjectProvider ownerOP)
{
Object value = ownerOP.provideField(mmd.getAbsoluteFieldNumber());
if (containerIsStoredInSingleColumn())
{
// Check that the elements are not managed by other PM, or are not yet persisted
SCOUtils.validateObjectsForWriting(ownerOP, value);
return;
}
if (value == null)
{
return;
}
if (!mmd.isCascadePersist())
{
// Field doesnt support cascade-persist so no reachability
if (NucleusLogger.PERSISTENCE.isDebugEnabled())
{
NucleusLogger.PERSISTENCE.debug(LOCALISER.msg("007006", mmd.getFullFieldName()));
}
// Check for any persistable keys/values that arent persistent
if (!mmd.getType().getComponentType().isPrimitive())
{
Object[] array = (Object[])value;
for (int i=0;i<array.length;i++)
{
if (!ownerOP.getExecutionContext().getApiAdapter().isDetached(array[i]) &&
!ownerOP.getExecutionContext().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()));
}
// Insert the array
((ArrayStore) storeMgr.getBackingStoreForField(ownerOP.getExecutionContext().getClassLoaderResolver(),mmd, null)).set(ownerOP, value);
}
}
/**
* Method to be called after any fetch of the owner class element.
* @param sm StateManager of the owner
*/
public void postFetch(ObjectProvider sm)
{
if (containerIsStoredInSingleColumn())
{
// Do nothing when stored in a single column since we are handled in the main request
return;
}
List elements = ((ArrayStore) storeMgr.getBackingStoreForField(sm.getExecutionContext().getClassLoaderResolver(),mmd,null)).getArray(sm);
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)
{
sm.replaceFieldMakeDirty(mmd.getAbsoluteFieldNumber(), null);
}
else
{
sm.replaceFieldMakeDirty(mmd.getAbsoluteFieldNumber(), array);
}
}
else
{
sm.replaceFieldMakeDirty(mmd.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)
{
Object value = ownerOP.provideField(mmd.getAbsoluteFieldNumber());
if (containerIsStoredInSingleColumn())
{
// Check that the elements are not managed by other PM, or are not yet persisted
SCOUtils.validateObjectsForWriting(ownerOP, value);
return;
}
if (value == null)
{
// array is now null so remove any elements in the array
((ArrayStore) storeMgr.getBackingStoreForField(ownerOP.getExecutionContext().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(ownerOP.getExecutionContext().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 sm StateManager of the owner
*/
public void preDelete(ObjectProvider sm)
{
if (containerIsStoredInSingleColumn())
{
// Do nothing when stored in a single column since we are handled in the main request
return;
}
// makes sure field is loaded
sm.getExecutionContext().getApiAdapter().isLoaded(sm, mmd.getAbsoluteFieldNumber());
Object value = sm.provideField(mmd.getAbsoluteFieldNumber());
if (value == null)
{
return;
}
// Clear the array via its backing store
ArrayStore backingStore = (ArrayStore) storeMgr.getBackingStoreForField(sm.getExecutionContext().getClassLoaderResolver(), mmd, null);
backingStore.clear(sm);
}
}