package org.atlasapi.persistence.content.cassandra; import static org.atlasapi.persistence.cassandra.CassandraSchema.CHILDREN_COLUMN; import static org.atlasapi.persistence.cassandra.CassandraSchema.CLIPS_COLUMN; import static org.atlasapi.persistence.cassandra.CassandraSchema.CONTAINER_CF; import static org.atlasapi.persistence.cassandra.CassandraSchema.CONTAINER_COLUMN; import static org.atlasapi.persistence.cassandra.CassandraSchema.ITEMS_CF; import static org.atlasapi.persistence.cassandra.CassandraSchema.ITEM_COLUMN; import static org.atlasapi.persistence.cassandra.CassandraSchema.VERSIONS_COLUMN; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.atlasapi.media.entity.ChildRef; import org.atlasapi.media.entity.Clip; import org.atlasapi.media.entity.Container; import org.atlasapi.media.entity.Content; import org.atlasapi.media.entity.Identified; import org.atlasapi.media.entity.Item; import org.atlasapi.media.entity.ParentRef; import org.atlasapi.media.entity.Version; import org.atlasapi.persistence.cassandra.CassandraPersistenceException; import org.atlasapi.persistence.content.ContentCategory; import org.atlasapi.persistence.content.ContentResolver; import org.atlasapi.persistence.content.ContentWriter; import org.atlasapi.persistence.content.ResolvedContent; import org.atlasapi.persistence.content.listing.ContentLister; import org.atlasapi.persistence.content.listing.ContentListingCriteria; import org.atlasapi.serialization.json.ContainerConfiguration; import org.atlasapi.serialization.json.ItemConfiguration; import org.atlasapi.serialization.json.JsonFactory; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.ser.FilterProvider; import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; import com.fasterxml.jackson.databind.type.TypeFactory; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.metabroadcast.common.base.Maybe; import com.netflix.astyanax.AstyanaxContext; import com.netflix.astyanax.Keyspace; import com.netflix.astyanax.MutationBatch; import com.netflix.astyanax.connectionpool.OperationResult; import com.netflix.astyanax.model.ColumnList; import com.netflix.astyanax.model.ConsistencyLevel; import com.netflix.astyanax.model.Row; import com.netflix.astyanax.model.Rows; import com.netflix.astyanax.query.AllRowsQuery; public class CassandraContentStore implements ContentWriter, ContentResolver, ContentLister { private final ObjectMapper mapper = JsonFactory.makeJsonMapper(); private final AstyanaxContext<Keyspace> context; private final int requestTimeout; private Keyspace keyspace; public CassandraContentStore(AstyanaxContext<Keyspace> context, int requestTimeout) { this.context = context; this.requestTimeout = requestTimeout; } @PostConstruct public void init() { context.start(); keyspace = context.getEntity(); } @PreDestroy public void close() { context.shutdown(); } @Override public Item createOrUpdate(Item item) { try { writeItem(item); attachItemToParent(item); return item; } catch (Exception ex) { throw new CassandraPersistenceException(ex.getMessage(), ex); } } @Override public void createOrUpdate(Container container) { try { writeContainer(container); } catch (Exception ex) { throw new CassandraPersistenceException(ex.getMessage(), ex); } } @Override public ResolvedContent findByCanonicalUris(Iterable<String> canonicalUris) { try { Map<String, Maybe<Identified>> results = new HashMap<String, Maybe<Identified>>(); for (String uri : canonicalUris) { Content foundContent = readContent(uri); Content foundContainer = readContainer(uri); // if (foundContent != null) { results.put(uri, Maybe.<Identified>just(foundContent)); } if (foundContainer != null) { results.put(uri, Maybe.<Identified>just(foundContainer)); } if (!results.containsKey(uri)) { results.put(uri, Maybe.<Identified>nothing()); } } return new ResolvedContent(results); } catch (Exception ex) { throw new CassandraPersistenceException(ex.getMessage(), ex); } } @Override public ResolvedContent findByUris(Iterable<String> uris) { throw new UnsupportedOperationException(); } @Override public Iterator<Content> listContent(ContentListingCriteria criteria) { try { Iterator<Content> items = Iterators.emptyIterator(); Iterator<Content> containers = Iterators.emptyIterator(); if (criteria.getCategories().contains(ContentCategory.CHILD_ITEM) || criteria.getCategories().contains(ContentCategory.TOP_LEVEL_ITEM)) { AllRowsQuery<String, String> allRowsQuery = keyspace.prepareQuery(ITEMS_CF).setConsistencyLevel(ConsistencyLevel.CL_QUORUM).getAllRows(); allRowsQuery.setRowLimit(100); OperationResult<Rows<String, String>> result = allRowsQuery.execute(); items = Iterators.transform(result.getResult().iterator(), new Function<Row<String, String>, Content>() { @Override public Content apply(Row<String, String> input) { try { return unmarshalItem(input.getColumns()); } catch (Exception ex) { return null; } } }); } if (criteria.getCategories().contains(ContentCategory.CONTAINER)) { AllRowsQuery<String, String> allRowsQuery = keyspace.prepareQuery(CONTAINER_CF).setConsistencyLevel(ConsistencyLevel.CL_QUORUM).getAllRows(); allRowsQuery.setRowLimit(100); OperationResult<Rows<String, String>> result = allRowsQuery.execute(); containers = Iterators.transform(result.getResult().iterator(), new Function<Row<String, String>, Content>() { @Override public Content apply(Row<String, String> input) { try { return unmarshalContainer(input.getColumns()); } catch (Exception ex) { return null; } } }); } return Iterators.concat(containers, items); } catch (Exception ex) { throw new CassandraPersistenceException(ex.getMessage(), ex); } } private void writeItem(Item item) throws Exception { MutationBatch mutation = keyspace.prepareMutationBatch(); mutation.setConsistencyLevel(ConsistencyLevel.CL_QUORUM); marshalItem(item, mutation); Future<OperationResult<Void>> result = mutation.executeAsync(); try { result.get(requestTimeout, TimeUnit.MILLISECONDS); } catch (Exception ex) { throw new CassandraPersistenceException(ex.getMessage(), ex); } } private void writeContainer(Container container) throws Exception { MutationBatch mutation = keyspace.prepareMutationBatch(); mutation.setConsistencyLevel(ConsistencyLevel.CL_QUORUM); marshalContainer(container, mutation); Future<OperationResult<Void>> result = mutation.executeAsync(); try { result.get(requestTimeout, TimeUnit.MILLISECONDS); } catch (Exception ex) { throw new CassandraPersistenceException(ex.getMessage(), ex); } } private void attachItemToParent(Item item) throws Exception { ParentRef parent = item.getContainer(); if (parent != null) { Container container = readContainer(parent.getUri()); if (container != null) { container.setChildRefs(ChildRef.dedupeAndSort(Iterables.concat(container.getChildRefs(), ImmutableList.of(item.childRef())))); writeContainer(container); } } } private Content readContent(String id) throws Exception { try { Future<OperationResult<ColumnList<String>>> result = keyspace.prepareQuery(ITEMS_CF). setConsistencyLevel(ConsistencyLevel.CL_QUORUM). getKey(id.toString()). executeAsync(); OperationResult<ColumnList<String>> columns = result.get(requestTimeout, TimeUnit.MILLISECONDS); if (!columns.getResult().isEmpty()) { return unmarshalItem(columns.getResult()); } else { return null; } } catch (Exception ex) { throw new CassandraPersistenceException(ex.getMessage(), ex); } } private Container readContainer(String id) throws Exception { try { Future<OperationResult<ColumnList<String>>> result = keyspace.prepareQuery(CONTAINER_CF). setConsistencyLevel(ConsistencyLevel.CL_QUORUM). getKey(id.toString()). executeAsync(); OperationResult<ColumnList<String>> columns = result.get(requestTimeout, TimeUnit.MILLISECONDS); if (!columns.getResult().isEmpty()) { return unmarshalContainer(columns.getResult()); } else { return null; } } catch (Exception ex) { throw new CassandraPersistenceException(ex.getMessage(), ex); } } private void marshalItem(Item item, MutationBatch mutation) throws IOException { FilterProvider filters = new SimpleFilterProvider().addFilter(ItemConfiguration.FILTER, SimpleBeanPropertyFilter.serializeAllExcept(ItemConfiguration.CLIPS_FILTER, ItemConfiguration.VERSIONS_FILTER)); ObjectWriter writer = mapper.writer(filters); byte[] itemBytes = writer.writeValueAsBytes(item); byte[] clipsBytes = writer.writeValueAsBytes(item.getClips()); byte[] versionsBytes = writer.writeValueAsBytes(item.getVersions()); mutation.withRow(ITEMS_CF, item.getCanonicalUri()). putColumn(ITEM_COLUMN, itemBytes, null). putColumn(CLIPS_COLUMN, clipsBytes, null). putColumn(VERSIONS_COLUMN, versionsBytes, null); } private void marshalContainer(Container container, MutationBatch mutation) throws IOException { FilterProvider filters = new SimpleFilterProvider().addFilter(ContainerConfiguration.FILTER, SimpleBeanPropertyFilter.serializeAllExcept(ContainerConfiguration.CLIPS_FILTER, ContainerConfiguration.SUB_ITEMS_FILTER)); ObjectWriter writer = mapper.writer(filters); byte[] containerBytes = writer.writeValueAsBytes(container); byte[] clipsBytes = writer.writeValueAsBytes(container.getClips()); byte[] subItemsBytes = writer.writeValueAsBytes(container.getChildRefs()); mutation.withRow(CONTAINER_CF, container.getCanonicalUri().toString()). putColumn(CONTAINER_COLUMN, containerBytes, null). putColumn(CLIPS_COLUMN, clipsBytes, null). putColumn(CHILDREN_COLUMN, subItemsBytes, null); } private Content unmarshalItem(ColumnList<String> columns) throws IOException { Item item = mapper.readValue(columns.getColumnByName(ITEM_COLUMN).getByteArrayValue(), Item.class); List<Clip> clips = mapper.readValue(columns.getColumnByName(CLIPS_COLUMN).getByteArrayValue(), TypeFactory.defaultInstance().constructCollectionType(List.class, Clip.class)); Set<Version> versions = mapper.readValue(columns.getColumnByName(VERSIONS_COLUMN).getByteArrayValue(), TypeFactory.defaultInstance().constructCollectionType(Set.class, Version.class)); item.setClips(clips); item.setVersions(versions); return item; } private Container unmarshalContainer(ColumnList<String> columns) throws IOException { Container container = mapper.readValue(columns.getColumnByName(CONTAINER_COLUMN).getByteArrayValue(), Container.class); List<Clip> clips = mapper.readValue(columns.getColumnByName(CLIPS_COLUMN).getByteArrayValue(), TypeFactory.defaultInstance().constructCollectionType(List.class, Clip.class)); List<ChildRef> children = mapper.readValue(columns.getColumnByName(CHILDREN_COLUMN).getByteArrayValue(), TypeFactory.defaultInstance().constructCollectionType(List.class, ChildRef.class)); container.setClips(clips); container.setChildRefs(children); return container; } }