/**********************************************************************
Copyright (c) 2004 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:
2007 Xuan Baldauf - Make error message "023011" a little bit more verbose
...
**********************************************************************/
package org.jpox.sco;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jpox.ClassLoaderResolver;
import org.jpox.ObjectManager;
import org.jpox.ObjectManagerFactoryImpl;
import org.jpox.ObjectManagerHelper;
import org.jpox.StateManager;
import org.jpox.TypeManager;
import org.jpox.api.ApiAdapter;
import org.jpox.exceptions.JPOXException;
import org.jpox.exceptions.JPOXObjectNotFoundException;
import org.jpox.exceptions.JPOXUserException;
import org.jpox.metadata.AbstractClassMetaData;
import org.jpox.metadata.AbstractMemberMetaData;
import org.jpox.state.FetchPlanState;
import org.jpox.store.FieldValues;
import org.jpox.store.scostore.CollectionStore;
import org.jpox.store.scostore.MapStore;
import org.jpox.store.scostore.SetStore;
import org.jpox.util.ClassUtils;
import org.jpox.util.JPOXLogger;
import org.jpox.util.JavaUtils;
import org.jpox.util.Localiser;
import org.jpox.util.StringUtils;
/**
* Collection of utilities for second class wrappers and objects.
*
* @version $Revision: 1.80 $
*/
public class SCOUtils
{
/** Localiser for messages. */
private static final Localiser LOCALISER = Localiser.getInstance("org.jpox.Localisation",
ObjectManagerFactoryImpl.class.getClassLoader());
/**
* Method to create a new SCO wrapper for a SCO type.
* The SCO wrapper will be appropriate for the passed value (which represents the instantiated type of the field)
* unless it is null when the wrapper will be appropriate for the declared type of the field.
* While the "instantiated type" and the type of "value" should be the same when value is non-null, there are
* situations where we need to create a List based collection yet have no value so pass in the declaredType
* as Collection, instantiatedType as ArrayList, and value as null.
* @param ownerSM State Manager for the owning object
* @param fmd The Field MetaData for the related field.
* @param declaredType The class of the object
* @param instantiatedType Instantiated type for the field if known
* @param value The value we are wrapping if known
* @param forInsert Whether the SCO needs inserting in the datastore with this value
* @param forUpdate Whether the SCO needs updating in the datastore with this value
* @param replaceField Whether to replace the field with this value
* @return The Second-Class Object
* @throws JPOXUserException if an error occurred when creating the SCO instance
*/
public static SCO newSCOInstance(StateManager ownerSM, AbstractMemberMetaData fmd,
Class declaredType, Class instantiatedType, Object value, boolean forInsert, boolean forUpdate,
boolean replaceField)
{
if (!fmd.getType().isAssignableFrom(declaredType))
{
throw new JPOXUserException(LOCALISER.msg("023010",
declaredType.getName(), fmd.getName(), fmd.getType()));
}
String typeName = declaredType.getName();
if (instantiatedType != null)
{
// Use instantiated type if available
typeName = instantiatedType.getName();
}
if (value != null)
{
// If we have a current value, use the actual type to define the wrapper type
typeName = value.getClass().getName();
}
// Find the SCO wrapper type most suitable
TypeManager typeMgr = fmd.getMetaDataManager().getOMFContext().getTypeManager();
boolean fullWrapper = ownerSM.getStoreManager().getSupportedOptions().contains("ContainerQueueing");
Class wrapperType = null;
if (fullWrapper)
{
wrapperType = typeMgr.getSecondClassWrapper(typeName);
}
else
{
wrapperType = typeMgr.getSecondClassWrapperSimple(typeName);
}
if (wrapperType == null)
{
if (value != null && typeMgr.isSecondClassWrapper(typeName))
{
// The passed in value is a wrapper type already, so just return it!
SCO sco = (SCO)value;
if (replaceField)
{
// Replace the field with this value
ownerSM.replaceField(fmd.getAbsoluteFieldNumber(), sco, false);
}
return sco;
}
else
{
// typeName not supported directly (no SCO wrapper for the precise type)
if (instantiatedType != null)
{
// Try the instantiated type
if (fullWrapper)
{
wrapperType = typeMgr.getSecondClassWrapper(instantiatedType.getName());
}
else
{
wrapperType = typeMgr.getSecondClassWrapperSimple(instantiatedType.getName());
}
}
if (wrapperType == null)
{
// Try the declared type
if (fullWrapper)
{
wrapperType = typeMgr.getSecondClassWrapper(declaredType.getName());
}
else
{
wrapperType = typeMgr.getSecondClassWrapperSimple(declaredType.getName());
}
}
}
}
if (wrapperType == null)
{
throw new JPOXUserException(LOCALISER.msg("023011",
declaredType.getName(), StringUtils.toJVMIDString(value), fmd.getFullFieldName()));
}
// Create the SCO wrapper
SCO sco = (SCO) ClassUtils.newInstance(wrapperType,
new Class[]{org.jpox.StateManager.class, String.class},
new Object[]{ownerSM, fmd.getName()});
if (replaceField)
{
// Replace the field with this value before initialising it
ownerSM.replaceField(fmd.getAbsoluteFieldNumber(), sco, false);
}
// Initialise the SCO for use
if (value != null)
{
// Apply the existing value
sco.initialise(value, forInsert, forUpdate);
}
else
{
// Just create it empty and load from the datastore
sco.initialise();
}
return sco;
}
/**
* Utility to generate a message representing the SCO container wrapper and its capabilities.
* @param ownerSM StateManager for the owner
* @param fieldName Field with the container
* @param cont The SCOContainer
* @param useCache Whether to use caching of values in the container
* @param queued Whether operations are queued in the wrapper
* @param allowNulls Whether to allow nulls
* @param lazyLoading Whether to use lazy loading in the wrapper
* @return The String
*/
public static String getContainerInfoMessage(StateManager ownerSM, String fieldName, SCOContainer cont,
boolean useCache, boolean queued, boolean allowNulls, boolean lazyLoading)
{
String msg = LOCALISER.msg("023004",
StringUtils.toJVMIDString(ownerSM.getObject()), fieldName,
cont.getClass().getName(),
"[cache-values=" + useCache +
", lazy-loading=" + SCOUtils.useCachedLazyLoading(ownerSM, fieldName) +
", queued-operations=" + queued +
", allow-nulls=" + allowNulls + "]");
return msg;
}
/**
* Convenience method to generate a message containing the options of this SCO wrapper.
* @param useCache Whether to cache the value in the wrapper (and not go to the datastore)
* @param queued Whether it supports queueing of updates
* @param allowNulls Whether it allows null entries
* @param lazyLoading Whether it is lazy loaded
* @return the message
*/
public static String getSCOWrapperOptionsMessage(boolean useCache, boolean queued, boolean allowNulls, boolean lazyLoading)
{
StringBuffer str = new StringBuffer();
if (useCache)
{
str.append("cached");
}
if (lazyLoading)
{
if (str.length() > 0)
{
str.append(",");
}
str.append("lazy-loaded");
}
if (queued)
{
if (str.length() > 0)
{
str.append(",");
}
str.append("queued");
}
if (allowNulls)
{
if (str.length() > 0)
{
str.append(",");
}
str.append("allowNulls");
}
return str.toString();
}
/**
* Utility to return whether or not to allow nulls in the container for the specified field.
* Uses the metadata extension "allow-nulls".
* @param defaultValue Default value for the container
* @param fmd MetaData for the field/property
* @return Whether to allow nulls
*/
public static boolean allowNullsInContainer(boolean defaultValue, AbstractMemberMetaData fmd)
{
boolean allow = defaultValue;
if (fmd.hasExtension("allow-nulls"))
{
// Override the default "nulls" value with the user specification
String extValue = fmd.getValueForExtension("allow-nulls");
if (extValue.equalsIgnoreCase("true"))
{
allow = true;
}
else if (extValue.equalsIgnoreCase("false"))
{
allow = false;
}
}
return allow;
}
/**
* Utility to return whether to use queueing of operations in the SCO container.
* @param ownerSM The StateManager for the SCO field
* @return Whether to use queueing
*/
public static boolean useContainerQueueing(StateManager ownerSM)
{
if (ownerSM.getObjectManager().getTransaction().getOptimistic())
{
return true;
}
else
{
return ownerSM.getObjectManager().getOMFContext().getPersistenceConfiguration().getBooleanProperty("org.jpox.datastoreTransactionDelayOperations");
}
}
/**
* Utility to return whether or not to use the container cache for the
* collection/map for the passed StateManager SCO.
* @param ownerSM The StateManager for the SCO field
* @param fieldName Name of the field.
* @return Whether to use the cache.
*/
public static boolean useContainerCache(StateManager ownerSM, String fieldName)
{
if (ownerSM == null)
{
return false;
}
// Get global value for PMF
boolean useCache = ownerSM.getObjectManager().getOMFContext().getPersistenceConfiguration().getBooleanProperty("org.jpox.cache.collections");
AbstractMemberMetaData fmd = ownerSM.getMetaDataManager().getMetaDataForMember(ownerSM.getObject().getClass(),
ownerSM.getObjectManager().getClassLoaderResolver(), fieldName);
if (fmd.getOrderMetaData() != null && !fmd.getOrderMetaData().isIndexedList())
{
// "Ordered Lists" have to use caching since most List operations are impossible without indexing
useCache = true;
}
else if (fmd.getContainer() != null && fmd.getContainer().hasExtension("cache"))
{
// User has marked the field caching policy
useCache = (new Boolean(fmd.getContainer().getValueForExtension("cache"))).booleanValue();
}
return useCache;
}
/**
* Accessor for whether the use lazy loading when caching the collection.
* @param ownerSM StateManager of the owning object
* @param fieldName Name of the collection/map field
* @return Whether to use lazy loading when caching the collection
*/
public static boolean useCachedLazyLoading(StateManager ownerSM, String fieldName)
{
if (ownerSM == null)
{
return false;
}
boolean lazy = false;
AbstractClassMetaData cmd = ownerSM.getClassMetaData();
AbstractMemberMetaData fmd = cmd.getMetaDataForMember(fieldName);
Boolean lazyCollections = ownerSM.getObjectManager().getOMFContext().getPersistenceConfiguration().getBooleanObjectProperty("org.jpox.cache.collections.lazy");
if (lazyCollections != null)
{
// Global setting for PMF
lazy = lazyCollections.booleanValue();
}
else if (fmd.getContainer() != null && fmd.getContainer().hasExtension("cache-lazy-loading"))
{
// Check if this container has a MetaData value defined
lazy = (new Boolean(fmd.getContainer().getValueForExtension("cache-lazy-loading"))).booleanValue();
}
else
{
// Check if this SCO is in the current FetchPlan
boolean inFP = false;
int[] fpFields = ownerSM.getObjectManager().getFetchPlan().getFetchPlanForClass(cmd).getFieldsInActualFetchPlan();
int fieldNo = fmd.getAbsoluteFieldNumber();
if (fpFields != null && fpFields.length > 0)
{
for (int i=0;i<fpFields.length;i++)
{
if (fpFields[i] == fieldNo)
{
inFP = true;
break;
}
}
}
// Default to lazy loading when not in FetchPlan, and non-lazy when in FetchPlan
lazy = !inFP;
}
return lazy;
}
/**
* Convenience method to return if a collection field has elements without their own identity.
* Checks if the elements are embedded in a join table, or in the main table, or serialised.
* @param fmd MetaData for the field
* @return Whether the elements have their own identity or not
*/
public static boolean collectionHasElementsWithoutIdentity(AbstractMemberMetaData fmd)
{
boolean elementsWithoutIdentity = false;
if (fmd.isSerialized())
{
// Elements serialised into main table
elementsWithoutIdentity = true;
}
else if (fmd.getElementMetaData() != null && fmd.getElementMetaData().getEmbeddedMetaData() != null && fmd.getJoinMetaData() != null)
{
// Elements embedded in join table using embedded mapping
elementsWithoutIdentity = true;
}
else if (fmd.getCollection() != null && fmd.getCollection().isEmbeddedElement())
{
// Elements are embedded (either serialised, or embedded in join table)
elementsWithoutIdentity = true;
}
return elementsWithoutIdentity;
}
/**
* Convenience method to return if a map field has keys without their own identity.
* Checks if the keys are embedded in a join table, or in the main table, or serialised.
* @param fmd MetaData for the field
* @return Whether the keys have their own identity or not
*/
public static boolean mapHasKeysWithoutIdentity(AbstractMemberMetaData fmd)
{
boolean keysWithoutIdentity = false;
if (fmd.isSerialized())
{
// Keys (and values) serialised into main table
keysWithoutIdentity = true;
}
else if (fmd.getKeyMetaData() != null && fmd.getKeyMetaData().getEmbeddedMetaData() != null && fmd.getJoinMetaData() != null)
{
// Keys embedded in join table using embedded mapping
keysWithoutIdentity = true;
}
else if (fmd.getMap() != null && fmd.getMap().isEmbeddedKey())
{
// Keys are embedded (either serialised, or embedded in join table)
keysWithoutIdentity = true;
}
return keysWithoutIdentity;
}
/**
* Convenience method to return if a map field has values without their own identity.
* Checks if the values are embedded in a join table, or in the main table, or serialised.
* @param fmd MetaData for the field
* @return Whether the values have their own identity or not
*/
public static boolean mapHasValuesWithoutIdentity(AbstractMemberMetaData fmd)
{
boolean valuesWithoutIdentity = false;
if (fmd.isSerialized())
{
// Values (and keys) serialised into main table
valuesWithoutIdentity = true;
}
else if (fmd.getValueMetaData() != null && fmd.getValueMetaData().getEmbeddedMetaData() != null && fmd.getJoinMetaData() != null)
{
// Values embedded in join table using embedded mapping
valuesWithoutIdentity = true;
}
else if (fmd.getMap() != null && fmd.getMap().isEmbeddedValue())
{
// Values are embedded (either serialised, or embedded in join table)
valuesWithoutIdentity = true;
}
return valuesWithoutIdentity;
}
/**
* Convenience method to return if a collection field has the elements serialised into the
* table of the field as a single BLOB.
* @param fmd MetaData for the field
* @return Whether the elements are serialised (either explicitly or implicitly)
*/
public static boolean collectionHasSerialisedElements(AbstractMemberMetaData fmd)
{
boolean serialised = fmd.isSerialized();
if (fmd.getCollection() != null && fmd.getCollection().isEmbeddedElement() && fmd.getJoinMetaData() == null)
{
// Elements are embedded but no join table so we serialise
serialised = true;
}
return serialised;
}
/**
* Convenience method to return if an array field has the elements stored into the
* table of the field as a single (BLOB) column.
* @param fmd MetaData for the field
* @return Whether the elements are stored in a single column
*/
public static boolean arrayIsStoredInSingleColumn(AbstractMemberMetaData fmd)
{
boolean singleColumn = fmd.isSerialized();
if (!singleColumn && fmd.getArray() != null && fmd.getJoinMetaData() == null)
{
if (fmd.getArray().isEmbeddedElement())
{
// Elements are embedded but no join table so we store in a single column
singleColumn = true;
}
Class elementClass = fmd.getType().getComponentType();
ApiAdapter api = fmd.getMetaDataManager().getApiAdapter();
if (!elementClass.isInterface() && !api.isPersistable(elementClass))
{
// Array of non-PC with no join table so store in single column of main table
singleColumn = true;
}
}
return singleColumn;
}
/**
* Convenience method to return if a map field has the keys/values serialised into the
* table of the field as a single BLOB.
* @param fmd MetaData for the field
* @return Whether the keys and values are serialised (either explicitly or implicitly)
*/
public static boolean mapHasSerialisedKeysAndValues(AbstractMemberMetaData fmd)
{
boolean inverseKeyField = false;
if (fmd.getKeyMetaData() != null && fmd.getKeyMetaData().getMappedBy() != null)
{
inverseKeyField = true;
}
boolean inverseValueField = false;
if (fmd.getValueMetaData() != null && fmd.getValueMetaData().getMappedBy() != null)
{
inverseValueField = true;
}
boolean serialised = fmd.isSerialized();
if (fmd.getMap() != null && fmd.getJoinMetaData() == null &&
(fmd.getMap().isEmbeddedKey() || fmd.getMap().isEmbeddedValue()) &&
!inverseKeyField && !inverseValueField)
{
// Keys/values are embedded but no join table so we serialise the whole map
// Note that we explicitly excluded the JPOX extension 1-N Map with the key stored in the value
serialised = true;
}
return serialised;
}
/**
* Convenience method to update a collection to contain the elements in another collection.
* Performs the updates by calling the necessary add(), remove() methods just for the
* elements that have changed. Allows for some elements in one collection being attached
* and some being detached (so having same id, but different state)
* @param api API Adapter
* @param coll The collection to update
* @param newColl The new collection whose elements we need in "coll"
*/
public static void updateCollectionWithCollection(ApiAdapter api, Collection coll, Collection newColl)
{
if (coll == null)
{
return;
}
if (newColl == null)
{
coll.clear();
return;
}
// Remove all elements no longer in the Collection
Iterator iter = coll.iterator();
while (iter.hasNext())
{
Object element = iter.next();
if (api.isPersistable(element))
{
Object id = api.getIdForObject(element);
if (id != null)
{
// Element has an id so compare the id
boolean present = false;
Iterator newIter = newColl.iterator();
while (newIter.hasNext())
{
Object newElement = newIter.next();
Object newId = api.getIdForObject(newElement);
if (id.equals(newId))
{
present = true;
break;
}
}
if (!present)
{
iter.remove();
}
}
else
{
if (!newColl.contains(element))
{
iter.remove();
}
}
}
else
{
if (!newColl.contains(element))
{
iter.remove();
}
}
}
// Add all new elements
Iterator newIter = newColl.iterator();
while (newIter.hasNext())
{
Object newElement = newIter.next();
if (api.isPersistable(newElement))
{
Object newId = api.getIdForObject(newElement);
if (newId != null)
{
boolean present = false;
iter = coll.iterator();
while (iter.hasNext())
{
Object element = iter.next();
Object id = api.getIdForObject(element);
if (newId.equals(id))
{
present = true;
break;
}
}
if (!present)
{
coll.add(newElement);
}
}
else
{
if (!coll.contains(newElement))
{
coll.add(newElement);
}
}
}
else
{
if (!coll.contains(newElement))
{
coll.add(newElement);
}
}
}
}
/**
* Convenience method to update a Store collection to contain the elements in another collection.
* Performs the updates by calling the necessary add(), remove() methods just for the
* elements that have changed. Allows for some elements in one collection being attached
* and some being detached (so having same id, but different state)
* @param store The store to apply changes to
* @param ownerSM StateManager of the owner
* @param newColl The new collection whose elements we need in "coll"
*/
public static void updateStoreWithCollection(CollectionStore store, StateManager ownerSM, Collection newColl)
{
if (store == null || ownerSM == null)
{
return;
}
if (newColl == null)
{
store.clear(ownerSM);
return;
}
// Retrieve the current elements from the store
Collection coll = new java.util.HashSet();
ApiAdapter api = ownerSM.getObjectManager().getApiAdapter();
Iterator iter = store.iterator(ownerSM);
while (iter.hasNext())
{
coll.add(iter.next());
}
// Remove all elements no longer in the store
iter = coll.iterator();
while (iter.hasNext())
{
Object element = iter.next();
if (api.isPersistable(element))
{
Object id = api.getIdForObject(element);
if (id != null)
{
// Element has an id so compare the id
boolean present = false;
Iterator newIter = newColl.iterator();
while (newIter.hasNext())
{
Object newElement = newIter.next();
Object newId = api.getIdForObject(newElement);
if (id.equals(newId))
{
present = true;
break;
}
}
if (!present)
{
store.remove(ownerSM, element, -1, true);
}
}
else
{
if (!newColl.contains(element))
{
store.remove(ownerSM, element, -1, true);
}
}
}
else
{
if (!newColl.contains(element))
{
store.remove(ownerSM, element, -1, true);
}
}
}
// Add all new elements to the store
Iterator newIter = newColl.iterator();
while (newIter.hasNext())
{
Object newElement = newIter.next();
if (api.isPersistable(newElement))
{
Object newId = api.getIdForObject(newElement);
if (newId != null)
{
boolean present = false;
iter = coll.iterator();
while (iter.hasNext())
{
Object element = iter.next();
Object id = api.getIdForObject(element);
if (newId.equals(id))
{
present = true;
break;
}
}
if (!present)
{
store.add(ownerSM, newElement, -1);
}
}
else
{
if (!coll.contains(newElement))
{
store.add(ownerSM, newElement, -1);
}
}
}
else
{
if (!coll.contains(newElement))
{
store.add(ownerSM, newElement, -1);
}
}
}
}
/**
* Convenience method for use by Collection/Set/HashSet attachCopy methods to
* update the passed (attached) collection using the (attached) elements passed.
* @param coll The current (attached) collection
* @param elements The collection of (attached) elements needed.
* @return If the Collection was updated
*/
public static boolean updateCollectionWithCollectionElements(Collection coll, Collection elements)
{
boolean updated = false;
// Delete any elements that are no longer in the collection
Iterator attachedIter = coll.iterator();
while (attachedIter.hasNext())
{
Object attachedElement = attachedIter.next();
if (!elements.contains(attachedElement))
{
// No longer present so remove it
attachedIter.remove();
updated = true;
}
}
// Add any new elements
Iterator elementsIter = elements.iterator();
while (elementsIter.hasNext())
{
Object element = elementsIter.next();
if (!coll.contains(element))
{
// Not present so add it
coll.add(element);
updated = true;
}
}
return updated;
}
/**
* Convenience method for use by List attachCopy methods to update the
* passed (attached) list using the (attached) list elements passed.
* @param list The current (attached) list
* @param elements The list of (attached) elements needed.
* @return If the List was updated
*/
public static boolean updateListWithListElements(List list, List elements)
{
boolean updated = false;
// This method needs to take the existing list and generate a list
// of add/remove/set/clear operations that change the list to the passed
// elements in as efficient a way as possible. The simplest is
// clear() then addAll()!, but if there are many objects and very little
// has changed this would be very inefficient.
// What we do currently is remove all elements no longer present, and then
// add any missing elements, correcting the ordering. This can be non-optimal
// in some situations.
// TODO Optimise the process
// Delete any elements that are no longer in the list
java.util.ArrayList newCopy = new java.util.ArrayList(elements);
Iterator attachedIter = list.iterator();
while (attachedIter.hasNext())
{
Object attachedElement = attachedIter.next();
if (!newCopy.remove(attachedElement))
{
// No longer present, so remove it
attachedIter.remove();
updated = true;
}
}
// Add any new elements that have been added
java.util.ArrayList oldCopy = new java.util.ArrayList(list);
Iterator elementsIter = elements.iterator();
while (elementsIter.hasNext())
{
Object element = elementsIter.next();
if (!oldCopy.remove(element))
{
// Now present, so add it
list.add(element);
updated = true;
}
}
// Update position of elements in the list to match the new order
elementsIter = elements.iterator();
int position = 0;
while (elementsIter.hasNext())
{
Object element = elementsIter.next();
Object currentElement = list.get(position);
boolean updatePosition = false;
if ((element == null && currentElement != null) ||
(element != null && currentElement == null))
{
// Cater for null elements in the list
updatePosition = true;
}
else if (element != null && currentElement != null && !currentElement.equals(element))
{
updatePosition = true;
}
if (updatePosition)
{
// Update the position, taking care not to have dependent-field deletes taking place
((SCOList)list).set(position, element, false);
updated = true;
}
position++;
}
return updated;
}
/**
* Convenience method for use by Map attachCopy methods to update the
* passed (attached) map using the (attached) map keys/values passed.
* @param api TODO
* @param map The current (attached) map
* @param keysValues The keys/values required
* @return If the map was updated
*/
public static boolean updateMapWithMapKeysValues(ApiAdapter api, Map map, Map keysValues)
{
boolean updated = false;
// Take a copy of the map so we can call remove() on the map itself
// TODO Change this to use EntrySet in the future.
// EntrySet.iterator().remove() doesn't seem to feed through to the DB at the moment
Map copy = new HashMap(map);
// Delete any keys that are no longer in the Map
Iterator attachedIter = copy.entrySet().iterator();
while (attachedIter.hasNext())
{
Map.Entry entry = (Map.Entry) attachedIter.next();
Object key = entry.getKey();
if (!keysValues.containsKey(key))
{
map.remove(key);
updated = true;
}
}
// Add any new keys/values and update any changed values
Iterator keysIter = keysValues.entrySet().iterator();
while (keysIter.hasNext())
{
Map.Entry entry = (Map.Entry) keysIter.next();
Object key = entry.getKey();
Object value = entry.getValue();
if (!map.containsKey(key))
{
// Not present so add it
map.put(key, keysValues.get(key));
updated = true;
}
else
{
// Update any values
if (api.isPersistable(value))
{
// In case they have changed the PC value for this key
// TODO Check if the PC has changed ?
map.put(key, value);
}
else
{
Object oldValue = map.get(key);
if ((oldValue == null && value != null) || (oldValue != null && !oldValue.equals(value)))
{
map.put(key, value);
}
}
}
}
return updated;
}
/**
* Convenience method to populate the passed delegate Map with the keys/values from
* the associated Store.
* <P>
* The issue here is that we need to load the keys and values in as few calls as possible.
* The method employed here reads in the keys (if PersistenceCapable), then the values
* (if PersistenceCapable), and then the "entries" (ids of keys and values) so we can
* associate the keys to the values.
* @param delegate The delegate
* @param store The Store
* @param ownerSM State Manager of the owner of the map.
*/
public static void populateMapDelegateWithStoreData(Map delegate, MapStore store, StateManager ownerSM)
{
java.util.HashSet keys = new java.util.HashSet();
if (!store.keysAreEmbedded() && !store.keysAreSerialised())
{
// Retrieve the PersistenceCapable keys
SetStore keystore = store.keySetStore(ownerSM.getObjectManager().getClassLoaderResolver());
Iterator keyIter = keystore.iterator(ownerSM);
while (keyIter.hasNext())
{
keys.add(keyIter.next());
}
}
java.util.HashSet values = new java.util.HashSet();
if (!store.valuesAreEmbedded() && !store.valuesAreSerialised())
{
// Retrieve the PersistenceCapable values
SetStore valuestore = store.valueSetStore(ownerSM.getObjectManager().getClassLoaderResolver());
Iterator valueIter = valuestore.iterator(ownerSM);
while (valueIter.hasNext())
{
values.add(valueIter.next());
}
}
// Retrieve the entries (key-value pairs so we can associate them)
ApiAdapter api = ownerSM.getObjectManager().getApiAdapter();
SetStore entries = store.entrySetStore();
Iterator entryIter = entries.iterator(ownerSM);
while (entryIter.hasNext())
{
Map.Entry entry = (Map.Entry)entryIter.next();
Object key = entry.getKey();
Object value = entry.getValue();
Object trueKey = null;
if (store.keysAreEmbedded() || store.keysAreSerialised())
{
// The value has been retrieved as part of the entry
trueKey = key;
}
else
{
// Find the true key (with its fields populated)
// The key in the entry contains just the identity
Object pcKeyId = api.getIdForObject(key);
Iterator keyIter = keys.iterator();
while (keyIter.hasNext())
{
Object obj = keyIter.next();
if (api.getIdForObject(obj).equals(pcKeyId))
{
trueKey = obj;
break;
}
}
}
Object trueValue = null;
if (store.valuesAreEmbedded() || store.valuesAreSerialised())
{
// The value has been retrieved as part of the entry
trueValue = value;
}
else
{
// Find the true value (with its fields populated)
// The value in the entry contains just the identity
Object pcValueId = api.getIdForObject(value);
Iterator valueIter = values.iterator();
while (valueIter.hasNext())
{
Object obj = valueIter.next();
if (api.getIdForObject(obj).equals(pcValueId))
{
trueValue = obj;
}
}
}
delegate.put(trueKey, trueValue);
}
keys.clear();
values.clear();
}
/**
* Returns <tt>true</tt> if this collection contains the specified
* element. More formally, returns <tt>true</tt> if and only if this
* collection contains at least one element <tt>it</tt> such that
* <tt>(o==null ? it==null : o.equals(it))</tt>.<p>
* <p>
* This implementation iterates over the elements in the collection,
* checking each element in turn for equality with the specified element.
* @param backingStore the Store
* @param sm the StateManager
* @return <tt>true</tt> if this collection contains the specified element.
*/
public static Object[] toArray(CollectionStore backingStore, StateManager sm)
{
Object[] result = new Object[backingStore.size(sm)];
Iterator it = backingStore.iterator(sm);
for (int i=0; it.hasNext(); i++)
{
result[i] = it.next();
}
return result;
}
/**
* Returns an array containing all of the elements in this collection;
*
* @param backingStore the Store
* @param sm the StateManager
* @param a the array into which the elements of the collection are to
* be stored, if it is big enough; otherwise, a new array of the
* same runtime type is allocated for this purpose.
* @return an array containing the elements of the collection.
*
* @throws NullPointerException if the specified array is <tt>null</tt>.
*
* @throws ArrayStoreException if the runtime type of the specified array
* is not a supertype of the runtime type of every element in this
* collection.
*/
public static Object[] toArray(CollectionStore backingStore, StateManager sm, Object a[])
{
int size = backingStore.size(sm);
if (a.length < size)
{
a = (Object[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);
}
Iterator it=backingStore.iterator(sm);
for (int i=0; i<size; i++)
{
a[i] = it.next();
}
if (a.length > size)
{
a[size] = null;
}
return a;
}
/**
* Convenience method for creating a Comparator using extension metadata tags for the specified field.
* Uses the extension key "comparator-name" (and allows for "comparatorName" - deprecated).
* @param fmd The field that needs the comparator
* @param clr ClassLoader resolver
* @return The Comparator
*/
public static Comparator getComparator(AbstractMemberMetaData fmd, ClassLoaderResolver clr)
{
Comparator comparator = null;
String comparatorName = null;
if (fmd.hasMap() && fmd.getMap().hasExtension("comparator-name"))
{
comparatorName = fmd.getMap().getValueForExtension("comparator-name");
}
else if (fmd.hasCollection() && fmd.getCollection().hasExtension("comparator-name"))
{
comparatorName = fmd.getCollection().getValueForExtension("comparator-name");
}
else if (fmd.hasMap() && fmd.getMap().hasExtension("comparatorName"))
{
comparatorName = fmd.getMap().getValueForExtension("comparatorName");
}
else if (fmd.hasCollection() && fmd.getCollection().hasExtension("comparatorName"))
{
comparatorName = fmd.getCollection().getValueForExtension("comparatorName");
}
if (comparatorName != null)
{
Class comparatorCls = null;
try
{
comparatorCls = clr.classForName(comparatorName);
comparator = (Comparator)ClassUtils.newInstance(comparatorCls, null, null);
}
catch (JPOXException jpe)
{
JPOXLogger.PERSISTENCE.warn(LOCALISER.msg("023012", fmd.getFullFieldName(), comparatorName));
}
}
return comparator;
}
/**
* Convenience method to refresh fetch plan fields for all elements for a collection field.
* All elements that are PersistenceCapable will be made transient.
* @param ownerSM StateManager for the owning object with the collection
* @param elements The elements in the collection
*/
public static void refreshFetchPlanFieldsForCollection(StateManager ownerSM, Object[] elements)
{
ApiAdapter api = ownerSM.getObjectManager().getApiAdapter();
for (int i = 0; i < elements.length; i++)
{
if (api.isPersistable(elements[i]))
{
ownerSM.getObjectManager().refreshObject(elements[i]);
}
}
}
/**
* Convenience method to refresh fetch plan fields for all elements for a map field.
* All elements that are PersistenceCapable will be made transient.
* @param ownerSM StateManager for the owning object with the map
* @param entries The entries in the map
*/
public static void refreshFetchPlanFieldsForMap(StateManager ownerSM, Set entries)
{
ApiAdapter api = ownerSM.getObjectManager().getApiAdapter();
for (Iterator it = entries.iterator(); it.hasNext();)
{
Map.Entry entry = (Map.Entry) it.next();
Object val = entry.getValue();
Object key = entry.getKey();
if (api.isPersistable(key))
{
ownerSM.getObjectManager().refreshObject(key);
}
if (api.isPersistable(val))
{
ownerSM.getObjectManager().refreshObject(val);
}
}
}
/**
* Convenience method to detach (recursively) all elements for a collection field.
* All elements that are PersistenceCapable will be detached.
* @param ownerSM StateManager for the owning object with the collection
* @param elements The elements in the collection
* @param state FetchPlan state
*/
public static void detachForCollection(StateManager ownerSM, Object[] elements, FetchPlanState state)
{
ApiAdapter api = ownerSM.getObjectManager().getApiAdapter();
for (int i = 0; i < elements.length; i++)
{
if (api.isPersistable(elements[i]))
{
ownerSM.getObjectManager().detachObject(elements[i], state);
}
}
}
/**
* Convenience method to detach copies (recursively) of all elements for a collection field.
* All elements that are PersistenceCapable will be detached.
* @param ownerSM StateManager for the owning object with the collection
* @param elements The elements in the collection
* @param state FetchPlan state
* @param detached Collection to add the detached copies to
*/
public static void detachCopyForCollection(StateManager ownerSM, Object[] elements, FetchPlanState state, Collection detached)
{
ApiAdapter api = ownerSM.getObjectManager().getApiAdapter();
for (int i = 0; i < elements.length; i++)
{
if (elements[i] == null)
{
detached.add(null);
}
else
{
Object object = elements[i];
if (api.isPersistable(object))
{
detached.add(ownerSM.getObjectManager().detachObjectCopy(object, state));
}
else
{
detached.add(object);
}
}
}
}
/**
* Convenience method to attach (recursively) all elements for a collection field.
* All elements that are PersistenceCapable will be attached.
* @param ownerSM StateManager for the owning object with the collection
* @param elements The elements in the collection
* @param elementsWithoutIdentity Whether the elements have their own identity
*/
public static void attachForCollection(StateManager ownerSM, Object[] elements, boolean elementsWithoutIdentity)
{
ApiAdapter api = ownerSM.getObjectManager().getApiAdapter();
for (int i = 0; i < elements.length; i++)
{
if (api.isPersistable(elements[i]))
{
ownerSM.getObjectManager().attachObject(elements[i], elementsWithoutIdentity);
}
}
}
/**
* Method to return an attached copy of the passed (detached) value. The returned attached copy
* is a SCO wrapper. Goes through the existing elements in the store for this owner field and
* removes ones no longer present, and adds new elements. All elements in the (detached)
* value are attached.
* @param ownerSM StateManager for the owning object with the collection
* @param detachedElements The detached elements in the collection
* @param attached Collection to add the attached copies to
* @param elementsWithoutIdentity Whether the elements have their own identity
*/
public static void attachCopyForCollection(StateManager ownerSM, Object[] detachedElements,
Collection attached, boolean elementsWithoutIdentity)
{
ApiAdapter api = ownerSM.getObjectManager().getApiAdapter();
for (int i = 0; i < detachedElements.length; i++)
{
if (api.isPersistable(detachedElements[i]) && api.isDetachable(detachedElements[i]))
{
attached.add(ownerSM.getObjectManager().attachObjectCopy(detachedElements[i], elementsWithoutIdentity));
}
else
{
attached.add(detachedElements[i]);
}
}
}
/**
* Convenience method to detach (recursively) all elements for a map field.
* All elements that are PersistenceCapable will be detached.
* @param ownerSM StateManager for the owning object with the map
* @param entries The entries in the map
* @param state FetchPlan state
*/
public static void detachForMap(StateManager ownerSM, Set entries, FetchPlanState state)
{
ApiAdapter api = ownerSM.getObjectManager().getApiAdapter();
for (Iterator it = entries.iterator(); it.hasNext();)
{
Map.Entry entry = (Map.Entry) it.next();
Object val = entry.getValue();
Object key = entry.getKey();
if (api.isPersistable(key))
{
ownerSM.getObjectManager().detachObject(key, state);
}
if (api.isPersistable(val))
{
ownerSM.getObjectManager().detachObject(val, state);
}
}
}
/**
* Convenience method to detach copies (recursively) of all elements for a map field.
* All elements that are PersistenceCapable will be detached.
* @param ownerSM StateManager for the owning object with the map
* @param entries The entries in the map
* @param state FetchPlan state
* @param detached Map to add the detached copies to
*/
public static void detachCopyForMap(StateManager ownerSM, Set entries, FetchPlanState state, Map detached)
{
ApiAdapter api = ownerSM.getObjectManager().getApiAdapter();
for (Iterator it = entries.iterator(); it.hasNext();)
{
Map.Entry entry = (Map.Entry) it.next();
Object val = entry.getValue();
Object key = entry.getKey();
if (api.isPersistable(val))
{
val = ownerSM.getObjectManager().detachObjectCopy(val, state);
}
if (api.isPersistable(key))
{
key = ownerSM.getObjectManager().detachObjectCopy(key, state);
}
detached.put(key, val);
}
}
/**
* Convenience method to attach (recursively) all elements for a map field.
* All elements that are PersistenceCapable will be attached.
* @param ownerSM StateManager for the owning object with the map
* @param entries The entries in the map
* @param keysWithoutIdentity Whether the keys have their own identity
* @param valuesWithoutIdentity Whether the values have their own identity
*/
public static void attachForMap(StateManager ownerSM, Set entries, boolean keysWithoutIdentity, boolean valuesWithoutIdentity)
{
ApiAdapter api = ownerSM.getObjectManager().getApiAdapter();
for (Iterator it = entries.iterator(); it.hasNext();)
{
Map.Entry entry = (Map.Entry) it.next();
Object val = entry.getValue();
Object key = entry.getKey();
if (api.isPersistable(key))
{
ownerSM.getObjectManager().attachObject(key, keysWithoutIdentity);
}
if (api.isPersistable(val))
{
ownerSM.getObjectManager().attachObject(val, valuesWithoutIdentity);
}
}
}
/**
* Method to return an attached copy of the passed (detached) value. The returned attached copy
* is a SCO wrapper. Goes through the existing elements in the store for this owner field and
* removes ones no longer present, and adds new elements. All elements in the (detached)
* value are attached.
* @param ownerSM StateManager for the owning object with the map
* @param detachedEntries The detached entries in the map
* @param attached Map to add the attached copies to
* @param keysWithoutIdentity Whether the keys have their own identity
* @param valuesWithoutIdentity Whether the values have their own identity
*/
public static void attachCopyForMap(StateManager ownerSM, Set detachedEntries,
Map attached, boolean keysWithoutIdentity, boolean valuesWithoutIdentity)
{
Iterator iter = detachedEntries.iterator();
ApiAdapter api = ownerSM.getObjectManager().getApiAdapter();
while (iter.hasNext())
{
Map.Entry entry = (Map.Entry) iter.next();
Object val = entry.getValue();
Object key = entry.getKey();
if (api.isPersistable(val) && api.isDetachable(val))
{
val = ownerSM.getObjectManager().attachObjectCopy(val, valuesWithoutIdentity);
}
if (api.isPersistable(key) && api.isDetachable(key))
{
key = ownerSM.getObjectManager().attachObjectCopy(key, keysWithoutIdentity);
}
attached.put(key, val);
}
}
/**
* Method to check if an object to be stored in a SCO container is already persistent, or is managed
* by a different ObjectManager. If not persistent, this call will persist it. If not yet flushed to the
* datastore this call will flush it.
* @param om ObjectManager that we are using
* @param object The object
* @param fieldValues Values for any fields when persisting (if the object needs persisting)
* @return Whether the object was persisted during this call
*/
public static boolean validateObjectForWriting(ObjectManager om, Object object, FieldValues fieldValues)
{
boolean persisted = false;
ApiAdapter api = om.getApiAdapter();
if (api.isPersistable(object))
{
ObjectManager objectOM = ObjectManagerHelper.getObjectManager(object);
if (objectOM != null && om != objectOM)
{
throw new JPOXUserException(
LOCALISER.msg("023009", StringUtils.toJVMIDString(object)),
api.getIdForObject(object));
}
else if (!api.isPersistent(object))
{
// Not persistent, so either is detached, or needs persisting for first time
boolean exists = false;
if (api.isDetached(object))
{
if (om.getOMFContext().getPersistenceConfiguration().getBooleanProperty("org.jpox.attachSameDatastore"))
{
// Assume that it is detached from this datastore
exists = true;
}
else
{
// Check if the (attached) object exists in this datastore
try
{
Object obj = om.findObject(api.getIdForObject(object), true, false, object.getClass().getName());
if (obj != null)
{
// PM.getObjectById creates a dummy object to represent this object and automatically
// enlists it in the txn. Evict it to avoid issues with reachability
StateManager objSM = om.findStateManager(obj);
if (objSM != null)
{
om.evictFromTransaction(objSM);
}
}
exists = true;
}
catch (JPOXObjectNotFoundException onfe)
{
exists = false;
}
}
}
if (!exists)
{
// Persist the object
om.persistObjectInternal(object, fieldValues, null, -1, StateManager.PC);
persisted = true;
}
}
else
{
// Persistent, but is it flushed to the datastore?
StateManager objectSM = om.findStateManager(object);
if (objectSM.isWaitingToBeFlushedToDatastore())
{
// Newly persistent but still not flushed (e.g in optimistic txn)
// Process any fieldValues
if (fieldValues != null)
{
objectSM.loadFieldValues(fieldValues);
}
// Now flush it
objectSM.flush();
}
}
}
return persisted;
}
/**
* Method to check if objects to be stored in a SCO container are already persistent, or are managed by
* a different ObjectManager. If not persistent, this call will persist them.
* @param om ObjectManager being used
* @param objects The objects (array, or Collection)
*/
public static void validateObjectsForWriting(ObjectManager om, Object objects)
{
if (objects != null)
{
if (objects.getClass().isArray())
{
if (!objects.getClass().getComponentType().isPrimitive())
{
Object[] obj = ((Object[]) objects);
for (int i = 0; i < obj.length; i++)
{
validateObjectForWriting(om, obj[i], null);
}
}
}
else if (objects instanceof Collection)
{
Collection col = (Collection) objects;
Iterator it = col.iterator();
while (it.hasNext())
{
validateObjectForWriting(om, it.next(), null);
}
}
}
}
/**
* Return whether the supplied type (collection) is list based.
* @return Whether it needs list ordering
*/
public static boolean isListBased(Class type)
{
if (type == null)
{
return false;
}
else if (java.util.List.class.isAssignableFrom(type))
{
return true;
}
else if (JavaUtils.isJRE1_5OrAbove())
{
if (java.util.Queue.class.isAssignableFrom(type))
{
// Queue needs ordering
return true;
}
}
return false;
}
}