com.netflix.paas.dao.astyanax.SimpleReverseIndexer.java Source code

Java tutorial

Introduction

Here is the source code for com.netflix.paas.dao.astyanax.SimpleReverseIndexer.java

Source

/*******************************************************************************
 * /***
 *  *
 *  *  Copyright 2013 Netflix, Inc.
 *  *
 *  *     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 com.netflix.paas.dao.astyanax;

import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;

import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.netflix.astyanax.ColumnListMutation;
import com.netflix.astyanax.Keyspace;
import com.netflix.astyanax.MutationBatch;
import com.netflix.astyanax.annotations.Component;
import com.netflix.astyanax.connectionpool.exceptions.ConnectionException;
import com.netflix.astyanax.model.Column;
import com.netflix.astyanax.model.ColumnFamily;
import com.netflix.astyanax.model.ColumnList;
import com.netflix.astyanax.model.Row;
import com.netflix.astyanax.serializers.AnnotatedCompositeSerializer;
import com.netflix.astyanax.serializers.StringSerializer;
import com.netflix.astyanax.util.TimeUUIDUtils;

/**
 * Very very very simple and inefficient tagger that stores a single row per tag.  
 * Use this when storing and tagging a relatively small number of 'documents'.
 * The tagger works on equality and does not provide prefix or wildcard searches
 * 
 * RowKey:  <TagName>
 * Column:  <ForeignKey><VersionUUID>
 * 
 * @author elandau
 *
 */
public class SimpleReverseIndexer implements Indexer {
    private final static String ID_PREFIX = "$";

    /**
     * Composite entry within the index CF
     * @author elandau
     *
     */
    public static class IndexEntry {
        public IndexEntry() {

        }

        public IndexEntry(String id, UUID uuid) {
            this.id = id;
            this.version = uuid;
        }

        @Component(ordinal = 0)
        public String id;

        @Component(ordinal = 1)
        public UUID version;
    }

    private static final AnnotatedCompositeSerializer<IndexEntry> EntrySerializer = new AnnotatedCompositeSerializer<IndexEntry>(
            IndexEntry.class);

    /**
     * Builder pattern
     * @author elandau
     */
    public static class Builder {
        private Keyspace keyspace;
        private String columnFamily;

        public Builder withKeyspace(Keyspace keyspace) {
            this.keyspace = keyspace;
            return this;
        }

        public Builder withColumnFamily(String columnFamily) {
            this.columnFamily = columnFamily;
            return this;
        }

        public SimpleReverseIndexer build() {
            return new SimpleReverseIndexer(this);
        }
    }

    public static Builder builder() {
        return new Builder();
    }

    private Keyspace keyspace;
    private ColumnFamily<String, IndexEntry> indexCf;
    private ColumnFamily<String, String> dataCf;

    private SimpleReverseIndexer(Builder builder) {
        indexCf = new ColumnFamily<String, IndexEntry>(builder.columnFamily + "_idx", StringSerializer.get(),
                EntrySerializer);
        dataCf = new ColumnFamily<String, String>(builder.columnFamily + "_data", StringSerializer.get(),
                StringSerializer.get());
        keyspace = builder.keyspace;
    }

    @Override
    public Collection<String> findUnion(Map<String, String> tags) throws IndexerException {
        Set<String> ids = Sets.newHashSet();
        MutationBatch mb = keyspace.prepareMutationBatch();
        try {
            for (Row<String, IndexEntry> row : keyspace.prepareQuery(indexCf).getKeySlice(fieldsToSet(tags))
                    .execute().getResult()) {
                ColumnListMutation<IndexEntry> mrow = null;
                IndexEntry previousEntry = null;
                for (Column<IndexEntry> column : row.getColumns()) {
                    IndexEntry entry = column.getName();
                    if (previousEntry != null && entry.id == previousEntry.id) {
                        if (mrow == null)
                            mrow = mb.withRow(indexCf, row.getKey());
                        mrow.deleteColumn(previousEntry);
                    }
                    ids.add(entry.id);
                }
            }
        } catch (ConnectionException e) {
            throw new IndexerException("Failed to get tags : " + tags, e);
        } finally {
            try {
                mb.execute();
            } catch (Exception e) {
                // OK to ignore
            }
        }
        return ids;
    }

    private Collection<String> fieldsToSet(Map<String, String> tags) {
        return Collections2.transform(tags.entrySet(), new Function<Entry<String, String>, String>() {
            public String apply(Entry<String, String> entry) {
                return entry.getKey() + "=" + entry.getValue();
            }
        });
    }

    @Override
    public Collection<String> findIntersection(Map<String, String> tags) throws IndexerException {
        Set<String> ids = Sets.newHashSet();

        MutationBatch mb = keyspace.prepareMutationBatch();
        try {
            boolean first = true;
            Set<Entry<String, String>> elements = tags.entrySet();

            for (Row<String, IndexEntry> row : keyspace.prepareQuery(indexCf).getKeySlice(fieldsToSet(tags))
                    .execute().getResult()) {
                Set<String> rowIds = Sets.newHashSet();
                ColumnListMutation<IndexEntry> mrow = null;
                IndexEntry previousEntry = null;
                for (Column<IndexEntry> column : row.getColumns()) {
                    IndexEntry entry = column.getName();
                    if (previousEntry != null && entry.id == previousEntry.id) {
                        if (mrow == null)
                            mrow = mb.withRow(indexCf, row.getKey());
                        mrow.deleteColumn(previousEntry);
                    }

                    rowIds.add(entry.id);
                }

                if (first) {
                    first = false;
                    ids = rowIds;
                } else {
                    ids = Sets.intersection(ids, rowIds);
                    if (ids.isEmpty())
                        return ids;
                }
            }
        } catch (ConnectionException e) {
            throw new IndexerException("Failed to get tags : " + tags, e);
        } finally {
            try {
                mb.execute();
            } catch (ConnectionException e) {
                // OK to ignore
            }
        }
        return ids;
    }

    @Override
    public Collection<String> find(String field, String value) throws IndexerException {
        Set<String> ids = Sets.newHashSet();
        String indexRowKey = field + "=" + value;

        MutationBatch mb = keyspace.prepareMutationBatch();
        try {
            boolean first = true;
            ColumnList<IndexEntry> row = keyspace.prepareQuery(indexCf).getRow(indexRowKey).execute().getResult();
            IndexEntry previousEntry = null;
            for (Column<IndexEntry> column : row) {
                IndexEntry entry = column.getName();
                ColumnListMutation<IndexEntry> mrow = null;
                if (previousEntry != null && entry.id == previousEntry.id) {
                    if (mrow == null)
                        mrow = mb.withRow(indexCf, indexRowKey);
                    mrow.deleteColumn(previousEntry);
                } else {
                    ids.add(entry.id);
                }
            }
        } catch (ConnectionException e) {
            throw new IndexerException("Failed to get tag : " + indexRowKey, e);
        } finally {
            try {
                mb.execute();
            } catch (ConnectionException e) {
                // OK to ignore
            }
        }
        return ids;
    }

    @Override
    public void tagId(String id, Map<String, String> tags) throws IndexerException {
        MutationBatch mb = keyspace.prepareMutationBatch();

        ColumnListMutation<String> idRow = mb.withRow(dataCf, id);
        UUID uuid = TimeUUIDUtils.getUniqueTimeUUIDinMicros();
        for (Map.Entry<String, String> tag : tags.entrySet()) {
            String rowkey = tag.getKey() + "=" + tag.getValue();
            System.out.println("Rowkey: " + rowkey);
            mb.withRow(indexCf, tag.getKey() + "=" + tag.getValue()).putEmptyColumn(new IndexEntry(id, uuid));
            //            idRow.putColumn(tag.getKey(), tag.getValue());
        }

        try {
            mb.execute();
        } catch (ConnectionException e) {
            throw new IndexerException("Failed to store tags : " + tags + " for id " + id, e);
        }
    }

    @Override
    public void removeId(String id) throws IndexerException {
        // TODO Auto-generated method stub

    }

    @Override
    public void createStorage() throws IndexerException {
        try {
            keyspace.createColumnFamily(indexCf, ImmutableMap.<String, Object>builder()
                    .put("comparator_type", "CompositeType(UTF8Type, TimeUUIDType)").build());
        } catch (ConnectionException e) {
            e.printStackTrace();
        }

        try {
            keyspace.createColumnFamily(dataCf,
                    ImmutableMap.<String, Object>builder().put("default_validation_class", "LongType")
                            .put("key_validation_class", "UTF8Type").put("comparator_type", "UTF8Type").build());
        } catch (ConnectionException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Map<String, String> getTags(String id) throws IndexerException {
        try {
            ColumnList<String> fields = keyspace.prepareQuery(dataCf).getRow(id).execute().getResult();
            Map<String, String> mapped = Maps.newHashMap();
            for (Column<String> column : fields) {
                mapped.put(column.getName(), column.getStringValue());
            }
            return mapped;
        } catch (ConnectionException e) {
            throw new IndexerException("Failed to get tags for id " + id, e);
        }
    }
}