org.mapfish.print.output.NativeProcessOutputFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.mapfish.print.output.NativeProcessOutputFactory.java

Source

/*
 * Copyright (C) 2013  Camptocamp
 *
 * This file is part of MapFish Print
 *
 * MapFish Print is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * MapFish Print 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MapFish Print.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.mapfish.print.output;

import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import org.apache.log4j.Logger;
import org.json.JSONException;
import org.mapfish.print.RenderingContext;
import org.mapfish.print.TimeLogger;
import org.mapfish.print.utils.PJsonArray;
import org.mapfish.print.utils.PJsonObject;

import com.lowagie.text.DocumentException;

/**
 * Print Output that generate a PNG. It will first generate a PDF ant convert it to PNG
 * using the convert command provide by a native process.
 * 
 * It include an hack to correct the transparency layer opacity.
 * 
 *  
 * @author Stephane Brunner
 * @author Jesse Eichar
 */
public class NativeProcessOutputFactory implements OutputFormatFactory {

    /**
     * The logger.
     */
    public static final Logger LOGGER = Logger.getLogger(NativeProcessOutputFactory.class);
    private String cmd;
    private List<String> cmdArgs = new ArrayList<String>();
    private List<String> formats = new ArrayList<String>();
    private int timeoutSeconds = 30;
    private final Semaphore runningProcesses;

    public NativeProcessOutputFactory(int maxProcesses) {
        runningProcesses = new Semaphore(maxProcesses, true);
        if (java.lang.management.ManagementFactory.getOperatingSystemMXBean().getName().toLowerCase()
                .contains("win")) {
            cmd = "convert";
        } else {
            cmd = "/usr/bin/convert";
        }

        cmdArgs.add("-density");
        cmdArgs.add("@@dpi@@x@@dpi@@");
        cmdArgs.add("-append");
        cmdArgs.add("@@sourceFile@@");
        cmdArgs.add("@@targetFile@@");

        formats.add("jpg");
        formats.add("gif");
        formats.add("png");
        formats.add("bmp");
        formats.add("tif");
        formats.add("tiff");
    }

    /**
     * Set the path and command of the image magic convert command.  
     * 
     * Default value is /usr/bin/convert on linux and just convert on windows (assumes it is on the path)
     * 
     * value is typically injected by spring dependency injection
     */
    public void setCmd(String cmd) {
        this.cmd = cmd;
    }

    /**
     * Set arguments when executing the Cmd.  
     * 
     * Default parameters are "-density", "@@dpi@@x@@dpi@@", "@@sourceFile@@" and "@@targetFile@@"
     * 
     * value is typically injected by spring dependency injection
     */
    public void setCmdArgs(List<String> cmdArgs) {
        this.cmdArgs = cmdArgs;
    }

    /**
     * Set the length of time in seconds to wait for a free process for executing conversion
     * 
     * @param timeoutSeconds
     */
    public void setTimeoutSeconds(int timeoutSeconds) {
        this.timeoutSeconds = timeoutSeconds;
    }

    /**
     * Set the formats that the current native process installation can support
     * @param formats
     */
    public void setFormats(List<String> formats) {
        this.formats = formats;
    }

    @Override
    public List<String> formats() {
        return formats;
    }

    @Override
    public OutputFormat create(String format) {
        return new ImageOutput(format);
    }

    @Override
    public String enablementStatus() {
        return null;
    }

    private class ImageOutput extends AbstractImageFormat {

        /**
         * Construct.
         * @param format
         */
        public ImageOutput(String format) {
            super(format);
        }

        @Override
        public RenderingContext print(PrintParams params) throws DocumentException, InterruptedException {
            // Hack to correct the transparency
            {
                PJsonArray layers = params.jsonSpec.getJSONArray("layers");

                // a*x+b*x+c
                final double a = -0.3;
                final double b = 0.9;
                final double c = 0.4;

                for (int i = 0; i < layers.size(); i++) {
                    PJsonObject layer = layers.getJSONObject(i);
                    if (layer.has("opacity")) {
                        double opacity = layer.getDouble("opacity");
                        opacity = a * opacity * opacity + b * opacity + c;
                        try {
                            layer.getInternalObj().put("opacity", opacity);
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }

            File tmpPdfFile = null;
            File tmpPngFile = null;
            try {
                tmpPdfFile = File.createTempFile("mapfishprint", ".pdf");
                FileOutputStream tmpOut = new FileOutputStream(tmpPdfFile);
                RenderingContext context;
                try {
                    TimeLogger timeLog = TimeLogger.info(LOGGER, "PDF Creation");
                    context = doPrint(params.withOutput(tmpOut));
                    timeLog.done();
                } finally {
                    tmpOut.close();
                }

                TimeLogger timeLog = TimeLogger.info(LOGGER, "Pdf to image conversion");
                tmpPngFile = File.createTempFile("mapfishprint", ".png");
                createImage(params.jsonSpec, tmpPdfFile, tmpPngFile, context);
                timeLog.done();

                timeLog = TimeLogger.info(LOGGER, "Write Image");
                drawImage(params.outputStream, tmpPngFile);
                timeLog.done();

                return context;
            } catch (IOException e) {
                throw new RuntimeException(e);
            } finally {
                if (tmpPdfFile != null) {
                    if (!tmpPdfFile.delete()) {
                        LOGGER.warn(tmpPdfFile
                                + " was not able to be deleted for unknown reason.  Will try again on shutdown");
                    }
                    tmpPdfFile.deleteOnExit();
                }
                if (tmpPngFile != null) {
                    if (!tmpPngFile.delete()) {
                        LOGGER.warn(tmpPngFile
                                + " was not able to be deleted for unknown reason.  Will try again on shutdown");
                    }
                    tmpPngFile.deleteOnExit();
                }
            }
        }

        /**
         * Write the image from the temporary file to the output stream.
         * @param out the output stream
         * @param tmpPngFile the temporary file
         * @throws IOException on IO error
         */
        private void drawImage(OutputStream out, File tmpPngFile) throws IOException {
            FileInputStream inputStream = new FileInputStream(tmpPngFile);
            FileChannel channel = inputStream.getChannel();
            try {
                channel.transferTo(0, tmpPngFile.length(), Channels.newChannel(out));
            } finally {
                closeQuiet(channel);
                closeQuiet(inputStream);
            }
        }

        private void closeQuiet(Closeable c) {
            try {
                if (c != null)
                    c.close();
            } catch (Throwable e) {
                LOGGER.error("Error closing resource", e);
            }
        }

        /**
           * Creates a PNG image from a PDF file using the native process
           *  
           * @param jsonSpec the spec used to know the DPI value
           * @param tmpPdfFile the PDF file
           * @param tmpPngFile the PNG file
           * @param context the context used to know the DPI value
           * @throws IOException on IO error
         * @throws InterruptedException if in able to acquire semaphore
           */
        private void createImage(PJsonObject jsonSpec, File tmpPdfFile, File tmpPngFile, RenderingContext context)
                throws IOException, InterruptedException {
            runningProcesses.tryAcquire(timeoutSeconds, TimeUnit.SECONDS);
            try {
                int dpi = calculateDPI(context, jsonSpec);

                String[] finalCommands = new String[cmdArgs.size() + 1];
                finalCommands[0] = cmd;

                for (int i = 1; i < finalCommands.length; i++) {
                    String arg = cmdArgs.get(i - 1).replace("@@dpi@@", "" + dpi)
                            .replace("@@targetFile@@", tmpPngFile.getAbsolutePath())
                            .replace("@@sourceFile@@", tmpPdfFile.getAbsolutePath()).replace("${dpi}", "" + dpi)
                            .replace("${targetFile}", tmpPngFile.getAbsolutePath())
                            .replace("${sourceFile}", tmpPdfFile.getAbsolutePath());

                    finalCommands[i] = arg;
                }

                ProcessBuilder builder = new ProcessBuilder(finalCommands);
                LOGGER.info("Executing process: " + builder.command());

                Process p = builder.start();

                writeOut(p, false);
                writeOut(p, true);
                try {
                    int exitCode = p.waitFor();

                    p.destroy();
                    if (exitCode != 0) {
                        LOGGER.error(cmd + " failed to create image from pdf.  Exit code was " + exitCode);
                    } else {
                        LOGGER.info(cmd + " exited correctly from image conversion process.  Exit code was "
                                + exitCode);
                    }
                } catch (InterruptedException e) {
                    LOGGER.error("Process interrupted", e);
                }
            } finally {
                runningProcesses.release();
            }
        }

        private void writeOut(Process p, boolean errorStream) throws IOException {
            InputStream stream;
            if (errorStream) {
                stream = p.getErrorStream();
            } else {
                stream = p.getInputStream();
            }
            BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
            String line = null;

            while ((line = reader.readLine()) != null) {
                if (errorStream) {
                    LOGGER.error(line);
                } else {
                    LOGGER.info(line);
                }
            }
        }
    }

}