package eu.europeana.cloud.service.mcs.persistent.cassandra; import com.datastax.driver.core.*; import com.datastax.driver.core.utils.Bytes; import com.google.common.io.BaseEncoding; import com.google.common.io.CountingInputStream; import com.google.common.primitives.Ints; import eu.europeana.cloud.cassandra.CassandraConnectionProvider; import eu.europeana.cloud.service.mcs.exception.FileAlreadyExistsException; import eu.europeana.cloud.service.mcs.exception.FileNotExistsException; import eu.europeana.cloud.service.mcs.persistent.swift.ContentDAO; import eu.europeana.cloud.service.mcs.persistent.swift.PutResult; import eu.europeana.cloud.service.mcs.persistent.util.QueryTracer; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.ByteArrayOutputStream; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Repository; import javax.annotation.PostConstruct; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; /** * Provides content DAO operations for Cassandra. * * @author krystian. */ @Repository public class CassandraContentDAO implements ContentDAO { @Autowired @Qualifier("dbService") private CassandraConnectionProvider connectionProvider; private PreparedStatement insert; private PreparedStatement select; private PreparedStatement delete; private final StreamCompressor streamCompressor = new StreamCompressor(); @PostConstruct private void prepareStatements() { Session s = connectionProvider.getSession(); insert = s.prepare("INSERT INTO files_content (fileName, data) VALUES (?,?) IF NOT EXISTS"); insert.setConsistencyLevel(connectionProvider .getConsistencyLevel()); select = s.prepare("SELECT data FROM files_content WHERE fileName = ?;"); select.setConsistencyLevel(connectionProvider .getConsistencyLevel()); delete = s.prepare("DELETE FROM files_content WHERE fileName = ? IF EXISTS;"); delete.setConsistencyLevel(connectionProvider .getConsistencyLevel()); } /** * @inheritDoc */ @Override public void copyContent(String sourceObjectId, String trgObjectId) throws FileNotExistsException, FileAlreadyExistsException, IOException { ByteArrayOutputStream os = new ByteArrayOutputStream(); try { getContent(sourceObjectId, -1, -1, os); } catch (FileNotExistsException e) { throw new FileNotExistsException(String.format("File %s not exists", sourceObjectId)); } checkIfObjectNotExists(trgObjectId); putContent(trgObjectId, new ByteArrayInputStream(os.toByteArray())); } /** * @inheritDoc */ @Override public void deleteContent(String fileName) throws FileNotExistsException { ResultSet rs = executeQueryWithLogger(delete.bind(fileName)); if (!rs.wasApplied()) { throw new FileNotExistsException(String.format("File %s not exists", fileName)); } } /** * @inheritDoc */ @Override public void getContent(String fileName, long start, long end, OutputStream result) throws IOException, FileNotExistsException { ResultSet rs = executeQueryWithLogger(select.bind(fileName)); Row row = rs.one(); if (row == null) { throw new FileNotExistsException(String.format("File %s not exists", fileName)); } ByteBuffer wrappedBytes = row.getBytes("data"); ByteArrayOutputStream os = new ByteArrayOutputStream(); streamCompressor.decompress(unwrap(wrappedBytes), os); copySelectBytes(os,start,end, result); } /** * @inheritDoc */ @Override public PutResult putContent(String fileName, InputStream data) throws IOException { CountingInputStream countingInputStream = new CountingInputStream(data); DigestInputStream md5DigestInputStream = prepareMd5DigestStream(countingInputStream); ByteBuffer wrappedBytes = ByteBuffer.wrap(streamCompressor.compress(md5DigestInputStream)); executeQueryWithLogger(insert.bind(fileName, wrappedBytes)); String md5 = BaseEncoding.base16().lowerCase().encode(md5DigestInputStream.getMessageDigest().digest()); Long contentLength = countingInputStream.getCount(); return new PutResult(md5, contentLength); } private void checkIfObjectNotExists(String trgObjectId) throws IOException, FileAlreadyExistsException { ResultSet rs = executeQueryWithLogger(select.bind(trgObjectId)); Row row = rs.one(); if (row != null) { throw new FileAlreadyExistsException(String.format("File %s already exists", trgObjectId)); } } private ResultSet executeQueryWithLogger(BoundStatement boundStatement) { ResultSet rs = connectionProvider.getSession().execute(boundStatement); QueryTracer.logConsistencyLevel(boundStatement, rs); return rs; } private void copySelectBytes(ByteArrayOutputStream input, long start, long end, OutputStream result) throws IOException { byte[] resultBytes; if (start < 0 && end < 0) { resultBytes = input.toByteArray(); } else { resultBytes = getSelectedBytes(input.toByteArray(), start, end); } IOUtils.copy(new ByteArrayInputStream(resultBytes),result); } private byte[] unwrap(ByteBuffer wrappedBytes) { return Bytes.getArray(wrappedBytes); } private byte[] getSelectedBytes(byte [] bytes, long start, long end) { byte[] outputBytes; final int from = start > -1 ? Ints.checkedCast(start) : 0; final int to = end > -1 ? Ints.checkedCast(end) + 1 : bytes.length; outputBytes = Arrays.copyOfRange(bytes, from, to); return outputBytes; } private DigestInputStream prepareMd5DigestStream(InputStream is) { try { MessageDigest md = MessageDigest.getInstance("MD5"); return new DigestInputStream(is, md); } catch (NoSuchAlgorithmException ex) { throw new AssertionError("Cannot get instance of MD5 but such algorithm should be provided", ex); } } }