org.wisdom.maven.pipeline.Pipeline.java Source code

Java tutorial

Introduction

Here is the source code for org.wisdom.maven.pipeline.Pipeline.java

Source

/*
 * #%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).
 * <p>
 * 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<Watcher> 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);
    }
}