You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by rm...@apache.org on 2021/02/26 08:41:38 UTC

[maven-scripting-plugin] branch rmannibucau/java-scripting-draft created (now 0346ed9)

This is an automated email from the ASF dual-hosted git repository.

rmannibucau pushed a change to branch rmannibucau/java-scripting-draft
in repository https://gitbox.apache.org/repos/asf/maven-scripting-plugin.git.


      at 0346ed9  basic java scripting engine support

This branch includes the following new commits:

     new 0346ed9  basic java scripting engine support

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[maven-scripting-plugin] 01/01: basic java scripting engine support

Posted by rm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rmannibucau pushed a commit to branch rmannibucau/java-scripting-draft
in repository https://gitbox.apache.org/repos/asf/maven-scripting-plugin.git

commit 0346ed96d069c6a4edcd6bf980fea7409169e84d
Author: Romain Manni-Bucau <rm...@gmail.com>
AuthorDate: Fri Feb 26 09:41:24 2021 +0100

    basic java scripting engine support
---
 pom.xml                                            |  38 ++
 src/it/java/pom.xml                                |  68 ++++
 src/it/java/verify.bsh                             |  44 +++
 .../plugins/scripting/AbstractScriptEvaluator.java |  22 +-
 .../apache/maven/plugins/scripting/EvalMojo.java   |  70 +++-
 .../plugins/scripting/FileScriptEvaluator.java     |   5 +-
 .../plugins/scripting/StringScriptEvaluator.java   |   4 +-
 .../plugins/scripting/engine/JavaScriptEngine.java | 422 +++++++++++++++++++++
 .../scripting/engine/JavaScriptEngineFactory.java  | 133 +++++++
 .../scripting/engine/JavaScriptEngineTest.java     |  99 +++++
 10 files changed, 897 insertions(+), 8 deletions(-)

diff --git a/pom.xml b/pom.xml
index 13fb174..5b20d0d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -69,6 +69,7 @@ under the License.
     <mavenVersion>3.0</mavenVersion>
     <maven.compiler.source>1.8</maven.compiler.source>
     <maven.compiler.target>1.8</maven.compiler.target>
+    <plexus.compiler.version>2.8.8</plexus.compiler.version>
     <project.build.outputTimestamp>2021-02-24T19:52:25Z</project.build.outputTimestamp>
   </properties>
 
@@ -96,6 +97,42 @@ under the License.
       <scope>provided</scope>
     </dependency>
 
+    <!-- java script engine -->
+    <dependency>
+      <groupId>org.codehaus.plexus</groupId>
+      <artifactId>plexus-compiler-api</artifactId>
+      <version>${plexus.compiler.version}</version>
+      <exclusions>
+        <exclusion>
+          <groupId>org.codehaus.plexus</groupId>
+          <artifactId>plexus-component-api</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.codehaus.plexus</groupId>
+      <artifactId>plexus-compiler-manager</artifactId>
+      <version>${plexus.compiler.version}</version>
+      <exclusions>
+        <exclusion>
+          <groupId>org.codehaus.plexus</groupId>
+          <artifactId>plexus-component-api</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.codehaus.plexus</groupId>
+      <artifactId>plexus-compiler-javac</artifactId>
+      <version>${plexus.compiler.version}</version>
+      <scope>runtime</scope>
+      <exclusions>
+        <exclusion>
+          <groupId>org.codehaus.plexus</groupId>
+          <artifactId>plexus-component-api</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+
     <!-- Test -->
     <dependency>
       <groupId>junit</groupId>
@@ -133,6 +170,7 @@ under the License.
                 <postBuildHookScript>verify</postBuildHookScript>
                 <localRepositoryPath>${project.build.directory}/local-repo</localRepositoryPath>
                 <settingsFile>src/it/settings.xml</settingsFile>
+                <postBuildHookScript>verify.bsh</postBuildHookScript>
                 <goals>
                   <goal>scripting:eval</goal>
                 </goals>
diff --git a/src/it/java/pom.xml b/src/it/java/pom.xml
new file mode 100644
index 0000000..0db926d
--- /dev/null
+++ b/src/it/java/pom.xml
@@ -0,0 +1,68 @@
+<?xml version='1.0' encoding='UTF-8'?>
+
+<!--
+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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.apache.maven.plugins.scripting.its</groupId>
+  <artifactId>java</artifactId>
+  <version>1.0.0-SNAPSHOT</version>
+  <packaging>pom</packaging>
+  
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-scripting-plugin</artifactId>
+        <version>@project.version@</version>
+        <configuration>
+          <engineName>java</engineName>
+          <scriptEngineConfiguration>
+            <main>MyMain</main>
+            <arg.00>1</arg.00>
+            <arg.01>2</arg.01>
+            <arg.02>3</arg.02>
+            <arg.03>4</arg.03>
+            <arg.10>5</arg.10>
+            <arg.110>6</arg.110>
+          </scriptEngineConfiguration>
+          <script>
+          <![CDATA[
+            import java.nio.file.Files;
+            import java.nio.file.Path;
+            import java.nio.file.Paths;
+            import java.nio.file.StandardOpenOption;
+
+            public class MyMain {
+              public static void main(String... args) throws Exception {
+                Path out = Paths.get("target/out");
+                Files.createDirectories(out.getParent());
+                Files.write(out, String.join(",", args).getBytes(),
+                  StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+              }
+            }
+          ]]>
+          </script>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/src/it/java/verify.bsh b/src/it/java/verify.bsh
new file mode 100644
index 0000000..a0f3f98
--- /dev/null
+++ b/src/it/java/verify.bsh
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+// beanshell does not like much java.nio.file so let's use java.io which is more than enough for us
+import java.util.stream.Collectors;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.File;
+
+File file = new File( basedir, "target/out" );
+if ( !file.exists() )
+{
+    throw new FileNotFoundException( "Could not find generated JAR: " + file );
+}
+String content;
+BufferedReader reader = new BufferedReader( new FileReader( file ) );
+try
+{
+  content = reader.lines().collect(Collectors.joining( "\n" ) );
+}
+finally
+{
+  reader.close();
+}
+if ( !"1,2,3,4,5,6".equals( content ) )
+{
+    throw new IllegalArgumentException( "Wrong content: '" + content + "'" );
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/maven/plugins/scripting/AbstractScriptEvaluator.java b/src/main/java/org/apache/maven/plugins/scripting/AbstractScriptEvaluator.java
index 7605680..cfd0866 100644
--- a/src/main/java/org/apache/maven/plugins/scripting/AbstractScriptEvaluator.java
+++ b/src/main/java/org/apache/maven/plugins/scripting/AbstractScriptEvaluator.java
@@ -19,6 +19,8 @@ package org.apache.maven.plugins.scripting;
  * under the License.
  */
 
+import org.apache.maven.plugins.scripting.engine.JavaScriptEngineFactory;
+
 import javax.script.Bindings;
 import javax.script.ScriptContext;
 import javax.script.ScriptEngine;
@@ -31,6 +33,12 @@ import javax.script.ScriptException;
  */
 abstract class AbstractScriptEvaluator
 {
+  protected final EvalMojo mojo;
+
+  protected AbstractScriptEvaluator( final EvalMojo mojo )
+  {
+    this.mojo = mojo;
+  }
 
   /**
    * @param bindings not null bindings to provide to the script to execute
@@ -40,7 +48,7 @@ abstract class AbstractScriptEvaluator
    */
   public final Object eval( Bindings bindings ) throws ScriptException, UnsupportedScriptEngineException
   {
-    ScriptEngineManager manager = new ScriptEngineManager();
+    ScriptEngineManager manager = newScriptEngineManager();
     ScriptEngine engine = getEngine( manager );
     ScriptContext context = engine.getContext();
 
@@ -65,4 +73,16 @@ abstract class AbstractScriptEvaluator
    */
   protected abstract ScriptEngine getEngine( ScriptEngineManager manager )
       throws UnsupportedScriptEngineException;
+
+  private ScriptEngineManager newScriptEngineManager()
+  {
+    // don't go through the spi to have the mojo
+    JavaScriptEngineFactory javaScriptEngineFactory = new JavaScriptEngineFactory( mojo );
+    ScriptEngineManager manager = new ScriptEngineManager();
+    javaScriptEngineFactory.getExtensions().forEach( ext -> manager.registerEngineExtension(
+            ext, javaScriptEngineFactory ) );
+    javaScriptEngineFactory.getNames().forEach( name -> manager.registerEngineName(
+            name, javaScriptEngineFactory ) );
+    return manager;
+  }
 }
diff --git a/src/main/java/org/apache/maven/plugins/scripting/EvalMojo.java b/src/main/java/org/apache/maven/plugins/scripting/EvalMojo.java
index 4a6dbbc..27b1391 100644
--- a/src/main/java/org/apache/maven/plugins/scripting/EvalMojo.java
+++ b/src/main/java/org/apache/maven/plugins/scripting/EvalMojo.java
@@ -20,17 +20,24 @@ package org.apache.maven.plugins.scripting;
  */
 
 import java.io.File;
+import java.util.Map;
 
 import javax.script.Bindings;
 import javax.script.ScriptException;
 import javax.script.SimpleBindings;
 
+import org.apache.maven.execution.MavenSession;
 import org.apache.maven.plugin.AbstractMojo;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.Component;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.project.MavenProject;
+import org.apache.maven.toolchain.ToolchainManager;
+import org.codehaus.plexus.compiler.manager.CompilerManager;
+
+import static org.apache.maven.plugins.annotations.ResolutionScope.COMPILE_PLUS_RUNTIME;
 
 /**
  * Evaluate the specified script or scriptFile
@@ -38,7 +45,7 @@ import org.apache.maven.project.MavenProject;
  * @author Robert Scholte
  * @since 3.0.0
  */
-@Mojo( name = "eval" )
+@Mojo( name = "eval", requiresDependencyResolution = COMPILE_PLUS_RUNTIME )
 public class EvalMojo
     extends AbstractMojo
 {
@@ -60,9 +67,39 @@ public class EvalMojo
     @Parameter
     private File scriptFile;
 
+    /**
+     * Script Engine specific configuration.
+     * For now it configured maven java script engine.
+     * Available keys today:
+     * <ul>
+     *     <li>javaVersion: java version for the compiler (source/target)</li>
+     *     <li>output: where to put generated files by the compilation</li>
+     *     <li>encoding: encoding for the compiler</li>
+     *     <li>outputFileName: folder name to compile into</li>
+     *     <li>main: main fqn</li>
+     *     <li>method: method to execute if not a main, can have optionally a String array as parameter</li>
+     *     <li>arg.{i}: i-th argument of the main</li>
+     *     <li>usePluginLoader: should the parent classloader of the mojo be the plugin one or not</li>
+     * </ul>
+     */
+    @Parameter
+    protected Map<String, String> scriptEngineConfiguration;
+
+    @Component
+    protected ToolchainManager toolchainManager;
+
+    @Component
+    protected CompilerManager compilerManager;
+
     // script variables
     @Parameter( defaultValue = "${project}", readonly = true )
-    private MavenProject project;
+    protected MavenProject project;
+
+    /**
+     * Build session.
+     */
+    @Parameter( defaultValue = "${session}", readonly = true, required = true )
+    protected MavenSession session;
 
     @Override
     public void execute()
@@ -100,12 +137,12 @@ public class EvalMojo
 
       if ( scriptFile != null )
       {
-         execute = new FileScriptEvaluator( engineName, scriptFile );
+         execute = new FileScriptEvaluator( engineName, scriptFile, this );
 
       }
       else if ( script != null )
       {
-         execute = new StringScriptEvaluator( engineName, script );
+         execute = new StringScriptEvaluator( engineName, script, this );
 
       }
       else
@@ -114,4 +151,29 @@ public class EvalMojo
       }
       return execute;
     }
+
+    public CompilerManager getCompilerManager()
+    {
+        return compilerManager;
+    }
+
+    public MavenProject getProject()
+    {
+        return project;
+    }
+
+    public Map<String, String> getScriptEngineConfiguration()
+    {
+        return scriptEngineConfiguration;
+    }
+
+    public MavenSession getSession()
+    {
+        return session;
+    }
+
+    public ToolchainManager getToolchainManager()
+    {
+        return toolchainManager;
+    }
 }
\ No newline at end of file
diff --git a/src/main/java/org/apache/maven/plugins/scripting/FileScriptEvaluator.java b/src/main/java/org/apache/maven/plugins/scripting/FileScriptEvaluator.java
index 9f7abd7..af056bf 100644
--- a/src/main/java/org/apache/maven/plugins/scripting/FileScriptEvaluator.java
+++ b/src/main/java/org/apache/maven/plugins/scripting/FileScriptEvaluator.java
@@ -51,10 +51,10 @@ public class FileScriptEvaluator extends AbstractScriptEvaluator
    * @param engineName optional engine name, used to override the engine selection from the file extension
    * @param scriptFile not null
    */
-  public FileScriptEvaluator( String engineName, File scriptFile )
+  public FileScriptEvaluator( String engineName, File scriptFile, EvalMojo mojo )
   {
+    super( mojo );
     this.scriptFile = scriptFile;
-
     this.engineName = engineName;
   }
 
@@ -69,6 +69,7 @@ public class FileScriptEvaluator extends AbstractScriptEvaluator
   {
     try ( FileReader reader = new FileReader( scriptFile ) )
     {
+        context.setAttribute( "maven.filename", scriptFile.getName(), ScriptContext.ENGINE_SCOPE );
         return engine.eval( reader, context );
     }
     catch ( IOException ex )
diff --git a/src/main/java/org/apache/maven/plugins/scripting/StringScriptEvaluator.java b/src/main/java/org/apache/maven/plugins/scripting/StringScriptEvaluator.java
index 5a31c85..f2ddd06 100644
--- a/src/main/java/org/apache/maven/plugins/scripting/StringScriptEvaluator.java
+++ b/src/main/java/org/apache/maven/plugins/scripting/StringScriptEvaluator.java
@@ -46,8 +46,9 @@ public class StringScriptEvaluator extends AbstractScriptEvaluator
    * @param script the script
    * @throws IllegalArgumentException if either engineName or script is null
    */
-  public StringScriptEvaluator( String engineName, String script )
+  public StringScriptEvaluator( String engineName, String script, EvalMojo mojo )
   {
+    super( mojo );
     if ( engineName == null || engineName.isEmpty() )
     {
       throw new IllegalArgumentException( "Expected a non-empty engine name provided" );
@@ -85,6 +86,7 @@ public class StringScriptEvaluator extends AbstractScriptEvaluator
    */
   protected Object eval( ScriptEngine engine, ScriptContext context ) throws ScriptException
   {
+    context.setAttribute( "maven.filename", "mem_" + script.hashCode() + ".java", ScriptContext.ENGINE_SCOPE );
     return engine.eval( script, context );
   }
 }
\ No newline at end of file
diff --git a/src/main/java/org/apache/maven/plugins/scripting/engine/JavaScriptEngine.java b/src/main/java/org/apache/maven/plugins/scripting/engine/JavaScriptEngine.java
new file mode 100644
index 0000000..a503f24
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/scripting/engine/JavaScriptEngine.java
@@ -0,0 +1,422 @@
+package org.apache.maven.plugins.scripting.engine;
+
+/*
+ * 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.
+ */
+
+import org.apache.commons.io.input.ReaderInputStream;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.plugins.scripting.EvalMojo;
+import org.apache.maven.toolchain.Toolchain;
+import org.codehaus.plexus.compiler.CompilerConfiguration;
+import org.codehaus.plexus.compiler.CompilerMessage;
+import org.codehaus.plexus.compiler.CompilerResult;
+
+import javax.script.AbstractScriptEngine;
+import javax.script.Bindings;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngineFactory;
+import javax.script.SimpleBindings;
+import java.io.File;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+import static java.lang.ClassLoader.getSystemClassLoader;
+import static java.util.Collections.singletonList;
+import static java.util.Comparator.comparing;
+import static java.util.Objects.requireNonNull;
+import static java.util.Optional.ofNullable;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+
+/**
+ * Maven-java implementation of a script engine.
+ */
+public class JavaScriptEngine extends AbstractScriptEngine
+{
+    private final JavaScriptEngineFactory factory;
+
+    private final List<Path> toDelete = new ArrayList<>();
+
+    public JavaScriptEngine(  final JavaScriptEngineFactory factory )
+    {
+        this.factory = factory;
+    }
+
+    @Override
+    public Object eval(  final String script, final ScriptContext context )
+    {
+        return eval( new StringReader( script ), context );
+    }
+
+    @Override
+    public Bindings createBindings()
+    {
+        return new SimpleBindings();
+    }
+
+    @Override
+    public ScriptEngineFactory getFactory()
+    {
+        return factory;
+    }
+
+    @Override
+    public Object eval(  final Reader reader, final ScriptContext context )
+    {
+        final EvalMojo mojo = factory.getMojo();
+        final String mainClassName = requireNonNull(
+                mojo.getScriptEngineConfiguration().get( "main" ), "missing main" );
+
+        final CompilerConfiguration conf = createConf( reader, mojo, mainClassName );
+        toDelete.add( Paths.get( conf.getOutputLocation() ) );
+        try
+        {
+            CompilerResult result;
+            try
+            {
+                result = mojo.getCompilerManager().getCompiler( "javac" ).performCompile( conf );
+            }
+            catch (  Exception e )
+            {
+                throw new IllegalStateException( "Fatal error compiling", e );
+            }
+
+            log( mojo, result );
+
+            final String[] args = ofNullable( mojo.getScriptEngineConfiguration() )
+                    .map( c -> c.keySet().stream()
+                            .filter( it -> it.startsWith( "arg." ) )
+                            .sorted( comparing( i -> Integer.parseInt( i.substring( "arg.".length() ) ) ) )
+                            .map( c::get )
+                            .toArray( String[]::new ) )
+                    .orElseGet( () -> new String[0] );
+
+            return doExecute( mainClassName, conf, args, mojo.getScriptEngineConfiguration() );
+        }
+        finally
+        {
+            toDelete.forEach( path ->
+            {
+                if (  !Files.isDirectory(  path ) )
+                {
+                    try
+                    {
+                        Files.delete(  path );
+                    }
+                    catch (  final IOException e )
+                    {
+                        throw new IllegalArgumentException(  e );
+                    }
+                }
+                else
+                {
+                    try
+                    {
+                        Files.walkFileTree( path, new SimpleFileVisitor<Path>()
+                        {
+                            @Override
+                            public FileVisitResult visitFile(  final Path file, final BasicFileAttributes attrs )
+                                    throws IOException
+                            {
+                                Files.delete( file );
+                                return super.visitFile( file, attrs );
+                            }
+
+                            @Override
+                            public FileVisitResult postVisitDirectory(  final Path dir, final IOException exc )
+                                    throws IOException
+                            {
+                                Files.delete( dir );
+                                return super.postVisitDirectory( dir, exc );
+                            }
+                        } );
+                    }
+                    catch (  final IOException e )
+                    {
+                        throw new IllegalArgumentException( e );
+                    }
+                }
+            } );
+            toDelete.clear();
+        }
+    }
+
+    private Object doExecute(  final String mainClassName, final CompilerConfiguration conf,
+                             final String[] args, final Map<String, String> mojoConf )
+    {
+        try
+        {
+            try (  final URLClassLoader classLoader = new URLClassLoader( 
+                    Stream.concat( 
+                            conf.getClasspathEntries().stream()
+                                    .map( Paths::get )
+                                    .map( p ->
+                                    {
+                                        try
+                                        {
+                                            return p.toUri().toURL();
+                                        }
+                                        catch (  final MalformedURLException e )
+                                        {
+                                            throw new IllegalArgumentException( e );
+                                        }
+                                    } ),
+                            Stream.of( Paths.get( conf.getOutputLocation() )
+                                    .toUri()
+                                    .toURL() ) )
+                            .toArray( URL[]::new ),
+                    Boolean.parseBoolean( mojoConf.get( "usePluginLoader" ) )
+                            ? Thread.currentThread().getContextClassLoader()
+                            : getSystemClassLoader() ) )
+            {
+                try
+                {
+                    final Class<?> mainClass = classLoader.loadClass( mainClassName.trim() );
+                    final String mtd = mojoConf.get( "method" );
+                    final Method main;
+                    if ( mtd == null )
+                    {
+                        main = mainClass.getDeclaredMethod( "main", String[].class );
+                    }
+                    else
+                    {
+                        main = Stream.of( mainClass.getDeclaredMethods() )
+                                .filter( m -> Objects.equals( mtd, m.getName() ) )
+                                .sorted( comparing( Method::getParameterCount ) )
+                                .findAny()
+                                .orElseThrow( () -> new IllegalArgumentException(
+                                        "No method '" + mtd + "' in " + mainClass ) );
+                    }
+                    if ( !main.isAccessible() )
+                    {
+                        main.setAccessible( true );
+                    }
+                    main.invoke( null, new Object[]{args} );
+                    return null;
+                }
+                catch (  final Exception e )
+                {
+                    throw new IllegalStateException( e );
+                }
+            }
+        }
+        catch (  final IOException e )
+        {
+            throw new IllegalArgumentException( e );
+        }
+    }
+
+    private CompilerConfiguration createConf(  final Reader reader, final EvalMojo mojo,
+                                             final String mainClassName )
+    {
+        final Toolchain tc = mojo.getToolchainManager().getToolchainFromBuildContext( "jdk", mojo.getSession() );
+        final String defaultJavaVersion = ofNullable( mojo.getScriptEngineConfiguration() )
+                .map( map -> map.get( "javaVersion" ) )
+                .orElseGet( () -> ofNullable( mojo.getProject().getProperties().getProperty( "maven.compiler.source" ) )
+                        .orElse( "8" ) );
+        final String output = ofNullable( mojo.getScriptEngineConfiguration() )
+                .map( map -> map.get( "output" ) )
+                .orElseGet( () -> Paths.get( mojo.getProject().getBuild().getDirectory() )
+                        .resolve( "maven-scripting-plugin/classes_" + context.getAttribute( "maven.filename" ) )
+                        .toString() );
+        final String encoding = ofNullable( mojo.getScriptEngineConfiguration() )
+                .map( map -> map.get( "encoding" ) )
+                .orElseGet( () -> ofNullable( mojo.getProject()
+                        .getProperties().getProperty( "project.build.sourceEncoding" ) )
+                        .orElse( "UTF-8" ) );
+        final String outputFileName = ofNullable( mojo.getScriptEngineConfiguration() )
+                .map( map -> map.get( "outputFileName" ) )
+                .orElse( "script" );
+        final String compileSourceRoot = ofNullable( mojo.getScriptEngineConfiguration() )
+                .map( map -> map.get( "compileSourceRoot" ) )
+                .filter( s -> !s.isEmpty() )
+                .orElseGet( () ->
+                {
+                    final File file = new File( mojo.getProject().getBasedir(), "src/main/scripting" );
+                    if ( !file.exists() )
+                    {
+                        return null;
+                    }
+                    return file.getAbsolutePath();
+                } );
+        final List<String> classpath = ofNullable( mojo.getScriptEngineConfiguration() )
+                .map( c -> c.get( "classpath" ) )
+                .map( c -> Stream.of( c.split( ":" ) ).collect( toList() ) )
+                .orElseGet( () -> mojo.getProject().getArtifacts().stream()
+                        .map( Artifact::getFile )
+                        .filter( Objects::nonNull )
+                        .map( File::getAbsolutePath )
+                        .collect( toList() ) );
+        final File tempSources = new File( mojo.getProject().getBuild().getDirectory(),
+                "maven-scripting-plugin/sources" );
+        toDelete.add( tempSources.toPath() );
+        try
+        {
+            final int lastDot = mainClassName.lastIndexOf( '.' ) + 1;
+            final String className = lastDot == 0 ? mainClassName : mainClassName.substring( 0, lastDot );
+            final File target = new File( tempSources, className + ".java" );
+            if ( !target.getParentFile().exists() && !target.getParentFile().mkdirs() )
+            {
+                throw new IllegalArgumentException( "Can't create " + target.getParentFile() );
+            }
+            Files.copy( new ReaderInputStream( reader, StandardCharsets.UTF_8 ), target.toPath(),
+                    StandardCopyOption.REPLACE_EXISTING );
+        }
+        catch (  final IOException e )
+        {
+            throw new IllegalArgumentException( e );
+        }
+
+        final CompilerConfiguration conf = new CompilerConfiguration();
+        conf.setOutputLocation( output );
+        conf.setParameters( true );
+        conf.setShowWarnings( true );
+        conf.setShowDeprecation( true );
+        conf.setSourceVersion( defaultJavaVersion );
+        conf.setTargetVersion( defaultJavaVersion );
+        if ( !"8".equals( defaultJavaVersion ) && !"1.8".equals( defaultJavaVersion ) )
+        {
+            conf.setReleaseVersion( defaultJavaVersion );
+        }
+        conf.setSourceLocations( compileSourceRoot == null
+                ? singletonList( tempSources.getAbsolutePath() )
+                : Stream.concat(
+                        Stream.of( tempSources.getAbsolutePath() ),
+                        Stream.of( compileSourceRoot.split( ":" ) ) )
+                        .distinct()
+                        .collect( toList() ) );
+        conf.setSourceEncoding( encoding );
+        conf.setFork( true );
+        conf.setWorkingDirectory( mojo.getProject().getBasedir() );
+        conf.setBuildDirectory( new File( mojo.getProject().getBuild().getDirectory() ) );
+        conf.setOutputFileName( outputFileName );
+        conf.setCompilerReuseStrategy( CompilerConfiguration.CompilerReuseStrategy.AlwaysNew );
+        conf.setForceJavacCompilerUse( true );
+        conf.setClasspathEntries( classpath );
+        if ( tc != null )
+        {
+            conf.setExecutable( tc.findTool( "javac" ) );
+        }
+        try
+        {
+            conf.setSourceFiles( compileSourceRoot == null ? null : Files.list( Paths.get( compileSourceRoot ) )
+                    .filter( it -> it.getFileName().toString().endsWith( ".java" ) )
+                    .map( Path::toFile )
+                    .collect( toSet() ) );
+        }
+        catch (  final IOException e )
+        {
+            throw new IllegalStateException( e );
+        }
+        return conf;
+    }
+
+    private void log(  final EvalMojo mojo, final CompilerResult result )
+    {
+        List<CompilerMessage> warnings = new ArrayList<>();
+        List<CompilerMessage> errors = new ArrayList<>();
+        List<CompilerMessage> others = new ArrayList<>();
+        for (  final CompilerMessage message : result.getCompilerMessages() )
+        {
+            if ( message.getKind() == CompilerMessage.Kind.ERROR )
+            {
+                errors.add( message );
+            }
+            else if ( message.getKind() == CompilerMessage.Kind.WARNING
+                    || message.getKind() == CompilerMessage.Kind.MANDATORY_WARNING )
+            {
+                warnings.add( message );
+            }
+            else
+            {
+                others.add( message );
+            }
+        }
+
+        if ( !result.isSuccess() )
+        {
+            others.forEach( message -> mojo.getLog().info( message.toString() ) );
+            if ( !warnings.isEmpty() )
+            {
+                mojo.getLog().info( "-------------------------------------------------------------" );
+                mojo.getLog().warn( "COMPILATION WARNING : " );
+                mojo.getLog().info( "-------------------------------------------------------------" );
+                others.forEach( warning -> mojo.getLog().warn( warning.toString() ) );
+                mojo.getLog().info( warnings.size() + ( ( warnings.size() > 1 ) ? " warnings " : " warning" ) );
+                mojo.getLog().info( "-------------------------------------------------------------" );
+            }
+
+            if ( !errors.isEmpty() )
+            {
+                mojo.getLog().info( "-------------------------------------------------------------" );
+                mojo.getLog().error( "COMPILATION ERROR : " );
+                mojo.getLog().info( "-------------------------------------------------------------" );
+                others.forEach( error -> mojo.getLog().error( error.toString() ) );
+                mojo.getLog().info( errors.size() + ( ( errors.size() > 1 ) ? " errors " : " error" ) );
+                mojo.getLog().info( "-------------------------------------------------------------" );
+            }
+
+            throw new IllegalStateException( ( !errors.isEmpty() ? errors : warnings ).stream()
+                    .map( CompilerMessage::toString )
+                    .collect( joining( "\n" ) ) );
+        }
+        else
+        {
+            result.getCompilerMessages().forEach( message ->
+            {
+                switch ( message.getKind() )
+                {
+                    case NOTE:
+                    case OTHER:
+                        mojo.getLog().info( message.toString() );
+                        break;
+
+                    case ERROR:
+                        mojo.getLog().error( message.toString() );
+                        break;
+
+                    case MANDATORY_WARNING:
+                    case WARNING:
+                    default:
+                        mojo.getLog().warn( message.toString() );
+                        break;
+                }
+            } );
+        }
+    }
+}
diff --git a/src/main/java/org/apache/maven/plugins/scripting/engine/JavaScriptEngineFactory.java b/src/main/java/org/apache/maven/plugins/scripting/engine/JavaScriptEngineFactory.java
new file mode 100644
index 0000000..8a28443
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/scripting/engine/JavaScriptEngineFactory.java
@@ -0,0 +1,133 @@
+package org.apache.maven.plugins.scripting.engine;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.plugins.scripting.EvalMojo;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineFactory;
+import java.util.List;
+
+import static java.util.Arrays.asList;
+
+/**
+ * Maven-java script engine factory.
+ */
+public class JavaScriptEngineFactory implements ScriptEngineFactory
+{
+    private final EvalMojo mojo;
+
+    public JavaScriptEngineFactory( final EvalMojo mojo )
+    {
+        this.mojo = mojo;
+    }
+
+    EvalMojo getMojo()
+    {
+        return mojo;
+    }
+
+    @Override
+    public String getEngineName()
+    {
+        return "maven";
+    }
+
+    @Override
+    public String getEngineVersion()
+    {
+        return "1.0.0";
+    }
+
+    @Override
+    public List<String> getExtensions()
+    {
+        return asList( "java", "maven" );
+    }
+
+    @Override
+    public List<String> getMimeTypes()
+    {
+        return asList( "text/java", "application/java" );
+    }
+
+    @Override
+    public List<String> getNames()
+    {
+        return asList( getClass().getSimpleName(), "MavenJava", "Java", "java", "maven" );
+    }
+
+    @Override
+    public String getLanguageName()
+    {
+        return "Java";
+    }
+
+    @Override
+    public String getLanguageVersion()
+    {
+        return System.getProperty( "java.version" );
+    }
+
+    @Override
+    public Object getParameter( final String key )
+    {
+        switch ( key )
+        {
+            case "javax.script.engine_version":
+                return getEngineVersion();
+            case "javax.script.engine":
+                return getEngineName();
+            case "javax.script.language":
+            case "javax.script.name":
+                return getLanguageName();
+            case "javax.script.language_version":
+                return getLanguageVersion();
+            case "THREADING":
+            default:
+                return null;
+        }
+    }
+
+    @Override
+    public String getMethodCallSyntax( final String obj, final String method,
+                                       final String... args )
+    {
+        return obj + '.' + method + '(' + String.join( ", ", args ) + ");";
+    }
+
+    @Override
+    public String getOutputStatement( final String toDisplay )
+    {
+        return "System.out.println(" + toDisplay + ");";
+    }
+
+    @Override
+    public String getProgram( final String... statements )
+    {
+        return String.join( ";\n", statements );
+    }
+
+    @Override
+    public ScriptEngine getScriptEngine()
+    {
+        return new JavaScriptEngine( this );
+    }
+}
diff --git a/src/test/java/org/apache/maven/plugins/scripting/engine/JavaScriptEngineTest.java b/src/test/java/org/apache/maven/plugins/scripting/engine/JavaScriptEngineTest.java
new file mode 100644
index 0000000..1389fa5
--- /dev/null
+++ b/src/test/java/org/apache/maven/plugins/scripting/engine/JavaScriptEngineTest.java
@@ -0,0 +1,99 @@
+package org.apache.maven.plugins.scripting.engine;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.execution.DefaultMavenExecutionRequest;
+import org.apache.maven.execution.MavenExecutionResult;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.model.Build;
+import org.apache.maven.plugins.scripting.EvalMojo;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.toolchain.DefaultToolchainManager;
+import org.codehaus.plexus.compiler.Compiler;
+import org.codehaus.plexus.compiler.javac.JavacCompiler;
+import org.codehaus.plexus.compiler.manager.DefaultCompilerManager;
+import org.codehaus.plexus.compiler.manager.NoSuchCompilerException;
+import org.junit.Test;
+
+import javax.script.SimpleScriptContext;
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+public class JavaScriptEngineTest {
+    @Test
+    public void execute() {
+        System.clearProperty("JavaScriptEngineTest.execute");
+
+        final Map<String, String> config = new HashMap<>();
+        config.put("compileSourceRoot", "");
+        config.put("main", "Run");
+        config.put("arg.01", "JavaScriptEngineTest.execute");
+
+        new JavaScriptEngine(new JavaScriptEngineFactory(new EvalMojo()
+        {
+            {
+                compilerManager = new DefaultCompilerManager()
+                {
+                    @Override
+                    public Compiler getCompiler(final String compilerId)
+                    {
+                        return new JavacCompiler();
+                    }
+                };
+                toolchainManager = new DefaultToolchainManager();
+                project = new MavenProject()
+                {
+                    {
+                        setBuild(new Build()
+                        {
+                            {
+                                setDirectory("target/test-work/JavaScriptEngineTest-execute");
+                            }
+                        });
+                    }
+
+                    @Override
+                    public File getBasedir()
+                    {
+                        return new File("target/test-work");
+                    }
+                };
+                session = new MavenSession(null, null, new DefaultMavenExecutionRequest(), (MavenExecutionResult) null);
+            }
+
+            @Override
+            public Map<String, String> getScriptEngineConfiguration()
+            {
+                return config;
+            }
+        })).eval("" +
+                "public class Run {\n" +
+                "  public static void main(String... args) {\n" +
+                "    System.setProperty(args[0], \"true\");\n" +
+                "  }\n" +
+                "}\n" +
+                "\n", new SimpleScriptContext());
+        assertEquals("true", System.getProperty("JavaScriptEngineTest.execute"));
+        System.clearProperty("JavaScriptEngineTest.execute");
+    }
+}