/* Copyright (c) 2012-2014 Boundless and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Distribution License v1.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/org/documents/edl-v10.html * * Contributors: * Gabriel Roldan (Boundless) - initial implementation */ package org.locationtech.geogig.api.plumbing; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import javax.annotation.Nullable; import org.locationtech.geogig.api.AbstractGeoGigOp; import org.locationtech.geogig.api.Node; import org.locationtech.geogig.api.NodeRef; import org.locationtech.geogig.api.ObjectId; import org.locationtech.geogig.api.RevObject.TYPE; import org.locationtech.geogig.api.RevTree; import org.locationtech.geogig.api.RevTreeBuilder; import org.locationtech.geogig.repository.SpatialOps; import org.locationtech.geogig.storage.ObjectDatabase; import org.locationtech.geogig.storage.StagingDatabase; import com.google.common.base.Optional; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.vividsolutions.jts.geom.Envelope; /** * Writes the contents of a given tree as a child of a given ancestor tree, creating any * intermediate tree needed, and returns the {@link ObjectId id} of the resulting new ancestor tree. * <p> * If no {@link #setAncestor(RevTreeBuilder) ancestor} is provided,it is assumed to be the current * HEAD tree. * <p> * If no {@link #setAncestorPath(String) ancestor path} is provided, the ancestor is assumed to be a * root tree * * @see CreateTree * @see RevObjectParse * @see FindTreeChild */ public class WriteBack extends AbstractGeoGigOp<ObjectId> { private boolean indexDb; private Supplier<RevTreeBuilder> ancestor; private String childPath; private Supplier<RevTree> tree; private String ancestorPath; private Optional<ObjectId> metadataId; public WriteBack() { this.metadataId = Optional.absent(); } /** * @param indexDb if {@code true} the trees will be stored to the {@link StagingDatabase}, * otherwise to the repository's {@link ObjectDatabase permanent store}. Defaults to * {@code false} * @return {@code this} */ public WriteBack setToIndex(boolean indexDb) { this.indexDb = indexDb; return this; } /** * @param oldRoot the root tree to which add the {@link #setTree(RevTree) child tree} and any * intermediate tree. If not set defaults to the current HEAD tree * @return {@code this} */ public WriteBack setAncestor(RevTreeBuilder oldRoot) { return setAncestor(Suppliers.ofInstance(oldRoot)); } /** * @param ancestor the root tree to which add the {@link #setTree(RevTree) child tree} and any * intermediate tree. If not set defaults to the current HEAD tree * @return {@code this} */ public WriteBack setAncestor(Supplier<RevTreeBuilder> ancestor) { this.ancestor = ancestor; return this; } /** * @param ancestorPath the path of the {@link #setAncestor(Supplier) ancestor tree}. If set not * the ancestor tree is assumed to be a root tree. * @return {@code this} */ public WriteBack setAncestorPath(String ancestorPath) { this.ancestorPath = ancestorPath; return this; } /** * @param childPath mandatory, the path to the child tree * @return {@code this} */ public WriteBack setChildPath(String childPath) { this.childPath = childPath; return this; } /** * @param tree the tree to store on the object database and to create any intermediate tree for * the given {@link #setAncestor(Supplier) ancestor tree} * @return {@code this} */ public WriteBack setTree(RevTree tree) { return setTree(Suppliers.ofInstance(tree)); } /** * @param tree the tree to store on the object database and to create any intermediate tree for * the given {@link #setAncestor(Supplier) ancestor tree} * @return {@code this} */ public WriteBack setTree(Supplier<RevTree> tree) { this.tree = tree; return this; } /** * Executes the write back operation. * * @return the {@link ObjectId id} of the resulting new ancestor tree. */ @Override protected ObjectId _call() { checkNotNull(tree, "child tree not set"); checkNotNull(childPath, "child tree path not set"); String ancestorPath = resolveAncestorPath(); checkArgument(NodeRef.isChild(ancestorPath, childPath), String.format( "child path '%s' is not a child of ancestor path '%s'", childPath, ancestorPath)); RevTree tree = this.tree.get(); checkState(null != tree, "child tree supplier returned null"); ObjectDatabase targetDb = indexDb ? stagingDatabase() : objectDatabase(); RevTreeBuilder root = resolveAncestor(); return writeBack(root, ancestorPath, tree, childPath, targetDb, metadataId.or(ObjectId.NULL)); } /** * @return the resolved ancestor path */ private String resolveAncestorPath() { return ancestorPath == null ? "" : ancestorPath; } /** * @return the resolved ancestor */ private RevTreeBuilder resolveAncestor() { RevTreeBuilder ancestor = this.ancestor.get(); checkState(ancestor != null, "provided ancestor tree supplier returned null"); return ancestor; } private ObjectId writeBack(RevTreeBuilder ancestor, final String ancestorPath, final RevTree childTree, final String childPath, final ObjectDatabase targetDatabase, final ObjectId metadataId) { final ObjectId treeId = childTree.getId(); targetDatabase.put(childTree); final boolean isDirectChild = NodeRef.isDirectChild(ancestorPath, childPath); if (isDirectChild) { Envelope treeBounds = null; if (!metadataId.isNull()) {// only include bounds for trees with a default feature type treeBounds = SpatialOps.boundsOf(childTree); } String childName = childPath; Node treeNode = Node.create(childName, treeId, metadataId, TYPE.TREE, treeBounds); ancestor.put(treeNode); RevTree newAncestor = ancestor.build(); targetDatabase.put(newAncestor); return newAncestor.getId(); } final String parentPath = NodeRef.parentPath(childPath); Optional<NodeRef> parentRef = getTreeChild(ancestor, parentPath); RevTreeBuilder parentBuilder; ObjectId parentMetadataId = ObjectId.NULL; if (parentRef.isPresent()) { ObjectId parentId = parentRef.get().objectId(); parentMetadataId = parentRef.get().getMetadataId(); parentBuilder = getTree(parentId, targetDatabase).builder(targetDatabase); } else { parentBuilder = RevTree.EMPTY.builder(targetDatabase); } String childName = NodeRef.nodeFromPath(childPath); Envelope treeBounds = null; if (!metadataId.isNull()) {// only include bounds for trees with a default feature type treeBounds = SpatialOps.boundsOf(childTree); } Node treeNode = Node.create(childName, treeId, metadataId, TYPE.TREE, treeBounds); parentBuilder.put(treeNode); RevTree parent = parentBuilder.build(); return writeBack(ancestor, ancestorPath, parent, parentPath, targetDatabase, parentMetadataId); } private RevTree getTree(ObjectId treeId, ObjectDatabase targetDb) { RevTree revTree = targetDb.getTree(treeId); return revTree; } private Optional<NodeRef> getTreeChild(RevTreeBuilder parent, String childPath) { RevTree realParent = parent.build(); FindTreeChild cmd = command(FindTreeChild.class).setIndex(true).setParent(realParent) .setChildPath(childPath); Optional<NodeRef> nodeRef = cmd.call(); return nodeRef; } /** * @param metadataId the (optional) metadata id for the resulting tree ref * @return */ public WriteBack setMetadataId(@Nullable ObjectId metadataId) { this.metadataId = Optional.fromNullable(metadataId); return this; } }