/* * Copyright 2014, Tuplejump 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.tuplejump.stargate; import com.tuplejump.stargate.cassandra.CassandraUtils; import com.tuplejump.stargate.cassandra.RowIndexSupport; import com.tuplejump.stargate.cassandra.SearchSupport; import com.tuplejump.stargate.cassandra.TableMapper; import com.tuplejump.stargate.lucene.*; 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.SecondaryIndexSearcher; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.utils.concurrent.OpOrder; import org.apache.commons.collections.map.LRUMap; import org.apache.lucene.index.Term; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * User: satya * A per row lucene index. * This index requires Options to be passed as a json using sg_options as key in the CQL Index options */ public class RowIndex extends PerRowSecondaryIndex { protected static final Logger logger = LoggerFactory.getLogger(RowIndex.class); protected ColumnDefinition columnDefinition; protected String keyspace; protected String indexName; protected String primaryColumnName; protected String tableName; protected Options options; protected RowIndexSupport rowIndexSupport; protected TableMapper tableMapper; protected CFMetaData cfMetaData; private ReadWriteLock indexLock = new ReentrantReadWriteLock(); private final Lock readLock = indexLock.readLock(); private final Lock writeLock = indexLock.writeLock(); IndexContainer indexContainer; boolean nearRealTime = false; protected volatile long latest; public Map<String, IndexEntryCollector> collectorMap = Collections.synchronizedMap(new LRUMap(10)); public TableMapper getTableMapper() { return tableMapper; } @Override public void index(ByteBuffer rowKey, ColumnFamily cf) { latest = Stargate.getInstance().publish(rowKey, cf); } @Override public void delete(DecoratedKey key, OpOrder.Group opGroup) { deleteByKey(key); } public void deleteByKey(DecoratedKey key) { readLock.lock(); try { AbstractType<?> rkValValidator = baseCfs.metadata.getKeyValidator(); Term term = LuceneUtils.rowkeyTerm(rkValValidator.getString(key.getKey())); indexContainer.indexer(key).delete(term); } finally { readLock.unlock(); } } public void delete(DecoratedKey decoratedKey, String pkString, Long ts) { readLock.lock(); try { indexContainer.indexer(decoratedKey).delete(LuceneUtils.primaryKeyTerm(pkString), LuceneUtils.tsTerm(ts)); } finally { readLock.unlock(); } } public <T> T search(SearcherCallback<T> searcherCallback) { return indexContainer.search(searcherCallback); } @Override public SecondaryIndexSearcher createSecondaryIndexSearcher(Set<ByteBuffer> columns) { readLock.lock(); try { waitForIndexBuilt(); return new SearchSupport(baseCfs.indexManager, this, columns, this.options); } finally { readLock.unlock(); } } private void waitForIndexBuilt() { while (true) { //spin busy //don't give the searcher out till this happens if (isIndexBuilt(columnDefinition.name.bytes)) break; } if (!nearRealTime) Stargate.getInstance().catchUp(latest); } @Override public void init() { writeLock.lock(); final Boolean isInfoLoggingEnabled = logger.isInfoEnabled(); try { assert baseCfs != null; assert columnDefs != null; assert columnDefs.size() > 0; columnDefinition = columnDefs.iterator().next(); //null comparator since this is a custom index. keyspace = baseCfs.metadata.ksName; indexName = columnDefinition.getIndexName(); tableName = baseCfs.name; cfMetaData = baseCfs.metadata; primaryColumnName = columnDefinition.name.toString().toLowerCase(); String optionsJson = columnDefinition.getIndexOptions().get(Constants.INDEX_OPTIONS_JSON); this.options = CassandraUtils.getOptions(primaryColumnName, baseCfs, optionsJson); this.nearRealTime = options.primary.isNearRealTime(); if (isInfoLoggingEnabled) { logger.info("Creating new RowIndex for {}", indexName); } // indexContainer = new PerVNodeIndexContainer(options.analyzer, keyspace, tableName, indexName); indexContainer = new MonolithIndexContainer(options.analyzer, keyspace, tableName, indexName); this.tableMapper = new TableMapper(baseCfs, options.primary.isMetaColumn(), columnDefinition); rowIndexSupport = new RowIndexSupport(keyspace, indexContainer, options, tableMapper); Stargate.getInstance().register(rowIndexSupport); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { if (isInfoLoggingEnabled) { logger.info("Closing RowIndex for {}", indexName); } if (indexContainer != null) indexContainer.close(); } }); } finally { writeLock.unlock(); } } @Override public void validateOptions() throws ConfigurationException { assert columnDefs != null && columnDefs.size() == 1; } @Override public String getIndexName() { assert indexName != null; return indexName; } @Override public boolean indexes(CellName name) { String toCheck = name.cql3ColumnName(cfMetaData).toString().trim(); for (String columnName : this.options.fields.keySet()) { boolean areEqual = toCheck.equalsIgnoreCase(columnName.trim()); if (logger.isDebugEnabled()) logger.debug(String.format("Comparing name for index - This column name [%s] - Passed column name [%s] - Equal [%s]", columnName, toCheck, areEqual)); if (areEqual) return true; } return false; } @Override public long estimateResultRows() { return indexContainer.rowCount(); } @Override public void forceBlockingFlush() { readLock.lock(); try { if (isIndexBuilt(columnDefinition.name.bytes)) { //flushes writes to the disk //also refreshes readers indexContainer.commit(); } } finally { readLock.unlock(); } } /** * This is not backed by a CF. Instead it is backed by a lucene index. * * @return null */ @Override public ColumnFamilyStore getIndexCfs() { return null; } @Override public void removeIndex(ByteBuffer byteBuffer) { if (logger.isInfoEnabled()) { logger.info("Got call to REMOVE index {}.", indexName); } invalidate(); } @Override public void reload() { if (logger.isInfoEnabled()) { logger.info("Got call to RELOAD index {}.", indexName); } if (indexContainer == null && columnDefinition.getIndexOptions() != null && !columnDefinition.getIndexOptions().isEmpty()) { init(); } } @Override public void invalidate() { writeLock.lock(); try { if (logger.isInfoEnabled()) { logger.info("Removing All Indexers for {}", indexName); } indexContainer.remove(); indexContainer = null; setIndexRemoved(); } finally { writeLock.unlock(); } } @Override public void truncateBlocking(long l) { readLock.lock(); try { indexContainer.truncate(l); } finally { readLock.unlock(); } } @Override public String toString() { return "RowIndex [index=" + indexName + ", keyspace=" + keyspace + ", table=" + tableName + ", column=" + primaryColumnName + "]"; } }