| /******************************************************************************* |
| * 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.sling.maven.htl; |
| |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.io.Reader; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| 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.LifecyclePhase; |
| import org.apache.maven.plugins.annotations.Mojo; |
| import org.apache.maven.plugins.annotations.Parameter; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.sling.scripting.sightly.compiler.CompilationResult; |
| import org.apache.sling.scripting.sightly.compiler.CompilationUnit; |
| import org.apache.sling.scripting.sightly.compiler.CompilerMessage; |
| import org.apache.sling.scripting.sightly.compiler.SightlyCompiler; |
| import org.codehaus.plexus.util.Scanner; |
| import org.codehaus.plexus.util.StringUtils; |
| import org.sonatype.plexus.build.incremental.BuildContext; |
| |
| /** |
| * This goal validates Sightly scripts syntax. |
| */ |
| @Mojo( |
| name = "validate", |
| defaultPhase = LifecyclePhase.COMPILE, |
| threadSafe = true |
| ) |
| public class ValidateMojo extends AbstractMojo { |
| |
| private static final String DEFAULT_INCLUDES = "**/*.html"; |
| private static final String DEFAULT_EXCLUDES = ""; |
| |
| @Component |
| private BuildContext buildContext; |
| |
| @Parameter(defaultValue = "${project}", readonly = true, required = true) |
| protected MavenProject project; |
| |
| /** |
| * Defines the root folder where this Mojo expects to find Sightly scripts to validate. |
| */ |
| @Parameter(property = "sourceDirectory", defaultValue = "${project.build.sourceDirectory}") |
| private File sourceDirectory; |
| |
| /** |
| * List of files to include. Specified as fileset patterns which are relative to the input directory whose contents will be scanned |
| * (see the sourceDirectory configuration option). |
| */ |
| @Parameter |
| private String[] includes; |
| |
| /** |
| * List of files to exclude. Specified as fileset patterns which are relative to the input directory whose contents will be scanned |
| * (see the sourceDirectory configuration option). |
| */ |
| @Parameter |
| private String[] excludes; |
| |
| /** |
| * If set to "true" it will fail the build on compiler warnings. |
| */ |
| @Parameter(property = "failOnWarnings", defaultValue = "false") |
| private boolean failOnWarnings; |
| |
| /** |
| * If set to "true" the validation will be skipped. |
| */ |
| @Parameter |
| private boolean skip; |
| |
| private boolean hasWarnings = false; |
| private boolean hasErrors = false; |
| private String processedIncludes = null; |
| private String processedExcludes = null; |
| private List<File> processedFiles = Collections.emptyList(); |
| |
| private int sourceDirectoryLength = 0; |
| |
| public void execute() throws MojoExecutionException, MojoFailureException { |
| if (skip) { |
| getLog().info("Skipping validation."); |
| return; |
| } |
| |
| long start = System.currentTimeMillis(); |
| |
| if (!sourceDirectory.isAbsolute()) { |
| sourceDirectory = new File(project.getBasedir(), sourceDirectory.getPath()); |
| } |
| if (!sourceDirectory.exists()) { |
| getLog().info("Source directory does not exist, skipping."); |
| return; |
| } |
| if (!sourceDirectory.isDirectory()) { |
| throw new MojoExecutionException( |
| String.format("Configured sourceDirectory={%s} is not a directory.", sourceDirectory.getAbsolutePath())); |
| } |
| |
| if ( !buildContext.hasDelta(sourceDirectory )) { |
| getLog().info("No files found to validate, skipping."); |
| return; |
| } |
| |
| // don't fail execution in Eclipse as it generates an error marker in the POM file, which is not desired |
| boolean mayFailExecution = !buildContext.getClass().getName().startsWith("org.eclipse.m2e"); |
| |
| sourceDirectoryLength = sourceDirectory.getAbsolutePath().length(); |
| processedIncludes = processIncludes(); |
| processedExcludes = processExcludes(); |
| try { |
| SightlyCompiler compiler = new SightlyCompiler(); |
| |
| Scanner scanner = buildContext.newScanner(sourceDirectory); |
| scanner.setExcludes(new String[] { processedExcludes } ); |
| scanner.setIncludes(new String[] { processedIncludes } ); |
| scanner.scan(); |
| |
| String[] includedFiles = scanner.getIncludedFiles(); |
| |
| processedFiles = new ArrayList<>(includedFiles.length); |
| for ( String includedFile : includedFiles ) { |
| processedFiles.add(new File(sourceDirectory, includedFile)); |
| } |
| Map<File, CompilationResult> compilationResults = new HashMap<>(); |
| for (File script : processedFiles) { |
| compilationResults.put(script, compiler.compile(getCompilationUnit(script))); |
| } |
| for (Map.Entry<File, CompilationResult> entry : compilationResults.entrySet()) { |
| File script = entry.getKey(); |
| CompilationResult result = entry.getValue(); |
| buildContext.removeMessages(script); |
| |
| if (result.getWarnings().size() > 0) { |
| for (CompilerMessage message : result.getWarnings()) { |
| buildContext.addMessage(script, message.getLine(), message.getColumn(), message.getMessage(), BuildContext.SEVERITY_WARNING, null); |
| } |
| hasWarnings = true; |
| } |
| if (result.getErrors().size() > 0) { |
| for (CompilerMessage message : result.getErrors()) { |
| String messageString = message.getMessage().replaceAll(System.lineSeparator(), ""); |
| buildContext.addMessage(script, message.getLine(), message.getColumn(), messageString, BuildContext.SEVERITY_ERROR, null); |
| } |
| hasErrors = true; |
| } |
| } |
| |
| getLog().info("Processed " + processedFiles.size() + " files in " + ( System.currentTimeMillis() - start ) + " milliseconds"); |
| |
| if (mayFailExecution && hasWarnings && failOnWarnings) { |
| throw new MojoFailureException("Compilation warnings were configured to fail the build."); |
| } |
| if (mayFailExecution && hasErrors) { |
| throw new MojoFailureException("Please check the reported syntax errors."); |
| } |
| } catch (IOException e) { |
| throw new MojoExecutionException(String.format("Cannot filter files from {%s} with includes {%s} and excludes {%s}.", |
| sourceDirectory.getAbsolutePath(), processedIncludes, processedExcludes), e); |
| } |
| |
| } |
| |
| public File getSourceDirectory() { |
| return sourceDirectory; |
| } |
| |
| public boolean shouldFailOnWarnings() { |
| return failOnWarnings; |
| } |
| |
| public boolean hasWarnings() { |
| return hasWarnings; |
| } |
| |
| public boolean hasErrors() { |
| return hasErrors; |
| } |
| |
| public String getIncludes() { |
| return processedIncludes; |
| } |
| |
| public String getExcludes() { |
| return processedExcludes; |
| } |
| |
| public List<File> getProcessedFiles() { |
| return processedFiles; |
| } |
| |
| private String processIncludes() { |
| if (includes == null) { |
| return DEFAULT_INCLUDES; |
| } |
| return join(includes, ','); |
| } |
| |
| private String processExcludes() { |
| if (excludes == null) { |
| return DEFAULT_EXCLUDES; |
| } |
| return join(excludes, ','); |
| } |
| |
| private String join(String[] array, char joinChar) { |
| StringBuilder stringBuilder = new StringBuilder(); |
| for (int index = 0; index < array.length; index++) { |
| stringBuilder.append(StringUtils.trim(array[index])); |
| if (index < array.length - 1) { |
| stringBuilder.append(joinChar); |
| } |
| } |
| return stringBuilder.toString(); |
| } |
| |
| private CompilationUnit getCompilationUnit(final File file) throws FileNotFoundException { |
| final Reader reader = new FileReader(file); |
| return new CompilationUnit() { |
| public String getScriptName() { |
| return file.getAbsolutePath().substring(sourceDirectoryLength); |
| } |
| |
| public Reader getScriptReader() { |
| return reader; |
| } |
| }; |
| } |
| |
| // visible for testing only |
| void setBuildContext(BuildContext buildContext) { |
| |
| this.buildContext = buildContext; |
| } |
| } |