Java tutorial
/* Copyright (c) 2013 OpenPlans. All rights reserved. * This code is licensed under the BSD New License, available at the root * application directory. */ package org.geogit.repository; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.annotation.Nullable; import org.geogit.api.CommandLocator; import org.geogit.api.FeatureBuilder; import org.geogit.api.Node; import org.geogit.api.NodeRef; import org.geogit.api.ObjectId; import org.geogit.api.Ref; import org.geogit.api.RevFeature; import org.geogit.api.RevFeatureBuilder; import org.geogit.api.RevFeatureType; import org.geogit.api.RevObject; import org.geogit.api.RevObject.TYPE; import org.geogit.api.RevTree; import org.geogit.api.RevTreeBuilder; import org.geogit.api.data.FindFeatureTypeTrees; import org.geogit.api.plumbing.DiffCount; import org.geogit.api.plumbing.DiffWorkTree; import org.geogit.api.plumbing.FindOrCreateSubtree; import org.geogit.api.plumbing.FindTreeChild; import org.geogit.api.plumbing.LsTreeOp; import org.geogit.api.plumbing.LsTreeOp.Strategy; import org.geogit.api.plumbing.ResolveTreeish; import org.geogit.api.plumbing.RevObjectParse; import org.geogit.api.plumbing.UpdateRef; import org.geogit.api.plumbing.WriteBack; import org.geogit.api.plumbing.diff.DiffEntry; import org.geogit.api.plumbing.diff.DiffObjectCount; import org.geogit.storage.StagingDatabase; import org.geotools.geometry.jts.ReferencedEnvelope; import org.opengis.feature.Feature; import org.opengis.feature.type.FeatureType; import org.opengis.feature.type.Name; import org.opengis.filter.Filter; import org.opengis.geometry.BoundingBox; import org.opengis.util.ProgressListener; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.Iterators; import com.google.common.collect.Maps; import com.google.common.collect.PeekingIterator; import com.google.inject.Inject; import com.vividsolutions.jts.geom.Envelope; /** * A working tree is the collection of Features for a single FeatureType in GeoServer that has a * repository associated with it (and hence is subject of synchronization). * <p> * It represents the set of Features tracked by some kind of geospatial data repository (like the * GeoServer Catalog). It is essentially a "tree" with various roots and only one level of nesting, * since the FeatureTypes held in this working tree are the equivalents of files in a git working * tree. * </p> * <p> * <ul> * <li>A WorkingTree represents the current working copy of the versioned feature types * <li>A WorkingTree has a Repository * <li>A Repository holds commits and branches * <li>You perform work on the working tree (insert/delete/update features) * <li>Then you commit to the current Repository's branch * <li>You can checkout a different branch from the Repository and the working tree will be updated * to reflect the state of that branch * </ul> * * @see Repository */ public class WorkingTree { private StagingDatabase indexDatabase; private CommandLocator commandLocator; @Inject public WorkingTree(final StagingDatabase indexDb, final CommandLocator commandLocator) { Preconditions.checkNotNull(indexDb); Preconditions.checkNotNull(commandLocator); this.indexDatabase = indexDb; this.commandLocator = commandLocator; } /** * Updates the WORK_HEAD ref to the specified tree. * * @param newTree the tree to be set as the new WORK_HEAD */ public void updateWorkHead(ObjectId newTree) { commandLocator.command(UpdateRef.class).setName(Ref.WORK_HEAD).setNewValue(newTree).call(); } /** * @return the tree represented by WORK_HEAD. If there is no tree set at WORK_HEAD, it will * return the HEAD tree (no unstaged changes). */ public RevTree getTree() { Optional<ObjectId> workTreeId = commandLocator.command(ResolveTreeish.class).setTreeish(Ref.WORK_HEAD) .call(); final RevTree workTree; if (!workTreeId.isPresent() || workTreeId.get().isNull()) { // Work tree was not resolved, update it to the head. Optional<ObjectId> headTreeId = commandLocator.command(ResolveTreeish.class).setTreeish(Ref.HEAD) .call(); final RevTree headTree; if (!headTreeId.isPresent() || headTreeId.get().isNull()) { headTree = RevTree.EMPTY; } else { headTree = commandLocator.command(RevObjectParse.class).setObjectId(headTreeId.get()) .call(RevTree.class).get(); } updateWorkHead(headTree.getId()); workTree = headTree; } else { workTree = commandLocator.command(RevObjectParse.class).setObjectId(workTreeId.get()) .call(RevTree.class).or(RevTree.EMPTY); } Preconditions.checkState(workTree != null); return workTree; } /** * @return a supplier for the working tree. */ private Supplier<RevTreeBuilder> getTreeSupplier() { Supplier<RevTreeBuilder> supplier = new Supplier<RevTreeBuilder>() { @Override public RevTreeBuilder get() { return getTree().builder(indexDatabase); } }; return Suppliers.memoize(supplier); } /** * Deletes a single feature from the working tree and updates the WORK_HEAD ref. * * @param path the path of the feature * @param featureId the id of the feature * @return true if the object was found and deleted, false otherwise */ public boolean delete(final String path, final String featureId) { Optional<NodeRef> typeTreeRef = commandLocator.command(FindTreeChild.class).setIndex(true) .setParent(getTree()).setChildPath(path).call(); ObjectId metadataId = null; if (typeTreeRef.isPresent()) { metadataId = typeTreeRef.get().getMetadataId(); } RevTreeBuilder parentTree = commandLocator.command(FindOrCreateSubtree.class).setIndex(true) .setParent(Suppliers.ofInstance(Optional.of(getTree()))).setChildPath(path).call() .builder(indexDatabase); String featurePath = NodeRef.appendChild(path, featureId); Optional<Node> node = findUnstaged(featurePath); if (node.isPresent()) { parentTree.remove(node.get().getName()); } ObjectId newTree = commandLocator.command(WriteBack.class).setAncestor(getTreeSupplier()).setChildPath(path) .setToIndex(true).setMetadataId(metadataId).setTree(parentTree.build()).call(); updateWorkHead(newTree); return node.isPresent(); } /** * Deletes a tree and the features it contains from the working tree and updates the WORK_HEAD * ref. * * @param path the path to the tree to delete * @throws Exception */ public void delete(final String path) { final String parentPath = NodeRef.parentPath(path); final String childName = NodeRef.nodeFromPath(path); final RevTree workHead = getTree(); RevTree parent; RevTreeBuilder parentBuilder; ObjectId parentMetadataId = ObjectId.NULL; if (parentPath.isEmpty()) { parent = workHead; parentBuilder = workHead.builder(indexDatabase); } else { Optional<NodeRef> parentRef = commandLocator.command(FindTreeChild.class).setParent(workHead) .setChildPath(parentPath).setIndex(true).call(); if (!parentRef.isPresent()) { return; } parentMetadataId = parentRef.get().getMetadataId(); parent = commandLocator.command(RevObjectParse.class).setObjectId(parentRef.get().objectId()) .call(RevTree.class).get(); parentBuilder = parent.builder(indexDatabase); } RevTree newParent = parentBuilder.remove(childName).build(); indexDatabase.put(newParent); if (parent.getId().equals(newParent.getId())) { return;// nothing changed } ObjectId newWorkHead; if (parentPath.isEmpty()) { newWorkHead = newParent.getId(); } else { newWorkHead = commandLocator.command(WriteBack.class).setToIndex(true) .setAncestor(workHead.builder(indexDatabase)).setChildPath(parentPath).setTree(newParent) .setMetadataId(parentMetadataId).call(); } updateWorkHead(newWorkHead); } /** * Deletes a collection of features of the same type from the working tree and updates the * WORK_HEAD ref. * * @param typeName feature type * @param filter - currently unused * @param affectedFeatures features to remove * @throws Exception */ public void delete(final Name typeName, final Filter filter, final Iterator<Feature> affectedFeatures) throws Exception { Optional<NodeRef> typeTreeRef = commandLocator.command(FindTreeChild.class).setIndex(true) .setParent(getTree()).setChildPath(typeName.getLocalPart()).call(); ObjectId parentMetadataId = null; if (typeTreeRef.isPresent()) { parentMetadataId = typeTreeRef.get().getMetadataId(); } RevTreeBuilder parentTree = commandLocator.command(FindOrCreateSubtree.class) .setParent(Suppliers.ofInstance(Optional.of(getTree()))).setIndex(true) .setChildPath(typeName.getLocalPart()).call().builder(indexDatabase); String fid; String featurePath; while (affectedFeatures.hasNext()) { fid = affectedFeatures.next().getIdentifier().getID(); featurePath = NodeRef.appendChild(typeName.getLocalPart(), fid); Optional<Node> ref = findUnstaged(featurePath); if (ref.isPresent()) { parentTree.remove(ref.get().getName()); } } ObjectId newTree = commandLocator.command(WriteBack.class).setAncestor(getTree().builder(indexDatabase)) .setMetadataId(parentMetadataId).setChildPath(typeName.getLocalPart()).setToIndex(true) .setTree(parentTree.build()).call(); updateWorkHead(newTree); } /** * Deletes a feature type from the working tree and updates the WORK_HEAD ref. * * @param typeName feature type to remove * @throws Exception */ public void delete(final Name typeName) throws Exception { checkNotNull(typeName); RevTreeBuilder workRoot = getTree().builder(indexDatabase); final String treePath = typeName.getLocalPart(); if (workRoot.get(treePath).isPresent()) { workRoot.remove(treePath); RevTree newRoot = workRoot.build(); indexDatabase.put(newRoot); updateWorkHead(newRoot.getId()); } } /** * * @param features the features to delete */ public void delete(Iterator<String> features) { Map<String, RevTreeBuilder> parents = Maps.newHashMap(); final RevTree currentWorkHead = getTree(); while (features.hasNext()) { String featurePath = features.next(); // System.err.println("removing " + feature); String parentPath = NodeRef.parentPath(featurePath); RevTreeBuilder parentTree; if (parents.containsKey(parentPath)) { parentTree = parents.get(parentPath); } else { parentTree = commandLocator.command(FindOrCreateSubtree.class).setIndex(true) .setParent(Suppliers.ofInstance(Optional.of(currentWorkHead))).setChildPath(parentPath) .call().builder(indexDatabase); parents.put(parentPath, parentTree); } String featureName = NodeRef.nodeFromPath(featurePath); parentTree.remove(featureName); } ObjectId newTree = null; for (Map.Entry<String, RevTreeBuilder> entry : parents.entrySet()) { String path = entry.getKey(); RevTreeBuilder parentTree = entry.getValue(); RevTree newTypeTree = parentTree.build(); ObjectId metadataId = null; Optional<NodeRef> currentTreeRef = commandLocator.command(FindTreeChild.class).setIndex(true) .setParent(currentWorkHead).setChildPath(path).call(); if (currentTreeRef.isPresent()) { metadataId = currentTreeRef.get().getMetadataId(); } newTree = commandLocator.command(WriteBack.class).setAncestor(getTreeSupplier()).setChildPath(path) .setToIndex(true).setTree(newTypeTree).setMetadataId(metadataId).call(); updateWorkHead(newTree); } } public NodeRef createTypeTree(final String treePath, final FeatureType featureType) { final RevTree workHead = getTree(); Optional<NodeRef> typeTreeRef = commandLocator.command(FindTreeChild.class).setIndex(true) .setParent(workHead).setChildPath(treePath).call(); Preconditions.checkArgument(!typeTreeRef.isPresent(), "Tree already exists at %s", treePath); final RevFeatureType revType = RevFeatureType.build(featureType); indexDatabase.put(revType); final ObjectId metadataId = revType.getId(); final RevTree newTree = new RevTreeBuilder(indexDatabase).build(); ObjectId newWorkHeadId = commandLocator.command(WriteBack.class).setToIndex(true) .setAncestor(workHead.builder(indexDatabase)).setChildPath(treePath).setTree(newTree) .setMetadataId(metadataId).call(); updateWorkHead(newWorkHeadId); return commandLocator.command(FindTreeChild.class).setIndex(true).setParent(getTree()) .setChildPath(treePath).call().get(); } /** * Insert a single feature into the working tree and updates the WORK_HEAD ref. * * @param parentTreePath path of the parent tree to insert the feature into * @param feature the feature to insert */ public Node insert(final String parentTreePath, final Feature feature) { final FeatureType featureType = feature.getType(); NodeRef treeRef; Optional<NodeRef> typeTreeRef = commandLocator.command(FindTreeChild.class).setIndex(true) .setParent(getTree()).setChildPath(parentTreePath).call(); ObjectId metadataId; if (typeTreeRef.isPresent()) { treeRef = typeTreeRef.get(); RevFeatureType newFeatureType = RevFeatureType.build(featureType); metadataId = newFeatureType.getId().equals(treeRef.getMetadataId()) ? ObjectId.NULL : newFeatureType.getId(); if (!newFeatureType.getId().equals(treeRef.getMetadataId())) { indexDatabase.put(newFeatureType); } } else { treeRef = createTypeTree(parentTreePath, featureType); metadataId = ObjectId.NULL;// treeRef.getMetadataId(); } // ObjectId metadataId = treeRef.getMetadataId(); final Node node = putInDatabase(feature, metadataId); RevTreeBuilder parentTree = commandLocator.command(FindOrCreateSubtree.class).setIndex(true) .setParent(Suppliers.ofInstance(Optional.of(getTree()))).setChildPath(parentTreePath).call() .builder(indexDatabase); parentTree.put(node); final ObjectId treeMetadataId = treeRef.getMetadataId(); ObjectId newTree = commandLocator.command(WriteBack.class).setAncestor(getTreeSupplier()) .setChildPath(parentTreePath).setToIndex(true).setTree(parentTree.build()) .setMetadataId(treeMetadataId).call(); updateWorkHead(newTree); final String featurePath = NodeRef.appendChild(parentTreePath, node.getName()); Optional<NodeRef> featureRef = commandLocator.command(FindTreeChild.class).setIndex(true) .setParent(getTree()).setChildPath(featurePath).call(); return featureRef.get().getNode(); } /** * Inserts a collection of features into the working tree and updates the WORK_HEAD ref. * * @param treePath the path of the tree to insert the features into * @param features the features to insert * @param listener a {@link ProgressListener} for the current process * @param insertedTarget if provided, inserted features will be added to this list * @param collectionSize number of features to add * @throws Exception */ public void insert(final String treePath, Iterator<? extends Feature> features, final ProgressListener listener, @Nullable final List<Node> insertedTarget, @Nullable final Integer collectionSize) { checkArgument(collectionSize == null || collectionSize.intValue() > -1); final NodeRef treeRef; { Optional<NodeRef> typeTreeRef = commandLocator.command(FindTreeChild.class).setIndex(true) .setParent(getTree()).setChildPath(treePath).call(); if (typeTreeRef.isPresent()) { treeRef = typeTreeRef.get(); } else { Preconditions.checkArgument(features.hasNext(), "Can't create new FeatureType tree %s as no features were provided, " + "try using createTypeTree() first", treePath); features = Iterators.peekingIterator(features); FeatureType featureType = ((PeekingIterator<Feature>) features).peek().getType(); treeRef = createTypeTree(treePath, featureType); } } final ObjectId defaultMetadataId = treeRef.getMetadataId(); final Map<Name, ObjectId> revFeatureTypes = Maps.newHashMap(); final RevTreeBuilder typeTreeBuilder = commandLocator.command(FindOrCreateSubtree.class).setIndex(true) .setParent(Suppliers.ofInstance(Optional.of(getTree()))).setChildPath(treePath).call() .builder(indexDatabase); Iterator<RevObject> objects = Iterators.transform(features, new Function<Feature, RevObject>() { private RevFeatureBuilder builder = new RevFeatureBuilder(); private int count; @Override public RevFeature apply(Feature feature) { final RevFeature revFeature = builder.build(feature); FeatureType featureType = feature.getType(); ObjectId revFeatureTypeId = revFeatureTypes.get(featureType.getName()); if (null == revFeatureTypeId) { RevFeatureType newFeatureType = RevFeatureType.build(featureType); revFeatureTypeId = newFeatureType.getId(); indexDatabase.put(newFeatureType); revFeatureTypes.put(feature.getType().getName(), revFeatureTypeId); } ObjectId metadataId = defaultMetadataId.equals(revFeatureTypeId) ? ObjectId.NULL : revFeatureTypeId; Node node = createNode(metadataId, feature, revFeature); if (insertedTarget != null) { insertedTarget.add(node); } typeTreeBuilder.put(node); count++; if (collectionSize != null) { listener.progress((float) (count * 100) / collectionSize.intValue()); } return revFeature; } }); // System.err.println("\n inserting rev features..."); // Stopwatch sw = new Stopwatch().start(); listener.started(); indexDatabase.putAll(objects); listener.complete(); // sw.stop(); // System.err.printf("\n%d features inserted in %s", collectionSize, sw); // System.err.println("\nBuilding final tree..."); // sw.reset().start(); RevTree newFeatureTree = typeTreeBuilder.build(); indexDatabase.put(newFeatureTree); // sw.stop(); // System.err.println("\nfinal tree built in " + sw); ObjectId newTree = commandLocator.command(WriteBack.class).setAncestor(getTreeSupplier()) .setChildPath(treePath).setMetadataId(treeRef.getMetadataId()).setToIndex(true) .setTree(newFeatureTree).call(); updateWorkHead(newTree); } private Node createNode(final ObjectId metadataId, Feature feature, final RevFeature revFeature) { final String name; final ObjectId oid; final Envelope env; name = feature.getIdentifier().getID(); BoundingBox bounds = feature.getBounds(); if (bounds instanceof ReferencedEnvelope) { env = (Envelope) bounds; } else if (bounds != null) { env = new Envelope(bounds.getMinX(), bounds.getMaxX(), bounds.getMinY(), bounds.getMaxY()); } else { env = null; } oid = revFeature.getId(); Node node = Node.create(name, oid, metadataId, TYPE.FEATURE, env); return node; } /** * Updates a collection of features in the working tree and updates the WORK_HEAD ref. * * @param treePath the path of the tree to insert the features into * @param features the features to insert * @param listener a {@link ProgressListener} for the current process * @param collectionSize number of features to add * @throws Exception */ public void update(final String treePath, final Iterator<Feature> features, final ProgressListener listener, @Nullable final Integer collectionSize) throws Exception { checkArgument(collectionSize == null || collectionSize.intValue() > -1); final Integer size = collectionSize == null || collectionSize.intValue() < 1 ? null : collectionSize.intValue(); insert(treePath, features, listener, null, size); } /** * Determines if a specific feature type is versioned (existing in the main repository). * * @param typeName feature type to check * @return true if the feature type is versioned, false otherwise. */ public boolean hasRoot(final Name typeName) { String localPart = typeName.getLocalPart(); Optional<NodeRef> typeNameTreeRef = commandLocator.command(FindTreeChild.class).setIndex(true) .setChildPath(localPart).call(); return typeNameTreeRef.isPresent(); } /** * @param pathFilter if specified, only changes that match the filter will be returned * @return an iterator for all of the differences between the work tree and the index based on * the path filter. */ public Iterator<DiffEntry> getUnstaged(final @Nullable String pathFilter) { Iterator<DiffEntry> unstaged = commandLocator.command(DiffWorkTree.class).setFilter(pathFilter) .setReportTrees(true).call(); return unstaged; } /** * @param pathFilter if specified, only changes that match the filter will be counted * @return the number differences between the work tree and the index based on the path filter. */ public DiffObjectCount countUnstaged(final @Nullable String pathFilter) { DiffObjectCount count = commandLocator.command(DiffCount.class).setOldVersion(Ref.STAGE_HEAD) .setNewVersion(Ref.WORK_HEAD).addFilter(pathFilter).call(); return count; } /** * Returns true if there are no unstaged changes, false otherwise */ public boolean isClean() { Optional<ObjectId> resolved = commandLocator.command(ResolveTreeish.class).setTreeish(Ref.STAGE_HEAD) .call(); return getTree().getId().equals(resolved.or(ObjectId.NULL)); } /** * @param path finds a {@link Node} for the feature at the given path in the index * @return the Node for the feature at the specified path if it exists in the work tree, * otherwise Optional.absent() */ public Optional<Node> findUnstaged(final String path) { Optional<NodeRef> nodeRef = commandLocator.command(FindTreeChild.class).setIndex(true).setParent(getTree()) .setChildPath(path).call(); if (nodeRef.isPresent()) { return Optional.of(nodeRef.get().getNode()); } else { return Optional.absent(); } } /** * Adds a single feature to the staging database. * * @param feature the feature to add * @param metadataId * @return the Node for the inserted feature */ private Node putInDatabase(final Feature feature, final ObjectId metadataId) { checkNotNull(feature); checkNotNull(metadataId); final RevFeature newFeature = new RevFeatureBuilder().build(feature); final ObjectId objectId = newFeature.getId(); final Envelope bounds = (ReferencedEnvelope) feature.getBounds(); final String nodeName = feature.getIdentifier().getID(); indexDatabase.put(newFeature); Node newObject = Node.create(nodeName, objectId, metadataId, TYPE.FEATURE, bounds); return newObject; } /** * Adds a collection of features to the staging database. * * @param parentTreepath path of features * @param objects the features to insert * @param progress the {@link ProgressListener} for this process * @param size number of features to add * @param target if specified, created {@link Node}s will be added to the list * @param defaultMetadataId */ private void putInDatabase(final String parentTreePath, final Iterator<? extends Feature> objects, final ProgressListener progress, final @Nullable Integer size, @Nullable final List<Node> target, final RevTreeBuilder parentTree, ObjectId defaultMetadataId) { checkNotNull(objects); checkNotNull(progress); checkNotNull(parentTree); Feature feature; int count = 0; progress.started(); Map<Name, ObjectId> revFeatureTypes = Maps.newHashMap(); while (objects.hasNext()) { count++; if (progress.isCanceled()) { return; } if (size != null) { progress.progress((float) (count * 100) / size.intValue()); } feature = objects.next(); final FeatureType featureType = feature.getType(); ObjectId revFeatureTypeId = revFeatureTypes.get(featureType.getName()); if (null == revFeatureTypeId) { RevFeatureType newFeatureType = RevFeatureType.build(featureType); revFeatureTypeId = newFeatureType.getId(); indexDatabase.put(newFeatureType); revFeatureTypes.put(featureType.getName(), revFeatureTypeId); } final Node objectRef = putInDatabase(feature, defaultMetadataId.equals(revFeatureTypeId) ? ObjectId.NULL : revFeatureTypeId); parentTree.put(objectRef); if (target != null) { target.add(objectRef); } } progress.complete(); } /** * @return a list of all the feature type names in the working tree * @see FindFeatureTypeTrees */ public List<NodeRef> getFeatureTypeTrees() { List<NodeRef> typeTrees = commandLocator.command(FindFeatureTypeTrees.class).setRootTreeRef(Ref.WORK_HEAD) .call(); return typeTrees; } /** * Updates the definition of a Feature type associated as default feature type to a given path. * It also modifies the metadataId associated to features under the passed path, which used the * previous default feature type. * * @param path the path * @param featureType the new feature type definition to set as default for the passed path */ public NodeRef updateTypeTree(final String treePath, final FeatureType featureType) { // TODO: This is not the optimal way of doing this. A better solution should be found. final RevTree workHead = getTree(); Optional<NodeRef> typeTreeRef = commandLocator.command(FindTreeChild.class).setIndex(true) .setParent(workHead).setChildPath(treePath).call(); Preconditions.checkArgument(typeTreeRef.isPresent(), "Tree does not exist: %s", treePath); Iterator<NodeRef> iter = commandLocator.command(LsTreeOp.class).setReference(treePath) .setStrategy(Strategy.DEPTHFIRST_ONLY_FEATURES).call(); final RevFeatureType revType = RevFeatureType.build(featureType); indexDatabase.put(revType); final ObjectId metadataId = revType.getId(); RevTreeBuilder treeBuilder = new RevTreeBuilder(indexDatabase); final RevTree newTree = treeBuilder.build(); ObjectId newWorkHeadId = commandLocator.command(WriteBack.class).setToIndex(true) .setAncestor(workHead.builder(indexDatabase)).setChildPath(treePath).setTree(newTree) .setMetadataId(metadataId).call(); updateWorkHead(newWorkHeadId); Map<ObjectId, FeatureBuilder> featureBuilders = Maps.newHashMap(); while (iter.hasNext()) { NodeRef noderef = iter.next(); RevFeature feature = commandLocator.command(RevObjectParse.class).setObjectId(noderef.objectId()) .call(RevFeature.class).get(); if (!featureBuilders.containsKey(noderef.getMetadataId())) { RevFeatureType ft = commandLocator.command(RevObjectParse.class) .setObjectId(noderef.getMetadataId()).call(RevFeatureType.class).get(); featureBuilders.put(noderef.getMetadataId(), new FeatureBuilder(ft)); } FeatureBuilder fb = featureBuilders.get(noderef.getMetadataId()); String parentPath = NodeRef.parentPath(NodeRef.appendChild(treePath, noderef.path())); insert(parentPath, fb.build(noderef.getNode().getName(), feature)); } return commandLocator.command(FindTreeChild.class).setIndex(true).setParent(getTree()) .setChildPath(treePath).call().get(); } }