/* * Copyright 2011 Future Systems, 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 org.krakenapps.confdb.file; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import org.krakenapps.codec.EncodingRule; import org.krakenapps.confdb.CollectionEntry; import org.krakenapps.confdb.CommitOp; import org.krakenapps.confdb.ConfigEntry; import org.krakenapps.confdb.Manifest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; class FileManifest implements Manifest { private int version; private int id; private Map<Integer, CollectionEntry> colMap = new TreeMap<Integer, CollectionEntry>(); // col id -> (doc id -> entry) map private Map<Integer, Map<Integer, ConfigEntry>> configMap = new TreeMap<Integer, Map<Integer, ConfigEntry>>(); public FileManifest duplicate() { FileManifest dup = new FileManifest(); dup.id = id; dup.version = version; dup.colMap = new TreeMap<Integer, CollectionEntry>(colMap); dup.configMap = new TreeMap<Integer, Map<Integer, ConfigEntry>>(); for (Integer key : configMap.keySet()) { Map<Integer, ConfigEntry> value = configMap.get(key); dup.configMap.put(key, new TreeMap<Integer, ConfigEntry>(value)); } return dup; } public static void upgradeManifest(FileManifest manifest, File dbDir) { final Logger logger = LoggerFactory.getLogger(FileManifest.class); for (String name : manifest.getCollectionNames()) { CollectionEntry col = manifest.getCollectionEntry(name); File logFile = new File(dbDir, "col" + col.getId() + ".log"); File datFile = new File(dbDir, "col" + col.getId() + ".dat"); if (!logFile.exists() || !datFile.exists()) continue; RevLogReader reader = null; try { reader = new RevLogReader(logFile, datFile); // build map Map<ConfigEntry, Long> indexMap = new HashMap<ConfigEntry, Long>(); long count = reader.count(); for (long i = 0; i < count; i++) { RevLog revLog = reader.read(i); indexMap.put(new ConfigEntry(col.getId(), revLog.getDocId(), revLog.getRev()), i); } for (ConfigEntry c : manifest.getConfigEntries(col.getName())) { Long index = indexMap.get(c); if (index != null) { c.setIndex(index.intValue()); logger.debug("kraken confdb: set index for " + c); } else logger.warn("kraken confdb: cannot find index for " + c); } } catch (IOException e) { logger.error("kraken confdb: cannot upgrade format", e); } finally { if (reader != null) reader.close(); } } } public static Manifest writeManifest(Manifest manifest, File manifestLogFile, File manifestDatFile) throws IOException { RevLogWriter writer = null; try { writer = new RevLogWriter(manifestLogFile, manifestDatFile); return writeManifest(manifest, writer); } finally { if (writer != null) writer.close(); } } public static Manifest writeManifest(Manifest manifest, RevLogWriter writer) throws IOException { RevLog log = new RevLog(); log.setRev(1); log.setOperation(CommitOp.CreateDoc); log.setDoc(manifest.serialize()); manifest.setId(writer.write(log)); return manifest; } public FileManifest() { } @Override public int getVersion() { return version; } public void setVersion(int version) { this.version = version; } @Override public int getId() { return id; } @Override public void setId(int id) { this.id = id; } @Override public void add(CollectionEntry e) { colMap.put(e.getId(), e); configMap.put(e.getId(), new TreeMap<Integer, ConfigEntry>()); } @Override public void remove(CollectionEntry e) { colMap.remove(e.getId()); configMap.remove(e.getId()); } @Override public void add(ConfigEntry e) { Map<Integer, ConfigEntry> m = configMap.get(e.getColId()); if (m == null) throw new IllegalStateException("col not found: " + e.getColId()); m.put(e.getDocId(), e); if (m.size() > 65535) throw new IllegalStateException("cannot add more than 65535 docs"); } @Override public void remove(ConfigEntry e) { Map<Integer, ConfigEntry> m = configMap.get(e.getColId()); if (m == null) throw new IllegalStateException("col not found: " + e.getColId()); m.remove(e.getDocId()); } @Override public int getCollectionId(String name) { CollectionEntry e = getCollectionEntry(name); if (e == null) throw new IllegalArgumentException("collection not found: " + name); return e.getId(); } @Override public CollectionEntry getCollectionEntry(String name) { for (CollectionEntry e : colMap.values()) if (e.getName().equals(name)) return e; return null; } @Override public List<ConfigEntry> getConfigEntries(String colName) { int colId = getCollectionId(colName); Map<Integer, ConfigEntry> m = configMap.get(colId); if (m == null) throw new IllegalStateException("col not found: " + colName); return new ArrayList<ConfigEntry>(m.values()); } public String getCollectionName(int id) { CollectionEntry e = colMap.get(id); if (e == null) return null; return e.getName(); } @Override public boolean containsDoc(String colName, int docId, long rev) { CollectionEntry col = getCollectionEntry(colName); Map<Integer, ConfigEntry> m = configMap.get(col.getId()); if (m == null) throw new IllegalStateException("col not found: " + colName); ConfigEntry ce = m.get(docId); return ce != null && ce.getRev() == rev; } @Override public byte[] serialize() { FileManifestCodec codec = new FileManifestCodec(); int len = EncodingRule.lengthOf(this, codec); ByteBuffer bb = ByteBuffer.allocate(len); EncodingRule.encode(bb, this, codec); return bb.array(); } public static FileManifest deserialize(byte[] b) { return deserialize(b, false); } public static FileManifest deserialize(byte[] b, boolean noConfigs) { ByteBuffer bb = ByteBuffer.wrap(b); Object doc = EncodingRule.decode(bb, new FileManifestCodec()); if (doc instanceof FileManifest) return (FileManifest) doc; @SuppressWarnings("unchecked") Map<String, Object> m = (Map<String, Object>) doc; FileManifest manifest = new FileManifest(); if (m.containsKey("ver")) manifest.setVersion((Integer) m.get("ver")); else manifest.setVersion(1); // manual coding instead of primitive converter for performance for (Object o : (Object[]) m.get("cols")) { if (o instanceof Map) { // legacy format support @SuppressWarnings("unchecked") Map<String, Object> col = (Map<String, Object>) o; manifest.add(new CollectionEntry((Integer) col.get("id"), (String) col.get("name"))); } else { Object[] arr = (Object[]) o; if (manifest.getVersion() == 1) { manifest.add(new CollectionEntry((Integer) arr[0], (String) arr[1])); } else if (manifest.getVersion() == 2) { int colId = (Integer) arr[0]; manifest.add(new CollectionEntry(colId, (String) arr[1])); for (Object o2 : (Object[]) arr[2]) { Object[] config = (Object[]) o2; manifest.add(new ConfigEntry(colId, (Integer) config[0], (Long) config[1], (Integer) config[2])); } } } } // for ensureCollection() acceleration if (noConfigs) return manifest; if (manifest.getVersion() == 1) { for (Object o : (Object[]) m.get("configs")) { if (o instanceof Map) { // legacy format support @SuppressWarnings("unchecked") Map<String, Object> c = (Map<String, Object>) o; manifest.add(new ConfigEntry((Integer) c.get("col_id"), (Integer) c.get("doc_id"), (Long) c.get("rev"))); } else { Object[] arr = (Object[]) o; manifest.add(new ConfigEntry((Integer) arr[0], (Integer) arr[1], (Long) arr[2])); } } } return manifest; } @Override public Set<String> getCollectionNames() { Set<String> names = new TreeSet<String>(); for (CollectionEntry e : colMap.values()) names.add(e.getName()); return names; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; result = prime * result + version; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; FileManifest other = (FileManifest) obj; if (id != other.id) return false; if (version != other.version) return false; return true; } @Override public String toString() { String manifest = "version=" + getVersion() + ", id=" + getId() + "\n" + "collections\n"; manifest += "-------------------------------\n"; if (colMap != null) { for (CollectionEntry c : colMap.values()) { manifest += c.toString() + ", count=" + configMap.get(c.getId()).values().size() + "\n"; } } return manifest; } }