/* * Copyright 2004-2009 the original author or authors. * * 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 org.compass.needle.terracotta; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.lucene.store.Directory; import org.compass.core.CompassException; import org.compass.core.config.CompassConfigurable; import org.compass.core.config.CompassEnvironment; import org.compass.core.config.CompassSettings; import org.compass.core.config.ConfigurationException; import org.compass.core.engine.SearchEngine; import org.compass.core.engine.SearchEngineException; import org.compass.core.engine.event.SearchEngineEventManager; import org.compass.core.engine.event.SearchEngineLifecycleEventListener; import org.compass.core.lucene.LuceneEnvironment; import org.compass.core.lucene.engine.store.AbstractDirectoryStore; import org.compass.core.lucene.engine.store.CopyFromHolder; /** * A Compass direcoty store that will use the {@link org.compass.needle.terracotta.TerracottaDirectory} (or one * of its sub classes). * * @author kimchy */ public class TerracottaDirectoryStore extends AbstractDirectoryStore implements CompassConfigurable { public static final String PROTOCOL = "tc://"; /** * @see org.compass.needle.terracotta.TerracottaDirectory#DEFAULT_BUFFER_SIZE */ public static final String BUFFER_SIZE_PROP = "compass.engine.store.tc.bufferSize"; /** * @see org.compass.needle.terracotta.TerracottaDirectory#DEFAULT_FLUSH_RATE */ public static final String FLUSH_RATE_PROP = "compass.engine.store.tc.flushRate"; /** * @see org.compass.needle.terracotta.TerracottaDirectory#DEFAULT_CHM_INITIAL_CAPACITY */ public static final String CHM_INITIAL_CAPACITY_PROP = "compass.engine.store.tc.chm.initialCapacity"; /** * @see org.compass.needle.terracotta.TerracottaDirectory#DEFAULT_CHM_LOAD_FACTOR */ public static final String CHM_LOAD_FACTOR_PROP = "compass.engine.store.tc.chm.loadFactor"; /** * @see org.compass.needle.terracotta.TerracottaDirectory#DEFAULT_CHM_CONCURRENCY_LEVEL */ public static final String CHM_CONCURRENCY_LEVEL_PROP = "compass.engine.store.tc.chm.concurrencyLevel"; /** * Allows to control which type of terracotta store will be used. Options are: * <ul> * <li>managed: Uses {@link ManagedTerracottaDirectory}</li> * <li>chm: Uses {@link TerracottaDirectory}</li> * <li>csm: USes {@link CSMTerracottaDirectory}</li>. * </ul> */ public static final String TYPE = "compass.engine.store.tc.type"; /** * Should operations performed within a single "Compass transaction" be performed in a concurrent manner. * The concurrent operations are jobs (create/update/delete) and commit (per sub index). */ public static final String CONCURRENT = "compass.engine.store.tc.concurrent"; private final Map<String, Map<String, Map<String, TerracottaDirectory>>> dirs = new HashMap<String, Map<String, Map<String, TerracottaDirectory>>>(); private final ReadWriteLock managedRWL = new ReentrantReadWriteLock(); private int bufferSize; private int flushRate; private int chmInitialCapacity; private float chmLoadFactor; private int chmConcurrencyLevel; private String type; private boolean managed; private boolean concurrent; private transient String indexName; public void configure(CompassSettings settings) throws CompassException { indexName = settings.getSetting(CompassEnvironment.CONNECTION).substring(PROTOCOL.length()); bufferSize = (int) settings.getSettingAsBytes(BUFFER_SIZE_PROP, TerracottaDirectory.DEFAULT_BUFFER_SIZE); flushRate = settings.getSettingAsInt(FLUSH_RATE_PROP, TerracottaDirectory.DEFAULT_FLUSH_RATE); type = settings.getSetting(TYPE, "managed"); managed = type.equals("managed"); concurrent = settings.getSettingAsBoolean(CONCURRENT, false); if (managed) { // when working in managed mode, we can't have concurrent commits, since we run into deadlock // we get the read lock on one thread, and then try to get write lock on another without releasing the // read lock concurrent = false; } chmInitialCapacity = settings.getSettingAsInt(CHM_CONCURRENCY_LEVEL_PROP, TerracottaDirectory.DEFAULT_CHM_INITIAL_CAPACITY); chmLoadFactor = settings.getSettingAsFloat(CHM_LOAD_FACTOR_PROP, TerracottaDirectory.DEFAULT_CHM_LOAD_FACTOR); chmConcurrencyLevel = settings.getSettingAsInt(CHM_CONCURRENCY_LEVEL_PROP, TerracottaDirectory.DEFAULT_CHM_CONCURRENCY_LEVEL); if (log.isDebugEnabled()) { log.debug("Terracotta directory store configured with index [" + indexName + "], bufferSize [" + bufferSize + "], flushRate [" + flushRate + "], type [" + type + "], concurrent [" + concurrent + "]"); } } public Directory open(String subContext, String subIndex) throws SearchEngineException { synchronized (dirs) { Map<String, Map<String, TerracottaDirectory>> index = dirs.get(indexName); if (index == null) { index = new HashMap<String, Map<String, TerracottaDirectory>>(); dirs.put(indexName, index); } Map<String, TerracottaDirectory> subIndexDirs = index.get(subContext); if (subIndexDirs == null) { subIndexDirs = new HashMap<String, TerracottaDirectory>(); index.put(subContext, subIndexDirs); } TerracottaDirectory dir = subIndexDirs.get(subIndex); if (dir == null) { if (type.equals("managed")) { dir = new ManagedTerracottaDirectory(managedRWL, bufferSize, flushRate, chmInitialCapacity, chmLoadFactor, chmConcurrencyLevel); } else if (type.equals("csm")) { dir = new CSMTerracottaDirectory(bufferSize, flushRate); } else if (type.equals("chm")) { dir = new TerracottaDirectory(bufferSize, flushRate, chmInitialCapacity, chmLoadFactor, chmConcurrencyLevel); } else { throw new ConfigurationException("No terracotta directory type [" + type + "]"); } subIndexDirs.put(subIndex, dir); } return dir; } } @Override public void cleanIndex(Directory dir, String subContext, String subIndex) throws SearchEngineException { deleteIndex(dir, subContext, subIndex); } @Override public void deleteIndex(Directory dir, String subContext, String subIndex) throws SearchEngineException { synchronized (dirs) { Map<String, Map<String, TerracottaDirectory>> index = dirs.get(indexName); if (index == null) { return; } Map<String, TerracottaDirectory> subIndexDirs = index.get(subContext); if (subIndexDirs != null) { subIndexDirs.remove(subIndex); } } } @Override public String[] listSubIndexes(String subContext) throws SearchEngineException, UnsupportedOperationException { synchronized (dirs) { Map<String, Map<String, TerracottaDirectory>> index = dirs.get(indexName); if (index == null) { return null; } Map<String, TerracottaDirectory> subIndexDirs = index.get(subContext); if (subIndexDirs == null) { return null; } return subIndexDirs.keySet().toArray(new String[subIndexDirs.size()]); } } @Override public CopyFromHolder beforeCopyFrom(String subContext, String subIndex, Directory dir) throws SearchEngineException { try { String[] files = dir.list(); for (String file : files) { dir.deleteFile(file); } } catch (IOException e) { throw new SearchEngineException("Faield to delete ram directory before copy", e); } return new CopyFromHolder(); } @Override public String suggestedIndexDeletionPolicy() { return LuceneEnvironment.IndexDeletionPolicy.ExpirationTime.NAME; } @Override public boolean supportsConcurrentCommits() { return concurrent; } @Override public boolean supportsConcurrentOperations() { return concurrent; } @Override public boolean requiresAsyncTransactionalContext() { // in managed, we need to start a transaction so we can start the "global" real lock for less lock creation return managed; } @Override public void registerEventListeners(SearchEngine searchEngine, SearchEngineEventManager eventManager) { if (managed) { eventManager.registerLifecycleListener(new SearchEngineLifecycleEventListener() { public void beforeBeginTransaction() throws SearchEngineException { managedRWL.readLock().lock(); } public void afterBeginTransaction() throws SearchEngineException { } public void afterPrepare() throws SearchEngineException { } public void afterCommit(boolean onePhase) throws SearchEngineException { managedRWL.readLock().unlock(); } public void afterRollback() throws SearchEngineException { try { managedRWL.readLock().unlock(); } catch (Exception e) { // ignore } } public void close() throws SearchEngineException { } }); } } }