/* * Copyright 2010 Niclas Hedhman. * * 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.entitystore.gae; import com.google.appengine.api.datastore.Entity; import com.google.appengine.api.datastore.Key; import com.google.appengine.api.datastore.Text; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.json.JSONException; import org.json.JSONObject; import org.qi4j.api.common.QualifiedName; import org.qi4j.api.common.TypeName; import org.qi4j.api.entity.EntityReference; import org.qi4j.spi.entity.EntityDescriptor; import org.qi4j.spi.entity.EntityState; import org.qi4j.spi.entity.EntityStatus; import org.qi4j.spi.entity.EntityType; import org.qi4j.spi.entity.ManyAssociationState; import org.qi4j.spi.entity.NamedAssociationState; import org.qi4j.spi.entity.association.NamedEntityReference; import org.qi4j.spi.property.PropertyType; import org.qi4j.spi.property.ValueType; import org.qi4j.spi.structure.ModuleSPI; public class GaeEntityState implements EntityState { static final String PROPERTY_TYPE = "$type"; private final Entity entity; private EntityStatus status; private final GaeEntityStoreUnitOfWork unitOfWork; private final EntityDescriptor descriptor; private final HashMap<QualifiedName, PropertyType> valueTypes; private final ModuleSPI module; public GaeEntityState( GaeEntityStoreUnitOfWork unitOfWork, Key key, EntityDescriptor descriptor, ModuleSPI module ) { System.out.println( "GaeEntityState( " + unitOfWork + ", " + key + ", " + descriptor + " )" ); this.module = module; this.unitOfWork = unitOfWork; this.descriptor = descriptor; entity = new Entity( key ); entity.setProperty( "$version", unitOfWork.identity() ); EntityType entityType = descriptor.entityType(); TypeName typeName = entityType.type(); String name = typeName.name(); System.out.println( "New Entity\n" + " descriptor:" + descriptor + "\n " + " entityType:" + entityType + "\n " + " typeName:" + typeName + "\n " + " name:" + name + "\n " ); entity.setUnindexedProperty( PROPERTY_TYPE, name ); status = EntityStatus.NEW; valueTypes = initializeValueTypes( descriptor ); } public GaeEntityState( GaeEntityStoreUnitOfWork unitOfWork, Entity entity, ModuleSPI module ) { System.out.println( "GaeEntityState( " + unitOfWork + ", " + entity + " )" ); if( entity == null ) { throw new NullPointerException(); } if( unitOfWork == null ) { throw new NullPointerException(); } this.module = module; this.unitOfWork = unitOfWork; this.entity = entity; String typeName = (String) entity.getProperty( GaeEntityState.PROPERTY_TYPE ); System.out.println( "LOADING [" + typeName + "]" ); descriptor = module.entityDescriptor( typeName ); status = EntityStatus.LOADED; valueTypes = initializeValueTypes( descriptor ); } private HashMap<QualifiedName, PropertyType> initializeValueTypes( EntityDescriptor descriptor ) { HashMap<QualifiedName, PropertyType> result = new HashMap<QualifiedName, PropertyType>(); for( PropertyType type : descriptor.entityType().properties() ) { if( type.type().isValue() ) { QualifiedName name = type.qualifiedName(); result.put( name, type ); } } return result; } Entity entity() { System.out.println( "entity() --> " + entity ); return entity; } public EntityReference identity() { EntityReference ref = new EntityReference( entity.getKey().getName() ); System.out.println( "identity() --> " + ref ); return ref; } public String version() { String version = (String) entity.getProperty( "$version" ); System.out.println( "version() --> " + version ); return version; } public long lastModified() { Long lastModified = (Long) entity.getProperty( "$lastModified" ); System.out.println( "lastModified() --> " + lastModified ); return lastModified; } public void remove() { System.out.println( "remove()" ); status = EntityStatus.REMOVED; } public EntityStatus status() { System.out.println( "status() --> " + status ); return status; } public boolean isOfType( TypeName type ) { System.out.println( "isOfType( " + type + " ) --> false" ); return false; } public EntityDescriptor entityDescriptor() { System.out.println( "entityDescriptor() --> " + descriptor ); return descriptor; } public Object getProperty( QualifiedName stateName ) { String uri = stateName.toURI(); Object value = entity.getProperty( uri ); if( value instanceof Text ) { value = ( (Text) value ).getValue(); } PropertyType type = valueTypes.get( stateName ); if( type != null ) { try { JSONObject json = new JSONObject( value ); ValueType valueType = type.type(); value = valueType.fromJSON( json, module ); } catch( JSONException e ) { String message = "\nqualifiedName: " + stateName + "\n stateName: " + stateName.name() + "\n uri: " + uri + "\n type: " + type + "\n value: " + value + "\n"; InternalError error = new InternalError( message ); error.initCause( e ); throw error; } } System.out.println( "getProperty( " + stateName + " ) --> " + uri + "=" + value ); return value; } public void setProperty( QualifiedName stateName, Object value ) { System.out.println( "setProperty( " + stateName + ", " + value + " )" ); if( value != null && Proxy.isProxyClass( value.getClass() ) ) { System.out.println( "handler: " + Proxy.getInvocationHandler( value ) ); PropertyType type = valueTypes.get( stateName ); try { ValueType valueType = type.type(); value = valueType.toJSON( value ).toString(); } catch( JSONException e ) { InternalError error = new InternalError(); error.initCause( e ); throw error; } } if( value instanceof String ) { value = new Text( (String) value ); } entity.setUnindexedProperty( stateName.toURI(), value ); } public EntityReference getAssociation( QualifiedName stateName ) { String uri = stateName.toURI(); String identity = (String) entity.getProperty( uri ); System.out.println( "getAssociation( " + stateName + " ) --> " + uri + " = " + identity ); EntityReference ref = new EntityReference( identity ); return ref; } public void setAssociation( QualifiedName stateName, EntityReference newEntity ) { System.out.println( "setAssociation( " + stateName + ", " + newEntity + " )" ); String uri = stateName.toURI(); String id = null; if( newEntity != null ) { id = newEntity.identity(); } entity.setUnindexedProperty( uri, id ); } public ManyAssociationState getManyAssociation( QualifiedName stateName ) { List<String> assocs = (List<String>) entity.getProperty( stateName.toURI() ); ManyAssociationState state = new GaeManyAssociationState( this, assocs ); return state; } @Override public NamedAssociationState getNamedAssociation( QualifiedName stateName ) { Map<String,String> assocs = (Map<String, String>) entity.getProperty( stateName.toURI() ); NamedAssociationState state = new GaeNamedAssociationState( this, assocs ); return state; } public void hasBeenApplied() { System.out.println( "hasBeenApplied()" ); status = EntityStatus.LOADED; } private static class GaeManyAssociationState implements ManyAssociationState { private List<String> assocs; private final GaeEntityState entityState; public GaeManyAssociationState( GaeEntityState entityState, List<String> listOfAssociations ) { this.entityState = entityState; if( listOfAssociations == null ) { this.assocs = new ArrayList<String>(); } else { this.assocs = listOfAssociations; } } public int count() { return assocs.size(); } public boolean contains( EntityReference entityReference ) { return assocs.contains( entityReference.identity() ); } public boolean add( int index, EntityReference entityReference ) { System.out.println( "NICLAS::" + entityReference ); String identity = entityReference.identity(); System.out.println( "NICLAS::" + identity ); System.out.println( "NICLAS::" + assocs ); if( assocs.contains( identity ) ) { return false; } assocs.add( index, entityReference.identity() ); entityState.markUpdated(); return true; } public boolean remove( EntityReference entityReference ) { return assocs.remove( entityReference.identity() ); } public EntityReference get( int index ) { String id = assocs.get( index ); return new EntityReference( id ); } public Iterator<EntityReference> iterator() { ArrayList<EntityReference> result = new ArrayList<EntityReference>(); for( String id : assocs ) { result.add( new EntityReference( id ) ); } return result.iterator(); } } private static class GaeNamedAssociationState implements NamedAssociationState { private Map<String, String> assocs; private final GaeEntityState entityState; public GaeNamedAssociationState( GaeEntityState entityState, Map<String, String> listOfAssociations ) { this.entityState = entityState; if( listOfAssociations == null ) { this.assocs = new HashMap<String, String>(); } else { this.assocs = listOfAssociations; } } public int count() { return assocs.size(); } public String contains( EntityReference entityReference ) { String lookingFor = entityReference.identity(); for( Map.Entry<String,String> entry : assocs.entrySet() ) { if( lookingFor.equals( entry.getValue() )) { return entry.getKey(); } } return null; } @Override public boolean containsKey( String name ) { return assocs.containsKey( name ); } public void put( String name, EntityReference entityReference ) { System.out.println( "NICLAS::" + entityReference ); String identity = entityReference.identity(); System.out.println( "NICLAS::" + name ); System.out.println( "NICLAS::" + identity ); System.out.println( "NICLAS::" + assocs ); assocs.put( name, entityReference.identity() ); entityState.markUpdated(); } public boolean remove( String name ) { return assocs.remove( name ) != null; } public EntityReference get( String name ) { String id = assocs.get( name ); return new EntityReference( id ); } @Override public Iterable<String> names() { return Collections.unmodifiableMap( assocs ).keySet(); } public Iterator<NamedEntityReference> iterator() { return new NamedEntityReferenceIterator(assocs.entrySet().iterator()); } private static class NamedEntityReferenceIterator implements Iterator<NamedEntityReference> { private Iterator<Map.Entry<String, String>> iterator; public NamedEntityReferenceIterator( Iterator<Map.Entry<String, String>> iterator ) { this.iterator = iterator; } @Override public boolean hasNext() { return iterator.hasNext(); } @Override public NamedEntityReference next() { Map.Entry<String, String> next = iterator.next(); String name = next.getKey(); EntityReference ref = new EntityReference( next.getValue() ); return new NamedEntityReference( name, ref ); } @Override public void remove() { throw new UnsupportedOperationException(); } } } private void markUpdated() { if( status == EntityStatus.LOADED ) { status = EntityStatus.UPDATED; } } }