diff --git a/pom.xml b/pom.xml index fd1a7ddea..719f268f0 100644 --- a/pom.xml +++ b/pom.xml @@ -148,6 +148,11 @@ Institute of Molecular Cell Biology and Genetics. eventbus 1.4 + + org.codehaus.groovy + groovy + 2.4.10 + @@ -155,6 +160,7 @@ Institute of Molecular Cell Biology and Genetics. junit test + diff --git a/src/main/java/org/scijava/grape/DefaultGrapeService.java b/src/main/java/org/scijava/grape/DefaultGrapeService.java new file mode 100644 index 000000000..a1fcdb775 --- /dev/null +++ b/src/main/java/org/scijava/grape/DefaultGrapeService.java @@ -0,0 +1,268 @@ +/* + * Copyright 2017 SciJava. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.scijava.grape; + +import groovy.grape.GrapeEngine; +import java.io.File; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.net.URI; +import java.nio.file.Paths; +import org.scijava.Context; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.service.AbstractService; +import org.scijava.service.Service; + +/** + * Facade to GrapeEngine. Kindly stolen from + * https://github.com/apache/groovy/blob/master/src/main/groovy/grape/Grape.java. + */ +@Plugin(type = Service.class) +public class DefaultGrapeService extends AbstractService implements GrapeService { + + public static final String AUTO_DOWNLOAD_SETTING = "autoDownload"; + public static final String DISABLE_CHECKSUMS_SETTING = "disableChecksums"; + public static final String SYSTEM_PROPERTIES_SETTING = "systemProperties"; + + @Parameter + private Context context; + + private boolean enableGrapes = Boolean.valueOf(System.getProperty("org.scijava.grape.enable", "true")); + private boolean enableAutoDownload = Boolean.valueOf(System.getProperty("org.scijava.grape.autoDownload", "true")); + private boolean disableChecksums = Boolean.valueOf(System.getProperty("org.scijava.grape.disableChecksums", "false")); + + private GrapeEngine grapeEngine = null; + + /** + * This is a static access kill-switch. All of the static shortcut methods in this class will + * not work if this property is set to false. By default it is set to true. + * + * @return + */ + @Override + public boolean getEnableGrapes() { + return enableGrapes; + } + + /** + * This is a static access kill-switch. All of the static shortcut methods in this class will + * not work if this property is set to false. By default it is set to true. + * + * @param enableGrapes + */ + @Override + public void setEnableGrapes(boolean enableGrapes) { + this.enableGrapes = enableGrapes; + } + + /** + * This is a static access auto download enabler. It will set the 'autoDownload' value to the + * passed in arguments map if not already set. If 'autoDownload' is set the value will not be + * adjusted. + *

+ * This applies to the grab and resolve calls. + *

+ * If it is set to false, only previously downloaded grapes will be used. This may cause failure + * in the grape call if the library has not yet been downloaded + *

+ * If it is set to true, then any jars not already downloaded will automatically be downloaded. + * Also, any versions expressed as a range will be checked for new versions and downloaded (with + * dependencies) if found. + *

+ * By default it is set to true. + */ + @Override + public boolean getEnableAutoDownload() { + return enableAutoDownload; + } + + /** + * This is a static access auto download enabler. It will set the 'autoDownload' value to the + * passed in arguments map if not already set. If 'autoDownload' is set the value will not be + * adjusted. + *

+ * This applies to the grab and resolve calls. + *

+ * If it is set to false, only previously downloaded grapes will be used. This may cause failure + * in the grape call if the library has not yet been downloaded. + *

+ * If it is set to true, then any jars not already downloaded will automatically be downloaded. + * Also, any versions expressed as a range will be checked for new versions and downloaded (with + * dependencies) if found. By default it is set to true. + * + * @param enableAutoDownload + */ + @Override + public void setEnableAutoDownload(boolean enableAutoDownload) { + this.enableAutoDownload = enableAutoDownload; + } + + /** + * Global flag to ignore checksums. By default it is set to false. + */ + @Override + public boolean getDisableChecksums() { + return disableChecksums; + } + + /** + * Set global flag to ignore checksums. By default it is set to false. + * + * @param disableChecksums + */ + @Override + public void setDisableChecksums(boolean disableChecksums) { + this.disableChecksums = disableChecksums; + } + + @Override + public GrapeEngine getGrapeEngine() { + if (this.grapeEngine == null) { + + // Set some settings for Ivy + System.setProperty("groovy.grape.report.downloads", "true"); + System.setProperty("grape.root", Paths.get(System.getProperty("user.home"), ".scijava").toString()); + + // Initialize the GrapeEngine + this.grapeEngine = new GrapeScijava(); + } + return grapeEngine; + } + + @Override + public void grab(String endorsed) { + if (enableGrapes) { + GrapeEngine instance = getGrapeEngine(); + if (instance != null) { + instance.grab(endorsed); + } + } + } + + @Override + public void grab(Map dependency) { + if (enableGrapes) { + GrapeEngine instance = getGrapeEngine(); + if (instance != null) { + if (!dependency.containsKey(AUTO_DOWNLOAD_SETTING)) { + dependency.put(AUTO_DOWNLOAD_SETTING, enableAutoDownload); + } + if (!dependency.containsKey(DISABLE_CHECKSUMS_SETTING)) { + dependency.put(DISABLE_CHECKSUMS_SETTING, disableChecksums); + } + + if (!dependency.keySet().contains("classLoader")) { + dependency.put("classLoader", this.context().getClass().getClassLoader()); + } + + instance.grab(dependency); + } + } + } + + @Override + public void grab(Map args, Map... dependencies) { + if (enableGrapes) { + GrapeEngine instance = getGrapeEngine(); + if (instance != null) { + if (!args.containsKey(AUTO_DOWNLOAD_SETTING)) { + args.put(AUTO_DOWNLOAD_SETTING, enableAutoDownload); + } + if (!args.containsKey(DISABLE_CHECKSUMS_SETTING)) { + args.put(DISABLE_CHECKSUMS_SETTING, disableChecksums); + } + + if (!args.keySet().contains("classLoader")) { + args.put("classLoader", this.context().getClass().getClassLoader()); + } + + instance.grab(args, dependencies); + } + } + } + + @Override + public Map>> enumerateGrapes() { + Map>> grapes = null; + if (enableGrapes) { + GrapeEngine instance = getGrapeEngine(); + if (instance != null) { + grapes = instance.enumerateGrapes(); + } + } + if (grapes == null) { + return Collections.emptyMap(); + } else { + return grapes; + } + } + + @Override + public URI[] resolve(Map args, Map... dependencies) { + return resolve(args, null, dependencies); + } + + @Override + public URI[] resolve(Map args, List depsInfo, Map... dependencies) { + URI[] uris = null; + if (enableGrapes) { + GrapeEngine instance = getGrapeEngine(); + if (instance != null) { + if (!args.containsKey(AUTO_DOWNLOAD_SETTING)) { + args.put(AUTO_DOWNLOAD_SETTING, enableAutoDownload); + } + if (!args.containsKey(DISABLE_CHECKSUMS_SETTING)) { + args.put(DISABLE_CHECKSUMS_SETTING, disableChecksums); + } + uris = instance.resolve(args, depsInfo, dependencies); + } + } + if (uris == null) { + return new URI[0]; + } else { + return uris; + } + } + + @Override + public Map[] listDependencies(ClassLoader cl) { + Map[] maps = null; + if (enableGrapes) { + GrapeEngine instance = getGrapeEngine(); + if (instance != null) { + maps = instance.listDependencies(cl); + } + } + if (maps == null) { + return new Map[0]; + } else { + return maps; + } + + } + + @Override + public void addResolver(Map args) { + if (enableGrapes) { + GrapeEngine instance = getGrapeEngine(); + if (instance != null) { + instance.addResolver(args); + } + } + } +} diff --git a/src/main/java/org/scijava/grape/GrapeScijava.java b/src/main/java/org/scijava/grape/GrapeScijava.java new file mode 100644 index 000000000..1ea9d00e7 --- /dev/null +++ b/src/main/java/org/scijava/grape/GrapeScijava.java @@ -0,0 +1,124 @@ +/* + * Copyright 2017 SciJava. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.scijava.grape; + +import groovy.grape.GrapeIvy; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.codehaus.groovy.reflection.ReflectionUtils; + +/** + * I had to extend GrapeIvy to use any CLassLoader (not only GroovyClassLoader). + * + * @author Hadrien Mary + */ +public class GrapeScijava extends GrapeIvy { + + Map> exclusiveGrabArgs = new HashMap>() { + { + put("group", Arrays.asList("groupId", "organisation", "organization", "org")); + put("groupId", Arrays.asList("group", "organisation", "organization", "org")); + put("organisation", Arrays.asList("group", "groupId", "organization", "org")); + put("organization", Arrays.asList("group", "groupId", "organisation", "org")); + put("org", Arrays.asList("group", "groupId", "organisation", "organization")); + put("module", Arrays.asList("artifactId", "artifact")); + put("artifactId", Arrays.asList("module", "artifact")); + put("artifact", Arrays.asList("module", "artifactId")); + put("version", Arrays.asList("revision", "rev")); + put("revision", Arrays.asList("version", "rev")); + put("rev", Arrays.asList("version", "revision")); + put("conf", Arrays.asList("scope", "configuration")); + put("scope", Arrays.asList("conf", "configuration")); + put("configuration", Arrays.asList("conf", "scope")); + + } + }; + + @Override + public ClassLoader chooseClassLoader(Map args) { + ClassLoader loader = (ClassLoader) args.get("classLoader"); + + if (this.isValidTargetClassLoader(loader)) { + if (args.get("refObject") == null) { + if (!args.keySet().contains("calleeDepth")) { + loader = ReflectionUtils.getCallingClass((int) args.get("calleeDepth")).getClassLoader(); + } else { + loader = ReflectionUtils.getCallingClass(1).getClassLoader(); + } + } + + while (loader != null && !this.isValidTargetClassLoader(loader)) { + loader = loader.getParent(); + } + //if (!isValidTargetClassLoader(loader)) { + // loader = Thread.currentThread().contextClassLoader + //} + //if (!isValidTargetClassLoader(loader)) { + // loader = GrapeIvy.class.classLoader + //} + if (!isValidTargetClassLoader(loader)) { + throw new RuntimeException("No suitable ClassLoader found for grab"); + } + } + return loader; + } + + private boolean isValidTargetClassLoader(ClassLoader loader) { + if (loader != null) { + return loader.getClass() == ClassLoader.class; + } else { + return false; + } + } + + private boolean isValidTargetClassLoaderClass(Class loaderClass) { + return isValidTargetClassLoader(loaderClass.getClassLoader()); + } + + @Override + public File getLocalGrapeConfig() { + + InputStream configStream = GrapeScijava.class.getResourceAsStream("/org/scijava/grape/scijavaGrapeConfig.xml"); + + // Copy the config file to a temporary file since + // the Groovy API only accept File object. + File configTempFile = new File(""); + try { + configTempFile = File.createTempFile("scijavaGrapeConfig", ".xml"); + try { + Files.copy(configStream, configTempFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException ex) { + Logger.getLogger(GrapeScijava.class.getName()).log(Level.SEVERE, null, ex); + } + } catch (IOException ex) { + Logger.getLogger(GrapeScijava.class.getName()).log(Level.SEVERE, null, ex); + } + + return configTempFile; + } +} diff --git a/src/main/java/org/scijava/grape/GrapeService.java b/src/main/java/org/scijava/grape/GrapeService.java new file mode 100644 index 000000000..6195984da --- /dev/null +++ b/src/main/java/org/scijava/grape/GrapeService.java @@ -0,0 +1,108 @@ +/* + * Copyright 2017 SciJava. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.scijava.grape; + +import groovy.grape.GrapeEngine; +import java.net.URI; +import java.util.List; +import java.util.Map; +import org.scijava.service.SciJavaService; + +/** + * + * @author Hadrien Mary + */ +public interface GrapeService extends SciJavaService { + + void addResolver(Map args); + + Map>> enumerateGrapes(); + + /** + * Global flag to ignore checksums. By default it is set to false. + * @return + */ + boolean getDisableChecksums(); + + /** + * This is a static access auto download enabler. It will set the 'autoDownload' value to the + * passed in arguments map if not already set. If 'autoDownload' is set the value will not be + * adjusted. + *

+ * This applies to the grab and resolve calls. + *

+ * If it is set to false, only previously downloaded grapes will be used. This may cause failure + * in the grape call if the library has not yet been downloaded + *

+ * If it is set to true, then any jars not already downloaded will automatically be downloaded. + * Also, any versions expressed as a range will be checked for new versions and downloaded (with + * dependencies) if found. + *

+ * By default it is set to true. + * @return + */ + boolean getEnableAutoDownload(); + + /** + * This is a static access kill-switch. All of the static shortcut methods in this class will + * not work if this property is set to false. By default it is set to true. + */ + boolean getEnableGrapes(); + + GrapeEngine getGrapeEngine(); + + void grab(String endorsed); + + void grab(Map dependency); + + void grab(Map args, Map... dependencies); + + Map[] listDependencies(ClassLoader cl); + + URI[] resolve(Map args, Map... dependencies); + + URI[] resolve(Map args, List depsInfo, Map... dependencies); + + /** + * Set global flag to ignore checksums. By default it is set to false. + */ + void setDisableChecksums(boolean disableChecksums); + + /** + * This is a static access auto download enabler. It will set the 'autoDownload' value to the + * passed in arguments map if not already set. If 'autoDownload' is set the value will not be + * adjusted. + *

+ * This applies to the grab and resolve calls. + *

+ * If it is set to false, only previously downloaded grapes will be used. This may cause failure + * in the grape call if the library has not yet been downloaded. + *

+ * If it is set to true, then any jars not already downloaded will automatically be downloaded. + * Also, any versions expressed as a range will be checked for new versions and downloaded (with + * dependencies) if found. By default it is set to true. + */ + void setEnableAutoDownload(boolean enableAutoDownload); + + /** + * This is a static access kill-switch. All of the static shortcut methods in this class will + * not work if this property is set to false. By default it is set to true. + * + * @param enableGrapes + */ + void setEnableGrapes(boolean enableGrapes); + +} diff --git a/src/main/java/org/scijava/script/ScriptInfo.java b/src/main/java/org/scijava/script/ScriptInfo.java index b93751a31..1bad998ee 100644 --- a/src/main/java/org/scijava/script/ScriptInfo.java +++ b/src/main/java/org/scijava/script/ScriptInfo.java @@ -56,6 +56,7 @@ import org.scijava.NullContextException; import org.scijava.command.Command; import org.scijava.convert.ConvertService; +import org.scijava.grape.GrapeService; import org.scijava.log.LogService; import org.scijava.module.AbstractModuleInfo; import org.scijava.module.DefaultMutableModuleItem; @@ -97,7 +98,7 @@ public class ScriptInfo extends AbstractModuleInfo implements Contextual { @Parameter private ConvertService convertService; - + /** True iff the return value should be appended as an output. */ private boolean appendReturnValue; @@ -421,7 +422,24 @@ private void parseParam(final String param, attrs.put("type", tokens[0]); typeName = tokens[1]; varName = tokens[2]; - } + } else if (tokens.length == 1) { + // Check for Grab or GrabResolver + GrapeService grape = this.context().getService(GrapeService.class); + typeName = tokens[0]; + varName = null; + + switch (typeName) { + case "Grab": + grape.grab(attrs); + return; + case "GrabResolver": + grape.resolve(attrs); + return; + default: + throw new ScriptException("Invalid parameter: " + param); + } + // Quit because we don't want to use addItem() + } else { // assume syntax: checkValid(tokens.length >= 2, param); diff --git a/src/main/resources/org/scijava/grape/scijavaGrapeConfig.xml b/src/main/resources/org/scijava/grape/scijavaGrapeConfig.xml new file mode 100644 index 000000000..8d1823345 --- /dev/null +++ b/src/main/resources/org/scijava/grape/scijavaGrapeConfig.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/org/scijava/ContextCreationTest.java b/src/test/java/org/scijava/ContextCreationTest.java index 8de76276e..c98f2e122 100644 --- a/src/test/java/org/scijava/ContextCreationTest.java +++ b/src/test/java/org/scijava/ContextCreationTest.java @@ -94,6 +94,7 @@ public void testFull() { org.scijava.convert.DefaultConvertService.class, org.scijava.display.DefaultDisplayService.class, org.scijava.event.DefaultEventHistory.class, + org.scijava.grape.DefaultGrapeService.class, org.scijava.input.DefaultInputService.class, org.scijava.io.DefaultDataHandleService.class, org.scijava.io.DefaultIOService.class,