/* * Copyright 2008-2009 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.File; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.codec.binary.Hex; import org.apache.commons.io.FileUtils; import org.apache.log4j.Logger; import voldemort.VoldemortException; import voldemort.store.AbstractStorageEngine; import voldemort.store.NoSuchCapabilityException; import voldemort.store.StoreCapabilityType; import voldemort.store.StoreUtils; import voldemort.store.metadata.MetadataStore; 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 FileSystem based Storage Engine to persist configuration metadata.<br> * <imp>Used only by {@link MetadataStore}</imp><br> * * */ public class ConfigurationStorageEngine extends AbstractStorageEngine<String, String, String> { private final static Logger logger = Logger.getLogger(ConfigurationStorageEngine.class); private static final String VERSION_DIRECTORY = ".version"; private static final String TEMP_DIRECTORY = ".temp"; private final File directory; public ConfigurationStorageEngine(String name, String directory) { super(name); this.directory = new File(directory); if(!this.directory.exists() && this.directory.canRead()) throw new IllegalArgumentException("Directory " + this.directory.getAbsolutePath() + " does not exist or can not be read."); } @Override public ClosableIterator<Pair<String, Versioned<String>>> entries() { List<String> fileNameList = new ArrayList<String>(); for(String fileName: this.directory.list()) { if(fileName.equals(VERSION_DIRECTORY) || fileName.equals(TEMP_DIRECTORY)) { continue; } fileNameList.add(fileName); } return new FilesIterator(fileNameList, this); } @Override public ClosableIterator<Pair<String, Versioned<String>>> entries(int partition) { throw new UnsupportedOperationException("Partition based entries scan not supported for this storage type"); } @Override public ClosableIterator<String> keys(int partition) { throw new UnsupportedOperationException("Partition based key scan not supported for this storage type"); } @Override public synchronized boolean delete(String key, Version version) throws VoldemortException { StoreUtils.assertValidKey(key); for(File file: getDirectory(key).listFiles()) { if(file.getName().equals(key)) { try { // delete the file and the version file return file.delete() && new File(getVersionDirectory(), file.getName()).delete(); } catch(Exception e) { logger.error("Error while attempt to delete key:" + key, e); } } } return false; } @Override public synchronized List<Versioned<String>> get(String key, String transforms) throws VoldemortException { StoreUtils.assertValidKey(key); return get(key, getDirectory(key).listFiles()); } @Override public List<Version> getVersions(String key) { List<Versioned<String>> values = get(key, (String) null); List<Version> versions = new ArrayList<Version>(values.size()); for(Versioned<?> value: values) { versions.add(value.getVersion()); } return versions; } @Override public synchronized Map<String, List<Versioned<String>>> getAll(Iterable<String> keys, Map<String, String> transforms) throws VoldemortException { StoreUtils.assertValidKeys(keys); Map<String, List<Versioned<String>>> result = StoreUtils.newEmptyHashMap(keys); for(String key: keys) { List<Versioned<String>> values = get(key, getDirectory(key).listFiles()); if(!values.isEmpty()) result.put(key, values); } return result; } @Override public synchronized void put(String key, Versioned<String> value, String transforms) throws VoldemortException { StoreUtils.assertValidKey(key); if(null == value.getValue()) { throw new VoldemortException("metadata cannot be null !!"); } // Check for obsolete version File[] files = getDirectory(key).listFiles(); if(files != null) { for(File file: files) { if(file.getName().equals(key)) { VectorClock clock = readVersion(key); if(value.getVersion().compare(clock) == Occurred.AFTER) { // continue } else if(value.getVersion().compare(clock) == Occurred.BEFORE) { throw new ObsoleteVersionException("A successor version " + clock + " to this " + value.getVersion() + " exists for key " + key); } else if(value.getVersion().compare(clock) == Occurred.CONCURRENTLY) { throw new ObsoleteVersionException("Concurrent Operation not allowed on Metadata."); } } } } File keyFile = new File(getDirectory(key), key); VectorClock newClock = (VectorClock) value.getVersion(); if(!keyFile.exists() || keyFile.delete()) { try { FileUtils.writeStringToFile(keyFile, value.getValue(), "UTF-8"); writeVersion(key, newClock); } catch(IOException e) { throw new VoldemortException(e); } } } private File getDirectory(String key) { if(MetadataStore.OPTIONAL_KEYS.contains(key)) return getTempDirectory(); else return this.directory; } private List<Versioned<String>> get(String key, File[] files) { try { List<Versioned<String>> found = new ArrayList<Versioned<String>>(); for(File file: files) { if(file.getName().equals(key)) { VectorClock clock = readVersion(key); if(null != clock) { found.add(new Versioned<String>(FileUtils.readFileToString(file, "UTF-8"), clock)); } } } return found; } catch(IOException e) { throw new VoldemortException(e); } } private VectorClock readVersion(String key) { try { File versionFile = new File(getVersionDirectory(), key); if(!versionFile.exists()) { // bootstrap file save default clock as version. VectorClock clock = new VectorClock(0); writeVersion(key, clock); return clock; } else { // read the version file and return version. String hexCode = FileUtils.readFileToString(versionFile, "UTF-8"); return new VectorClock(Hex.decodeHex(hexCode.toCharArray())); } } catch(Exception e) { throw new VoldemortException("Failed to read Version for Key:" + key, e); } } private void writeVersion(String key, VectorClock version) { try { File versionFile = new File(getVersionDirectory(), key); if(!versionFile.exists() || versionFile.delete()) { // write the version file. String hexCode = new String(Hex.encodeHex(version.toBytes())); FileUtils.writeStringToFile(versionFile, hexCode, "UTF-8"); } } catch(Exception e) { throw new VoldemortException("Failed to write Version for Key:" + key, e); } } private File getVersionDirectory() { File versionDir = new File(this.directory, VERSION_DIRECTORY); if(!versionDir.exists() || !versionDir.isDirectory()) { versionDir.delete(); versionDir.mkdirs(); } return versionDir; } private File getTempDirectory() { File tempDir = new File(this.directory, TEMP_DIRECTORY); if(!tempDir.exists() || !tempDir.isDirectory()) { tempDir.delete(); tempDir.mkdirs(); } return tempDir; } @Override public Object getCapability(StoreCapabilityType capability) { throw new NoSuchCapabilityException(capability, getName()); } @Override public ClosableIterator<String> keys() { throw new VoldemortException("keys iteration not supported."); } @Override public void truncate() { throw new VoldemortException("Truncate not supported in ConfigurationStorageEngine"); } private static class FilesIterator implements ClosableIterator<Pair<String, Versioned<String>>> { private final Iterator<String> iterator; private final ConfigurationStorageEngine storageEngineRef; public FilesIterator(List<String> fileNames, ConfigurationStorageEngine storageEngine) { iterator = fileNames.iterator(); storageEngineRef = storageEngine; } @Override public boolean hasNext() { return iterator.hasNext(); } @Override public Pair<String, Versioned<String>> next() { String fileName = iterator.next(); Pair<String, Versioned<String>> nextValue = null; if(fileName != null) { String key = fileName; List<Versioned<String>> resultStringList = this.storageEngineRef.get(key, ""); if(resultStringList != null && resultStringList.size() > 0) { nextValue = Pair.create(key, resultStringList.get(0)); } } return nextValue; } @Override public void remove() { throw new UnsupportedOperationException("No removal y'all."); } @Override public void close() {} } }