/**********************************************************************
Copyright (c) 2003 Mike Martin 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
2004 Andy Jefferson - implementation of newScalarExpression, newLiteral
2005 Andy Jefferson - basic serialisation support
2005 Andy Jefferson - updated serialisation using SCOUtils methods
...
**********************************************************************/
package org.jpox.store.mapped.mapping;
import java.util.Iterator;
import java.util.Set;
import org.jpox.StateManager;
import org.jpox.api.ApiAdapter;
import org.jpox.exceptions.JPOXException;
import org.jpox.exceptions.JPOXUserException;
import org.jpox.sco.Map;
import org.jpox.sco.SCO;
import org.jpox.sco.SCOContainer;
import org.jpox.sco.SCOMap;
import org.jpox.sco.SCOUtils;
import org.jpox.store.exceptions.ReachableObjectNotCascadedException;
import org.jpox.store.mapped.expression.LogicSetExpression;
import org.jpox.store.mapped.expression.MapExpression;
import org.jpox.store.mapped.expression.MapLiteral;
import org.jpox.store.mapped.expression.QueryExpression;
import org.jpox.store.mapped.expression.ScalarExpression;
import org.jpox.store.scostore.MapStore;
import org.jpox.util.JPOXLogger;
/**
* SCO Mapping for Map types.
*
* @version $Revision: 1.52 $
**/
public class MapMapping 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;
}
MapMapping sm = (MapMapping)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 Map.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)
{
java.util.Map value = (java.util.Map) sm.provideField(fmd.getAbsoluteFieldNumber());
if (containerIsStoredInSingleColumn())
{
// Do nothing when serialised since we are handled in the main request
if (value != null)
{
// Make sure the keys/values are ok for proceeding
SCOUtils.validateObjectsForWriting(sm.getObjectManager(), value.keySet());
SCOUtils.validateObjectsForWriting(sm.getObjectManager(), value.values());
}
return;
}
if (value == null)
{
// replace null map with an empty SCO wrapper
replaceFieldWithWrapper(sm, null, false, false);
return;
}
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 keys/values that arent persistent
ApiAdapter api = sm.getObjectManager().getApiAdapter();
Set entries = value.entrySet();
Iterator iter = entries.iterator();
while (iter.hasNext())
{
Map.Entry entry = (Map.Entry)iter.next();
if (api.isPersistable(entry.getKey()))
{
if (!api.isPersistent(entry.getKey()) && !api.isDetached(entry.getKey()))
{
// Key is not persistent so throw exception
throw new ReachableObjectNotCascadedException(fmd.getFullFieldName(), entry.getKey());
}
}
if (api.isPersistable(entry.getValue()))
{
if (!api.isPersistent(entry.getValue()) && !api.isDetached(entry.getValue()))
{
// Value is not persistent so throw exception
throw new ReachableObjectNotCascadedException(fmd.getFullFieldName(), entry.getValue());
}
}
}
}
else
{
// Reachability
if (JPOXLogger.REACHABILITY.isDebugEnabled())
{
JPOXLogger.REACHABILITY.debug(LOCALISER.msg("007007", fmd.getFullFieldName()));
}
if (value.size() > 0)
{
// Add the entries direct to the datastore
((MapStore) storeMgr.getBackingStoreForField(sm.getObjectManager().getClassLoaderResolver(), fmd, value.getClass())).putAll(sm, value);
// Create a SCO wrapper with the entries 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)
{
java.util.Map value = (java.util.Map) sm.provideField(fmd.getAbsoluteFieldNumber());
if (containerIsStoredInSingleColumn())
{
// Do nothing when serialised since we are handled in the main request
if (value != null)
{
// Make sure the keys/values are ok for proceeding
SCOUtils.validateObjectsForWriting(sm.getObjectManager(), value.keySet());
SCOUtils.validateObjectsForWriting(sm.getObjectManager(), value.values());
}
return;
}
if (value == null)
{
// replace null map with empty SCO wrapper
replaceFieldWithWrapper(sm, null, false, false);
return;
}
if (value instanceof SCOContainer)
{
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("Owned second-class object was somehow assigned to a field other than its owner's").setFatal();
}
}
if (!fmd.isCascadeUpdate())
{
// 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 map (clear old entries and add new ones)
// TODO Consider making this more efficient picking the ones to remove/add
MapStore store = ((MapStore) storeMgr.getBackingStoreForField(sm.getObjectManager().getClassLoaderResolver(), fmd, value.getClass()));
store.clear(sm);
store.putAll(sm, value);
// Replace the field with a wrapper containing these entries
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)
{
// Do nothing - dependent deletion is performed by deleteDependent()
if (containerIsStoredInSingleColumn())
{
// Do nothing when serialised since we are handled in the main request
return;
}
// makes sure field is loaded
sm.getObjectManager().getApiAdapter().isLoaded(sm, fmd.getAbsoluteFieldNumber());
java.util.Map value = (java.util.Map) sm.provideField(fmd.getAbsoluteFieldNumber());
if (value == null || value.isEmpty())
{
return;
}
if (!(value instanceof SCO))
{
// Make sure we have a SCO wrapper so we can clear from the datastore
value = (java.util.Map)sm.wrapSCOField(fmd.getAbsoluteFieldNumber(), value, false, false, true);
}
value.clear();
((SCOMap)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();
}
return new MapLiteral(qs, this, (java.util.Map)value);
}
/**
* Method to return a scalar expression for the Map.
* @param qs The QueryStatement
* @param te The TableExpression
* @return The ScalarExpression
**/
public ScalarExpression newScalarExpression(QueryExpression qs, LogicSetExpression te)
{
if (containerIsStoredInSingleColumn())
{
throw new JPOXUserException(LOCALISER.msg("041025", fmd.getFullFieldName())).setFatal();
}
return new MapExpression(qs, datastoreContainer.getIDMapping(), te, (MapStore) storeMgr.getBackingStoreForField(qs.getClassLoaderResolver(), fmd, null), fieldName);
}
}