Package voldemort.store.configuration

Source Code of voldemort.store.configuration.FileBackedCachingStorageEngine$FileBackedStorageIterator

/*
* Copyright 2008-2012 LinkedIn, 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 voldemort.store.configuration;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;

import voldemort.VoldemortException;
import voldemort.annotations.concurrency.NotThreadsafe;
import voldemort.store.AbstractStorageEngine;
import voldemort.store.StoreCapabilityType;
import voldemort.store.StoreUtils;
import voldemort.utils.ByteArray;
import voldemort.utils.ClosableIterator;
import voldemort.utils.Pair;
import voldemort.versioning.ObsoleteVersionException;
import voldemort.versioning.Occurred;
import voldemort.versioning.VectorClock;
import voldemort.versioning.Version;
import voldemort.versioning.Versioned;

/**
* A Storage Engine used to persist the keys and values in a human readable
* format on disk. The data is primarily served off of the cache. After each
* put, the entire cache state is flushed to the backing file. The data is UTF-8
* serialized when writing to the file in order to make it human readable.
*
* The primary purpose of this storage engine is for maintaining the cluster
* metadata which is characterized by low QPS and not latency sensitive.
*
*
*/
@NotThreadsafe
public class FileBackedCachingStorageEngine extends
        AbstractStorageEngine<ByteArray, byte[], byte[]> {

    private final static Logger logger = Logger.getLogger(FileBackedCachingStorageEngine.class);
    private static final CharSequence NEW_PROPERTY_SEPARATOR = "[name=";
    private static final String NEW_LINE = System.getProperty("line.separator");

    private final String inputPath;
    private final String inputDirectory;
    private ConcurrentHashMap<String, String> metadataMap;
    private VectorClock cachedVersion = null;

    public FileBackedCachingStorageEngine(String name, String inputDirectory) {
        super(name);
        this.inputDirectory = inputDirectory;
        File directory = new File(this.inputDirectory);
        if(!directory.exists() && directory.canRead()) {
            throw new IllegalArgumentException("Directory " + directory.getAbsolutePath()
                                               + " does not exist or can not be read.");
        }

        this.inputPath = inputDirectory + System.getProperty("file.separator") + name;
        this.metadataMap = new ConcurrentHashMap<String, String>();
        this.loadData();
        if(logger.isDebugEnabled()) {
            logger.debug("Created a new File backed caching engine. File location = " + inputPath);
        }
    }

    private File getVersionFile() {
        return new File(this.inputDirectory, getName() + ".version");
    }

    // Read the Vector clock stored in '${name}.version' file
    private VectorClock readVersion() {
        try {

            if(this.cachedVersion == null) {
                File versionFile = getVersionFile();
                if(versionFile.exists()) {
                    // read the version file and return version.
                    String hexCode = FileUtils.readFileToString(versionFile, "UTF-8");
                    this.cachedVersion = new VectorClock(Hex.decodeHex(hexCode.toCharArray()));
                }
            }
            return this.cachedVersion;
        } catch(Exception e) {
            throw new VoldemortException("Failed to read Version for file :" + getName(), e);
        }
    }

    // Write a new Vector clock stored in '${name}.version' file
    private void writeVersion(VectorClock newClock) {
        File versionFile = getVersionFile();
        try {
            if(!versionFile.exists() || versionFile.delete()) {
                String hexCode = new String(Hex.encodeHex(newClock.toBytes()));
                FileUtils.writeStringToFile(versionFile, hexCode, "UTF-8");
                this.cachedVersion = newClock;
            }
        } catch(Exception e) {
            throw new VoldemortException("Failed to write Version for the current file :"
                                         + getName(), e);
        }
    }

    private void loadData() {

        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader(new File(this.inputPath)));
            String line = reader.readLine();

            while(line != null) {
                if(line.startsWith(NEW_PROPERTY_SEPARATOR.toString())) {
                    String key = null;
                    StringBuilder value = new StringBuilder();
                    String parts[] = line.split("=");

                    // Found a new property block.
                    // First read the key
                    if(parts.length == 2) {
                        key = parts[1].substring(0, parts[1].length() - 1);

                        // Now read the value block !
                        while((line = reader.readLine()) != null && line.length() != 0
                              && !line.startsWith(NEW_PROPERTY_SEPARATOR.toString())) {
                            if(value.length() == 0) {
                                value.append(line);
                            } else {
                                value.append(NEW_LINE + line);
                            }
                        }

                        // Now add the key and value to the hashmap
                        this.metadataMap.put(key, value.toString());
                    }
                } else {
                    line = reader.readLine();
                }
            }
        } catch(FileNotFoundException e) {
            logger.debug("File used for persistence does not exist !!");
        } catch(IOException e) {
            logger.info("Error in flushing data to file for store " + getName(), e);
        } finally {
            if(reader != null) {
                try {
                    reader.close();
                } catch(IOException e) {
                    logger.debug("Error closing reader!", e);
                }
            }
        }
    }

    // Flush the in-memory data to the file
    private synchronized void flushData() {
        BufferedWriter writer = null;
        try {
            writer = new BufferedWriter(new FileWriter(new File(this.inputPath)));
            for(String key: this.metadataMap.keySet()) {
                writer.write(NEW_PROPERTY_SEPARATOR + key.toString() + "]" + NEW_LINE);
                writer.write(this.metadataMap.get(key).toString());
                writer.write("" + NEW_LINE + "" + NEW_LINE);
            }
            writer.flush();
        } catch(IOException e) {
            logger.error("IO exception while flushing data to file backed storage: "
                         + e.getMessage());
        }

        try {
            if(writer != null)
                writer.close();
        } catch(Exception e) {
            logger.error("Error while flushing data to file backed storage: " + e.getMessage());
        }
    }

    @Override
    public Object getCapability(StoreCapabilityType capability) {
        throw new VoldemortException("No extra capability.");
    }

    @Override
    public ClosableIterator<Pair<ByteArray, Versioned<byte[]>>> entries() {
        return new FileBackedStorageIterator(this.metadataMap, this);
    }

    @Override
    public ClosableIterator<ByteArray> keys() {
        return StoreUtils.keys(entries());
    }

    @Override
    public void truncate() {
        throw new VoldemortException("Truncate not supported in FileBackedCachingStorageEngine");
    }

    // Assigning new Vector clock here: TODO: Decide what vector clock to use ?
    @Override
    public List<Versioned<byte[]>> get(ByteArray key, byte[] transforms) throws VoldemortException {
        StoreUtils.assertValidKey(key);
        String keyString = new String(key.get());
        List<Versioned<byte[]>> found = new ArrayList<Versioned<byte[]>>();
        byte[] resultBytes = null;
        String value = this.metadataMap.get(keyString);
        if(value != null) {
            resultBytes = value.getBytes();
            found.add(new Versioned<byte[]>(resultBytes, readVersion()));
        }
        return found;
    }

    public String cacheGet(String key) {
        return this.metadataMap.get(key);
    }

    @Override
    public Map<ByteArray, List<Versioned<byte[]>>> getAll(Iterable<ByteArray> keys,
                                                          Map<ByteArray, byte[]> transforms)
            throws VoldemortException {
        StoreUtils.assertValidKeys(keys);
        Map<ByteArray, List<Versioned<byte[]>>> result = StoreUtils.newEmptyHashMap(keys);
        for(ByteArray key: keys) {
            List<Versioned<byte[]>> values = get(key, null);
            if(!values.isEmpty())
                result.put(key, values);
        }
        return result;
    }

    @Override
    public List<Version> getVersions(ByteArray key) {
        List<Versioned<byte[]>> values = get(key, null);
        List<Version> versions = new ArrayList<Version>(values.size());
        for(Versioned<?> value: values) {
            versions.add(value.getVersion());
        }
        return versions;
    }

    @Override
    /**
     * FIXME There is a problem here.. Since the versioning is on the file level, SystemStoreClient.put()
     * will throw OVE, on the insert of the second key, value pair.Ideally, the version should be persisted
     * along with the entries in the file too..
     */
    public void put(ByteArray key, Versioned<byte[]> value, byte[] transforms)
            throws VoldemortException {
        StoreUtils.assertValidKey(key);

        // Validate the Vector clock
        VectorClock clock = readVersion();
        if(clock != null) {
            if(value.getVersion().compare(clock) == Occurred.BEFORE) {
                throw new ObsoleteVersionException("A successor version " + clock + "  to this "
                                                   + value.getVersion()
                                                   + " exists for the current file : " + getName());
            } else if(value.getVersion().compare(clock) == Occurred.CONCURRENTLY) {
                throw new ObsoleteVersionException("Concurrent Operation not allowed on Metadata.");
            }
        }

        // Update the cache copy
        this.metadataMap.put(new String(key.get()), new String(value.getValue()));

        // Flush the data to the file
        this.flushData();

        // Persist the new Vector clock
        writeVersion((VectorClock) value.getVersion());
    }

    @Override
    public boolean delete(ByteArray key, Version version) throws VoldemortException {
        boolean deleteSuccessful = false;
        StoreUtils.assertValidKey(key);
        String keyString = new String(key.get());
        String initialValue = this.metadataMap.get(keyString);
        if(initialValue != null) {
            String removedValue = this.metadataMap.remove(keyString);
            if(removedValue != null) {
                deleteSuccessful = (initialValue.equals(removedValue));
            }
        }
        if(deleteSuccessful) {
            this.flushData();
            // Reset the vector clock and persist it.
            // FIXME this also needs to be done per entry, as opposed to
            // versioning the file.
            writeVersion(new VectorClock());
        }
        return deleteSuccessful;
    }

    private static class FileBackedStorageIterator implements
            ClosableIterator<Pair<ByteArray, Versioned<byte[]>>> {

        private final Iterator<Entry<String, String>> iterator;
        private final FileBackedCachingStorageEngine storageEngineRef;

        public FileBackedStorageIterator(Map<String, String> metadataMap,
                                         FileBackedCachingStorageEngine storageEngine) {
            iterator = metadataMap.entrySet().iterator();
            storageEngineRef = storageEngine;
        }

        @Override
        public boolean hasNext() {
            return iterator.hasNext();
        }

        @Override
        public Pair<ByteArray, Versioned<byte[]>> next() {
            Entry<String, String> entry = iterator.next();
            Pair<ByteArray, Versioned<byte[]>> nextValue = null;
            if(entry != null && entry.getKey() != null && entry.getValue() != null) {
                ByteArray key = new ByteArray(entry.getKey().getBytes());
                byte[] resultBytes = entry.getValue().getBytes();
                Versioned<byte[]> versionedValue = new Versioned<byte[]>(resultBytes,
                                                                         storageEngineRef.readVersion());
                nextValue = Pair.create(key, versionedValue);
            }

            return nextValue;
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException("No removal y'all.");
        }

        @Override
        public void close() {}

    }

    @Override
    public ClosableIterator<Pair<ByteArray, Versioned<byte[]>>> entries(int partition) {
        throw new UnsupportedOperationException("Partition based entries scan not supported for this storage type");
    }

    @Override
    public ClosableIterator<ByteArray> keys(int partition) {
        throw new UnsupportedOperationException("Partition based keys scan not supported for this storage type");
    }
}
TOP

Related Classes of voldemort.store.configuration.FileBackedCachingStorageEngine$FileBackedStorageIterator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.