/* Copyright (c) 2013-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.repository; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import org.locationtech.geogig.api.Context; import org.locationtech.geogig.api.Node; import org.locationtech.geogig.api.NodeRef; import org.locationtech.geogig.api.ObjectId; import org.locationtech.geogig.api.Platform; import org.locationtech.geogig.api.RevFeatureType; import org.locationtech.geogig.api.RevFeatureTypeImpl; import org.locationtech.geogig.api.RevObject; import org.locationtech.geogig.api.RevObject.TYPE; import org.locationtech.geogig.api.RevTree; import org.locationtech.geogig.api.plumbing.FindOrCreateSubtree; import org.locationtech.geogig.api.plumbing.FindTreeChild; import org.locationtech.geogig.storage.ObjectDatabase; import org.opengis.feature.Feature; import org.opengis.feature.type.FeatureType; import org.opengis.geometry.BoundingBox; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Throwables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.vividsolutions.jts.geom.Envelope; class WorkingTreeInsertHelper { private final ObjectDatabase indexDatabase; private final Context context; private final RevTree workHead; private Function<Feature, String> treePathResolver; private final Map<String, RevTreeBuilder2> treeBuilders = Maps.newHashMap(); private final ExecutorService executorService; public WorkingTreeInsertHelper(ObjectDatabase db, Context context, RevTree workHead, final Function<Feature, String> treePathResolver, final ExecutorService executorService) { this.indexDatabase = db; this.context = context; this.workHead = workHead; this.treePathResolver = treePathResolver; this.executorService = executorService; } public List<String> getTreeNames() { return new ArrayList<String>(treeBuilders.keySet()); } public Node put(final ObjectId revFeatureId, final Feature feature) { final RevTreeBuilder2 treeBuilder = getTreeBuilder(feature); String fid = feature.getIdentifier().getID(); BoundingBox bounds = feature.getBounds(); FeatureType type = feature.getType(); final Node node = treeBuilder.putFeature(revFeatureId, fid, bounds, type); return node; } public void remove(FeatureToDelete feature) { final RevTreeBuilder2 treeBuilder = getTreeBuilder(feature); String fid = feature.getIdentifier().getID(); treeBuilder.removeFeature(fid); } private RevTreeBuilder2 getTreeBuilder(final Feature feature) { final String treePath = treePathResolver.apply(feature); RevTreeBuilder2 builder = treeBuilders.get(treePath); if (builder == null) { FeatureType type = feature.getType(); builder = createBuilder(treePath, type); treeBuilders.put(treePath, builder); } return builder; } private NodeRef findOrCreateTree(final String treePath, final FeatureType type) { RevTree tree = context.command(FindOrCreateSubtree.class).setChildPath(treePath) .setIndex(true).setParent(workHead).setParentPath(NodeRef.ROOT).call(); ObjectId metadataId = ObjectId.NULL; if (type != null) { RevFeatureType revFeatureType = RevFeatureTypeImpl.build(type); if (tree.isEmpty()) { indexDatabase.put(revFeatureType); } metadataId = revFeatureType.getId(); } Envelope bounds = SpatialOps.boundsOf(tree); Node node = Node.create(NodeRef.nodeFromPath(treePath), tree.getId(), metadataId, TYPE.TREE, bounds); String parentPath = NodeRef.parentPath(treePath); return new NodeRef(node, parentPath, ObjectId.NULL); } private RevTreeBuilder2 createBuilder(String treePath, FeatureType type) { final NodeRef treeRef = findOrCreateTree(treePath, type); final ObjectId treeId = treeRef.objectId(); final RevTree origTree = indexDatabase.getTree(treeId); ObjectId defaultMetadataId = treeRef.getMetadataId(); RevTreeBuilder2 builder; Platform platform = context.platform(); builder = new RevTreeBuilder2(indexDatabase, origTree, defaultMetadataId, platform, executorService); return builder; } public Map<NodeRef, RevTree> buildTrees() { final Map<NodeRef, RevTree> result = Maps.newConcurrentMap(); List<AsyncBuildTree> tasks = Lists.newArrayList(); for (Entry<String, RevTreeBuilder2> builderEntry : treeBuilders.entrySet()) { final String treePath = builderEntry.getKey(); final RevTreeBuilder2 builder = builderEntry.getValue(); tasks.add(new AsyncBuildTree(treePath, builder, result)); } try { executorService.invokeAll(tasks); } catch (InterruptedException e) { throw Throwables.propagate(e); } return result; } private class AsyncBuildTree implements Callable<Void> { private String treePath; private RevTreeBuilder2 builder; private Map<NodeRef, RevTree> target; AsyncBuildTree(final String treePath, final RevTreeBuilder2 builder, final Map<NodeRef, RevTree> target) { this.treePath = treePath; this.builder = builder; this.target = target; } @Override public Void call() { RevTree tree = builder.build(); Node treeNode; { ObjectId treeMetadataId = builder.getDefaultMetadataId(); String name = NodeRef.nodeFromPath(treePath); ObjectId oid = tree.getId(); Envelope bounds = SpatialOps.boundsOf(tree); treeNode = Node.create(name, oid, treeMetadataId, RevObject.TYPE.TREE, bounds); } final String parentPath = NodeRef.parentPath(treePath); final ObjectId parentMetadataId; if (NodeRef.ROOT.equals(parentPath)) { parentMetadataId = ObjectId.NULL; } else { Optional<NodeRef> parentRef = context.command(FindTreeChild.class) .setChildPath(parentPath).setIndex(true).setParent(workHead) .setParentPath(NodeRef.ROOT).call(); parentMetadataId = parentRef.isPresent() ? parentRef.get().getMetadataId() : ObjectId.NULL; } NodeRef newTreeRef = new NodeRef(treeNode, parentPath, parentMetadataId); target.put(newTreeRef, tree); return null; } } }