/**********************************************************************
Copyright (c) 2002 Kelly Grizzle (TJDO) 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:
2003 Andy Jefferson - coding standards
2005 Andy Jefferson - basic serialisation support
2005 Andy Jefferson - updated serialisation using SCOUtils methods
2005 Andy Jefferson - changed to allow the store to be set or list
...
**********************************************************************/
package org.jpox.store.mapped.mapping;
import java.util.Collection;
import org.jpox.StateManager;
import org.jpox.exceptions.JPOXException;
import org.jpox.exceptions.JPOXUserException;
import org.jpox.metadata.AbstractMemberMetaData;
import org.jpox.sco.SCO;
import org.jpox.sco.SCOCollection;
import org.jpox.sco.SCOContainer;
import org.jpox.sco.SCOUtils;
import org.jpox.store.exceptions.ReachableObjectNotCascadedException;
import org.jpox.store.mapped.expression.CollectionExpression;
import org.jpox.store.mapped.expression.CollectionLiteral;
import org.jpox.store.mapped.expression.CollectionSubqueryExpression;
import org.jpox.store.mapped.expression.LogicSetExpression;
import org.jpox.store.mapped.expression.QueryExpression;
import org.jpox.store.mapped.expression.ScalarExpression;
import org.jpox.store.mapped.query.Queryable;
import org.jpox.store.scostore.CollectionStore;
import org.jpox.util.JPOXLogger;
/**
* Mapping for Collection (Set/List) types.
*
* @version $Revision: 1.67 $
**/
public class CollectionMapping extends AbstractContainerMapping implements MappingCallbacks
{
/**
* Equality operator.
* @param obj Object to compare against
* @return Whether they are equal
*/
public boolean equals(Object obj)
{
if (obj == this)
{
return true;
}
if (!obj.getClass().equals(getClass()))
{
return false;
}
CollectionMapping sm = (CollectionMapping)obj;
return fmd.equals(sm.fmd) && storeMgr.equals(sm.storeMgr);
}
/**
* Accessor for the Java type represented here.
* @return The java type
*/
public Class getJavaType()
{
return Collection.class;
}
// --------------- Implementation of MappingCallbacks -------------------
/**
* Method to be called after the insert of the owner class element.
* @param sm StateManager of the owner
*/
public void postInsert(StateManager sm)
{
Collection value = (Collection) sm.provideField(fmd.getAbsoluteFieldNumber());
if (containerIsStoredInSingleColumn())
{
// Make sure the elements are ok for proceeding
SCOUtils.validateObjectsForWriting(sm.getObjectManager(), value);
return;
}
if (value == null)
{
// replace null collections with an empty SCO wrapper
replaceFieldWithWrapper(sm, null, false, false);
return;
}
Object[] collElements = value.toArray();
if (!fmd.isCascadePersist())
{
// Field doesnt support cascade-persist so no reachability
if (JPOXLogger.REACHABILITY.isDebugEnabled())
{
JPOXLogger.REACHABILITY.debug(LOCALISER.msg("007006", fmd.getFullFieldName()));
}
// Check for any persistable elements that arent persistent
for (int i=0;i<collElements.length;i++)
{
if (!sm.getObjectManager().getApiAdapter().isDetached(collElements[i]) &&
!sm.getObjectManager().getApiAdapter().isPersistent(collElements[i]))
{
// Element is not persistent so throw exception
throw new ReachableObjectNotCascadedException(fmd.getFullFieldName(), collElements[i]);
}
}
}
else
{
// Reachability
if (JPOXLogger.REACHABILITY.isDebugEnabled())
{
JPOXLogger.REACHABILITY.debug(LOCALISER.msg("007007", fmd.getFullFieldName()));
}
// Check if some elements need attaching
// TODO Investigate if we can just use the attachCopy route below and skip off this check
boolean needsAttaching = false;
for (int i=0;i<collElements.length;i++)
{
if (sm.getObjectManager().getApiAdapter().isDetached(collElements[i]))
{
needsAttaching = true;
break;
}
}
if (needsAttaching)
{
// Create a wrapper and attach the elements (and add the others)
SCO collWrapper = replaceFieldWithWrapper(sm, null, false, false);
collWrapper.attachCopy(value);
}
else
{
if (value.size() > 0)
{
// Add the elements direct to the datastore
((CollectionStore) storeMgr.getBackingStoreForField(sm.getObjectManager().getClassLoaderResolver(),fmd, value.getClass())).addAll(sm, value, 0);
// Create a SCO wrapper with the elements loaded
replaceFieldWithWrapper(sm, value, false, false);
}
else
{
// Create a SCO wrapper
replaceFieldWithWrapper(sm, null, false, false);
}
}
}
}
/**
* Method to be called after any update of the owner class element.
* @param sm StateManager of the owner
*/
public void postUpdate(StateManager sm)
{
Collection value = (Collection) sm.provideField(fmd.getAbsoluteFieldNumber());
if (containerIsStoredInSingleColumn())
{
// Make sure the elements are ok for proceeding
SCOUtils.validateObjectsForWriting(sm.getObjectManager(), value);
return;
}
if (value == null)
{
// remove any elements in the collection and replace it with an empty SCO wrapper
((CollectionStore) storeMgr.getBackingStoreForField(sm.getObjectManager().getClassLoaderResolver(),fmd,null)).clear(sm);
replaceFieldWithWrapper(sm, null, false, false);
return;
}
if (value instanceof SCOContainer)
{
// Already have a SCO value
SCOContainer sco = (SCOContainer) value;
if (sm.getObject() == sco.getOwner() && fieldName.equals(sco.getFieldName()))
{
// Flush any outstanding updates
sco.flush();
return;
}
if (sco.getOwner() != null)
{
throw new JPOXException(LOCALISER.msg("CollectionMapping.WrongOwnerError")).setFatal();
}
}
if (!fmd.isCascadeUpdate())
{
// TODO Should this throw exception if the element doesnt exist?
// User doesnt want to update by reachability
if (JPOXLogger.REACHABILITY.isDebugEnabled())
{
JPOXLogger.REACHABILITY.debug(LOCALISER.msg("007008", fmd.getFullFieldName()));
}
return;
}
if (JPOXLogger.REACHABILITY.isDebugEnabled())
{
JPOXLogger.REACHABILITY.debug(LOCALISER.msg("007009", fmd.getFullFieldName()));
}
// Update the datastore with this value of collection (clear old elements and add new ones)
// TODO Consider making this more efficient picking the ones to remove/add
CollectionStore backingStore = ((CollectionStore) storeMgr.getBackingStoreForField(sm.getObjectManager().getClassLoaderResolver(),fmd, value.getClass()));
backingStore.clear(sm);
backingStore.addAll(sm, value, 0);
// Replace the field with a wrapper containing these elements
replaceFieldWithWrapper(sm, value, false, false);
}
/**
* Method to be called before any delete of the owner class element.
* @param sm StateManager of the owner
*/
public void preDelete(StateManager sm)
{
if (containerIsStoredInSingleColumn())
{
// Field is stored with the main object so nothing to clean up
return;
}
// makes sure field is loaded
sm.getObjectManager().getApiAdapter().isLoaded(sm, fmd.getAbsoluteFieldNumber());
Collection value = (Collection) sm.provideField(fmd.getAbsoluteFieldNumber());
if (value == null)
{
return;
}
boolean isDependentElement = fmd.getCollection().isDependentElement();
boolean hasJoin = (fmd.getJoinMetaData() != null);
boolean hasFK = false;
if (!hasJoin)
{
if (fmd.getElementMetaData() != null && fmd.getElementMetaData().getForeignKeyMetaData() != null)
{
// FK collection, using <element> FK spec
hasFK = true;
}
else if (fmd.getForeignKeyMetaData() != null)
{
// FK collection, using <field> FK spec
hasFK = true;
}
AbstractMemberMetaData[] relatedMmds = fmd.getRelatedMemberMetaData(sm.getObjectManager().getClassLoaderResolver());
if (relatedMmds != null && relatedMmds[0].getForeignKeyMetaData() != null)
{
// FK collection (bidir), using <field> FK spec at other end
hasFK = true;
}
}
if (sm.getObjectManager().getOMFContext().getPersistenceConfiguration().getStringProperty("org.jpox.deletionPolicy").equals("JDO2"))
{
// JDO2 doesnt currently (2.0 spec) take note of foreign-key
hasFK = false;
}
// TODO Why dont we just do clear here always ? THe backing store should take care of if nulling or deleting etc
if (isDependentElement || hasJoin || !hasFK)
{
// Elements are either dependent (in which case we need to delete them) or theres a join (in which case
// we need to remove the join entries), or there are no FKs specified (in which case we need to clean up)
if (!(value instanceof SCO))
{
value = (Collection)sm.wrapSCOField(fmd.getAbsoluteFieldNumber(), value, false, false, true);
}
value.clear();
((SCOCollection)value).flush();
}
}
// ------------------------------- JDOQL Query Methods --------------------------------------
/**
* Accessor for a literal representing this type.
* @param qs The Query
* @param value the value of this object in the literal
* @return The literal
*/
public ScalarExpression newLiteral(QueryExpression qs, Object value)
{
if (containerIsStoredInSingleColumn())
{
throw new JPOXUserException(LOCALISER.msg("041025", fmd.getFullFieldName())).setFatal();
}
if (value instanceof Queryable)
{
return new CollectionSubqueryExpression(qs, ((Queryable)value).newQueryStatement());
}
else
{
return new CollectionLiteral(qs, this, (Collection)value);
}
}
/**
* Accessor for a scalar expression involving this object.
* @param qs The Query
* @param te The table holding this object.
* @return The expression
*/
public ScalarExpression newScalarExpression(QueryExpression qs, LogicSetExpression te)
{
if (containerIsStoredInSingleColumn())
{
throw new JPOXUserException(LOCALISER.msg("041025", fmd.getFullFieldName())).setFatal();
}
return new CollectionExpression(qs, datastoreContainer.getIDMapping(), te, ((CollectionStore) storeMgr.getBackingStoreForField(qs.getClassLoaderResolver(),fmd,null)), fieldName);
}
}