
com.cloudhopper.datastore.BaseDataStore Maven / Gradle / Ivy
The newest version!
package com.cloudhopper.datastore;
/*
* #%L
* ch-datastore
* %%
* Copyright (C) 2012 Cloudhopper by Twitter
* %%
* 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.
* #L%
*/
import java.io.File;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base implementation of a DataStore.
*
* @author Joseph Lauer
*/
public abstract class BaseDataStore implements DataStore {
private final static Logger logger = LoggerFactory.getLogger(BaseDataStore.class);
/** all possible states of a DataStore */
private final static int STATE_CLOSED = 0;
private final static int STATE_CLOSING = 1;
private final static int STATE_OPENING = 2;
private final static int STATE_OPEN = 3;
private final static int STATE_DELETING = 4;
// descriptive type of DataStore such as "TC-BDB" or "JDBM"
private final String type;
// atomic check if DataStore is opening, open, or closing
private final AtomicInteger state;
// directory for any data store specific files
private File directory;
// name of DataStore (for filenames,
private String name;
// should we creating any missing directories? true by default
private boolean createMissingDirectories;
// should we register a shutdown hook while opening; protected so inheritable
protected boolean shutdownHookEnabled;
// the shutdown hook thread
private DataStoreShutdownHook shutdownHook;
public BaseDataStore(String type) {
this.type = type;
// default settings
this.directory = new File("data");
this.name = "default";
this.state = new AtomicInteger(STATE_CLOSED);
this.createMissingDirectories = true;
this.shutdownHookEnabled = true;
}
public void setDirectory(File dir) {
this.directory = dir;
}
public File getDirectory() {
return this.directory;
}
public void setName(String value) {
this.name = value;
}
public String getName() {
return this.name;
}
public void setCreateMissingDirectories(boolean value) {
this.createMissingDirectories = value;
}
public boolean getCreateMissingDirectories() {
return this.createMissingDirectories;
}
public boolean isShutdownHookEnabled() {
return shutdownHookEnabled;
}
public void setShutdownHookEnabled(boolean value) {
this.shutdownHookEnabled = value;
}
/**
* Helper method to verify the directory for the DataStore exists. If
* "createMissingDirectories" is enabled (which it is by default) and the
* directory does not exist, this method will attempt to create it. Finally,
* it will verify the directory is writable by the current user.
* @throws DataStoreFatalException Thrown if there is an error while verifying
* the DataStore directory.
*/
private void verifyDirectory() throws DataStoreFatalException {
// does the "file" exist?
if (!getDirectory().exists()) {
// directory does not exist, should we try to create it?
if (this.createMissingDirectories) {
if (!getDirectory().mkdirs()) {
throw new DataStoreFatalException("DataStore directory '" + getDirectory() + "' does not exist and unable to create it");
}
} else {
throw new DataStoreFatalException("DataStore directory '" + getDirectory() + "' does not exist");
}
}
// "file" exists, let's make sure its a directory
if (!getDirectory().isDirectory()) {
throw new DataStoreFatalException("The filesystem contains '" + getDirectory() + "', but its not a directory");
}
// make sure we can write to it
if (!getDirectory().canWrite()) {
throw new DataStoreFatalException("DataStore directory '" + getDirectory() + "' is not writable by current user");
}
}
public boolean isOpen() {
return (this.state.get() == STATE_OPEN);
}
public boolean isOpening() {
return (this.state.get() == STATE_OPENING);
}
public boolean isClosed() {
return (this.state.get() == STATE_CLOSED);
}
public boolean isClosing() {
return (this.state.get() == STATE_CLOSING);
}
static private String getStateName(int state) {
switch (state) {
case STATE_CLOSED:
return "CLOSED";
case STATE_CLOSING:
return "CLOSING";
case STATE_OPENING:
return "OPENING";
case STATE_OPEN:
return "OPEN";
case STATE_DELETING:
return "DELETING";
default:
return "UKNOWN";
}
}
public void open() throws DataStoreFatalException {
// NOTE: opportunistic locking
// try to set the state to "opening" only if the current state is "closed"
if (!state.compareAndSet(STATE_CLOSED, STATE_OPENING)) {
int currentState = state.get();
throw new DataStoreFatalException("Unable to open " + toString() + " since its not currently closed [current state is " + getStateName(currentState) + "]");
}
boolean opened = false;
try {
// verify the directory exists
verifyDirectory();
logger.info("Opening " + toString());
// do the open
doOpen();
if (!this.shutdownHookEnabled) {
logger.warn("ShutdownHook not enabled for " + toString());
} else {
// add a shutdown hook to properly close the DataStore
this.shutdownHook = new DataStoreShutdownHook(toString());
this.shutdownHook.setName("DataStoreShutdownHook-" +getName());
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
logger.info("ShutdownHook added for " + toString());
}
logger.info("Successfully opened " + toString());
// if we got here, then the open succeeded
opened = true;
} finally {
// always make sure to clean up the state
if (opened) {
state.set(STATE_OPEN);
} else {
state.set(STATE_CLOSED);
}
}
}
public void close() throws DataStoreFatalException {
// NOTE: opportunistic locking
// try to set the state to "closing" only if the current state is "open"
if (!state.compareAndSet(STATE_OPEN, STATE_CLOSING)) {
int currentState = state.get();
// is it already closed or closing?
if (currentState == STATE_CLOSED || currentState == STATE_CLOSING) {
logger.debug("Ignoring close request for datastore since its already closed or closing");
return;
} else {
throw new DataStoreFatalException("Unable to close " + toString() + " since its not currently open [current state is " + getStateName(currentState) + "]");
}
}
// do we need to cancel the shutdown hook?
if (this.shutdownHook != null) {
try {
Runtime.getRuntime().removeShutdownHook(shutdownHook);
} catch (Throwable t) {
// ignore this since the JVM may be shutting down...
}
this.shutdownHook = null;
}
try {
logger.info("Closing " + toString());
// do the close
doClose();
logger.info("Successfully closed " + toString());
} finally {
// always make sure to clean up the state
// a close always should succeed
state.set(STATE_CLOSED);
}
}
public void delete() throws DataStoreFatalException {
// NOTE: opportunistic locking
// try to set the state to "deleting" only if the current state is "closed"
if (!state.compareAndSet(STATE_CLOSED, STATE_DELETING)) {
int currentState = state.get();
throw new DataStoreFatalException("Unable to delete " + toString() + " since its not currently closed [current state is " + getStateName(currentState) + "]");
}
try {
logger.info("Deleting " + toString());
// do the close
doDelete();
logger.info("Successfully deleted " + toString());
} finally {
// always make sure to clean up the state
// a delete should always succeed
state.set(STATE_CLOSED);
}
}
protected void verifyIsOpen() throws DataStoreFatalException {
if (!isOpen()) {
throw new DataStoreFatalException("The " + toString() + " is not open, unable to complete request");
}
}
public void setRecord(byte[] key, byte[] value) throws DataStoreFatalException {
// verify datastore is open
verifyIsOpen();
// do it
doSetRecord(key, value);
}
public byte[] getRecord(byte[] key) throws DataStoreFatalException, RecordNotFoundException {
// verify datastore is open
verifyIsOpen();
// do it
return doGetRecord(key);
}
public void deleteRecord(byte[] key) throws DataStoreFatalException, RecordNotFoundException {
// verify datastore is open
verifyIsOpen();
// do it
doDeleteRecord(key);
}
public boolean hasAscendingIteratorSupport() {
// check if the implementing class implements the AscendingIteratorSupport class
if (this instanceof AscendingIteratorSupport) {
return true;
} else {
return false;
}
}
public DataStoreIterator getAscendingIterator() throws DataStoreFatalException {
// is an ascending iterator supported?
if (!hasAscendingIteratorSupport()) {
throw new DataStoreFatalException("An ascending DataStoreIterator is not supported on class " + this.getClass() + " for " + toString());
}
// verify datastore is open
verifyIsOpen();
// cast current instance to access method
AscendingIteratorSupport iteratorSupport = (AscendingIteratorSupport)this;
// delegate it
return iteratorSupport.doGetAscendingIterator();
}
abstract protected void doOpen() throws DataStoreFatalException;
abstract protected void doClose() throws DataStoreFatalException;
abstract protected void doDelete() throws DataStoreFatalException;
abstract protected void doSetRecord(byte[] key, byte[] value) throws DataStoreFatalException;
abstract protected byte[] doGetRecord(byte[] key) throws DataStoreFatalException, RecordNotFoundException;
abstract protected void doDeleteRecord(byte[] key) throws DataStoreFatalException, RecordNotFoundException;
@Override
public String toString() {
return new StringBuilder(50)
.append(this.getClass().getSimpleName())
.append(" {name=")
.append(getName())
.append(", type=")
.append(this.type)
.append("}")
.toString();
}
public String toInfoString() {
return new StringBuilder(50)
.append(this.getClass().getSimpleName())
.append(" {name=")
.append(getName())
.append(", type=")
.append(this.type)
.append(", state=")
.append(getStateName(this.state.get()))
.append("}")
.toString();
}
private class DataStoreShutdownHook extends Thread {
private String description;
public DataStoreShutdownHook(String description) {
this.description = description;
}
@Override
public void run() {
logger.info("Ensuring " + description + " is properly closed before shutdown");
try {
close();
} catch (Exception e) {
logger.error("Unable to properly close " + toString() + " during shutdown", e);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy