/*******************************************************************************
* Mission Control Technologies, Copyright (c) 2009-2012, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* The MCT platform is 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.
*
* MCT includes source code licensed under additional open source licenses. See
* the MCT Open Source Licenses file included with this distribution or the About
* MCT Licenses dialog available at runtime from the MCT Help menu for additional
* information.
*******************************************************************************/
package gov.nasa.arc.mct.buffer.config;
import gov.nasa.arc.mct.api.feed.DataProvider.LOS;
import gov.nasa.arc.mct.util.FilepathReplacer;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Durability;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.TransactionConfig;
import com.sleepycat.persist.EntityStore;
import com.sleepycat.persist.StoreConfig;
public class FastDiskBufferEnv implements DataBufferEnv, Cloneable {
private static final Logger LOGGER = LoggerFactory.getLogger(FastDiskBufferEnv.class);
private static final String META_DATABASE_PATH = "metaBuffer";
private static final String META_DATABASE_NAME = "meta";
private static enum STATE {
unInitialized, initializing, initialized;
}
private Environment dbufferEnv;
private STATE state = STATE.unInitialized;
private final Properties prop;
private volatile long bufferTimeMills;
private long evictorRecurrMills;
private File envHome;
private final int concurrency;
private final int bufferWriteThreadPoolSize;
private final int numOfBufferPartitions;
private final int currentBufferPartition;
private final long partitionOverlapMillis;
private final long metaRefreshMillis;
private TransactionConfig txnConfig;
private CursorConfig cursorConfig;
private List<EntityStore> openStores = new LinkedList<EntityStore>();
private DiskQuotaHelper diskQuotaHelper;
private static Properties loadDefaultPropertyFile() {
Properties prop = new Properties();
InputStream is = null;
try {
is = ClassLoader.getSystemResourceAsStream("properties/feed.properties");
prop.load(is);
} catch (Exception e) {
LOGGER.error("Cannot initialized DataBufferEnv properties", e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException ioe) {
// ignore exception
}
}
}
return prop;
}
public FastDiskBufferEnv(Properties prop) {
if (prop == null) {
prop = loadDefaultPropertyFile();
}
this.prop = prop;
this.currentBufferPartition = 0;
File bufferHome = new File(FilepathReplacer.substitute(getPropertyWithPrecedence(prop, "buffer.disk.loc")));
if (!bufferHome.exists()) {
bufferHome.mkdirs();
}
envHome = new File(bufferHome, META_DATABASE_PATH);
if (!envHome.exists()) {
envHome.mkdirs();
}
concurrency = Integer.parseInt(prop.getProperty("buffer.concurrency"));
evictorRecurrMills = Long.parseLong(prop.getProperty("buffer.evictor.recurrMills"));
bufferWriteThreadPoolSize = Integer.parseInt(prop.getProperty("buffer.write.threadPool.size"));
numOfBufferPartitions = Integer.parseInt(prop.getProperty("buffer.partitions"));
bufferTimeMills = Long.parseLong(prop.getProperty("buffer.time.millis"));
metaRefreshMillis = Long.parseLong(prop.getProperty("meta.buffer.refresh.millis"));
if (bufferTimeMills > numOfBufferPartitions) {
bufferTimeMills = bufferTimeMills / numOfBufferPartitions;
}
partitionOverlapMillis = Long.parseLong(prop.getProperty("buffer.partition.overlap.millis"));
diskQuotaHelper = new DiskQuotaHelper(prop, bufferHome);
this.state = STATE.initializing;
setup(false);
}
public FastDiskBufferEnv(Properties prop, int currentBufferPartition) {
if (prop == null) {
prop = loadDefaultPropertyFile();
}
this.prop = prop;
this.currentBufferPartition = currentBufferPartition;
File bufferHome = new File(FilepathReplacer.substitute(getPropertyWithPrecedence(prop, "buffer.disk.loc")));
if (!bufferHome.exists()) {
bufferHome.mkdirs();
}
envHome = new File(bufferHome, String.valueOf(currentBufferPartition));
if (!envHome.exists()) {
envHome.mkdirs();
}
concurrency = Integer.parseInt(prop.getProperty("buffer.concurrency"));
evictorRecurrMills = Long.parseLong(prop.getProperty("buffer.evictor.recurrMills"));
bufferWriteThreadPoolSize = Integer.parseInt(prop.getProperty("buffer.write.threadPool.size"));
numOfBufferPartitions = Integer.parseInt(prop.getProperty("buffer.partitions"));
bufferTimeMills = Long.parseLong(prop.getProperty("buffer.time.millis"));
bufferTimeMills = bufferTimeMills / numOfBufferPartitions;
partitionOverlapMillis = Long.parseLong(prop.getProperty("buffer.partition.overlap.millis"));
metaRefreshMillis = Long.parseLong(prop.getProperty("meta.buffer.refresh.millis"));
diskQuotaHelper = new DiskQuotaHelper(prop, bufferHome);
this.state = STATE.initializing;
setup(false);
}
private void setup(boolean readOnly) {
assertState(STATE.initializing);
// Instantiate an environment configuration object
EnvironmentConfig envConfig = new EnvironmentConfig();
envConfig.setSharedCache(true);
String cachePercent = prop.getProperty("bdb.cache.percent");
if (cachePercent != null) {
envConfig.setCachePercent(Integer.parseInt(cachePercent));
}
// Configure the environment for the read-only state as identified by
// the readOnly parameter on this method call.
envConfig.setReadOnly(readOnly);
// If the environment is opened for write, then we want to be able to
// create the environment if it does not exist.
envConfig.setAllowCreate(!readOnly);
envConfig.setConfigParam(EnvironmentConfig.CHECKPOINTER_BYTES_INTERVAL, "40000000");
envConfig.setTransactional(false);
envConfig.setDurability(Durability.COMMIT_NO_SYNC);
envConfig.setConfigParam(EnvironmentConfig.ENV_RUN_CLEANER, Boolean.FALSE.toString());
envConfig.setConfigParam(EnvironmentConfig.ENV_IS_LOCKING, Boolean.FALSE.toString());
setupConfig();
// Instantiate the Environment. This opens it and also possibly
// creates it.
try {
dbufferEnv = new Environment(envHome, envConfig);
state = STATE.initialized;
} catch (DatabaseException de) {
LOGGER.error("DatabaseException in setup", de);
state = STATE.unInitialized;
}
}
private void setupConfig() {
txnConfig = new TransactionConfig();
txnConfig.setReadUncommitted(true);
txnConfig.setDurability(Durability.COMMIT_NO_SYNC);
cursorConfig = new CursorConfig();
cursorConfig.setReadUncommitted(true);
}
public boolean isDiskBufferFull() {
return diskQuotaHelper.isDiskBufferFull();
}
public String getErrorMsg() {
return diskQuotaHelper.getErrorMsg();
}
private String getPropertyWithPrecedence(Properties localProps, String key) {
String systemProp = System.getProperty(key);
return systemProp != null ? systemProp.trim() : localProps.getProperty(key, "unset").trim();
}
public EntityStore openMetaDiskStore() throws DatabaseException {
assertState(STATE.initialized);
StoreConfig storeConfig = new StoreConfig();
storeConfig.setAllowCreate(true);
storeConfig.setDeferredWrite(true);
storeConfig.setTransactional(false);
ClassLoader originalClassloader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
return new EntityStore(dbufferEnv, META_DATABASE_NAME, storeConfig);
} finally {
Thread.currentThread().setContextClassLoader(originalClassloader);
}
}
public EntityStore openDiskStore(String dbName) throws DatabaseException {
assertState(STATE.initialized);
StoreConfig storeConfig = new StoreConfig();
storeConfig.setAllowCreate(true);
storeConfig.setDeferredWrite(true);
storeConfig.setTransactional(false);
EntityStore store = new EntityStore(dbufferEnv, dbName, storeConfig);
openStores.add(store);
return store;
}
public void removeEnvironment() throws DatabaseException {
dbufferEnv.cleanLog();
dbufferEnv.close();
deleteDatabaseFile(currentBufferPartition);
this.state = STATE.unInitialized;
}
public void closeEnvironment() throws DatabaseException {
dbufferEnv.cleanLog();
dbufferEnv.close();
this.state = STATE.unInitialized;
}
public void removeAndCloseAllDiskStores() throws DatabaseException {
for (EntityStore store: openStores) {
store.close();
}
openStores.clear();
removeEnvironment();
}
public void closeDatabase(EntityStore store) throws DatabaseException {
if (store == null) { return; }
store.close();
openStores.remove(store);
}
public void closeAndRestartEnvironment() throws DatabaseException {
boolean isReadOnly = dbufferEnv.getConfig().getReadOnly();
removeAndCloseAllDiskStores();
restartEnvironment(isReadOnly);
}
public void restartEnvironment(boolean isReadOnly) throws DatabaseException {
state = STATE.initializing;
setup(isReadOnly);
}
public int getConcurrencyDegree() {
return concurrency;
}
public int getBufferWriteThreadPoolSize() {
return bufferWriteThreadPoolSize;
}
public long getBufferTime() {
return bufferTimeMills;
}
public long getEvictorRecurr() {
return evictorRecurrMills;
}
public int getNumOfBufferPartitions() {
return numOfBufferPartitions;
}
public void setBufferTime(long bufferTimeMills) {
this.bufferTimeMills = bufferTimeMills;
}
public long getBufferPartitionOverlap() {
return partitionOverlapMillis;
}
public int getCurrentBufferPartition() {
return currentBufferPartition;
}
public DataBufferEnv advanceBufferPartition() {
int nextBufferPartition = nextBufferPartition();
deleteDatabaseFile(nextBufferPartition);
FastDiskBufferEnv newBufferEnv = new FastDiskBufferEnv(prop, (this.currentBufferPartition + 1) % numOfBufferPartitions);
return newBufferEnv;
}
private void deleteDatabaseFile(int partitionNo) {
File parentDir = this.envHome.getParentFile();
File nextBufferPartitionDir = new File(parentDir, String.valueOf(partitionNo));
if (nextBufferPartitionDir.exists()) {
if (nextBufferPartitionDir.isDirectory()) {
File[] files = nextBufferPartitionDir.listFiles();
for (File f: files) {
f.delete();
}
}
nextBufferPartitionDir.delete();
}
}
public int nextBufferPartition() {
return (this.currentBufferPartition+1)%numOfBufferPartitions;
}
public int previousBufferPartition(int currentPartition) {
int i = currentPartition;
if (i == 0) {
i = this.numOfBufferPartitions-1;
} else {
i--;
}
return i;
}
public long getMetaRefresh() {
return this.metaRefreshMillis;
}
@Override
public Object clone() {
return new FastDiskBufferEnv(prop, 0);
}
@Override
public Object cloneMetaBuffer() {
return new FastDiskBufferEnv(prop);
}
private void assertState(STATE expectedState) {
assert this.state == expectedState;
}
@Override
public Properties getConfigProperties() {
return this.prop;
}
public void flush() {
this.dbufferEnv.sync();
}
@Override
public LOS getLOS() {
return LOS.medium;
}
}