/* * Copyright 2009 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.gae2; import com.google.appengine.api.datastore.DatastoreService; import com.google.appengine.api.datastore.DatastoreServiceConfig; import com.google.appengine.api.datastore.DatastoreServiceFactory; import com.google.appengine.api.datastore.Entity; import com.google.appengine.api.datastore.Key; import com.google.appengine.api.datastore.KeyFactory; import com.google.appengine.api.datastore.PreparedQuery; import com.google.appengine.api.datastore.Query; import com.google.appengine.api.datastore.QueryResultIterable; import com.google.appengine.api.datastore.ReadPolicy; import com.google.appengine.api.datastore.Text; import com.google.appengine.api.datastore.Transaction; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.util.concurrent.locks.ReadWriteLock; import org.qi4j.api.configuration.Configuration; import org.qi4j.api.entity.EntityDescriptor; import org.qi4j.api.entity.EntityReference; import org.qi4j.api.injection.scope.This; import org.qi4j.api.util.Classes; import org.qi4j.io.Input; import org.qi4j.io.Output; import org.qi4j.io.Receiver; import org.qi4j.io.Sender; import org.qi4j.spi.entitystore.EntityNotFoundException; import org.qi4j.spi.entitystore.EntityStoreException; import org.qi4j.spi.entitystore.helpers.MapEntityStore; import static com.google.appengine.api.datastore.DatastoreServiceConfig.Builder.withReadPolicy; import static org.qi4j.functional.Iterables.first; public class GaeEntityStoreMixin implements GaeEntityStoreActivation, MapEntityStore { @This private ReadWriteLock lock; @This private Configuration<GaeEntityStoreConfiguration> config; private DatastoreService datastore; private String entityKind; @Override public void activateGaeEntityStore() throws Exception { GaeEntityStoreConfiguration conf = config.get(); // eventually consistent reads with a 5 second deadline DatastoreServiceConfig configuration = withReadPolicy( new ReadPolicy( ReadPolicy.Consistency.valueOf( conf.readPolicy().get().toUpperCase() ) ) ) .deadline( conf.deadline().get() ); datastore = DatastoreServiceFactory.getDatastoreService( configuration ); entityKind = conf.entityKind().get(); System.out.println( "\nActivating Google App Engine Store" + "\n----------------------------------" + "\n Read Policy: " + conf.readPolicy().get() + "\n Deadline: " + conf.deadline().get() + "\n Entity Kind: " + entityKind + "\n Datastore: " + datastore + "\n Configuration: " + configuration + "\n" ); } @Override public Reader get( EntityReference ref ) throws EntityStoreException { try { Key key = KeyFactory.createKey( entityKind, ref.toURI() ); Entity entity = datastore.get( key ); Text serializedState = (Text) entity.getProperty( "value" ); if( serializedState == null ) { throw new EntityNotFoundException( ref ); } return new StringReader( serializedState.getValue() ); } catch( com.google.appengine.api.datastore.EntityNotFoundException e ) { e.printStackTrace(); throw new EntityNotFoundException( ref ); } } @Override public void applyChanges( MapChanges changes ) throws IOException { final Transaction transaction = datastore.beginTransaction(); try { changes.visitMap( new GaeMapChanger( transaction ) ); transaction.commit(); } catch( RuntimeException e ) { if( transaction.isActive() ) { transaction.rollback(); } if( e instanceof EntityStoreException ) { throw (EntityStoreException) e; } else { throw new IOException( e ); } } } @Override public Input<Reader, IOException> entityStates() { return new Input<Reader, IOException>() { @Override public <ReceiverThrowableType extends Throwable> void transferTo( Output<? super Reader, ReceiverThrowableType> output ) throws IOException, ReceiverThrowableType { Query query = new Query(); PreparedQuery preparedQuery = datastore.prepare( query ); final QueryResultIterable<Entity> iterable = preparedQuery.asQueryResultIterable(); output.receiveFrom( new Sender<Reader, IOException>() { @Override public <ReceiverThrowableType extends Throwable> void sendTo( Receiver<? super Reader, ReceiverThrowableType> receiver ) throws ReceiverThrowableType, IOException { for( Entity entity : iterable ) { Text serializedState = (Text) entity.getProperty( "value" ); receiver.receive( new StringReader( serializedState.getValue() ) ); } } } ); } }; } private class GaeMapChanger implements MapChanger { private final Transaction transaction; public GaeMapChanger( Transaction transaction ) { this.transaction = transaction; } @Override public Writer newEntity( final EntityReference ref, final EntityDescriptor descriptor ) { return new StringWriter( 1000 ) { @Override public void close() throws IOException { super.close(); Key key = KeyFactory.createKey( entityKind, ref.toURI() ); Entity entity = new Entity( key ); Text value = new Text( toString() ); entity.setUnindexedProperty( "value", value ); entity.setProperty( "ref", ref.identity() ); entity.setProperty( "type", Classes.toURI( first( descriptor.types() ) ) ); datastore.put( transaction, entity ); } }; } @Override public Writer updateEntity( final EntityReference ref, final EntityDescriptor descriptor ) { return new StringWriter( 1000 ) { @Override public void close() throws IOException { super.close(); Key key = KeyFactory.createKey( entityKind, ref.toURI() ); Entity entity = new Entity( key ); Text value = new Text( toString() ); entity.setUnindexedProperty( "value", value ); entity.setProperty( "ref", ref.identity() ); entity.setProperty( "type", Classes.toURI( first( descriptor.types() ) ) ); datastore.put( transaction, entity ); } }; } @Override public void removeEntity( EntityReference ref, EntityDescriptor descriptor ) throws EntityNotFoundException { Key key = KeyFactory.createKey( entityKind, ref.toURI() ); datastore.delete( transaction, key ); } } }