You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by js...@apache.org on 2017/08/21 18:59:43 UTC
[1/2] geode git commit: GEODE-3313: Test utility supports building
jar files with multiple classes
Repository: geode
Updated Branches:
refs/heads/release/1.2.1 ce3ed8ace -> aa36d3c3c
GEODE-3313: Test utility supports building jar files with multiple classes
(cherry picked from commit 06b839c)
Project: http://git-wip-us.apache.org/repos/asf/geode/repo
Commit: http://git-wip-us.apache.org/repos/asf/geode/commit/df954000
Tree: http://git-wip-us.apache.org/repos/asf/geode/tree/df954000
Diff: http://git-wip-us.apache.org/repos/asf/geode/diff/df954000
Branch: refs/heads/release/1.2.1
Commit: df95400032a08040a7b71f0d8d3752fedc9b04c8
Parents: ce3ed8a
Author: Jared Stewart <js...@pivotal.io>
Authored: Tue Aug 8 10:11:07 2017 -0700
Committer: Jared Stewart <js...@pivotal.io>
Committed: Mon Aug 21 11:43:27 2017 -0700
----------------------------------------------------------------------
geode-junit/build.gradle | 6 +-
.../geode/test/compiler/ClassNameExtractor.java | 33 +++++
.../geode/test/compiler/CompiledSourceCode.java | 35 ++++++
.../apache/geode/test/compiler/JarBuilder.java | 120 ++++++++++++++++++
.../geode/test/compiler/JavaCompiler.java | 123 ++++++++++++++++++
.../test/compiler/UncompiledSourceCode.java | 71 +++++++++++
.../test/compiler/ClassNameExtractorTest.java | 54 ++++++++
.../geode/test/compiler/JarBuilderTest.java | 126 +++++++++++++++++++
.../geode/test/compiler/JavaCompilerTest.java | 78 ++++++++++++
.../test/compiler/UncompiledSourceCodeTest.java | 43 +++++++
.../geode/test/compiler/AbstractClass.java | 18 +++
.../geode/test/compiler/ConcreteClass.java | 19 +++
12 files changed, 725 insertions(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/geode/blob/df954000/geode-junit/build.gradle
----------------------------------------------------------------------
diff --git a/geode-junit/build.gradle b/geode-junit/build.gradle
index e47095f..7c533ad 100755
--- a/geode-junit/build.gradle
+++ b/geode-junit/build.gradle
@@ -19,7 +19,11 @@ dependencies {
compile 'com.jayway.jsonpath:json-path:' + project.'json-path.version'
testCompile 'commons-lang:commons-lang:' + project.'commons-lang.version'
testCompile 'com.google.guava:guava:' + project.'guava.version'
- testCompile 'org.assertj:assertj-core:' + project.'assertj-core.version'
+ compile 'org.assertj:assertj-core:' + project.'assertj-core.version'
+ compile 'commons-io:commons-io:' + project.'commons-io.version'
+ compile 'commons-lang:commons-lang:' + project.'commons-lang.version'
+ compile 'com.google.guava:guava:' + project.'guava.version'
+
compile('junit:junit:' + project.'junit.version') {
exclude module: 'hamcrest-core'
http://git-wip-us.apache.org/repos/asf/geode/blob/df954000/geode-junit/src/main/java/org/apache/geode/test/compiler/ClassNameExtractor.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/main/java/org/apache/geode/test/compiler/ClassNameExtractor.java b/geode-junit/src/main/java/org/apache/geode/test/compiler/ClassNameExtractor.java
new file mode 100644
index 0000000..c48224c
--- /dev/null
+++ b/geode-junit/src/main/java/org/apache/geode/test/compiler/ClassNameExtractor.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.test.compiler;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class ClassNameExtractor {
+ private static final Pattern EXTRACT_CLASS_NAME_REGEX =
+ Pattern.compile("(?:public|private|protected)* *(?:abstract)* *(?:class|interface) +(\\w+)");
+
+ public String extractFromSourceCode(String sourceCode) {
+ Matcher m = EXTRACT_CLASS_NAME_REGEX.matcher(sourceCode);
+ if (m.find()) {
+ return m.group(1);
+ } else {
+ throw new IllegalArgumentException(
+ String.format("Unable to parse class or interface name from: \n'%s'", sourceCode));
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/geode/blob/df954000/geode-junit/src/main/java/org/apache/geode/test/compiler/CompiledSourceCode.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/main/java/org/apache/geode/test/compiler/CompiledSourceCode.java b/geode-junit/src/main/java/org/apache/geode/test/compiler/CompiledSourceCode.java
new file mode 100644
index 0000000..c1bfa3d
--- /dev/null
+++ b/geode-junit/src/main/java/org/apache/geode/test/compiler/CompiledSourceCode.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.test.compiler;
+
+public class CompiledSourceCode {
+
+ /**
+ * Fully qualified classname in a format suitable for Class.forName
+ */
+ public String className;
+
+ public byte[] compiledBytecode;
+
+ public CompiledSourceCode(String className, byte[] aBytes) {
+ this.className = className.replace('/', '.');
+ this.compiledBytecode = aBytes;
+ }
+
+ @Override
+ public String toString() {
+ return className;
+ }
+}
http://git-wip-us.apache.org/repos/asf/geode/blob/df954000/geode-junit/src/main/java/org/apache/geode/test/compiler/JarBuilder.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/main/java/org/apache/geode/test/compiler/JarBuilder.java b/geode-junit/src/main/java/org/apache/geode/test/compiler/JarBuilder.java
new file mode 100644
index 0000000..bd9222e
--- /dev/null
+++ b/geode-junit/src/main/java/org/apache/geode/test/compiler/JarBuilder.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.test.compiler;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+
+/**
+ * This class accepts java source code in the format of .java source files or strings containing the
+ * contents of .java source files, and compiles the given source code into a jar file specified by
+ * the user.
+ *
+ * <p>
+ * Example of usage:
+ *
+ * <pre>
+ * @Rule
+ * public TemporaryFolder temporaryFolder= new TemporaryFolder();
+ *
+ * @Test
+ * public void buildJarUsingStrings() {
+ * File tempDir = temporaryFolder.getRoot()
+ * JarBuilder jarBuilder = new JarBuilder(tempDir);
+ * File outputJar = new File("output.jar");
+ *
+ * String classInFooBarPackage = "package foo.bar; public class ClassA {int n = 10;}";
+ * String classInDefaultPackage = "public class ClassB {}";
+ * jarBuilder.buildJar(outputJar, classInFooBarPackage, classInDefaultPackage);
+ * }
+ *
+ * @Test
+ * public void buildJarUsingFiles() {
+ * File tempDir = temporaryFolder.getRoot()
+ * JarBuilder jarBuilder = new JarBuilder(tempDir);
+ * File outputJar = new File("output.jar");
+ *
+ * File sourceFileOne = new File("ClassA.java");
+ * File sourceFileTwo = new File("ClassB.java");
+ * jarBuilder.buildJar(outputJar, sourceFileOne, sourceFileTwo);
+ * }
+ *
+ * @Test
+ * public void buildJarUsingClassNames() {
+ * File tempDir = temporaryFolder.getRoot()
+ * JarBuilder jarBuilder = new JarBuilder(tempDir);
+ * File outputJar = new File("output.jar");
+ *
+ * String classInFooBarPackage = "foo.bar.ClassInFooBarPackage";
+ * String classInDefaultPackage = "ClassInDefaultPackage";
+ * jarBuilder.buildJar(outputJar, classInFooBarPackage, classInDefaultPackage);
+ * }
+ * </pre>
+ **/
+public class JarBuilder {
+ private final JavaCompiler javaCompiler = new JavaCompiler();
+
+ public void buildJarFromClassNames(File outputJarFile, String... classNames) throws IOException {
+ UncompiledSourceCode[] uncompiledSourceCodes = Arrays.stream(classNames)
+ .map(UncompiledSourceCode::fromClassName).toArray(UncompiledSourceCode[]::new);
+
+ List<CompiledSourceCode> compiledSourceCodes = javaCompiler.compile(uncompiledSourceCodes);
+
+ buildJar(outputJarFile, compiledSourceCodes);
+ }
+
+ public void buildJar(File outputJarFile, String... sourceFileContents) throws IOException {
+ List<CompiledSourceCode> compiledSourceCodes = javaCompiler.compile(sourceFileContents);
+
+ buildJar(outputJarFile, compiledSourceCodes);
+ }
+
+ public void buildJar(File outputJarFile, File... sourceFiles) throws IOException {
+ List<CompiledSourceCode> compiledSourceCodes = javaCompiler.compile(sourceFiles);
+
+ buildJar(outputJarFile, compiledSourceCodes);
+ }
+
+ private void buildJar(File outputJarFile, List<CompiledSourceCode> compiledSourceCodes)
+ throws IOException {
+ assertThat(outputJarFile).doesNotExist();
+
+ try (FileOutputStream outputStream = new FileOutputStream(outputJarFile)) {
+ JarOutputStream jarOutputStream = new JarOutputStream(outputStream);
+ for (CompiledSourceCode compiledSource : compiledSourceCodes) {
+
+ String formattedName = compiledSource.className.replace(".", "/");
+ if (!formattedName.endsWith(".class")) {
+ formattedName = formattedName.concat(".class");
+ }
+
+ JarEntry entry = new JarEntry(formattedName);
+ entry.setTime(System.currentTimeMillis());
+ jarOutputStream.putNextEntry(entry);
+ jarOutputStream.write(compiledSource.compiledBytecode);
+ jarOutputStream.closeEntry();
+ }
+ jarOutputStream.close();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/geode/blob/df954000/geode-junit/src/main/java/org/apache/geode/test/compiler/JavaCompiler.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/main/java/org/apache/geode/test/compiler/JavaCompiler.java b/geode-junit/src/main/java/org/apache/geode/test/compiler/JavaCompiler.java
new file mode 100644
index 0000000..8449605
--- /dev/null
+++ b/geode-junit/src/main/java/org/apache/geode/test/compiler/JavaCompiler.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.test.compiler;
+
+import static java.util.stream.Collectors.toList;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Stream;
+
+import javax.tools.ToolProvider;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+import org.apache.commons.io.FileUtils;
+
+
+public class JavaCompiler {
+ private File tempDir;
+
+ public JavaCompiler() {
+ this.tempDir = Files.createTempDir();
+ tempDir.deleteOnExit();
+ }
+
+ public List<CompiledSourceCode> compile(File... sourceFiles) throws IOException {
+ String[] sourceFileContents =
+ Arrays.stream(sourceFiles).map(this::readFileToString).toArray(String[]::new);
+
+ return compile(sourceFileContents);
+ }
+
+ public List<CompiledSourceCode> compile(String... sourceFileContents) throws IOException {
+ UncompiledSourceCode[] uncompiledSourceCodes = Arrays.stream(sourceFileContents)
+ .map(UncompiledSourceCode::fromSourceCode).toArray(UncompiledSourceCode[]::new);
+
+ return compile(uncompiledSourceCodes);
+ }
+
+ public List<CompiledSourceCode> compile(UncompiledSourceCode... uncompiledSources)
+ throws IOException {
+ File temporarySourcesDirectory = createSubdirectory(tempDir, "sources");
+ File temporaryClassesDirectory = createSubdirectory(tempDir, "classes");
+
+ List<String> options = Stream.of("-d", temporaryClassesDirectory.getAbsolutePath(),
+ "-classpath", System.getProperty("java.class.path")).collect(toList());
+
+ try {
+ for (UncompiledSourceCode sourceCode : uncompiledSources) {
+ File sourceFile = new File(temporarySourcesDirectory, sourceCode.simpleClassName + ".java");
+ FileUtils.writeStringToFile(sourceFile, sourceCode.sourceCode, Charsets.UTF_8);
+ options.add(sourceFile.getAbsolutePath());
+ }
+
+ int exitCode = ToolProvider.getSystemJavaCompiler().run(System.in, System.out, System.err,
+ options.toArray(new String[] {}));
+
+ if (exitCode != 0) {
+ throw new RuntimeException(
+ "Unable to compile the given source code. See System.err for details.");
+ }
+
+ List<CompiledSourceCode> compiledSourceCodes = new ArrayList<>();
+ addCompiledClasses(compiledSourceCodes, "", temporaryClassesDirectory);
+ return compiledSourceCodes;
+ } finally {
+ FileUtils.deleteDirectory(temporaryClassesDirectory);
+ }
+ }
+
+ private static void addCompiledClasses(List<CompiledSourceCode> ret, String pkgName, File dir)
+ throws IOException {
+ for (File file : dir.listFiles()) {
+ String filename = file.getName();
+
+ if (file.isDirectory()) {
+ String qname = pkgName + filename + ".";
+ addCompiledClasses(ret, qname, file);
+ } else if (filename.endsWith(".class")) {
+ String qname = pkgName + filename.substring(0, filename.length() - 6);
+ ret.add(new CompiledSourceCode(qname, FileUtils.readFileToByteArray(file)));
+ } else {
+ System.err.println("Unexpected file : " + file.getAbsolutePath());
+ }
+ }
+ }
+
+ private File createSubdirectory(File parent, String directoryName) {
+ File subdirectory = parent.toPath().resolve(directoryName).toFile();
+ if (!subdirectory.exists()) {
+ subdirectory.mkdirs();
+ }
+
+ if (!subdirectory.exists() || !subdirectory.isDirectory()) {
+ throw new IllegalArgumentException("Invalid directory" + subdirectory.getAbsolutePath());
+ }
+
+ return subdirectory;
+ }
+
+ private String readFileToString(File file) {
+ try {
+ return FileUtils.readFileToString(file, Charsets.UTF_8);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/geode/blob/df954000/geode-junit/src/main/java/org/apache/geode/test/compiler/UncompiledSourceCode.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/main/java/org/apache/geode/test/compiler/UncompiledSourceCode.java b/geode-junit/src/main/java/org/apache/geode/test/compiler/UncompiledSourceCode.java
new file mode 100644
index 0000000..4161f5e
--- /dev/null
+++ b/geode-junit/src/main/java/org/apache/geode/test/compiler/UncompiledSourceCode.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.test.compiler;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.SystemUtils;
+
+public class UncompiledSourceCode {
+ public String simpleClassName;
+ public String sourceCode;
+
+ private UncompiledSourceCode(String simpleClassName, String sourceCode) {
+ this.simpleClassName = simpleClassName;
+ this.sourceCode = sourceCode;
+ }
+
+ public static UncompiledSourceCode fromSourceCode(String sourceCode) {
+ String simpleClassName = new ClassNameExtractor().extractFromSourceCode(sourceCode);
+ return new UncompiledSourceCode(simpleClassName, sourceCode);
+ }
+
+ public static UncompiledSourceCode fromClassName(String fullyQualifiedClassName) {
+ ClassNameWithPackage classNameWithPackage = ClassNameWithPackage.of(fullyQualifiedClassName);
+ boolean isPackageSpecified = StringUtils.isNotBlank(classNameWithPackage.packageName);
+
+ StringBuilder sourceCode = new StringBuilder();
+ if (isPackageSpecified) {
+ sourceCode.append(String.format("package %s;", classNameWithPackage.packageName));
+ sourceCode.append(SystemUtils.LINE_SEPARATOR);
+ }
+
+ sourceCode.append(String.format("public class %s {}", classNameWithPackage.simpleClassName));
+
+ return new UncompiledSourceCode(classNameWithPackage.simpleClassName, sourceCode.toString());
+ }
+
+ private static class ClassNameWithPackage {
+ String packageName;
+ String simpleClassName;
+
+ static ClassNameWithPackage of(String fqClassName) {
+ int indexOfLastDot = fqClassName.lastIndexOf(".");
+
+ if (indexOfLastDot == -1) {
+ return new ClassNameWithPackage("", fqClassName);
+ } else {
+ String specifiedPackage = fqClassName.substring(0, indexOfLastDot);
+ String simpleClassName = fqClassName.substring(indexOfLastDot + 1);
+
+ return new ClassNameWithPackage(specifiedPackage, simpleClassName);
+ }
+ }
+
+ private ClassNameWithPackage(String packageName, String simpleClassName) {
+ this.packageName = packageName;
+ this.simpleClassName = simpleClassName;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/geode/blob/df954000/geode-junit/src/test/java/org/apache/geode/test/compiler/ClassNameExtractorTest.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/test/java/org/apache/geode/test/compiler/ClassNameExtractorTest.java b/geode-junit/src/test/java/org/apache/geode/test/compiler/ClassNameExtractorTest.java
new file mode 100644
index 0000000..db060f8
--- /dev/null
+++ b/geode-junit/src/test/java/org/apache/geode/test/compiler/ClassNameExtractorTest.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.test.compiler;
+
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.test.junit.categories.UnitTest;
+
+@Category(UnitTest.class)
+public class ClassNameExtractorTest {
+ private static final String SPACE = " ";
+ private static final String CLASS_NAME_TO_FIND = "MyClassNameToFind";
+
+ @Test
+ public void extractsClassNames() throws Exception {
+ SoftAssertions softAssertions = new SoftAssertions();
+ ClassNameExtractor classNameExtractor = new ClassNameExtractor();
+
+ Set<List<String>> permutationsToTest = Sets.cartesianProduct(
+ ImmutableSet.of("public ", "private ", "protected ", ""), ImmutableSet.of("abstract ", ""),
+ ImmutableSet.of("static ", ""), ImmutableSet.of("class ", "interface "),
+ ImmutableSet.of("extends Foo ", ""), ImmutableSet.of("implements Bar ", ""));
+
+ for (List<String> permutation : permutationsToTest) {
+ String firstLineOfSource =
+ permutation.get(0) + permutation.get(1) + permutation.get(2) + permutation.get(3)
+ + CLASS_NAME_TO_FIND + SPACE + permutation.get(4) + permutation.get(5) + " {";
+
+ String className = classNameExtractor.extractFromSourceCode(firstLineOfSource);
+ softAssertions.assertThat(className).isEqualTo(CLASS_NAME_TO_FIND);
+ }
+
+ softAssertions.assertAll();
+ }
+}
http://git-wip-us.apache.org/repos/asf/geode/blob/df954000/geode-junit/src/test/java/org/apache/geode/test/compiler/JarBuilderTest.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/test/java/org/apache/geode/test/compiler/JarBuilderTest.java b/geode-junit/src/test/java/org/apache/geode/test/compiler/JarBuilderTest.java
new file mode 100644
index 0000000..a718fde
--- /dev/null
+++ b/geode-junit/src/test/java/org/apache/geode/test/compiler/JarBuilderTest.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.test.compiler;
+
+import static java.util.stream.Collectors.toSet;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.TemporaryFolder;
+
+import org.apache.geode.test.junit.categories.IntegrationTest;
+
+@Category(IntegrationTest.class)
+public class JarBuilderTest {
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ private JarBuilder jarBuilder;
+ private File outputJar;
+
+ @Before
+ public void setup() {
+ jarBuilder = new JarBuilder();
+ outputJar = new File(temporaryFolder.getRoot(), "output.jar");
+ }
+
+ @Test
+ public void jarWithSingleClass() throws Exception {
+ File classContents = loadTestResource("AbstractClass.java");
+ jarBuilder.buildJar(outputJar, classContents);
+
+ Set<String> jarEntryNames = jarEntryNamesFromFile(outputJar);
+ assertThat(jarEntryNames)
+ .containsExactlyInAnyOrder("org/apache/geode/test/compiler/AbstractClass.class");
+ }
+
+ @Test
+ public void jarWithTwoDependentClasses() throws Exception {
+ File sourceFileOne = loadTestResource("AbstractClass.java");
+ File sourceFileTwo = loadTestResource("ConcreteClass.java");
+
+ jarBuilder.buildJar(outputJar, sourceFileOne, sourceFileTwo);
+
+ Set<String> jarEntryNames = jarEntryNamesFromFile(outputJar);
+
+ assertThat(jarEntryNames).containsExactlyInAnyOrder(
+ "org/apache/geode/test/compiler/AbstractClass.class",
+ "org/apache/geode/test/compiler/ConcreteClass.class");
+ }
+
+ @Test
+ public void jarWithClassInDefaultPackage() throws Exception {
+ String classInFooBarPackage = "package foo.bar; public class ClassInFooBarPackage {}";
+ String classInDefaultPackage = "public class ClassInDefaultPackage {}";
+ jarBuilder.buildJar(outputJar, classInFooBarPackage, classInDefaultPackage);
+
+ Set<String> jarEntryNames = jarEntryNamesFromFile(outputJar);
+ assertThat(jarEntryNames).containsExactlyInAnyOrder("ClassInDefaultPackage.class",
+ "foo/bar/ClassInFooBarPackage.class");
+ }
+
+
+ @Test
+ public void jarFromOnlyClassNames() throws Exception {
+ String defaultPackageClassName = "DefaultClass";
+ String otherPackageClassName = "foo.bar.OtherClass";
+ jarBuilder.buildJarFromClassNames(outputJar, defaultPackageClassName, otherPackageClassName);
+
+ Set<String> jarEntryNames = jarEntryNamesFromFile(outputJar);
+ assertThat(jarEntryNames).containsExactlyInAnyOrder("DefaultClass.class",
+ "foo/bar/OtherClass.class");
+ }
+
+ @Test
+ public void canLoadClassesFromJar() throws Exception {
+ String defaultPackageClassName = "DefaultClass";
+ String otherPackageClassName = "foo.bar.OtherClass";
+ jarBuilder.buildJarFromClassNames(outputJar, defaultPackageClassName, otherPackageClassName);
+
+ URLClassLoader jarClassLoader = new URLClassLoader(new URL[] {outputJar.toURL()});
+
+ jarClassLoader.loadClass("DefaultClass");
+ jarClassLoader.loadClass("foo.bar.OtherClass");
+ }
+
+ private Set<String> jarEntryNamesFromFile(File jarFile) throws Exception {
+ assertThat(jarFile).exists();
+
+ Enumeration<JarEntry> jarEntries = new JarFile(jarFile).entries();
+ return Collections.list(jarEntries).stream().map(JarEntry::getName).collect(toSet());
+ }
+
+ private File loadTestResource(String fileName) throws URISyntaxException {
+ URL resourceFileURL = this.getClass().getResource(fileName);
+ assertThat(resourceFileURL).isNotNull();
+
+ URI resourceUri = resourceFileURL.toURI();
+ return new File(resourceUri);
+ }
+}
http://git-wip-us.apache.org/repos/asf/geode/blob/df954000/geode-junit/src/test/java/org/apache/geode/test/compiler/JavaCompilerTest.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/test/java/org/apache/geode/test/compiler/JavaCompilerTest.java b/geode-junit/src/test/java/org/apache/geode/test/compiler/JavaCompilerTest.java
new file mode 100644
index 0000000..b306511
--- /dev/null
+++ b/geode-junit/src/test/java/org/apache/geode/test/compiler/JavaCompilerTest.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.test.compiler;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.List;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.TemporaryFolder;
+
+import org.apache.geode.test.junit.categories.IntegrationTest;
+
+@Category(IntegrationTest.class)
+public class JavaCompilerTest {
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ @Test
+ public void compileSingleClass() throws Exception {
+ File implementsFunctionSourceFile = getFileFromTestResources("AbstractClass.java");
+ String classContents = FileUtils.readFileToString(implementsFunctionSourceFile, "UTF-8");
+
+ List<CompiledSourceCode> compiledSourceCodes = new JavaCompiler().compile(classContents);
+
+ assertThat(compiledSourceCodes).hasSize(1);
+ }
+
+ @Test
+ public void compileTwoDependentClasses() throws Exception {
+ File sourceFileOne = getFileFromTestResources("AbstractClass.java");
+ File sourceFileTwo = getFileFromTestResources("ConcreteClass.java");
+
+ List<CompiledSourceCode> compiledSourceCodes =
+ new JavaCompiler().compile(sourceFileOne, sourceFileTwo);
+
+ assertThat(compiledSourceCodes).hasSize(2);
+ }
+
+ @Test
+ public void invalidSourceThrowsException() throws Exception {
+ JavaCompiler javaCompiler = new JavaCompiler();
+ String sourceCode = "public class foo {this is not valid java source code}";
+ assertThatThrownBy(() -> javaCompiler.compile(sourceCode)).isInstanceOf(Exception.class);
+ }
+
+ private File getFileFromTestResources(String fileName) throws URISyntaxException {
+ URL resourceFileURL = this.getClass().getResource(fileName);
+ assertThat(resourceFileURL).isNotNull();
+
+ URI resourceUri = resourceFileURL.toURI();
+ File file = new File(resourceUri);
+
+ assertThat(file).exists();
+ return file;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/geode/blob/df954000/geode-junit/src/test/java/org/apache/geode/test/compiler/UncompiledSourceCodeTest.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/test/java/org/apache/geode/test/compiler/UncompiledSourceCodeTest.java b/geode-junit/src/test/java/org/apache/geode/test/compiler/UncompiledSourceCodeTest.java
new file mode 100644
index 0000000..9db2808
--- /dev/null
+++ b/geode-junit/src/test/java/org/apache/geode/test/compiler/UncompiledSourceCodeTest.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.test.compiler;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.commons.lang.SystemUtils;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.test.junit.categories.UnitTest;
+
+@Category(UnitTest.class)
+public class UncompiledSourceCodeTest {
+ @Test
+ public void fromClassNameWithNoPackage() throws Exception {
+ UncompiledSourceCode uncompiledSourceCode = UncompiledSourceCode.fromClassName("NoPackage");
+ assertThat(uncompiledSourceCode.simpleClassName).isEqualTo("NoPackage");
+ assertThat(uncompiledSourceCode.sourceCode).isEqualTo("public class NoPackage {}");
+ }
+
+ @Test
+ public void fromClassNameWithPackage() throws Exception {
+ UncompiledSourceCode uncompiledSourceCode =
+ UncompiledSourceCode.fromClassName("foo.bar.ClassName");
+ assertThat(uncompiledSourceCode.simpleClassName).isEqualTo("ClassName");
+ assertThat(uncompiledSourceCode.sourceCode)
+ .isEqualTo("package foo.bar;" + SystemUtils.LINE_SEPARATOR + "public class ClassName {}");
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/geode/blob/df954000/geode-junit/src/test/resources/org/apache/geode/test/compiler/AbstractClass.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/test/resources/org/apache/geode/test/compiler/AbstractClass.java b/geode-junit/src/test/resources/org/apache/geode/test/compiler/AbstractClass.java
new file mode 100644
index 0000000..24ed0c6
--- /dev/null
+++ b/geode-junit/src/test/resources/org/apache/geode/test/compiler/AbstractClass.java
@@ -0,0 +1,18 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.test.compiler;
+
+public abstract class AbstractClass {
+}
http://git-wip-us.apache.org/repos/asf/geode/blob/df954000/geode-junit/src/test/resources/org/apache/geode/test/compiler/ConcreteClass.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/test/resources/org/apache/geode/test/compiler/ConcreteClass.java b/geode-junit/src/test/resources/org/apache/geode/test/compiler/ConcreteClass.java
new file mode 100644
index 0000000..bf98672
--- /dev/null
+++ b/geode-junit/src/test/resources/org/apache/geode/test/compiler/ConcreteClass.java
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.test.compiler;
+
+
+public class ConcreteClass extends AbstractClass {
+}
[2/2] geode git commit: GEODE-3235: Deploy jar registers functions
which extend FunctionAdapter
Posted by js...@apache.org.
GEODE-3235: Deploy jar registers functions which extend FunctionAdapter
(cherry picked from commit 64f33c3)
Project: http://git-wip-us.apache.org/repos/asf/geode/repo
Commit: http://git-wip-us.apache.org/repos/asf/geode/commit/aa36d3c3
Tree: http://git-wip-us.apache.org/repos/asf/geode/tree/aa36d3c3
Diff: http://git-wip-us.apache.org/repos/asf/geode/diff/aa36d3c3
Branch: refs/heads/release/1.2.1
Commit: aa36d3c3cfaa3e1f3de2cdfdd5f45e671fe79703
Parents: df95400
Author: Jared Stewart <js...@pivotal.io>
Authored: Tue Jul 25 15:32:18 2017 -0700
Committer: Jared Stewart <js...@pivotal.io>
Committed: Mon Aug 21 11:43:41 2017 -0700
----------------------------------------------------------------------
.../org/apache/geode/internal/DeployedJar.java | 49 ++++----
.../internal/deployment/FunctionScanner.java | 47 ++++++++
...loyCommandFunctionRegistrationDUnitTest.java | 118 +++++++++++++++++++
.../deployment/FunctionScannerTest.java | 106 +++++++++++++++++
.../AbstractExtendsFunctionAdapter.java | 24 ++++
.../internal/deployment/AbstractFunction.java | 33 ++++++
.../deployment/AbstractImplementsFunction.java | 24 ++++
...teExtendsAbstractExtendsFunctionAdapter.java | 23 ++++
...ncreteExtendsAbstractImplementsFunction.java | 23 ++++
.../deployment/ExtendsAbstractFunction.java | 25 ++++
.../deployment/ExtendsFunctionAdapter.java | 25 ++++
.../internal/deployment/ImplementsFunction.java | 24 ++++
12 files changed, 494 insertions(+), 27 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/geode/blob/aa36d3c3/geode-core/src/main/java/org/apache/geode/internal/DeployedJar.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/internal/DeployedJar.java b/geode-core/src/main/java/org/apache/geode/internal/DeployedJar.java
index 037ef9e..a341ee3 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/DeployedJar.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/DeployedJar.java
@@ -14,19 +14,6 @@
*/
package org.apache.geode.internal;
-import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
-import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult;
-import org.apache.commons.collections.CollectionUtils;
-import org.apache.geode.cache.CacheClosedException;
-import org.apache.geode.cache.CacheFactory;
-import org.apache.geode.cache.Declarable;
-import org.apache.geode.cache.execute.Function;
-import org.apache.geode.cache.execute.FunctionService;
-import org.apache.geode.internal.cache.InternalCache;
-import org.apache.geode.internal.logging.LogService;
-import org.apache.geode.pdx.internal.TypeRegistry;
-import org.apache.logging.log4j.Logger;
-
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -38,7 +25,6 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
-import java.net.URLClassLoader;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -53,9 +39,22 @@ import java.util.jar.JarInputStream;
import java.util.regex.Pattern;
import java.util.stream.Stream;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.logging.log4j.Logger;
+
+import org.apache.geode.cache.CacheClosedException;
+import org.apache.geode.cache.CacheFactory;
+import org.apache.geode.cache.Declarable;
+import org.apache.geode.cache.execute.Function;
+import org.apache.geode.cache.execute.FunctionService;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.management.internal.deployment.FunctionScanner;
+import org.apache.geode.pdx.internal.TypeRegistry;
+
/**
* ClassLoader for a single JAR file.
- *
+ *
* @since GemFire 7.0
*/
public class DeployedJar {
@@ -123,7 +122,7 @@ public class DeployedJar {
/**
* Peek into the JAR data and make sure that it is valid JAR content.
- *
+ *
* @param inputStream InputStream containing data to be validated.
* @return True if the data has JAR content, false otherwise
*/
@@ -149,7 +148,7 @@ public class DeployedJar {
/**
* Peek into the JAR data and make sure that it is valid JAR content.
- *
+ *
* @param jarBytes Bytes of data to be validated.
* @return True if the data has JAR content, false otherwise
*/
@@ -171,7 +170,7 @@ public class DeployedJar {
JarInputStream jarInputStream = null;
try {
- List<String> functionClasses = findFunctionsInThisJar();
+ Collection<String> functionClasses = findFunctionsInThisJar();
jarInputStream = new JarInputStream(byteArrayInputStream);
JarEntry jarEntry = jarInputStream.getNextJarEntry();
@@ -259,7 +258,7 @@ public class DeployedJar {
/**
* Uses MD5 hashes to determine if the original byte content of this DeployedJar is the same as
* that past in.
- *
+ *
* @param compareToBytes Bytes to compare the original content to
* @return True of the MD5 hash is the same o
*/
@@ -281,7 +280,7 @@ public class DeployedJar {
* Check to see if the class implements the Function interface. If so, it will be registered with
* FunctionService. Also, if the functions's class was originally declared in a cache.xml file
* then any properties specified at that time will be reused when re-registering the function.
- *
+ *
* @param clazz Class to check for implementation of the Function class
* @return A collection of Objects that implement the Function interface.
*/
@@ -333,15 +332,11 @@ public class DeployedJar {
return registerableFunctions;
}
- private List<String> findFunctionsInThisJar() throws IOException {
- URLClassLoader urlClassLoader =
- new URLClassLoader(new URL[] {this.getFile().getCanonicalFile().toURL()});
- FastClasspathScanner fastClasspathScanner = new FastClasspathScanner()
- .removeTemporaryFilesAfterScan(true).overrideClassLoaders(urlClassLoader);
- ScanResult scanResult = fastClasspathScanner.scan();
- return scanResult.getNamesOfClassesImplementing(Function.class);
+ protected Collection<String> findFunctionsInThisJar() throws IOException {
+ return new FunctionScanner().findFunctionsInJar(this.file);
}
+
private Function newFunction(final Class<Function> clazz, final boolean errorOnNoSuchMethod) {
try {
final Constructor<Function> constructor = clazz.getConstructor();
http://git-wip-us.apache.org/repos/asf/geode/blob/aa36d3c3/geode-core/src/main/java/org/apache/geode/management/internal/deployment/FunctionScanner.java
----------------------------------------------------------------------
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/deployment/FunctionScanner.java b/geode-core/src/main/java/org/apache/geode/management/internal/deployment/FunctionScanner.java
new file mode 100644
index 0000000..9b7d6c4
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/deployment/FunctionScanner.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.management.internal.deployment;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
+import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult;
+
+import org.apache.geode.cache.execute.Function;
+import org.apache.geode.cache.execute.FunctionAdapter;
+
+public class FunctionScanner {
+
+ public Collection<String> findFunctionsInJar(File jarFile) throws IOException {
+ URLClassLoader urlClassLoader =
+ new URLClassLoader(new URL[] {jarFile.getCanonicalFile().toURL()});
+ FastClasspathScanner fastClasspathScanner = new FastClasspathScanner()
+ .removeTemporaryFilesAfterScan(true).overrideClassLoaders(urlClassLoader);
+ ScanResult scanResult = fastClasspathScanner.scan();
+
+ Set<String> functionClasses = new HashSet<>();
+
+ functionClasses.addAll(scanResult.getNamesOfClassesImplementing(Function.class));
+ functionClasses.addAll(scanResult.getNamesOfSubclassesOf(FunctionAdapter.class));
+
+ return functionClasses;
+ }
+}
http://git-wip-us.apache.org/repos/asf/geode/blob/aa36d3c3/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/DeployCommandFunctionRegistrationDUnitTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/DeployCommandFunctionRegistrationDUnitTest.java b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/DeployCommandFunctionRegistrationDUnitTest.java
new file mode 100644
index 0000000..6b933bc
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/DeployCommandFunctionRegistrationDUnitTest.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.management.internal.cli.commands;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.File;
+import java.io.Serializable;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.cache.execute.Execution;
+import org.apache.geode.cache.execute.FunctionService;
+import org.apache.geode.distributed.DistributedSystem;
+import org.apache.geode.internal.ClassPathLoader;
+import org.apache.geode.internal.cache.GemFireCacheImpl;
+import org.apache.geode.test.compiler.JarBuilder;
+import org.apache.geode.test.dunit.rules.GfshShellConnectionRule;
+import org.apache.geode.test.dunit.rules.LocatorServerStartupRule;
+import org.apache.geode.test.dunit.rules.MemberVM;
+import org.apache.geode.test.junit.categories.DistributedTest;
+import org.apache.geode.test.junit.rules.serializable.SerializableTemporaryFolder;
+
+@Category(DistributedTest.class)
+public class DeployCommandFunctionRegistrationDUnitTest implements Serializable {
+ private MemberVM locator;
+ private MemberVM server;
+
+ @Rule
+ public SerializableTemporaryFolder temporaryFolder = new SerializableTemporaryFolder();
+
+ @Rule
+ public LocatorServerStartupRule lsRule = new LocatorServerStartupRule();
+
+ @Rule
+ public transient GfshShellConnectionRule gfshConnector = new GfshShellConnectionRule();
+
+ @Before
+ public void setup() throws Exception {
+ locator = lsRule.startLocatorVM(0);
+ server = lsRule.startServerVM(1, locator.getPort());
+
+ gfshConnector.connectAndVerify(locator);
+ }
+
+ @Test
+ public void deployImplements() throws Exception {
+ JarBuilder jarBuilder = new JarBuilder();
+ File source = loadTestResource(
+ "/org/apache/geode/management/internal/deployment/ImplementsFunction.java");
+
+ File outputJar = new File(temporaryFolder.getRoot(), "output.jar");
+ jarBuilder.buildJar(outputJar, source);
+
+ gfshConnector.executeAndVerifyCommand("deploy --jar=" + outputJar.getCanonicalPath());
+ server.invoke(() -> assertThatCanLoad(
+ "org.apache.geode.management.internal.deployment.ImplementsFunction"));
+ server.invoke(() -> assertThatFunctionHasVersion(
+ "org.apache.geode.management.internal.deployment.ImplementsFunction",
+ "ImplementsFunctionResult"));
+ }
+
+ @Test
+ public void deployExtends() throws Exception {
+ JarBuilder jarBuilder = new JarBuilder();
+ File source = loadTestResource(
+ "/org/apache/geode/management/internal/deployment/ExtendsFunctionAdapter.java");
+
+ File outputJar = new File(temporaryFolder.getRoot(), "output.jar");
+ jarBuilder.buildJar(outputJar, source);
+
+ gfshConnector.executeAndVerifyCommand("deploy --jar=" + outputJar.getCanonicalPath());
+ server.invoke(() -> assertThatCanLoad(
+ "org.apache.geode.management.internal.deployment.ExtendsFunctionAdapter"));
+ server.invoke(() -> assertThatFunctionHasVersion(
+ "org.apache.geode.management.internal.deployment.ExtendsFunctionAdapter",
+ "ExtendsFunctionAdapterResult"));
+ }
+
+ private File loadTestResource(String fileName) throws URISyntaxException {
+ URL resourceFileURL = this.getClass().getResource(fileName);
+ assertThat(resourceFileURL).isNotNull();
+
+ URI resourceUri = resourceFileURL.toURI();
+ return new File(resourceUri);
+ }
+
+ private void assertThatFunctionHasVersion(String functionId, String version) {
+ GemFireCacheImpl gemFireCache = GemFireCacheImpl.getInstance();
+ DistributedSystem distributedSystem = gemFireCache.getDistributedSystem();
+ Execution execution = FunctionService.onMember(distributedSystem.getDistributedMember());
+ List<String> result = (List<String>) execution.execute(functionId).getResult();
+ assertThat(result.get(0)).isEqualTo(version);
+ }
+
+ private void assertThatCanLoad(String className) throws ClassNotFoundException {
+ assertThat(ClassPathLoader.getLatest().forName(className)).isNotNull();
+ }
+}
http://git-wip-us.apache.org/repos/asf/geode/blob/aa36d3c3/geode-core/src/test/java/org/apache/geode/management/internal/deployment/FunctionScannerTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/management/internal/deployment/FunctionScannerTest.java b/geode-core/src/test/java/org/apache/geode/management/internal/deployment/FunctionScannerTest.java
new file mode 100644
index 0000000..af9ffdf
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/management/internal/deployment/FunctionScannerTest.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.management.internal.deployment;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Collection;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.TemporaryFolder;
+
+import org.apache.geode.test.compiler.JarBuilder;
+import org.apache.geode.test.junit.categories.IntegrationTest;
+
+@Category(IntegrationTest.class)
+public class FunctionScannerTest {
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ private JarBuilder jarBuilder;
+ private FunctionScanner functionScanner;
+ private File outputJar;
+
+ @Before
+ public void setup() {
+ jarBuilder = new JarBuilder();
+ functionScanner = new FunctionScanner();
+ outputJar = new File(temporaryFolder.getRoot(), "output.jar");
+ }
+
+ @Test
+ public void implementsFunction() throws Exception {
+ File sourceFileOne = loadTestResource("ImplementsFunction.java");
+
+ jarBuilder.buildJar(outputJar, sourceFileOne);
+
+ Collection<String> functionsFoundInJar = functionScanner.findFunctionsInJar(outputJar);
+ assertThat(functionsFoundInJar)
+ .containsExactly("org.apache.geode.management.internal.deployment.ImplementsFunction");
+ }
+
+ @Test
+ public void extendsFunctionAdapter() throws Exception {
+ File sourceFileOne = loadTestResource("ExtendsFunctionAdapter.java");
+
+ jarBuilder.buildJar(outputJar, sourceFileOne);
+
+ Collection<String> functionsFoundInJar = functionScanner.findFunctionsInJar(outputJar);
+ assertThat(functionsFoundInJar)
+ .containsExactly("org.apache.geode.management.internal.deployment.ExtendsFunctionAdapter");
+ }
+
+ @Test
+ public void testConcreteExtendsAbstractExtendsFunctionAdapter() throws Exception {
+ File sourceFileOne = loadTestResource("AbstractExtendsFunctionAdapter.java");
+ File sourceFileTwo = loadTestResource("ConcreteExtendsAbstractExtendsFunctionAdapter.java");
+
+ jarBuilder.buildJar(outputJar, sourceFileOne, sourceFileTwo);
+
+ Collection<String> functionsFoundInJar = functionScanner.findFunctionsInJar(outputJar);
+ assertThat(functionsFoundInJar).containsExactlyInAnyOrder(
+ "org.apache.geode.management.internal.deployment.ConcreteExtendsAbstractExtendsFunctionAdapter",
+ "org.apache.geode.management.internal.deployment.AbstractExtendsFunctionAdapter");
+ }
+
+ @Test
+ public void testConcreteExtendsAbstractImplementsFunction() throws Exception {
+ File sourceFileOne = loadTestResource("AbstractImplementsFunction.java");
+ File sourceFileTwo = loadTestResource("ConcreteExtendsAbstractImplementsFunction.java");
+
+ jarBuilder.buildJar(outputJar, sourceFileOne, sourceFileTwo);
+
+ Collection<String> functionsFoundInJar = functionScanner.findFunctionsInJar(outputJar);
+ assertThat(functionsFoundInJar).containsExactlyInAnyOrder(
+ "org.apache.geode.management.internal.deployment.ConcreteExtendsAbstractImplementsFunction",
+ "org.apache.geode.management.internal.deployment.AbstractImplementsFunction");
+ }
+
+ private File loadTestResource(String fileName) throws URISyntaxException {
+ URL resourceFileURL = this.getClass().getResource(fileName);
+ assertThat(resourceFileURL).isNotNull();
+
+ URI resourceUri = resourceFileURL.toURI();
+ return new File(resourceUri);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/geode/blob/aa36d3c3/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/AbstractExtendsFunctionAdapter.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/AbstractExtendsFunctionAdapter.java b/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/AbstractExtendsFunctionAdapter.java
new file mode 100644
index 0000000..5bcc22c
--- /dev/null
+++ b/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/AbstractExtendsFunctionAdapter.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.management.internal.deployment;
+
+import org.apache.geode.cache.execute.FunctionAdapter;
+import org.apache.geode.cache.execute.FunctionContext;
+
+public abstract class AbstractExtendsFunctionAdapter extends FunctionAdapter {
+ public void execute(FunctionContext context) {
+ context.getResultSender().lastResult("AbstractExtendsFunctionAdapterResult");
+ }
+}
http://git-wip-us.apache.org/repos/asf/geode/blob/aa36d3c3/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/AbstractFunction.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/AbstractFunction.java b/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/AbstractFunction.java
new file mode 100644
index 0000000..afc83ab
--- /dev/null
+++ b/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/AbstractFunction.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.management.internal.deployment;
+
+import org.apache.geode.cache.execute.FunctionContext;
+
+public class AbstractFunction implements Function {
+ public void execute(FunctionContext context) {
+ context.getResultSender().lastResult("ConcreteResult");
+ }
+
+ public static abstract class AbstractImplementsFunction implements Function {
+ public abstract void execute(FunctionContext context);
+ }
+
+ public static class Concrete extends AbstractImplementsFunction {
+ public void execute(FunctionContext context) {
+ context.getResultSender().lastResult("ConcreteResult");
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/geode/blob/aa36d3c3/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/AbstractImplementsFunction.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/AbstractImplementsFunction.java b/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/AbstractImplementsFunction.java
new file mode 100644
index 0000000..a31399d
--- /dev/null
+++ b/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/AbstractImplementsFunction.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.management.internal.deployment;
+
+import org.apache.geode.cache.execute.Function;
+import org.apache.geode.cache.execute.FunctionContext;
+
+public abstract class AbstractImplementsFunction implements Function {
+ public void execute(FunctionContext context) {
+ context.getResultSender().lastResult("AbstractImplementsFunctionResult");
+ }
+}
http://git-wip-us.apache.org/repos/asf/geode/blob/aa36d3c3/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/ConcreteExtendsAbstractExtendsFunctionAdapter.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/ConcreteExtendsAbstractExtendsFunctionAdapter.java b/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/ConcreteExtendsAbstractExtendsFunctionAdapter.java
new file mode 100644
index 0000000..3515558
--- /dev/null
+++ b/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/ConcreteExtendsAbstractExtendsFunctionAdapter.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.management.internal.deployment;
+
+import org.apache.geode.cache.execute.FunctionContext;
+
+public class ConcreteExtendsAbstractExtendsFunctionAdapter extends AbstractExtendsFunctionAdapter {
+ public void execute(FunctionContext context) {
+ context.getResultSender().lastResult("ConcreteExtendsAbstractExtendsFunctionAdapter");
+ }
+}
http://git-wip-us.apache.org/repos/asf/geode/blob/aa36d3c3/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/ConcreteExtendsAbstractImplementsFunction.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/ConcreteExtendsAbstractImplementsFunction.java b/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/ConcreteExtendsAbstractImplementsFunction.java
new file mode 100644
index 0000000..b62f38b
--- /dev/null
+++ b/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/ConcreteExtendsAbstractImplementsFunction.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.management.internal.deployment;
+
+import org.apache.geode.cache.execute.FunctionContext;
+
+public class ConcreteExtendsAbstractImplementsFunction extends AbstractImplementsFunction {
+ public void execute(FunctionContext context) {
+ context.getResultSender().lastResult("ConcreteExtendsAbstractImplementsFunctionResult");
+ }
+}
http://git-wip-us.apache.org/repos/asf/geode/blob/aa36d3c3/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/ExtendsAbstractFunction.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/ExtendsAbstractFunction.java b/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/ExtendsAbstractFunction.java
new file mode 100644
index 0000000..cf7c7a2
--- /dev/null
+++ b/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/ExtendsAbstractFunction.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.management.internal.deployment;
+
+
+import org.apache.geode.cache.execute.FunctionAdapter;
+import org.apache.geode.cache.execute.FunctionContext;
+
+public class ExtendsFunctionAdapter extends FunctionAdapter {
+ public void execute(FunctionContext context) {
+ context.getResultSender().lastResult("ExtendsFunctionAdapterResult");
+ }
+}
http://git-wip-us.apache.org/repos/asf/geode/blob/aa36d3c3/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/ExtendsFunctionAdapter.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/ExtendsFunctionAdapter.java b/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/ExtendsFunctionAdapter.java
new file mode 100644
index 0000000..cf7c7a2
--- /dev/null
+++ b/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/ExtendsFunctionAdapter.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.management.internal.deployment;
+
+
+import org.apache.geode.cache.execute.FunctionAdapter;
+import org.apache.geode.cache.execute.FunctionContext;
+
+public class ExtendsFunctionAdapter extends FunctionAdapter {
+ public void execute(FunctionContext context) {
+ context.getResultSender().lastResult("ExtendsFunctionAdapterResult");
+ }
+}
http://git-wip-us.apache.org/repos/asf/geode/blob/aa36d3c3/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/ImplementsFunction.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/ImplementsFunction.java b/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/ImplementsFunction.java
new file mode 100644
index 0000000..c9fef3c
--- /dev/null
+++ b/geode-core/src/test/resources/org/apache/geode/management/internal/deployment/ImplementsFunction.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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.apache.geode.management.internal.deployment;
+
+import org.apache.geode.cache.execute.Function;
+import org.apache.geode.cache.execute.FunctionContext;
+
+public class ImplementsFunction implements Function {
+ public void execute(FunctionContext context) {
+ context.getResultSender().lastResult("ImplementsFunctionResult");
+ }
+}