/*
* Copyright 2007, Niclas Hedhman. All Rights Reserved.
* Copyright 2009, Rickard Öberg. All Rights Reserved.
* Copyright 2013, Paul Merlin. 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.
*/
package org.qi4j.spi.entitystore.helpers;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.qi4j.api.common.QualifiedName;
import org.qi4j.api.entity.EntityDescriptor;
import org.qi4j.api.entity.EntityReference;
import org.qi4j.api.property.PropertyDescriptor;
import org.qi4j.api.type.ValueType;
import org.qi4j.api.value.ValueSerialization;
import org.qi4j.api.value.ValueSerializationException;
import org.qi4j.spi.entity.EntityState;
import org.qi4j.spi.entity.EntityStatus;
import org.qi4j.spi.entity.ManyAssociationState;
import org.qi4j.spi.entitystore.DefaultEntityStoreUnitOfWork;
import org.qi4j.spi.entitystore.EntityStoreException;
/**
* Standard implementation of EntityState.
*/
public final class JSONEntityState
implements EntityState
{
public static final String JSON_KEY_PROPERTIES = "properties";
public static final String JSON_KEY_ASSOCIATIONS = "associations";
public static final String JSON_KEY_MANYASSOCIATIONS = "manyassociations";
public static final String JSON_KEY_IDENTITY = "identity";
public static final String JSON_KEY_APPLICATION_VERSION = "application_version";
public static final String JSON_KEY_TYPE = "type";
public static final String JSON_KEY_VERSION = "version";
public static final String JSON_KEY_MODIFIED = "modified";
private static final String[] EMPTY_NAMES = new String[ 0 ];
private static final String[] CLONE_NAMES = {
JSON_KEY_IDENTITY,
JSON_KEY_APPLICATION_VERSION,
JSON_KEY_TYPE,
JSON_KEY_VERSION,
JSON_KEY_MODIFIED
};
private final DefaultEntityStoreUnitOfWork unitOfWork;
private final ValueSerialization valueSerialization;
private final String version;
private final EntityReference identity;
private final EntityDescriptor entityDescriptor;
private EntityStatus status;
private long lastModified;
private JSONObject state;
/* package */ JSONEntityState( DefaultEntityStoreUnitOfWork unitOfWork,
ValueSerialization valueSerialization,
EntityReference identity,
EntityDescriptor entityDescriptor,
JSONObject initialState )
{
this( unitOfWork,
valueSerialization,
"",
unitOfWork.currentTime(),
identity,
EntityStatus.NEW,
entityDescriptor,
initialState );
}
/* package */ JSONEntityState( DefaultEntityStoreUnitOfWork unitOfWork,
ValueSerialization valueSerialization,
String version,
long lastModified,
EntityReference identity,
EntityStatus status,
EntityDescriptor entityDescriptor,
JSONObject state
)
{
this.unitOfWork = unitOfWork;
this.valueSerialization = valueSerialization;
this.version = version;
this.lastModified = lastModified;
this.identity = identity;
this.status = status;
this.entityDescriptor = entityDescriptor;
this.state = state;
}
// EntityState implementation
@Override
public final String version()
{
return version;
}
@Override
public long lastModified()
{
return lastModified;
}
@Override
public EntityReference identity()
{
return identity;
}
@Override
public Object propertyValueOf( QualifiedName stateName )
{
try
{
Object json = state.getJSONObject( JSON_KEY_PROPERTIES ).opt( stateName.name() );
if( JSONObject.NULL.equals( json ) )
{
return null;
}
else
{
PropertyDescriptor descriptor = entityDescriptor.state().findPropertyModelByQualifiedName( stateName );
if( descriptor == null )
{
return null;
}
return valueSerialization.deserialize( descriptor.valueType(), json.toString() );
}
}
catch( ValueSerializationException e )
{
throw new EntityStoreException( e );
}
catch( JSONException e )
{
throw new EntityStoreException( e );
}
}
@Override
public void setPropertyValue( QualifiedName stateName, Object newValue )
{
try
{
Object jsonValue;
if( newValue == null || ValueType.isPrimitiveValue( newValue ) )
{
jsonValue = newValue;
}
else
{
String serialized = valueSerialization.serialize( newValue );
if( serialized.startsWith( "{" ) )
{
jsonValue = new JSONObject( serialized );
}
else if( serialized.startsWith( "[" ) )
{
jsonValue = new JSONArray( serialized );
}
else
{
jsonValue = serialized;
}
}
cloneStateIfGlobalStateLoaded();
state.getJSONObject( JSON_KEY_PROPERTIES ).put( stateName.name(), jsonValue );
markUpdated();
}
catch( ValueSerializationException e )
{
throw new EntityStoreException( e );
}
catch( JSONException e )
{
throw new EntityStoreException( e );
}
}
private JSONObject cloneJSON( JSONObject jsonObject )
throws JSONException
{
String[] names = JSONObject.getNames( jsonObject );
if( names == null )
{
names = EMPTY_NAMES;
}
return new JSONObject( jsonObject, names );
}
@Override
public EntityReference associationValueOf( QualifiedName stateName )
{
try
{
Object jsonValue = state.getJSONObject( JSON_KEY_ASSOCIATIONS ).opt( stateName.name() );
if( jsonValue == null )
{
return null;
}
EntityReference value = jsonValue == JSONObject.NULL ? null : EntityReference.parseEntityReference(
(String) jsonValue );
return value;
}
catch( JSONException e )
{
throw new EntityStoreException( e );
}
}
@Override
public void setAssociationValue( QualifiedName stateName, EntityReference newEntity )
{
try
{
cloneStateIfGlobalStateLoaded();
state.getJSONObject( JSON_KEY_ASSOCIATIONS )
.put( stateName.name(), newEntity == null ? null : newEntity.identity() );
markUpdated();
}
catch( JSONException e )
{
throw new EntityStoreException( e );
}
}
@Override
public ManyAssociationState manyAssociationValueOf( QualifiedName stateName )
{
try
{
JSONObject manyAssociations = state.getJSONObject( JSON_KEY_MANYASSOCIATIONS );
JSONArray jsonValues = manyAssociations.optJSONArray( stateName.name() );
if( jsonValues == null )
{
jsonValues = new JSONArray();
manyAssociations.put( stateName.name(), jsonValues );
}
return new JSONManyAssociationState( this, jsonValues );
}
catch( JSONException e )
{
throw new EntityStoreException( e );
}
}
@Override
public void remove()
{
status = EntityStatus.REMOVED;
}
@Override
public EntityStatus status()
{
return status;
}
@Override
public boolean isAssignableTo( Class<?> type )
{
return entityDescriptor.isAssignableTo( type );
}
@Override
public EntityDescriptor entityDescriptor()
{
return entityDescriptor;
}
public JSONObject state()
{
return state;
}
@Override
public String toString()
{
return identity + "(" + state + ")";
}
public void markUpdated()
{
if( status == EntityStatus.LOADED )
{
status = EntityStatus.UPDATED;
}
}
boolean isStateNotCloned()
{
return status == EntityStatus.LOADED;
}
void cloneStateIfGlobalStateLoaded()
{
if( isStateNotCloned() )
{
return;
}
try
{
JSONObject newProperties = cloneJSON( state.getJSONObject( JSON_KEY_PROPERTIES ) );
JSONObject newAssoc = cloneJSON( state.getJSONObject( JSON_KEY_ASSOCIATIONS ) );
JSONObject newManyAssoc = cloneJSON( state.getJSONObject( JSON_KEY_MANYASSOCIATIONS ) );
JSONObject stateClone = new JSONObject( state, CLONE_NAMES );
stateClone.put( JSON_KEY_PROPERTIES, newProperties );
stateClone.put( JSON_KEY_ASSOCIATIONS, newAssoc );
stateClone.put( JSON_KEY_MANYASSOCIATIONS, newManyAssoc );
state = stateClone;
}
catch( JSONException e )
{
throw new EntityStoreException( e );
}
}
}