/* * Copyright (c) [2016] [ <ether.camp> ] * This file is part of the ethereumJ library. * * The ethereumJ library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The ethereumJ library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>. */ package org.ethereum.net.swarm; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.spongycastle.util.encoders.Hex; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.*; /** * Hierarchical structure of path items * The item can be one of two kinds: * - the leaf item which contains reference to the actual data with its content type * - the non-leaf item which contains reference to the child manifest with the dedicated content type */ public class Manifest { public enum Status { OK(200), NOT_FOUND(404); private int code; Status(int code) { this.code = code; } } // used for Json [de]serialization only private static class ManifestRoot { public List<ManifestEntry> entries = new ArrayList<>(); public ManifestRoot() { } public ManifestRoot(List<ManifestEntry> entries) { this.entries = entries; } public ManifestRoot(ManifestEntry parent) { entries.addAll(parent.getChildren()); } } /** * Manifest item */ @JsonAutoDetect(getterVisibility= JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY, isGetterVisibility = JsonAutoDetect.Visibility.NONE) public static class ManifestEntry extends StringTrie.TrieNode<ManifestEntry> { public String hash; public String contentType; public Status status; private Manifest thisMF; public ManifestEntry() { super(null, ""); } public ManifestEntry(String path, String hash, String contentType, Status status) { super(null, ""); this.path = path; this.hash = hash; this.contentType = contentType; this.status = status; } ManifestEntry(ManifestEntry parent, String path) { super(parent, path); this.path = path; } /** * Indicates if this entry contains reference to a child manifest */ public boolean isManifestType() { return MANIFEST_MIME_TYPE.equals(contentType);} boolean isValid() {return hash != null;} void invalidate() {hash = null;} @Override public boolean isLeaf() { return !(isManifestType() || !children.isEmpty()); } /** * loads the child manifest */ @Override protected Map<String, ManifestEntry> loadChildren() { if (isManifestType() && children.isEmpty() && isValid()) { ManifestRoot manifestRoot = load(thisMF.dpa, hash); children = new HashMap<>(); for (Manifest.ManifestEntry entry : manifestRoot.entries) { children.put(getKey(entry.path), entry); } } return children; } @JsonProperty public String getPath() { return path; } @JsonProperty public void setPath(String path) { this.path = path; } @Override protected ManifestEntry createNode(ManifestEntry parent, String path) { return new ManifestEntry(parent, path).setThisMF(parent.thisMF); } @Override protected void nodeChanged() { if (!isLeaf()) { contentType = MANIFEST_MIME_TYPE; invalidate(); } } ManifestEntry setThisMF(Manifest thisMF) { this.thisMF = thisMF; return this; } @Override public String toString() { return "ManifestEntry{" + "path='" + path + '\'' + ", hash='" + hash + '\'' + ", contentType='" + contentType + '\'' + ", status=" + status + '}'; } } public final static String MANIFEST_MIME_TYPE = "application/bzz-manifest+json"; private DPA dpa; private final StringTrie<ManifestEntry> trie; /** * Constructs the Manifest instance with backing DPA storage * @param dpa DPA */ public Manifest(DPA dpa) { this(dpa, new ManifestEntry(null, "")); } private Manifest(DPA dpa, ManifestEntry root) { this.dpa = dpa; trie = new StringTrie<ManifestEntry>(root.setThisMF(this)) {}; } /** * Retrieves the entry with the specified path loading necessary nested manifests on demand */ public ManifestEntry get(String path) { return trie.get(path); } /** * Adds a new entry to the manifest hierarchy with loading necessary nested manifests on demand. * The entry path should contain the absolute path relative to this manifest root */ public void add(ManifestEntry entry) { add(null, entry); } void add(ManifestEntry parent, ManifestEntry entry) { ManifestEntry added = parent == null ? trie.add(entry.path) : trie.add(parent, entry.path); added.hash = entry.hash; added.contentType = entry.contentType; added.status = entry.status; } /** * Deletes the leaf manifest entry with the specified path */ public void delete(String path) { trie.delete(path); } /** * Loads the manifest with the specified hashKey from the DPA storage */ public static Manifest loadManifest(DPA dpa, String hashKey) { ManifestRoot manifestRoot = load(dpa, hashKey); Manifest ret = new Manifest(dpa); for (Manifest.ManifestEntry entry : manifestRoot.entries) { ret.add(entry); } return ret; } private static Manifest.ManifestRoot load(DPA dpa, String hashKey) { try { SectionReader sr = dpa.retrieve(new Key(hashKey)); ObjectMapper om = new ObjectMapper(); String s = Util.readerToString(sr); ManifestRoot manifestRoot = om.readValue(s, ManifestRoot.class); return manifestRoot; } catch (IOException e) { throw new RuntimeException(e); } } /** * Saves this manifest (all its modified nodes) to this manifest DPA storage * @return hashKey of the saved Manifest */ public String save() { return save(trie.rootNode); } private String save(ManifestEntry e) { if (e.isValid()) return e.hash; for (ManifestEntry c : e.getChildren()) { save(c); } e.hash = serialize(dpa, e); return e.hash; } private String serialize(DPA dpa, ManifestEntry manifest) { try { ObjectMapper om = new ObjectMapper(); ManifestRoot mr = new ManifestRoot(manifest); String s = om.writeValueAsString(mr); String hash = dpa.store(Util.stringToReader(s)).getHexString(); return hash; } catch (Exception e) { throw new RuntimeException(e); } } /** * @return manifest dump for debug purposes */ public String dump() { return Util.dumpTree(trie.rootNode); } }