com.xpn.xwiki.plugin.graphviz.GraphVizPlugin.java Source code

Java tutorial

Introduction

Here is the source code for com.xpn.xwiki.plugin.graphviz.GraphVizPlugin.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This 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 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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 this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package com.xpn.xwiki.plugin.graphviz;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.xpn.xwiki.XWiki;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.api.Api;
import com.xpn.xwiki.plugin.XWikiDefaultPlugin;
import com.xpn.xwiki.plugin.XWikiPluginInterface;
import com.xpn.xwiki.web.XWikiResponse;

/**
 * Plugin which wraps the <a href="http://graphviz.org/">GraphViz</a> <tt>dot</tt> executable; transforming dot source
 * files (representing graphs) into images, image maps, or other output formats supported by GraphViz.
 * <p>
 * See http://www.graphviz.org/doc/info/lang.html for the dot language specification. See
 * http://www.graphviz.org/doc/info/output.html for the possible output formats
 * </p>
 * 
 * @deprecated the plugin technology is deprecated
 * @version $Id: 5e4b5706aa4b9e1f3c4afc81e9aa6d161396cfc0 $
 */
@Deprecated
public class GraphVizPlugin extends XWikiDefaultPlugin {
    /** Logging helper object. */
    private static final Logger LOGGER = LoggerFactory
            .getLogger(com.xpn.xwiki.plugin.graphviz.GraphVizPlugin.class);

    /** The default output format to use: PNG image. */
    private static final String DEFAULT_FORMAT = "png";

    /** The default engine to use: dot. */
    private static final String DOT_ENGINE = "dot";

    /** An alternative engine to use: neato. */
    private static final String NEATO_ENGINE = "neato";

    /** Temporary directory where generated files are stored. */
    private File tempDir;

    /** The path to the dot executable. */
    private String dotPath;

    /** The path to the neato executable. */
    private String neatoPath;

    /**
     * The mandatory plugin constructor, this is the method called (through reflection) by the plugin manager.
     * 
     * @param name the plugin name
     * @param className the name of this class, ignored
     * @param context the current request context
     */
    public GraphVizPlugin(String name, String className, XWikiContext context) {
        super(name, className, context);
        init(context);
    }

    /**
     * {@inheritDoc}
     * 
     * @see XWikiPluginInterface#getName()
     */
    @Override
    public String getName() {
        return "graphviz";
    }

    /**
     * {@inheritDoc}
     * 
     * @see XWikiPluginInterface#getPluginApi(XWikiPluginInterface, XWikiContext)
     */
    @Override
    public Api getPluginApi(XWikiPluginInterface plugin, XWikiContext context) {
        return new GraphVizPluginApi((GraphVizPlugin) plugin, context);
    }

    /**
     * {@inheritDoc}
     * 
     * @see XWikiPluginInterface#flushCache(XWikiContext)
     */
    @Override
    public void flushCache() {
        try {
            FileUtils.cleanDirectory(this.tempDir);
        } catch (Exception e) {
            // Public APIs shouldn't throw errors; this shouldn't happen anyway
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see XWikiPluginInterface#init(XWikiContext)
     */
    @Override
    public void init(XWikiContext context) {
        super.init(context);

        File dir = context.getWiki().getTempDirectory(context);
        this.tempDir = new File(dir, this.getName());
        try {
            this.tempDir.mkdirs();
        } catch (Exception ex) {
            LOGGER.warn("Failed to create temporary file", ex);
        }

        this.dotPath = context.getWiki().Param("xwiki.plugin.graphviz.dotpath", DOT_ENGINE);
        if (!this.dotPath.equals(DOT_ENGINE)) {
            try {
                File dfile = new File(this.dotPath);
                if (!dfile.exists()) {
                    LOGGER.error("Cannot find graphiz dot program at " + this.dotPath);
                }
            } catch (Exception e) {
                // Access restrictions, not important
            }
        }

        this.neatoPath = context.getWiki().Param("xwiki.plugin.graphviz.neatopath", NEATO_ENGINE);
        if (!this.neatoPath.equals(NEATO_ENGINE)) {
            try {
                File dfile = new File(this.neatoPath);
                if (!dfile.exists()) {
                    LOGGER.error("Cannot find graphiz neato program at " + this.neatoPath);
                }
            } catch (Exception e) {
                // Access restrictions, not important
            }
        }

    }

    /**
     * Executes GraphViz and returns the URL for the produced file, a PNG image.
     * 
     * @param content the dot source
     * @param dot which engine to execute: {@code dot} if {@code true}, {@code neato} if {@code false}
     * @param context the current request context
     * @return the URL which can be used to access the generated image
     * @throws IOException if writing the input or output files to the disk fails
     * @see #getDotResultURL(String, boolean, String, XWikiContext) allows to chose another output format instead of PNG
     */
    public String getDotImageURL(String content, boolean dot, XWikiContext context) throws IOException {
        return getDotResultURL(content, dot, DEFAULT_FORMAT, context);
    }

    /**
     * Executes GraphViz and returns the URL for the produced file.
     * 
     * @param content the dot source code
     * @param dot which engine to execute: {@code dot} if {@code true}, {@code neato} if {@code false}
     * @param outputFormat the output format to use
     * @param context the current request context
     * @return the URL which can be used to access the result
     * @throws IOException if writing the input or output files to the disk fails
     * @see #getDotImageURL(String, boolean, XWikiContext) if the output should be a simple PNG image
     */
    public String getDotResultURL(String content, boolean dot, String outputFormat, XWikiContext context)
            throws IOException {
        String filename = writeDotImage(content, outputFormat, dot);
        return context.getDoc().getAttachmentURL(filename, DOT_ENGINE, context);
    }

    /**
     * Executes GraphViz and return the content of the resulting image (PNG format).
     * 
     * @param content the dot source code
     * @param dot which engine to execute: {@code dot} if {@code true}, {@code neato} if {@code false}
     * @return the content of the generated image
     * @throws IOException if writing the input or output files to the disk fails
     */
    public byte[] getDotImage(String content, boolean dot) throws IOException {
        return getDotImage(content, DEFAULT_FORMAT, dot);
    }

    /**
     * Executes GraphViz and return the content of the resulting image (PNG format).
     * 
     * @param content the dot source code
     * @param extension the output file extension
     * @param dot which engine to execute: {@code dot} if {@code true}, {@code neato} if {@code false}
     * @return the content of the generated file
     * @throws IOException if writing the input or output files to the disk fails
     */
    public byte[] getDotImage(String content, String extension, boolean dot) throws IOException {
        int hashCode = Math.abs(content.hashCode());
        return getDotImage(hashCode, content, extension, dot);
    }

    /**
     * Executes GraphViz, writes the resulting image (PNG format) in a temporary file on disk, and returns the filename
     * which can be later used in {@link #outputDotImageFromFile(String, XWikiContext)}.
     * 
     * @param content the dot source code
     * @param dot which engine to execute: {@code dot} if {@code true}, {@code neato} if {@code false}
     * @return the name of the file where the generated output is stored
     * @throws IOException if writing the input or output files to the disk fails
     */
    public String writeDotImage(String content, boolean dot) throws IOException {
        return writeDotImage(content, DEFAULT_FORMAT, dot);
    }

    /**
     * Executes GraphViz, writes the resulting image (in the requested format) in a temporary file on disk, and returns
     * the filename which can be later used in {@link #outputDotImageFromFile(String, XWikiContext)}.
     * 
     * @param content the dot source code
     * @param extension the output file extension
     * @param dot which engine to execute: {@code dot} if {@code true}, {@code neato} if {@code false}
     * @return the name of the file where the generated output is stored
     * @throws IOException if writing the input or output files to the disk fails
     */
    public String writeDotImage(String content, String extension, boolean dot) throws IOException {
        int hashCode = Math.abs(content.hashCode());
        getDotImage(hashCode, content, extension, dot);
        String name = (dot ? DOT_ENGINE : NEATO_ENGINE) + '-';
        return name + hashCode + "." + extension;
    }

    /**
     * Executes GraphViz and writes the resulting image (PNG format) into the response.
     * 
     * @param content the dot source code
     * @param dot which engine to execute: {@code dot} if {@code true}, {@code neato} if {@code false}
     * @param context the current request context
     * @throws IOException if writing the input or output files to the disk fails, or if writing the response body fails
     */
    public void outputDotImage(String content, boolean dot, XWikiContext context) throws IOException {
        outputDotImage(content, DEFAULT_FORMAT, dot, context);
    }

    /**
     * Executes GraphViz and writes the resulting image (in the requested format) into the response.
     * 
     * @param content the dot source code
     * @param extension the output file extension
     * @param dot which engine to execute: {@code dot} if {@code true}, {@code neato} if {@code false}
     * @param context the current request context
     * @throws IOException if writing the input or output files to the disk fails, or if writing the response body fails
     */
    public void outputDotImage(String content, String extension, boolean dot, XWikiContext context)
            throws IOException {
        byte[] dotbytes = getDotImage(content, extension, dot);
        XWikiResponse response = context.getResponse();
        context.setFinished(true);
        response.setContentLength(dotbytes.length);
        response.setContentType(context.getEngineContext().getMimeType("toto." + extension));
        OutputStream os = response.getOutputStream();
        os.write(dotbytes);
        os.flush();
    }

    /**
     * Writes an already generated result from the temporary file into the response.
     * 
     * @param filename the name of the temporary file, previously returned by
     *            {@link #writeDotImage(String, String, boolean)}
     * @param context the current request context
     * @throws IOException if reading the file from the disk fails, or if writing the response body fails
     */
    public void outputDotImageFromFile(String filename, XWikiContext context) throws IOException {
        File ofile = getTempFile(filename);
        byte[] dotbytes = readDotImage(ofile);
        XWikiResponse response = context.getResponse();
        context.setFinished(true);
        response.setDateHeader("Last-Modified", ofile.lastModified());
        response.setContentLength(dotbytes.length);
        response.setContentType(context.getEngineContext().getMimeType(filename));
        OutputStream os = response.getOutputStream();
        os.write(dotbytes);
    }

    /**
     * Executes GraphViz, writes the resulting image (in the requested format) in a temporary file on disk, and returns
     * the generated content from that file.
     * 
     * @param hashCode the hascode of the content, to be used as the temporary file name
     * @param content the dot source code
     * @param extension the output file extension
     * @param dot which engine to execute: {@code dot} if {@code true}, {@code neato} if {@code false}
     * @return the content of the generated file
     * @throws IOException if writing the input or output files to the disk fails, or if writing the response body fails
     */
    private byte[] getDotImage(int hashCode, String content, String extension, boolean dot) throws IOException {
        File dfile = getTempFile(hashCode, "input.dot", dot);
        if (!dfile.exists()) {
            FileUtils.write(dfile, content, XWiki.DEFAULT_ENCODING);
        }

        File ofile = getTempFile(hashCode, extension, dot);
        if (!ofile.exists()) {
            Runtime rt = Runtime.getRuntime();
            String[] command = new String[5];
            command[0] = dot ? this.dotPath : this.neatoPath;
            command[1] = "-T" + extension;
            command[2] = dfile.getAbsolutePath();
            command[3] = "-o";
            command[4] = ofile.getAbsolutePath();
            Process p = rt.exec(command);
            int exitValue = -1;
            final Thread thisThread = Thread.currentThread();
            Thread t = new Thread(new Hangcheck(thisThread), "dot-hangcheck");
            t.run();
            try {
                exitValue = p.waitFor();
                t.interrupt();
            } catch (InterruptedException ex) {
                p.destroy();
                LOGGER.error("Timeout while generating image from dot", ex);
            }

            if (exitValue != 0) {
                LOGGER.error("Error while generating image from dot: "
                        + IOUtils.toString(p.getErrorStream(), XWiki.DEFAULT_ENCODING));
            }
        }
        return FileUtils.readFileToByteArray(ofile);
    }

    /**
     * Get the contents of a previously generated temporary file.
     * 
     * @param ofile the file to read
     * @return the content found inside the file, if any
     * @throws IOException when reading the file fails
     */
    private byte[] readDotImage(File ofile) throws IOException {
        return FileUtils.readFileToByteArray(ofile);
    }

    /**
     * Return the temporary disk file corresponding to the given parameters.
     * 
     * @param hashcode the hashcode of the dot content, used as the main part for the filename
     * @param extension the output file extension
     * @param dot which engine to execute: {@code dot} if {@code true}, {@code neato} if {@code false}
     * @return the corresponding File
     */
    private File getTempFile(int hashcode, String extension, boolean dot) {
        String name = (dot ? DOT_ENGINE : NEATO_ENGINE) + '-';
        return getTempFile(name + hashcode + '.' + extension);
    }

    /**
     * Return the temporary disk file corresponding to the given filename.
     * 
     * @param filename the filename to look for
     * @return the corresponding File
     */
    private File getTempFile(String filename) {
        return new File(this.tempDir, filename);
    }

    /**
     * Hangcheck runnable, which interrupts the main thread after 10 seconds of waiting for the conversion to end. If
     * the conversion ends normally before the 10 seconds timeout expires, then this runnable should be terminated by
     * {@link Thread#interrupt() interrupting it}.
     * 
     * @version $Id: 5e4b5706aa4b9e1f3c4afc81e9aa6d161396cfc0 $
     */
    private static class Hangcheck implements Runnable {
        /** The main thread that should be interrupted if the timeout expires. */
        private Thread converterThread;

        /**
         * Simple constructor which specifies the thread to monitor.
         * 
         * @param converterThread the thread to monitor
         */
        public Hangcheck(Thread converterThread) {
            this.converterThread = converterThread;
        }

        /**
         * {@inheritDoc}
         */
        public void run() {
            try {
                Thread.sleep(10000);
                this.converterThread.interrupt();
            } catch (InterruptedException ex) {
                // Expected result if the dot process terminates on time
            }
        }
    }
}