/*
* Copyright (c) 2005-2016 Vincent Vandenschrick. All rights reserved.
*
* This file is part of the Jspresso framework.
*
* Jspresso is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Jspresso is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Jspresso. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jspresso.maven;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.util.GroovyScriptEngine;
import groovy.util.ResourceException;
import groovy.util.ScriptException;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
/**
* Goal which performs SJS compilation for a Jspresso project.
*
* @author Vincent Vandenschrick
*/
@Mojo(name = "compile-sjs", defaultPhase = LifecyclePhase.GENERATE_SOURCES,
requiresDependencyResolution = ResolutionScope.COMPILE, threadSafe = true)
public class SjsMojo extends AbstractMojo {
private static final String GROOVY_EXT = ".groovy";
private static final String SJSEXT = ".sjs";
/**
* The Maven project.
*/
@Parameter(defaultValue = "${project}", required = true, readonly = true)
private MavenProject project;
/**
* The directory containing the groovy dsl sources.
*/
@Parameter(defaultValue = "${basedir}/src/main/dsl", required = true)
private File srcDir;
/**
* The name of the application groovy file that packs the application.
*/
@Parameter(defaultValue = "application.sjs", required = true)
private String applicationFileName;
/**
* Sets the output directory for generated resources.
*/
@Parameter(defaultValue = "${project.build.directory}/generated-resources/dsl", required = true)
private File outputDir;
/**
* The target file name for the model Spring beans.
*/
@Parameter(defaultValue = "dsl-model.xml", required = true)
private String modelOutputFileName;
/**
* The target file name for the view Spring beans.
*/
@Parameter(defaultValue = "dsl-view.xml", required = true)
private String viewOutputFileName;
/**
* The target file name for the backend Spring beans.
*/
@Parameter(defaultValue = "dsl-backend.xml", required = true)
private String backOutputFileName;
/**
* The target file name for the frontend Spring beans.
*/
@Parameter(defaultValue = "dsl-frontend.xml", required = true)
private String frontOutputFileName;
/**
* Triggers thee execution of SJS compilation.
*
* @throws MojoExecutionException
* whenever an unexpected error occurs when executing mojo.
*/
@Override
public void execute() throws MojoExecutionException {
if (isChangeDetected()) {
try {
runSjsCompilation();
} catch (IOException | DependencyResolutionRequiredException | ScriptException | ResourceException ex) {
throw new MojoExecutionException(
"An unexpected exception occurred when running SJS compilation.", ex);
}
} else {
getLog().info("No change detected. Skipping generation.");
}
Resource outputResource = new Resource();
outputResource.setDirectory(outputDir.getPath());
project.addResource(outputResource);
}
private void runSjsCompilation() throws IOException, ResourceException,
ScriptException, DependencyResolutionRequiredException {
Properties projectProperties = project.getProperties();
projectProperties.put("srcDir", srcDir.getAbsolutePath());
projectProperties.put("outputDir", outputDir.getAbsolutePath());
projectProperties.put("modelOutputFileName", modelOutputFileName);
projectProperties.put("viewOutputFileName", viewOutputFileName);
projectProperties.put("backOutputFileName", backOutputFileName);
projectProperties.put("frontOutputFileName", frontOutputFileName);
List<URL> classpath;
classpath = new ArrayList<>();
classpath.add(srcDir.toURI().toURL());
List<String> compileClasspathElements = project
.getCompileClasspathElements();
for (String element : compileClasspathElements) {
if (!element.equals(project.getBuild().getOutputDirectory())) {
File elementFile = new File(element);
getLog().debug(
"Adding element to plugin classpath " + elementFile.getPath());
URL url = elementFile.toURI().toURL();
classpath.add(url);
}
}
GroovyScriptEngine gse = new GroovyScriptEngine(
classpath.toArray(new URL[classpath.size()]));
Binding binding = new Binding();
binding.setVariable("project", project);
binding.setVariable("fail", new FailClosure());
String refinedApplicationFileName = applicationFileName;
if (!new File(srcDir + File.separator + applicationFileName).exists()) {
if (applicationFileName.contains(SJSEXT)) {
refinedApplicationFileName = applicationFileName.replaceAll(SJSEXT, GROOVY_EXT);
} else if(applicationFileName.contains(GROOVY_EXT)) {
refinedApplicationFileName = applicationFileName.replaceAll(GROOVY_EXT, SJSEXT);
}
}
gse.run(refinedApplicationFileName, binding);
}
private boolean isChangeDetected() {
if (!outputDir.exists() || outputDir.list().length == 0) {
return true;
}
long outputLastModified = latestModified(outputDir,
outputDir.lastModified());
getLog().info("Scanning for changes : " + srcDir);
return hasChangedSourceFile(srcDir, outputLastModified);
}
private long latestModified(File root, long maxLastModified) {
long latest = maxLastModified;
if (root.lastModified() > maxLastModified) {
latest = root.lastModified();
}
if (root.isDirectory()) {
File[] files = root.listFiles();
if (files != null) {
for (File child : files) {
latest = latestModified(child, latest);
}
}
}
return latest;
}
private boolean hasChangedSourceFile(File source, long maxLastModified) {
if (source.isDirectory()) {
File[] files = source.listFiles();
if (files != null) {
for (File childSource : files) {
if (hasChangedSourceFile(childSource, maxLastModified)) {
return true;
}
}
}
} else {
if (source.lastModified() > maxLastModified) {
getLog().info(
"Detected a change on source " + source.toString() + ". "
+ new Date(source.lastModified()) + " > "
+ new Date(maxLastModified));
return true;
}
}
return false;
}
//
// FailClosure
//
private class FailClosure extends Closure<Object> {
private static final long serialVersionUID = 1L;
/**
* Constructs a new {@code FailClosure} instance.
*/
public FailClosure() {
super(SjsMojo.this);
}
@Override
public Object call(Object... args) {
if (args == null || args.length == 0) {
throwRuntimeException(new MojoExecutionException("Failed"));
} else if (args.length == 1) {
if (args[0] instanceof Throwable) {
Throwable cause = (Throwable) args[0];
throwRuntimeException(new MojoExecutionException(cause.getMessage(),
cause));
} else {
throwRuntimeException(new MojoExecutionException(
String.valueOf(args[0])));
}
} else if (args.length == 2) {
if (args[1] instanceof Throwable) {
throwRuntimeException(new MojoExecutionException(
String.valueOf(args[0]), (Throwable) args[1]));
} else {
throw new Error(
"Invalid arguments to fail(Object, Throwable), second argument must be a Throwable");
}
} else {
throw new Error(
"Too many arguments for fail(), expected one of: fail(), fail(Object) or fail(Object, Throwable)");
}
return null;
}
}
}