From e1a87ca5ef2e08a643da6d4b3e460f6360d781ae Mon Sep 17 00:00:00 2001 From: "Lincoln Baxter, III" Date: Fri, 30 May 2014 15:16:19 -0400 Subject: [PATCH] Added simple groovy rule discovery (still needs boilerplate DSL to enable rule list creation) --- .../jboss/windup/addon/config/RulePhase.java | 27 +++-- engine/groovy/pom.xml | 59 +++++---- .../windup/addon/groovy/ExampleRuleFile.wrl | 99 ++++++++++------ .../addon/groovy/ExampleRuleFile.groovy | 79 +++++++++--- .../groovy/FurnaceGroovyRuleScanner.java | 112 ++++++++++++++++++ .../groovy/GroovyConfigurationProvider.java | 81 +++++++++++++ .../java/org/groovy/AbstractTestCase.java | 3 - .../addon/groovy/LoadGroovyRulesTest.java | 60 ++++++++++ 8 files changed, 435 insertions(+), 85 deletions(-) create mode 100644 engine/groovy/src/main/java/org/jboss/windup/addon/groovy/FurnaceGroovyRuleScanner.java create mode 100644 engine/groovy/src/main/java/org/jboss/windup/addon/groovy/GroovyConfigurationProvider.java delete mode 100644 engine/groovy/src/test/java/org/groovy/AbstractTestCase.java create mode 100644 engine/groovy/src/test/java/org/jboss/windup/addon/groovy/LoadGroovyRulesTest.java diff --git a/engine/config/api/src/main/java/org/jboss/windup/addon/config/RulePhase.java b/engine/config/api/src/main/java/org/jboss/windup/addon/config/RulePhase.java index 5df6724596..5bc94868a7 100644 --- a/engine/config/api/src/main/java/org/jboss/windup/addon/config/RulePhase.java +++ b/engine/config/api/src/main/java/org/jboss/windup/addon/config/RulePhase.java @@ -7,34 +7,41 @@ public enum RulePhase */ DISCOVERY(100), /** - * Called to perform basic analysis (extract all method names from class files, extract metadata from xml files, etc) + * Called to perform basic analysis (extract all method names from class files, extract metadata from xml files, + * etc) */ INITIAL_ANALYSIS(200), /** * Perform high-level composition operations on the graph. * - * Eg, these may attach metadata from XML files to related Java classes, or perform other high-level graph operations, now that - * all metadata has been extracted + * Eg, these may attach metadata from XML files to related Java classes, or perform other high-level graph + * operations, now that all metadata has been extracted */ COMPOSITION(300), /** * Migration rules will attach data to the graph associated with migration. This could include: * - * - Hints to migrators for manual migration - * - Automated migration of schemas or source segments - * - Blacklists to indicate vendor specific APIs + * - Hints to migrators for manual migration - Automated migration of schemas or source segments - Blacklists to + * indicate vendor specific APIs */ MIGRATION_RULES(400), /** * Reporting visitors produce reports based upon the information contained within the graph */ - REPORTING(500); - - + REPORTING(500), + + /** + * Clean up resources and close streams. + */ + FINALIZE(600); + private int priority; - RulePhase(int priority) { + + RulePhase(int priority) + { this.priority = priority; } + public int getPriority() { return priority; diff --git a/engine/groovy/pom.xml b/engine/groovy/pom.xml index e6dea8aeb4..3649827b9c 100644 --- a/engine/groovy/pom.xml +++ b/engine/groovy/pom.xml @@ -7,33 +7,35 @@ windup-engine-parent 2.0.0-SNAPSHOT - org.jboss.windup.addon groovy - + + + + org.jboss.forge.furnace.container + cdi + 2.6.0.Final + test + + + - org.jboss.windup.addon config - forge-addon - ${project.version} - provided - - - org.jboss.windup.addon - config-api ${project.version} + forge-addon provided - - org.jboss.forge.furnace.container cdi forge-addon provided + + + org.jboss.forge.furnace.test furnace-test-harness @@ -44,24 +46,29 @@ arquillian-furnace-classpath test - - - groovy-all org.codehaus.groovy + groovy-all 2.3.1 + + org.jboss.windup.addon + graph + ${project.version} + forge-addon + test + - src/main/groovy + + src/main/resources + - - org.jboss.forge.furnace furnace-maven-plugin @@ -70,8 +77,12 @@ generate-dot prepare-package - generate-dot - true + + generate-dot + + + true + @@ -81,8 +92,12 @@ create-forge-addon package - jar - forge-addon + + jar + + + forge-addon + diff --git a/engine/groovy/src/main/groovy/org/jboss/windup/addon/groovy/ExampleRuleFile.wrl b/engine/groovy/src/main/groovy/org/jboss/windup/addon/groovy/ExampleRuleFile.wrl index dfa7451a6c..012b29dd37 100644 --- a/engine/groovy/src/main/groovy/org/jboss/windup/addon/groovy/ExampleRuleFile.wrl +++ b/engine/groovy/src/main/groovy/org/jboss/windup/addon/groovy/ExampleRuleFile.wrl @@ -1,40 +1,67 @@ -/* - * Copyright 2014 Red Hat, Inc. and/or its affiliates. - * - * Licensed under the Eclipse Public License version 1.0, available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.jboss.windup.addon.groovy - -import org.jboss.windup.addon.config.RulePhase -import org.jboss.windup.addon.config.WindupConfigurationProvider -import org.jboss.windup.addon.config.graphsearch.GraphSearchConditionBuilder -import org.jboss.windup.addon.config.operation.Log -import org.jboss.windup.graph.GraphContext -import org.jboss.windup.graph.model.meta.javaclass.JavaClassMetaModel -import org.ocpsoft.logging.Logger.Level -import org.ocpsoft.rewrite.config.Configuration -import org.ocpsoft.rewrite.config.ConfigurationBuilder - -/** - * @author Lincoln Baxter, III - * - */ -class ExampleRuleFile extends WindupConfigurationProvider { - @Override - public RulePhase getPhase() { - return RulePhase.MIGRATION_RULES; + +// ---- A bit of script initialization ---------------------------- + +// use the script binding for silent sentence words like "to", "the" +binding = new CustomBinding() + + +rule "XYZ371" when { +} perform { +} + + + +def r = rule "ruleid1234" when { + println "evaluating"; return true +} perform { println "performing" }; + +r.ex(); + +class RuleBuilder { + String id; + Closure c; + Closure p; + + RuleBuilder(String id) { + this.id = id; + } + + def when (Closure c) { + this.c = c; + return this; + } + + def perform (Closure p) { + this.p = p; + return this; + } + + def ex() + { + if( c() ) + p(); + } + + def rule (Closure c) { + c() + return this + } + + String toString() { + "ID: $id, Closure: $c" } +} + +class CustomBinding extends Binding { + def getVariable(String word) { + // return System.out when the script requests to write to 'out' + if (word == "out") System.out - @Override - public Configuration getConfiguration(GraphContext context) { - return ConfigurationBuilder.begin() - .addRule() - .when( - GraphSearchConditionBuilder.create("javaClasses").ofType(JavaClassMetaModel.class) - ) - .perform( - Log.message(Level.INFO, "Sample groovy rule") - ) + // don't thrown an exception and return null + // when a silent sentence word is used, + // like "to" and "the" in our DSL + null } } + +def rule(ruleID) { return new RuleBuilder(ruleID) } diff --git a/engine/groovy/src/main/java/org/jboss/windup/addon/groovy/ExampleRuleFile.groovy b/engine/groovy/src/main/java/org/jboss/windup/addon/groovy/ExampleRuleFile.groovy index 987c2a98ba..012b29dd37 100644 --- a/engine/groovy/src/main/java/org/jboss/windup/addon/groovy/ExampleRuleFile.groovy +++ b/engine/groovy/src/main/java/org/jboss/windup/addon/groovy/ExampleRuleFile.groovy @@ -1,16 +1,67 @@ -/* - * Copyright 2014 Red Hat, Inc. and/or its affiliates. - * - * Licensed under the Eclipse Public License version 1.0, available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.jboss.windup.addon.groovy - -/** - * @author Lincoln Baxter, III - * - */ -class ExampleRuleFile -{ +// ---- A bit of script initialization ---------------------------- + +// use the script binding for silent sentence words like "to", "the" +binding = new CustomBinding() + + +rule "XYZ371" when { +} perform { } + + + +def r = rule "ruleid1234" when { + println "evaluating"; return true +} perform { println "performing" }; + +r.ex(); + +class RuleBuilder { + String id; + Closure c; + Closure p; + + RuleBuilder(String id) { + this.id = id; + } + + def when (Closure c) { + this.c = c; + return this; + } + + def perform (Closure p) { + this.p = p; + return this; + } + + def ex() + { + if( c() ) + p(); + } + + def rule (Closure c) { + c() + return this + } + + String toString() { + "ID: $id, Closure: $c" + } +} + +class CustomBinding extends Binding { + def getVariable(String word) { + // return System.out when the script requests to write to 'out' + if (word == "out") System.out + + // don't thrown an exception and return null + // when a silent sentence word is used, + // like "to" and "the" in our DSL + null + } +} + +def rule(ruleID) { return new RuleBuilder(ruleID) } diff --git a/engine/groovy/src/main/java/org/jboss/windup/addon/groovy/FurnaceGroovyRuleScanner.java b/engine/groovy/src/main/java/org/jboss/windup/addon/groovy/FurnaceGroovyRuleScanner.java new file mode 100644 index 0000000000..8fcbf5c1f0 --- /dev/null +++ b/engine/groovy/src/main/java/org/jboss/windup/addon/groovy/FurnaceGroovyRuleScanner.java @@ -0,0 +1,112 @@ +package org.jboss.windup.addon.groovy; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import javax.inject.Inject; + +import org.jboss.forge.furnace.Furnace; +import org.jboss.forge.furnace.addons.Addon; +import org.jboss.forge.furnace.util.AddonFilters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class FurnaceGroovyRuleScanner +{ + private static final Logger LOG = LoggerFactory.getLogger(FurnaceGroovyRuleScanner.class); + + @Inject + private Furnace furnace; + + public Iterable scan() + { + List discoveredRuleFiles = new ArrayList<>(); + + for (Addon addon : furnace.getAddonRegistry().getAddons(AddonFilters.allStarted())) + { + List discoveredRuleFileNames = new ArrayList<>(); + List addonResources = addon.getRepository().getAddonResources(addon.getId()); + for (File addonFile : addonResources) + { + if (addonFile.isDirectory()) + { + handleDirectory(addonFile, null, discoveredRuleFileNames); + } + else + { + handleArchiveByFile(addonFile, discoveredRuleFileNames); + } + } + + for (String discoveredRuleFileName : discoveredRuleFileNames) + { + URL ruleFile = addon.getClassLoader().getResource(discoveredRuleFileName); + if (ruleFile != null) + discoveredRuleFiles.add(ruleFile); + } + } + return discoveredRuleFiles; + } + + private void handleArchiveByFile(File file, List discoveredFiles) + { + try + { + String archiveUrl = "jar:" + file.toURI().toURL().toExternalForm() + "!/"; + ZipFile zip = new ZipFile(file); + Enumeration entries = zip.entries(); + + while (entries.hasMoreElements()) + { + ZipEntry entry = entries.nextElement(); + String name = entry.getName(); + handle(name, new URL(archiveUrl + name), discoveredFiles); + } + zip.close(); + } + catch (IOException e) + { + throw new RuntimeException("Error handling file " + file, e); + } + } + + private void handleDirectory(File file, String path, List discoveredFiles) + { + for (File child : file.listFiles()) + { + String newPath = (path == null) ? child.getName() : (path + '/' + child.getName()); + + if (child.isDirectory()) + { + handleDirectory(child, newPath, discoveredFiles); + } + else + { + try + { + handle(newPath, child.toURI().toURL(), discoveredFiles); + } + catch (MalformedURLException e) + { + LOG.error("Error loading file: " + newPath, e); + } + } + } + } + + protected void handle(String name, URL url, List discoveredFiles) + { + if (name.endsWith(".wrl")) // TODO handlers should be extensible + { + discoveredFiles.add(name); + } + } + +} diff --git a/engine/groovy/src/main/java/org/jboss/windup/addon/groovy/GroovyConfigurationProvider.java b/engine/groovy/src/main/java/org/jboss/windup/addon/groovy/GroovyConfigurationProvider.java new file mode 100644 index 0000000000..6e2d9895eb --- /dev/null +++ b/engine/groovy/src/main/java/org/jboss/windup/addon/groovy/GroovyConfigurationProvider.java @@ -0,0 +1,81 @@ +/* + * Copyright 2014 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Eclipse Public License version 1.0, available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.jboss.windup.addon.groovy; + +import groovy.lang.Binding; +import groovy.lang.GroovyShell; + +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.URL; +import java.util.List; + +import javax.inject.Inject; + +import org.jboss.windup.addon.config.RulePhase; +import org.jboss.windup.addon.config.WindupConfigurationProvider; +import org.jboss.windup.engine.util.exception.WindupException; +import org.jboss.windup.graph.GraphContext; +import org.ocpsoft.rewrite.config.Configuration; +import org.ocpsoft.rewrite.config.ConfigurationBuilder; +import org.ocpsoft.rewrite.config.Rule; + +/** + * @author Lincoln Baxter, III + * + */ +public class GroovyConfigurationProvider extends WindupConfigurationProvider +{ + @Inject + private FurnaceGroovyRuleScanner scanner; + + @Override + public Configuration getConfiguration(GraphContext context) + { + ConfigurationBuilder builder = ConfigurationBuilder.begin(); + + /* + * Bindings can be used to pre-configure syntactical variables, functions, and shortcuts for the groovy script. + */ + Binding binding = new Binding(); + binding.setVariable("foo", new Integer(2)); + + GroovyShell shell = new GroovyShell(binding); + + for (URL resource : getScripts()) + { + try (Reader reader = new InputStreamReader(resource.openStream())) + { + @SuppressWarnings("unchecked") + List rules = (List) shell.evaluate(reader); + for (Rule rule : rules) + { + builder.addRule(rule); + } + } + catch (Exception e) + { + throw new WindupException("Failed to evaluate configuration: ", e); + } + } + + return builder; + } + + private Iterable getScripts() + { + Iterable scripts = scanner.scan(); + return scripts; + } + + @Override + public RulePhase getPhase() + { + return RulePhase.COMPOSITION; + } + +} diff --git a/engine/groovy/src/test/java/org/groovy/AbstractTestCase.java b/engine/groovy/src/test/java/org/groovy/AbstractTestCase.java deleted file mode 100644 index ae6d5551e1..0000000000 --- a/engine/groovy/src/test/java/org/groovy/AbstractTestCase.java +++ /dev/null @@ -1,3 +0,0 @@ -package org.groovy; -public abstract class AbstractTestCase { -} \ No newline at end of file diff --git a/engine/groovy/src/test/java/org/jboss/windup/addon/groovy/LoadGroovyRulesTest.java b/engine/groovy/src/test/java/org/jboss/windup/addon/groovy/LoadGroovyRulesTest.java new file mode 100644 index 0000000000..617c7d8f9a --- /dev/null +++ b/engine/groovy/src/test/java/org/jboss/windup/addon/groovy/LoadGroovyRulesTest.java @@ -0,0 +1,60 @@ +package org.jboss.windup.addon.groovy; + +import java.util.List; + +import javax.inject.Inject; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.forge.arquillian.AddonDependency; +import org.jboss.forge.arquillian.Dependencies; +import org.jboss.forge.arquillian.archive.ForgeArchive; +import org.jboss.forge.furnace.repositories.AddonDependencyEntry; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.windup.graph.GraphContext; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.ocpsoft.rewrite.config.Configuration; +import org.ocpsoft.rewrite.config.Rule; + +@RunWith(Arquillian.class) +/** + * + */ +public class LoadGroovyRulesTest +{ + @Deployment + @Dependencies({ + @AddonDependency(name = "org.jboss.forge.furnace.container:cdi"), + @AddonDependency(name = "org.jboss.windup.addon:groovy"), + @AddonDependency(name = "org.jboss.windup.addon:graph") + }) + public static ForgeArchive getDeployment() + { + ForgeArchive archive = ShrinkWrap + .create(ForgeArchive.class) + .addBeansXML() + .addAsAddonDependencies( + AddonDependencyEntry.create("org.jboss.forge.furnace.container:cdi"), + AddonDependencyEntry.create("org.jboss.windup.addon:groovy"), + AddonDependencyEntry.create("org.jboss.windup.addon:graph") + ); + return archive; + } + + @Inject + private GraphContext context; + + @Inject + private GroovyConfigurationProvider provider; + + @Test + public void testLoadGroovyConfigs() throws Exception + { + Configuration configuration = provider.getConfiguration(context); + List rules = configuration.getRules(); + + Assert.assertEquals(1, rules.size()); + } +} \ No newline at end of file