package org.qi4j.entitystore.cassandra; import me.prettyprint.cassandra.serializers.BytesSerializer; import me.prettyprint.cassandra.serializers.StringSerializer; import me.prettyprint.hector.api.Cluster; import me.prettyprint.hector.api.Keyspace; import me.prettyprint.hector.api.beans.HColumn; import me.prettyprint.hector.api.factory.HFactory; import me.prettyprint.hector.api.mutation.MutationResult; import me.prettyprint.hector.api.query.ColumnQuery; import me.prettyprint.hector.api.query.QueryResult; import org.qi4j.api.entity.EntityReference; import org.qi4j.api.injection.scope.Service; import org.qi4j.api.io.Input; import org.qi4j.api.service.Activatable; import org.qi4j.entitystore.map.MapEntityStore; import org.qi4j.spi.entity.EntityType; import org.qi4j.spi.entitystore.EntityAlreadyExistsException; import org.qi4j.spi.entitystore.EntityNotFoundException; import org.qi4j.spi.entitystore.EntityStoreException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.Writer; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import static me.prettyprint.hector.api.factory.HFactory.*; /** * // TODO: Document this * * @author pvdyck * @since 4.0 */ public class CassandraMapEntityStoreMixin implements MapEntityStore, Activatable { private static final String COLUMN_NAME = "entry"; private final Logger logger = LoggerFactory.getLogger(CassandraMapEntityStoreMixin.class); private static final String keySpace = "Qi4j"; static final String cf = "Qi4jEntries"; static final int BYTE_ARRAY_BUFFER_INITIAL_SIZE = 512; Cluster c; Keyspace ko; private static final StringSerializer se = new StringSerializer(); private static final BytesSerializer bs = new BytesSerializer(); @Service private CassandraConfiguration conf; public void activate() throws Exception { c = HFactory.getOrCreateCluster("Qi4jCluster", conf.getHost() + ":" + conf.getPort()); ko = HFactory.createKeyspace(keySpace, c); logger.info("started cassandra store"); } public void passivate() throws Exception { logger.info("shutting down cassandra"); } public void applyChanges(final MapChanges changes) throws IOException { if (conf.readOnly()) { throw new EntityStoreException("Read-only Entity Store"); } try { final MapUpdater changer = new MapUpdater(); changes.visitMap(changer); MutationResult result = changer.m.execute(); logger.info("applying changes to cassandra store " + result.getExecutionTimeMicro() + " / " + result.getHostUsed()); } catch (Throwable e) { throw new EntityStoreException("Exception during cassandra batch " + " - ", e); } } boolean contains(EntityReference ref) throws EntityStoreException { try { //TODO optimise this... no need to fetch everything get(ref); return true; } catch (final EntityNotFoundException e1) { return false; } catch (final Exception e1) { throw new EntityStoreException(e1); } } public Reader get(final EntityReference ref) { //TODO .. should be able to use only one ColumnQuery ... btw .. is it thread-safe ? ColumnQuery<String, byte[]> q = createColumnQuery(ko, se, bs); q.setName(COLUMN_NAME).setColumnFamily(cf); QueryResult<HColumn<String, byte[]>> r; try { r = q.setKey(ref.toString()).execute(); } catch (Exception e) { throw new EntityStoreException(e); } if (r.get() == null) throw new EntityNotFoundException(ref); try { return createReader(new ByteArrayInputStream(r.get().getValue())); } catch (IOException e) { throw new EntityStoreException(e); } } public Input<Reader, IOException> entityStates() { throw new UnsupportedOperationException("Not implemented yet"); } Writer createWriter(OutputStream out) throws IOException { if (conf.gzipCompress()) return new OutputStreamWriter(new GZIPOutputStream(out)); return new OutputStreamWriter(out); } private Reader createReader(InputStream in) throws IOException { if (conf.gzipCompress()) return new InputStreamReader(new GZIPInputStream(in)); return new InputStreamReader(in); } void checkAbsentBeforeCreate(EntityReference ref) { if (!conf.checkAbsentBeforeCreate()) return; if (contains(ref)) throw new EntityAlreadyExistsException(ref); } void checkPresentBeforeDelete(EntityReference ref) { if (!conf.checkPresentBeforeDelete()) return; if (!contains(ref)) throw new EntityNotFoundException(ref); } void checkPresentBeforeUpdate(EntityReference ref) { if (!conf.checkPresentBeforeUpdate()) return; if (!contains(ref)) throw new EntityNotFoundException(ref); } class MapUpdater implements MapEntityStore.MapChanger { me.prettyprint.hector.api.mutation.Mutator m = createMutator(ko); public Writer newEntity(final EntityReference ref, EntityType entityType) { checkAbsentBeforeCreate(ref); return getWriter(ref); } public Writer updateEntity(final EntityReference ref, EntityType entityType) throws IOException { checkPresentBeforeUpdate(ref); return getWriter(ref); } public void removeEntity(EntityReference ref, EntityType entityType) throws EntityNotFoundException { checkPresentBeforeDelete(ref); m.addDeletion(ref.identity(), cf, COLUMN_NAME, se); } private Writer getWriter(final EntityReference ref) { try { return createWriter(new ByteArrayOutputStream(CassandraMapEntityStoreMixin.BYTE_ARRAY_BUFFER_INITIAL_SIZE) { @Override public void close() throws IOException { super.close(); m.addInsertion(ref.identity(), cf, createColumn(COLUMN_NAME, toByteArray(), se, bs)); } }); } catch (final Exception e) { throw new EntityStoreException(e); } } } }