/* * Copyright 2014, Stratio. * * 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.stratio.cassandra.index; import com.google.common.base.Objects; import com.stratio.cassandra.index.service.RowService; import com.stratio.cassandra.util.Log; import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.db.ColumnFamily; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.db.composites.CellName; import org.apache.cassandra.db.index.PerRowSecondaryIndex; import org.apache.cassandra.db.index.SecondaryIndexManager; import org.apache.cassandra.db.index.SecondaryIndexSearcher; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.utils.concurrent.OpOrder; import java.nio.ByteBuffer; import java.util.Map; import java.util.Set; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * A {@link PerRowSecondaryIndex} that uses Apache Lucene as backend. It allows, among others, multi-column and * full-text search. * * @author Andres de la Pena <adelapena@stratio.com> */ public class RowIndex extends PerRowSecondaryIndex { private SecondaryIndexManager secondaryIndexManager; private ColumnDefinition columnDefinition; private String keyspaceName; private String tableName; private String indexName; private String columnName; private String logName; private RowService rowService; // Concurrency lock private ReadWriteLock lock = new ReentrantReadWriteLock(); @Override public String getIndexName() { return indexName; } /** * Returns the indexed keyspace name. * * @return The indexed keyspace name. */ public String getKeyspaceName() { return keyspaceName; } /** * Returns the indexed table name. * * @return The indexed table name. */ public String getTableName() { return tableName; } /** * Returns the indexed column name. * * @return The indexed column name. */ public String getColumnName() { return columnName; } /** * Returns the indexed column definition. * * @return The indexed column definition. */ public ColumnDefinition getColumnDefinition() { return columnDefinition; } @Override public void init() { Log.info("Initializing index %s", logName); lock.writeLock().lock(); try { setup(); Log.info("Initialized index %s", logName); } catch (Exception e) { Log.error(e, "Error while initializing index %s", logName); throw new RuntimeException(e); } finally { lock.writeLock().unlock(); } } @Override public void validate(CFMetaData metadata, Map<String, String> indexOptions) { try { new RowIndexConfig(metadata, indexOptions); } catch (RuntimeException e) { Log.error(e, "Error validating index config"); throw e; } } private void setup() { // Load column family info secondaryIndexManager = baseCfs.indexManager; columnDefinition = columnDefs.iterator().next(); indexName = columnDefinition.getIndexName(); keyspaceName = baseCfs.metadata.ksName; tableName = baseCfs.metadata.cfName; columnName = columnDefinition.name.toString(); logName = String.format("%s.%s.%s", keyspaceName, tableName, indexName); // Build row mapper rowService = RowService.build(baseCfs, columnDefinition); } /** * Index the given row. * * @param key The partition key. * @param columnFamily The column family data to be indexed */ @Override public void index(ByteBuffer key, ColumnFamily columnFamily) { Log.debug("Indexing row %s in index %s ", key, logName); lock.readLock().lock(); try { if (rowService != null) { long timestamp = System.currentTimeMillis(); rowService.index(key, columnFamily, timestamp); } } catch (RuntimeException e) { Log.error("Error while indexing row %s", key); throw e; } finally { lock.readLock().unlock(); } } /** * cleans up deleted columns from cassandra cleanup compaction * * @param key The partition key of the physical row to be deleted. */ @Override public void delete(DecoratedKey key, OpOrder.Group opGroup) { Log.debug("Removing row %s from index %s", key, logName); lock.writeLock().lock(); try { rowService.delete(key); rowService = null; } catch (RuntimeException e) { Log.error(e, "Error deleting row %s", key); throw e; } finally { lock.writeLock().unlock(); } } @Override public boolean indexes(CellName cellName) { return true; } @Override public void validateOptions() throws ConfigurationException { Log.debug("Validating"); try { ColumnDefinition columnDefinition = columnDefs.iterator().next(); if (baseCfs != null) { new RowIndexConfig(baseCfs.metadata, columnDefinition.getIndexOptions()); Log.debug("Index options are valid"); } else { Log.debug("Validation skipped"); } } catch (Exception e) { String message = "Error while validating index options: " + e.getMessage(); Log.error(e, message); throw new ConfigurationException(message, e); } } @Override public long estimateResultRows() { try { return rowService.getIndexSize(); } catch (Exception e) { Log.error(e, "Estimating row results for index %s", logName); throw new RuntimeException(e); } } @Override public ColumnFamilyStore getIndexCfs() { return null; } @Override public void removeIndex(ByteBuffer columnName) { Log.info("Removing index %s", logName); lock.writeLock().lock(); try { if (rowService != null) { rowService.delete(); rowService = null; } Log.info("Removed index %s", logName); } catch (Exception e) { Log.error(e, "Removing index %s", logName); throw new RuntimeException(e); } finally { lock.writeLock().unlock(); } } @Override public void invalidate() { Log.info("Invalidating index %s", logName); lock.writeLock().lock(); try { if (rowService != null) { rowService.delete(); rowService = null; } Log.info("Invalidated index %s", logName); } catch (Exception e) { Log.error(e, "Invalidating index %s", logName); throw new RuntimeException(e); } finally { lock.writeLock().unlock(); } } @Override public void truncateBlocking(long truncatedAt) { Log.info("Truncating index %s", logName); lock.writeLock().lock(); try { if (rowService != null) { rowService.truncate(); } Log.info("Truncated index %s", logName); } catch (Exception e) { Log.error(e, "Truncating index %s", logName); throw new RuntimeException(e); } finally { lock.writeLock().unlock(); } } @Override public void reload() { Log.info("Reloading index %s", logName); } @Override public void forceBlockingFlush() { Log.info("Flushing index %s", logName); lock.writeLock().lock(); try { rowService.commit(); Log.info("Flushed index %s", logName); } catch (RuntimeException e) { Log.error(e, "Flushing index %s", logName); throw e; } finally { lock.writeLock().unlock(); } } @Override protected SecondaryIndexSearcher createSecondaryIndexSearcher(Set<ByteBuffer> columns) { Log.debug("Creating searcher for index %s", logName); return new RowIndexSearcher(secondaryIndexManager, this, columns, rowService); } @Override public void optimize() { Log.info("Compacting index %s", logName); try { rowService.optimize(); } catch (Exception e) { Log.error(e, "Error while compacting index %s", logName); throw new RuntimeException(e); } } /** {@inheritDoc} */ @Override public String toString() { return Objects.toStringHelper(this) .add("indexName", indexName) .add("keyspaceName", keyspaceName) .add("tableName", tableName) .add("columnName", columnName) .toString(); } }