org.wisdom.maven.pipeline.Pipeline Maven / Gradle / Ivy
Show all versions of wisdom-maven-plugin Show documentation
/*
* #%L
* Wisdom-Framework
* %%
* Copyright (C) 2013 - 2014 Wisdom Framework
* %%
* Licensed 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.
* #L%
*/
package org.wisdom.maven.pipeline;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.apache.maven.plugin.Mojo;
import org.json.simple.JSONObject;
import org.wisdom.maven.Watcher;
import org.wisdom.maven.WatchingException;
import org.wisdom.maven.utils.DefensiveThreadFactory;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* The pipeline is the spine of the watching system of Wisdom.
* Each Mojo, i.e. Maven Plugin, willing to become a watcher, will be plugged to the pipeline. Registration is made
* using org.wisdom.maven.pipeline.Watchers#add(org.apache.maven.execution.MavenSession, org.wisdom.maven.Watcher).
*
* The pipeline is an internal class and should not be used directly. It just delegates the file events to the
* watchers. So this class holds the file alteration monitor that triggers the reactions of the different mojos.
*/
public class Pipeline {
public static final String EMPTY_STRING = "";
private final boolean pomFileMonitoring;
private List watchers = new ArrayList<>();
private final Mojo mojo;
private FileAlterationMonitor watcher;
private final File baseDir;
/**
* The file used to store a JSON representation of Watching Exceptions happening on a watched event.
*/
private File error;
private static final String WATCHING_EXCEPTION_MESSAGE = "Watching exception: %s (check log for more details)";
/**
* Creates a new pipeline. Notice that the set of watchers cannot change.
* @param mojo the 'run' mojo
* @param baseDir the base directory of the watched project
* @param list the set of watchers plugged on the pipeline, the order of the list will be the notification order.
* @param pomFileMonitoring flag enabling or disabling the pom file monitoring
*/
public Pipeline(Mojo mojo, File baseDir, List extends Watcher> list, boolean pomFileMonitoring) {
this.mojo = mojo;
this.baseDir = baseDir;
this.pomFileMonitoring = pomFileMonitoring;
mojo.getLog().debug("Initializing watch mode with " + list);
watchers = new ArrayList<>();
for (Object o : list) {
watchers.add(new WatcherDelegate(o));
}
}
/**
* Shuts down the pipeline. This methods stops the FAM.
*/
public void shutdown() {
try {
watcher.stop();
} catch (Exception e) { //NOSONAR
mojo.getLog().debug("Something went terribly wrong when we try to stopped the FAM of the pipeline", e);
// ignore it.
}
}
/**
* Starts the watching.
*
* @return the current pipeline.
*/
public Pipeline watch() {
// Delete all error reports before starting the watcher.
error = new File(baseDir, "target/pipeline");
FileUtils.deleteQuietly(error);
mojo.getLog().debug("Creating the target/pipeline directory : " + error.mkdirs());
// Start the watching process.
watcher = new FileAlterationMonitor(Integer.getInteger("watch.period", 2) * 1000);
watcher.setThreadFactory(new DefensiveThreadFactory("wisdom-pipeline-watcher", mojo));
FileAlterationObserver srcObserver = new FileAlterationObserver(new File(baseDir, "src"),
TrueFileFilter.INSTANCE);
PipelineWatcher listener = new PipelineWatcher(this);
srcObserver.addListener(listener);
watcher.addObserver(srcObserver);
if (pomFileMonitoring) {
FileAlterationObserver pomObserver = new FileAlterationObserver(baseDir, new FileFilter() {
@Override
public boolean accept(File file) {
return file.equals(new File(baseDir, "pom.xml"));
}
});
pomObserver.addListener(listener);
watcher.addObserver(pomObserver);
}
try {
mojo.getLog().info("Start watching " + baseDir.getAbsolutePath());
watcher.start();
} catch (Exception e) {
mojo.getLog().error("Cannot start the watcher", e);
}
return this;
}
/**
* The FAM has detected a new file. It dispatches this event to the watchers plugged on the current pipeline.
*
* @param file the created file
*/
public void onFileCreate(File file) {
mojo.getLog().info(EMPTY_STRING);
mojo.getLog().info("The watcher has detected a new file: " + file.getAbsolutePath());
mojo.getLog().info(EMPTY_STRING);
for (Watcher watcher : watchers) {
if (watcher.accept(file)) {
// This flag will be set to false if the processing must be interrupted.
boolean continueProcessing;
try {
cleanupErrorFile(watcher);
continueProcessing = watcher.fileCreated(file);
} catch (WatchingException e) { //NOSONAR
mojo.getLog().debug(watcher + " has thrown an exception while handling the " + file.getName() + EMPTY_STRING +
" creation", e);
mojo.getLog().error(String.format(WATCHING_EXCEPTION_MESSAGE, e.getMessage()));
createErrorFile(watcher, e);
continueProcessing = false;
}
if (!continueProcessing) {
break;
}
}
}
mojo.getLog().info(EMPTY_STRING);
mojo.getLog().info(EMPTY_STRING);
}
/**
* Creates the error file storing the information from the given exception in JSON. This file is consumed by the
* Wisdom server to generate an error page reporting the watching exception.
*
* @param watcher the watcher having thrown the exception
* @param e the exception
*/
@SuppressWarnings("unchecked")
private void createErrorFile(Watcher watcher, WatchingException e) {
mojo.getLog().debug("Creating error file for '" + e.getMessage() + "' happening at " + e.getLine() + ":" + e
.getCharacter() + " of " + e.getFile() + ", created by watcher : " + watcher);
JSONObject obj = new JSONObject();
obj.put("message", e.getMessage());
if (watcher instanceof WatcherDelegate) {
obj.put("watcher", ((WatcherDelegate) watcher).getDelegate().getClass().getName());
} else {
obj.put("watcher", watcher.getClass().getName());
}
if (e.getFile() != null) {
obj.put("file", e.getFile().getAbsolutePath());
}
if (e.getLine() != -1) {
obj.put("line", e.getLine());
}
if (e.getCharacter() != -1) {
obj.put("character", e.getCharacter());
}
if (e.getCause() != null) {
obj.put("cause", e.getCause().getMessage());
}
if (e.getTitle() != null) {
obj.put("title", e.getTitle());
}
try {
FileUtils.writeStringToFile(getErrorFileForWatcher(watcher), obj.toJSONString(), false);
} catch (IOException e1) {
mojo.getLog().error("Cannot write the error file", e1);
}
}
/**
* Method called on each event before the processing, deleting the error file is this file exists.
*
* @param watcher the watcher
*/
private void cleanupErrorFile(Watcher watcher) {
File file = getErrorFileForWatcher(watcher);
FileUtils.deleteQuietly(file);
}
private File getErrorFileForWatcher(Watcher watcher) {
if (watcher instanceof WatcherDelegate) {
return new File(error, ((WatcherDelegate) watcher).getDelegate().toString() + ".json");
} else {
return new File(error, watcher + ".json");
}
}
/**
* The FAM has detected a change in a file. It dispatches this event to the watchers plugged on the current
* pipeline.
*
* @param file the updated file
*/
public void onFileChange(File file) {
mojo.getLog().info(EMPTY_STRING);
mojo.getLog().info("The watcher has detected a change in " + file.getAbsolutePath());
mojo.getLog().info(EMPTY_STRING);
for (Watcher watcher : watchers) {
if (watcher.accept(file)) {
cleanupErrorFile(watcher);
// This flag will be set to false if the processing must be interrupted.
boolean continueProcessing;
try {
continueProcessing = watcher.fileUpdated(file);
} catch (WatchingException e) { //NOSONAR
mojo.getLog().debug(watcher + " has thrown an exception while handling the " + file.getName() + EMPTY_STRING +
" update", e);
mojo.getLog().error(String.format(WATCHING_EXCEPTION_MESSAGE, e.getMessage()));
createErrorFile(watcher, e);
continueProcessing = false;
}
if (!continueProcessing) {
break;
}
}
}
mojo.getLog().info(EMPTY_STRING);
mojo.getLog().info(EMPTY_STRING);
}
/**
* The FAM has detected a file deletion. It dispatches this event to the watchers plugged on the current
* pipeline.
*
* @param file the deleted file
*/
public void onFileDelete(File file) {
mojo.getLog().info(EMPTY_STRING);
mojo.getLog().info("The watcher has detected a deleted file: " + file.getAbsolutePath());
mojo.getLog().info(EMPTY_STRING);
for (Watcher watcher : watchers) {
if (watcher.accept(file)) {
cleanupErrorFile(watcher);
// This flag will be set to false if the processing must be interrupted.
boolean continueProcessing;
try {
continueProcessing = watcher.fileDeleted(file);
} catch (WatchingException e) { //NOSONAR
mojo.getLog().debug(watcher + " has thrown an exception while handling the " + file.getName() + EMPTY_STRING +
" deletion", e);
mojo.getLog().error(String.format(WATCHING_EXCEPTION_MESSAGE, e.getMessage()));
createErrorFile(watcher, e);
continueProcessing = false;
}
if (!continueProcessing) {
break;
}
}
}
mojo.getLog().info(EMPTY_STRING);
mojo.getLog().info(EMPTY_STRING);
}
}