/* 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:
* Victor Olaya (Boundless) - initial implementation
*/
package org.locationtech.geogig.api.hooks;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.locationtech.geogig.api.AbstractGeoGigOp;
import org.locationtech.geogig.api.FeatureBuilder;
import org.locationtech.geogig.api.NodeRef;
import org.locationtech.geogig.api.RevFeature;
import org.locationtech.geogig.api.RevFeatureType;
import org.locationtech.geogig.api.plumbing.ResolveFeatureType;
import org.locationtech.geogig.api.plumbing.RevObjectParse;
import org.locationtech.geogig.api.plumbing.diff.DiffEntry;
import org.locationtech.geogig.api.plumbing.diff.DiffEntry.ChangeType;
import org.locationtech.geogig.api.porcelain.DiffOp;
import org.locationtech.geogig.repository.Repository;
import org.opengis.feature.Feature;
import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
/**
* This class contains a facade for some of the most common operations to perform on a GeoGig
* repository. It is designed to be used mainly from hooks written in a scripting language supported
* by GeoGig, to give an easier and more detailed access to data and elements in the repository
*
*/
public class GeoGigAPI {
private Repository repository;
/**
* @param repo the command locator to use when finding commands
*/
@Inject
public GeoGigAPI(Repository repository) {
this.repository = repository;
}
/**
* A convenience method to throw an exception indicating that the operation after a hook script
* should not be executed
*
* @throws CannotRunGeogigOperationException
*/
public void throwHookException(String msg) throws CannotRunGeogigOperationException {
throw new CannotRunGeogigOperationException(msg);
}
/**
* Returns an array with the features that are staged and ready to be commited. If noDeletions
* is true, it doesn't include features to be removed, only those ones to be added or modified,
* so it can be used to check the new data that will get commited into the repository
*
* @return
*/
public Feature[] getFeaturesToCommit(String path, boolean noDeletions) {
DiffOp diffOp = repository.command(DiffOp.class);
diffOp.setCompareIndex(true);
diffOp.setFilter(path);
Iterator<DiffEntry> diffs = diffOp.call();
List<Feature> list = Lists.newArrayList();
while (diffs.hasNext()) {
DiffEntry diff = diffs.next();
if (!diff.changeType().equals(ChangeType.REMOVED) || !noDeletions) {
RevFeature revFeature = repository.command(RevObjectParse.class)
.setObjectId(diff.newObjectId()).call(RevFeature.class).get();
RevFeatureType revFeatureType = repository.command(RevObjectParse.class)
.setObjectId(diff.getNewObject().getMetadataId())
.call(RevFeatureType.class).get();
FeatureBuilder builder = new FeatureBuilder(revFeatureType);
list.add(builder.build(diff.getNewObject().name(), revFeature));
}
}
return list.toArray(new Feature[0]);
}
public Feature[] getUnstagedFeatures(String path, boolean noDeletions) {
Iterator<DiffEntry> diffs = repository.workingTree().getUnstaged(path);
List<Feature> list = Lists.newArrayList();
while (diffs.hasNext()) {
DiffEntry diff = diffs.next();
if (!diff.changeType().equals(ChangeType.REMOVED) || !noDeletions) {
RevFeature revFeature = repository.command(RevObjectParse.class)
.setObjectId(diff.newObjectId()).call(RevFeature.class).get();
RevFeatureType revFeatureType = repository.command(RevObjectParse.class)
.setObjectId(diff.getNewObject().getMetadataId())
.call(RevFeatureType.class).get();
FeatureBuilder builder = new FeatureBuilder(revFeatureType);
list.add(builder.build(diff.getNewObject().name(), revFeature));
}
}
return list.toArray(new Feature[0]);
}
/**
* Returns a feature from the Head of the repository, given its full path
*
* Returns null if the given path doesn't resolve to a feature
*
* @param path the path to the feature to return
*/
public Feature getFeatureFromHead(String path) {
String name = NodeRef.nodeFromPath(path);
String refSpec = "HEAD:" + path;
Optional<RevFeature> revFeature = repository.command(RevObjectParse.class)
.setRefSpec(refSpec).call(RevFeature.class);
if (revFeature.isPresent()) {
RevFeatureType revFeatureType = repository.command(ResolveFeatureType.class)
.setRefSpec(refSpec).call().get();
FeatureBuilder builder = new FeatureBuilder(revFeatureType);
return builder.build(name, revFeature.get());
} else {
return null;
}
}
/**
* Returns a feature from the working tree of the repository, given its full path
*
* Returns null if the given path doesn't resolve to a feature
*
* @param path the path to the feature to return
*/
public Feature getFeatureFromWorkingTree(String path) {
String name = NodeRef.nodeFromPath(path);
String refSpec = "WORK_HEAD:" + path;
Optional<RevFeature> revFeature = repository.command(RevObjectParse.class)
.setRefSpec(refSpec).call(RevFeature.class);
if (revFeature.isPresent()) {
RevFeatureType revFeatureType = repository.command(ResolveFeatureType.class)
.setRefSpec(refSpec).call().get();
FeatureBuilder builder = new FeatureBuilder(revFeatureType);
return builder.build(name, revFeature.get());
} else {
return null;
}
}
/**
* Runs a {@link AbstractGeoGigOp command} given by its class name and map of arguments.
*
* @param className the name of the {@link AbstractGeoGigOp command} to run
* @param params expected an instanceo of {@code java.util.Map} or
* {@code sun.org.mozilla.javascript.internal.NativeObject} (which may or may not
* implement java.util.Map depending on the Java/JVM version)
* @return the result of calling the named command with the given parameters.
* @throws ClassNotFoundException if no command named after {@code className} exists
*/
@SuppressWarnings("unchecked")
public Object run(String className, Object params) throws ClassNotFoundException {
Map<String, Object> paramsMap;
if (params instanceof Map) {
paramsMap = (Map<String, Object>) params;
} else {
paramsMap = java6NativeObjectToMap(params);
}
return runCommand(className, paramsMap);
}
/**
* Converts an argument passed by a script to a Map.
* <p>
* This method is only needed when running with Oracle JDK 6, since its version of NativeObject
* does not implement java.util.Map. Oracle JDK 7+ and OpenJDK6+ versions of NativeObject
* already implement the java.util.Map interface.
* <p>
* Impl. detail: due to differences in package naming between oracle and
*/
private Map<String, Object> java6NativeObjectToMap(Object params) {
Map<String, Object> paramsMap = new HashMap<String, Object>();
try {
Class<?> NativeObject;
Class<?> Scriptable;
// Oracle JDK 6 location of the needed classes
NativeObject = Class.forName("sun.org.mozilla.javascript.internal.NativeObject");
Scriptable = Class.forName("sun.org.mozilla.javascript.internal.Scriptable");
Method getPropertyIds = NativeObject.getMethod("getPropertyIds", Scriptable);
Method getProperty = NativeObject.getMethod("getProperty", Scriptable, String.class);
Object[] propertyIds = (Object[]) getPropertyIds.invoke(null, params);
paramsMap = new HashMap<String, Object>();
for (Object pid : propertyIds) {
String key = String.valueOf(pid);
Object value = getProperty.invoke(null, params, key);
paramsMap.put(key, value);
}
return paramsMap;
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
/**
* Runs the {@link AbstractGeoGigOp command} given by its {@code className} with the provided
* {@code parameters}
*/
private Object runCommand(String className, Map<String, Object> parameters)
throws ClassNotFoundException {
@SuppressWarnings("unchecked")
Class<AbstractGeoGigOp<?>> clazz = (Class<AbstractGeoGigOp<?>>) Class.forName(className);
AbstractGeoGigOp<?> operation = repository.command(clazz);
@SuppressWarnings("unused")
Map<String, Object> oldParams = Scripting.getParamMap(operation);
Scripting.setParamMap(parameters, operation);
return operation.call();
}
}