All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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