Package org.qi4j.spi.entitystore.helpers

Source Code of org.qi4j.spi.entitystore.helpers.JSONMapEntityStoreMixin

package org.qi4j.spi.entitystore.helpers;

import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.qi4j.api.cache.CacheOptions;
import org.qi4j.api.common.Optional;
import org.qi4j.api.entity.EntityDescriptor;
import org.qi4j.api.entity.EntityReference;
import org.qi4j.api.injection.scope.Service;
import org.qi4j.api.injection.scope.Structure;
import org.qi4j.api.injection.scope.This;
import org.qi4j.api.injection.scope.Uses;
import org.qi4j.api.service.Activatable;
import org.qi4j.api.service.ServiceDescriptor;
import org.qi4j.api.structure.Application;
import org.qi4j.api.structure.Module;
import org.qi4j.api.unitofwork.EntityTypeNotFoundException;
import org.qi4j.api.usecase.Usecase;
import org.qi4j.api.usecase.UsecaseBuilder;
import org.qi4j.io.Input;
import org.qi4j.io.Output;
import org.qi4j.io.Receiver;
import org.qi4j.io.Sender;
import org.qi4j.spi.cache.Cache;
import org.qi4j.spi.cache.CachePool;
import org.qi4j.spi.cache.NullCache;
import org.qi4j.spi.entity.EntityState;
import org.qi4j.spi.entity.EntityStatus;
import org.qi4j.spi.entitystore.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
* Implementation of EntityStore that works with an implementation of MapEntityStore. Implement
* MapEntityStore and add as mixin to the service using this mixin.
* See {@link org.qi4j.entitystore.memory.MemoryMapEntityStoreMixin} for reference.
*/
public class JSONMapEntityStoreMixin
        implements EntityStore, EntityStoreSPI, StateStore, Activatable
{
    @This
    private MapEntityStore mapEntityStore;

    @This
    private EntityStoreSPI entityStoreSpi;

    @Structure
    private Application application;

    @Optional
    @Service
    private Migration migration;

    @Uses
    private ServiceDescriptor descriptor;

    @Optional
    @Service
    private CachePool caching;
    private Cache<CacheState> cache;

    protected String uuid;
    private int count;

    private Logger logger;

    public JSONMapEntityStoreMixin()
    {
    }

    public void activate()
            throws Exception
    {
        logger = LoggerFactory.getLogger( descriptor.identity() );

        uuid = UUID.randomUUID().toString() + "-";
        if( caching != null )
        {
            cache = caching.fetchCache( uuid, CacheState.class );
        } else
        {
            cache = new NullCache<CacheState>();
        }
    }

    public void passivate()
            throws Exception
    {
        if( caching != null )
        {
            caching.returnCache( cache );
        }
    }

    // EntityStore

    public EntityStoreUnitOfWork newUnitOfWork( Usecase usecaseMetaInfo, Module module, long currentTime )
    {
        return new DefaultEntityStoreUnitOfWork( entityStoreSpi, newUnitOfWorkId(), module, usecaseMetaInfo, currentTime );
    }

    // EntityStoreSPI

    public EntityState newEntityState( EntityStoreUnitOfWork unitOfWork,
                                       EntityReference identity,
                                       EntityDescriptor entityDescriptor )
    {
        try
        {
            JSONObject state = new JSONObject();
            state.put( JSONEntityState.JSON_KEY_IDENTITY, identity.identity() );
            state.put( JSONEntityState.JSON_KEY_APPLICATION_VERSION, application.version() );
            state.put( JSONEntityState.JSON_KEY_TYPE, entityDescriptor.type().getName() );
            state.put( JSONEntityState.JSON_KEY_VERSION, unitOfWork.identity() );
            state.put( JSONEntityState.JSON_KEY_MODIFIED, unitOfWork.currentTime() );
            state.put( JSONEntityState.JSON_KEY_PROPERTIES, new JSONObject() );
            state.put( JSONEntityState.JSON_KEY_ASSOCIATIONS, new JSONObject() );
            state.put( JSONEntityState.JSON_KEY_MANYASSOCIATIONS, new JSONObject() );
            return new JSONEntityState( (DefaultEntityStoreUnitOfWork) unitOfWork, identity, entityDescriptor, state );
        } catch( JSONException e )
        {
            throw new EntityStoreException( e );
        }
    }

    public synchronized EntityState getEntityState( EntityStoreUnitOfWork unitOfWork, EntityReference identity )
    {
        EntityState state = fetchCachedState( identity, (DefaultEntityStoreUnitOfWork) unitOfWork );
        if( state != null )
        {
            return state;
        }
        // Get state
        Reader in = mapEntityStore.get( identity );
        JSONEntityState loadedState = readEntityState( (DefaultEntityStoreUnitOfWork) unitOfWork, in );
        if( doCacheOnRead( (DefaultEntityStoreUnitOfWork) unitOfWork ) )
        {
            cache.put( identity.identity(), new CacheState(loadedState.state()) );
        }
        return loadedState;
    }

    public StateCommitter applyChanges( final EntityStoreUnitOfWork unitOfWork, final Iterable<EntityState> state
    )
            throws EntityStoreException
    {
        return new StateCommitter()
        {
            public void commit()
            {
                try
                {
                    mapEntityStore.applyChanges( new MapEntityStore.MapChanges()
                    {
                        public void visitMap( MapEntityStore.MapChanger changer )
                                throws IOException
                        {
                            DefaultEntityStoreUnitOfWork uow = (DefaultEntityStoreUnitOfWork) unitOfWork;
                            CacheOptions options = uow.usecase().metaInfo( CacheOptions.class );
                            if( options == null )
                            {
                                options = CacheOptions.ALWAYS;
                            }

                            for( EntityState entityState : state )
                            {
                                JSONEntityState state = (JSONEntityState) entityState;
                                if( state.status().equals( EntityStatus.NEW ) )
                                {
                                    Writer writer = changer.newEntity( state.identity(),
                                            state.entityDescriptor() );
                                    writeEntityState( state, writer, unitOfWork.identity(), unitOfWork.currentTime() );
                                    writer.close();
                                    if( options.cacheOnNew() )
                                    {
                                        cache.put( state.identity().identity(), new CacheState(state.state()) );
                                    }
                                } else if( state.status().equals( EntityStatus.UPDATED ) )
                                {
                                    Writer writer = changer.updateEntity( state.identity(),
                                            state.entityDescriptor() );
                                    writeEntityState( state, writer, unitOfWork.identity(), unitOfWork.currentTime() );
                                    writer.close();
                                    if( options.cacheOnWrite() )
                                    {
                                        cache.put( state.identity().identity(), new CacheState(state.state()) );
                                    }
                                } else if( state.status().equals( EntityStatus.REMOVED ) )
                                {
                                    changer.removeEntity( state.identity(), state.entityDescriptor() );
                                    cache.remove( state.identity().identity() );
                                }
                            }
                        }
                    } );
                } catch( IOException e )
                {
                    throw new EntityStoreException( e );
                }
            }

            public void cancel()
            {
            }
        };
    }

    public Input<EntityState, EntityStoreException> entityStates( final Module module )
    {
        return new Input<EntityState, EntityStoreException>()
        {
            @Override
            public <ReceiverThrowableType extends Throwable> void transferTo( Output<? super EntityState, ReceiverThrowableType> output ) throws EntityStoreException, ReceiverThrowableType
            {
                output.receiveFrom( new Sender<EntityState, EntityStoreException>()
                {
                    @Override
                    public <ReceiverThrowableType extends Throwable> void sendTo( final Receiver<? super EntityState, ReceiverThrowableType> receiver ) throws ReceiverThrowableType, EntityStoreException
                    {
                        Usecase usecase = UsecaseBuilder
                                .buildUsecase( "qi4j.entitystore.entitystates" )
                                .with( CacheOptions.NEVER )
                                .newUsecase();

                        final DefaultEntityStoreUnitOfWork uow =
                                new DefaultEntityStoreUnitOfWork( entityStoreSpi, newUnitOfWorkId(), module, usecase, System.currentTimeMillis() );

                        final List<EntityState> migrated = new ArrayList<EntityState>();

                        try
                        {
                            mapEntityStore.entityStates().transferTo( new Output<Reader, ReceiverThrowableType>()
                            {
                                @Override
                                public <SenderThrowableType extends Throwable> void receiveFrom( Sender<? extends Reader, SenderThrowableType> sender ) throws ReceiverThrowableType, SenderThrowableType
                                {
                                    sender.sendTo( new Receiver<Reader, ReceiverThrowableType>()
                                    {
                                        public void receive( Reader item )
                                                throws ReceiverThrowableType
                                        {
                                            final EntityState entity = readEntityState( uow, item );
                                            if( entity.status() == EntityStatus.UPDATED )
                                            {
                                                migrated.add( entity );

                                                // Synch back 100 at a time
                                                if( migrated.size() > 100 )
                                                {
                                                    synchMigratedEntities( migrated );
                                                }
                                            }
                                            receiver.receive( entity );
                                        }
                                    } );

                                    // Synch any remaining migrated entities
                                    if( !migrated.isEmpty() )
                                    {
                                        synchMigratedEntities( migrated );
                                    }
                                }
                            } );
                        } catch( IOException e )
                        {
                            throw new EntityStoreException( e );
                        }
                    }
                } );
            }
        };
    }

    private void synchMigratedEntities( final List<EntityState> migratedEntities )
    {
        try
        {
            mapEntityStore.applyChanges( new MapEntityStore.MapChanges()
            {
                public void visitMap( MapEntityStore.MapChanger changer )
                        throws IOException
                {
                    for( EntityState migratedEntity : migratedEntities )
                    {
                        JSONEntityState state = (JSONEntityState) migratedEntity;
                        Writer writer = changer.updateEntity( state.identity(),
                                state.entityDescriptor() );
                        writeEntityState( state, writer, state.version(), state.lastModified() );
                        writer.close();
                    }
                }
            } );
            migratedEntities.clear();
        } catch( IOException e )
        {
            logger.warn( "Could not store migrated entites", e );
        }
    }

    protected String newUnitOfWorkId()
    {
        return uuid + Integer.toHexString( count++ );
    }

    protected void writeEntityState( JSONEntityState state, Writer writer, String identity, long lastModified )
            throws EntityStoreException
    {
        try
        {
            JSONObject jsonState = state.state();
            jsonState.put( "version", identity );
            jsonState.put( "modified", lastModified );
            writer.append( jsonState.toString() );
        } catch( Exception e )
        {
            throw new EntityStoreException( "Could not store EntityState", e );
        }
    }

    protected JSONEntityState readEntityState( DefaultEntityStoreUnitOfWork unitOfWork, Reader entityState )
            throws EntityStoreException
    {
        try
        {
            Module module = unitOfWork.module();
            JSONObject jsonObject = new JSONObject( new JSONTokener( entityState ) );
            EntityStatus status = EntityStatus.LOADED;

            String version = jsonObject.getString( "version" );
            long modified = jsonObject.getLong( "modified" );
            String identity = jsonObject.getString( "identity" );

            // Check if version is correct
            String currentAppVersion = jsonObject.optString( MapEntityStore.JSONKeys.application_version.name(),
                    "0.0" );
            if( !currentAppVersion.equals( application.version() ) )
            {
                if( migration != null )
                {
                    migration.migrate( jsonObject, application.version(), this );
                } else
                {
                    // Do nothing - set version to be correct
                    jsonObject.put( MapEntityStore.JSONKeys.application_version.name(), application.version() );
                }

                LoggerFactory.getLogger( getClass() )
                        .debug(
                                "Updated version nr on " + identity + " from " + currentAppVersion + " to " + application.version() );

                // State changed
                status = EntityStatus.UPDATED;
            }

            String type = jsonObject.getString( "type" );

            EntityDescriptor entityDescriptor = module.entityDescriptor( type );
            if( entityDescriptor == null )
            {
                throw new EntityTypeNotFoundException( type );
            }

            return new JSONEntityState( unitOfWork,
                    version,
                    modified,
                    EntityReference.parseEntityReference( identity ),
                    status,
                    entityDescriptor,
                    jsonObject
            );
        } catch( JSONException e )
        {
            throw new EntityStoreException( e );
        }
    }

    public JSONObject getState( String id )
            throws IOException
    {
        Reader reader = mapEntityStore.get( EntityReference.parseEntityReference( id ) );
        JSONObject jsonObject;
        try
        {
            jsonObject = new JSONObject( new JSONTokener( reader ) );
        } catch( JSONException e )
        {
            throw (IOException) new IOException().initCause( e );
        }
        reader.close();
        return jsonObject;
    }

    private EntityState fetchCachedState( EntityReference identity, DefaultEntityStoreUnitOfWork unitOfWork )
    {
        CacheState cacheState = cache.get( identity.identity() );
        if( cacheState != null )
        {
            JSONObject data = cacheState.json;
            try
            {
                String type = data.getString( "type" );
                EntityDescriptor entityDescriptor = unitOfWork.module().entityDescriptor( type );
                return new JSONEntityState( unitOfWork, identity, entityDescriptor, data );
            } catch( JSONException e )
            {
                // Should not be able to happen, unless internal error in the cache system.
                throw new EntityStoreException( e );
            }
        }
        return null;
    }

    private boolean doCacheOnRead( DefaultEntityStoreUnitOfWork unitOfWork )
    {
        CacheOptions cacheOptions = (unitOfWork).usecase().metaInfo( CacheOptions.class );
        return cacheOptions == null || cacheOptions.cacheOnRead();
    }

    private static class CacheState
            implements Externalizable
    {
        public JSONObject json;

        public CacheState()
        {
        }

        public CacheState( JSONObject state )
        {
            json = state;
        }

        @Override
        public void writeExternal( ObjectOutput out ) throws IOException
        {
            out.writeUTF( json.toString() );
        }

        @Override
        public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
        {
            try
            {
                json = new JSONObject( in.readUTF() );
            } catch( JSONException e )
            {
                throw new IOException( e );
            }
        }
    }
}
TOP

Related Classes of org.qi4j.spi.entitystore.helpers.JSONMapEntityStoreMixin

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.