com.highcharts.export.controller.ExportController.java Source code

Java tutorial

Introduction

Here is the source code for com.highcharts.export.controller.ExportController.java

Source

/**
 * @license Highcharts JS v2.3.3 (2012-11-02)
 *
 * (c) 20012-2014
 *
 * Author: Gert Vaartjes
 *
 * License: www.highcharts.com/license
 */
package com.highcharts.export.controller;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.NoSuchElementException;
import java.util.concurrent.TimeoutException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import com.highcharts.export.converter.SVGConverter;
import com.highcharts.export.converter.SVGConverterException;
import com.highcharts.export.pool.PoolException;
import com.highcharts.export.util.MimeType;
import com.highcharts.export.util.TempDir;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.servlet.http.HttpSession;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.RequestBody;

@Controller
@RequestMapping("/")
public class ExportController extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private static final Float MAX_WIDTH = 2000.0F;
    private static final Float MAX_SCALE = 4.0F;
    protected static Logger logger = Logger.getLogger("exporter");

    @Autowired
    private SVGConverter converter;

    @RequestMapping(value = "/demo", method = RequestMethod.GET)
    public String demo() {
        return "demo";
    }

    @RequestMapping(method = { RequestMethod.POST, RequestMethod.GET })
    public HttpEntity<byte[]> exporter(@RequestParam(value = "svg", required = false) String svg,
            @RequestParam(value = "type", required = false) String type,
            @RequestParam(value = "filename", required = false) String filename,
            @RequestParam(value = "width", required = false) String width,
            @RequestParam(value = "scale", required = false) String scale,
            @RequestParam(value = "options", required = false) String options,
            @RequestParam(value = "globaloptions", required = false) String globalOptions,
            @RequestParam(value = "constr", required = false) String constructor,
            @RequestParam(value = "callback", required = false) String callback,
            @RequestParam(value = "callbackHC", required = false) String callbackHC,
            @RequestParam(value = "async", required = false, defaultValue = "false") Boolean async,
            @RequestParam(value = "jsonp", required = false, defaultValue = "false") Boolean jsonp,
            HttpServletRequest request, HttpSession session)
            throws ServletException, InterruptedException, SVGConverterException, NoSuchElementException,
            PoolException, TimeoutException, IOException, ZeroRequestParameterException {

        MimeType mime = getMime(type);
        String randomFilename = null;
        String jsonpCallback = "";
        boolean isAndroid = request.getHeader("user-agent") != null
                && request.getHeader("user-agent").contains("Android");

        if ("GET".equalsIgnoreCase(request.getMethod())) {

            // Handle redirect downloads for Android devices, these come in without request parameters
            String tempFile = (String) session.getAttribute("tempFile");
            session.removeAttribute("tempFile");

            if (tempFile != null && !tempFile.isEmpty()) {
                logger.debug("filename stored in session, read and stream from filesystem");
                String basename = FilenameUtils.getBaseName(tempFile);
                String extension = FilenameUtils.getExtension(tempFile);

                return getFile(basename, extension);

            }
        }

        // check for visitors who don't know this domain is really only for the exporting service ;)
        if (request.getParameterMap().isEmpty()) {
            throw new ZeroRequestParameterException();
        }

        /* Most JSONP implementations use the 'callback' request parameter and this overwrites
         * the original callback parameter for chart creation with Highcharts. If JSONP is
         * used we recommend using the requestparameter callbackHC as the callback for Highcharts.
         * store the callback method name and reset the callback parameter,
         * otherwise it will be used when creation charts
         */
        if (jsonp) {
            async = true;
            jsonpCallback = callback;
            callback = null;

            if (callbackHC != null) {
                callback = callbackHC;
            }
        }

        if (isAndroid || MimeType.PDF.equals(mime) || async) {
            randomFilename = createRandomFileName(mime.name().toLowerCase());
        }

        /* If randomFilename is not null, then we want to save the filename in session, in case of GET is used later on*/
        if (isAndroid) {
            logger.debug("storing randomfile in session: " + FilenameUtils.getName(randomFilename));
            session.setAttribute("tempFile", FilenameUtils.getName(randomFilename));
        }

        String output = convert(svg, mime, width, scale, options, constructor, callback, globalOptions,
                randomFilename);
        ByteArrayOutputStream stream;

        HttpHeaders headers = new HttpHeaders();

        if (async) {
            String link = TempDir.getDownloadLink(randomFilename);
            stream = new ByteArrayOutputStream();
            if (jsonp) {
                StringBuilder sb = new StringBuilder(jsonpCallback);
                sb.append("('");
                sb.append(link);
                sb.append("')");
                stream.write(sb.toString().getBytes("utf-8"));
                headers.add("Content-Type", "text/javascript; charset=utf-8");
            } else {
                stream.write(link.getBytes("utf-8"));
                headers.add("Content-Type", "text/html; charset=UTF-8");
            }
        } else {
            headers.add("Content-Type", mime.getType() + "; charset=utf-8");
            if (randomFilename != null && randomFilename.equals(output)) {
                stream = writeFileToStream(randomFilename);
            } else {
                boolean base64 = !mime.getExtension().equals("svg");
                stream = outputToStream(output, base64);
            }
            filename = getFilename(filename);
            headers.add("Content-Disposition",
                    "attachment; filename=" + filename.replace(" ", "_") + "." + mime.name().toLowerCase());
        }

        headers.setContentLength(stream.size());

        return new HttpEntity<byte[]>(stream.toByteArray(), headers);
    }

    @RequestMapping(value = "/files/{name}.{ext}", method = RequestMethod.GET)
    public HttpEntity<byte[]> getFile(@PathVariable("name") String name, @PathVariable("ext") String extension)
            throws SVGConverterException, IOException {

        Path path = Paths.get(TempDir.getOutputDir().toString(), name + "." + extension);
        String filename = path.toString();
        MimeType mime = getMime(extension);

        ByteArrayOutputStream stream = writeFileToStream(filename);

        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", mime.getType() + "; charset=utf-8");
        headers.setContentLength(stream.size());

        return new HttpEntity<byte[]>(stream.toByteArray(), headers);
    }

    @RequestMapping(value = "/json/{name}.{ext}", method = RequestMethod.POST)
    public HttpEntity<byte[]> exportFromJson(@PathVariable("name") String name,
            @PathVariable("ext") String extension, @RequestBody String requestBody)
            throws SVGConverterException, TimeoutException, NoSuchElementException, PoolException {

        String randomFilename;
        randomFilename = null;
        String json = requestBody;
        MimeType mime = getMime(extension);

        // add outfile parameter to the json with a simple string replace
        if (MimeType.PDF.equals(mime)) {
            randomFilename = createRandomFileName(mime.name().toLowerCase());
            int ind = json.lastIndexOf("}");
            json = new StringBuilder(json).replace(ind, ind + 1, ",\"outfile\":\"" + randomFilename).toString()
                    + "\"}";
        }

        String result = converter.requestServer(json);
        ByteArrayOutputStream stream;
        if (randomFilename != null && randomFilename.equals(result)) {
            stream = writeFileToStream(randomFilename);
        } else {
            boolean base64 = !mime.getExtension().equals("svg");
            stream = outputToStream(result, base64);
        }

        HttpHeaders headers = new HttpHeaders();

        headers.add("Content-Type", mime.getType() + "; charset=utf-8");
        headers.add("Content-Disposition", "attachment; filename=" + name + "." + extension);
        headers.setContentLength(stream.size());

        return new HttpEntity<byte[]>(stream.toByteArray(), headers);
    }

    /*
     * INSTANCE METHODS
     */

    private String convert(String svg, MimeType mime, String width, String scale, String options,
            String constructor, String callback, String globalOptions, String filename)
            throws SVGConverterException, PoolException, NoSuchElementException, TimeoutException,
            ServletException {

        Float parsedWidth = widthToFloat(width);
        Float parsedScale = scaleToFloat(scale);
        options = sanitize(options);
        String input;
        String output;

        boolean convertSvg = false;

        if (options != null) {
            // create a svg file out of the options
            input = options;
            callback = sanitize(callback);
            globalOptions = sanitize(globalOptions);
        } else {
            // assume SVG conversion
            svg = sanitize(svg);
            if (svg == null) {
                logger.error("The mandatory 'svg' or 'options' POST parameter is undefined.");
                throw new ServletException("The mandatory 'svg' or 'options' POST parameter is undefined.");
            } else {
                convertSvg = true;
                input = svg;
            }
        }

        if (convertSvg && mime.equals(MimeType.SVG)) {
            output = converter.redirectSVG(input, filename);
        } else {
            output = converter.convert(input, mime, constructor, callback, globalOptions, parsedWidth, parsedScale,
                    filename);
        }

        return output;
    }

    private String getFilename(String name) {
        name = sanitize(name);
        return (name != null) ? name : "chart";
    }

    private static MimeType getMime(String mime) {
        MimeType type = MimeType.get(mime);
        if (type != null) {
            return type;
        }
        return MimeType.PNG;
    }

    private static String sanitize(String parameter) {
        if (parameter == null || parameter.trim().isEmpty() || parameter.compareToIgnoreCase("undefined") == 0
                || parameter.compareTo("null") == 0 || parameter.compareTo("{}") == 0) {
            return null;
        }
        return parameter.trim();
    }

    private static Float widthToFloat(String width) throws SVGConverterException {
        width = sanitize(width);
        if (width != null) {
            width = width.replace("px", "");
            try {
                Float parsedWidth = Float.valueOf(width);
                if (parsedWidth.compareTo(MAX_WIDTH) > 0) {
                    return MAX_WIDTH;
                }
                if (parsedWidth.compareTo(0.0F) > 0) {
                    return parsedWidth;
                }
            } catch (NumberFormatException nfe) {
                logger.error("Parameter width is wrong for value: " + width, nfe.fillInStackTrace());
                throw new SVGConverterException("Parameter width is wrong for value: " + width);
            }

        }
        return null;
    }

    private static Float scaleToFloat(String scale) throws SVGConverterException {
        scale = sanitize(scale);
        if (scale != null) {
            try {
                Float parsedScale = Float.valueOf(scale);
                if (parsedScale.compareTo(MAX_SCALE) > 0) {
                    return MAX_SCALE;
                } else if (parsedScale.compareTo(0.0F) > 0) {
                    return parsedScale;
                }
            } catch (NumberFormatException nfe) {
                logger.error("Parameter scale is wrong for value: " + scale, nfe.fillInStackTrace());
                throw new SVGConverterException("Parameter scale is wrong for value: " + scale);
            }
        }
        return null;
    }

    public String createRandomFileName(String extension) {
        Path path = Paths.get(TempDir.outputDir.toString(),
                RandomStringUtils.randomAlphanumeric(8) + "." + extension);
        return path.toString();
    }

    private ByteArrayOutputStream outputToStream(String output, boolean base64) throws SVGConverterException {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        try {
            if (base64) {
                //decode the base64 string
                stream.write(Base64.decodeBase64(output));
            } else {
                stream.write(output.getBytes());
            }
        } catch (IOException ex) {
            logger.error("Error in outputToStream: " + ex.getMessage());
            throw new SVGConverterException("Error while converting");
        }

        return stream;
    }

    private ByteArrayOutputStream writeFileToStream(String filename) throws SVGConverterException {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();

        try {
            stream.write(FileUtils.readFileToByteArray(new File(filename)));
        } catch (IOException ioex) {
            logger.error("Tried to read file from filesystem: " + ioex.getMessage());
            throw new SVGConverterException("IOException: cannot find your file to download...");
        }

        return stream;
    }

    /* EXCEPTION HANDLERS */

    @ExceptionHandler(ZeroRequestParameterException.class)
    public ModelAndView handleZeroRequestParameterException(Exception ex, HttpServletResponse response) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("demo");
        response.setStatus(302);
        return modelAndView;
    }

    @ExceptionHandler(IOException.class)
    public ModelAndView handleIOException(Exception ex, HttpServletResponse response) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error");
        modelAndView.addObject("message", ex.getMessage());
        response.setStatus(500);
        return modelAndView;
    }

    @ExceptionHandler(TimeoutException.class)
    public ModelAndView handleTimeoutException(Exception ex, HttpServletResponse response) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error");
        modelAndView.addObject("message",
                "Timeout converting SVG, is your file this big, or maybe you have a syntax error in the javascript callback?");
        response.setStatus(500);
        return modelAndView;
    }

    @ExceptionHandler(PoolException.class)
    public ModelAndView handleServerPoolException(Exception ex, HttpServletResponse response) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error");
        modelAndView.addObject("message",
                "Sorry, the server is handling too many requests at the moment. Please try again.");
        response.setStatus(500);
        return modelAndView;
    }

    @ExceptionHandler(SVGConverterException.class)
    public ModelAndView handleSVGRasterizeException(Exception ex, HttpServletResponse response) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error");
        modelAndView.addObject("message", "Something went wrong while converting. " + ex.getMessage());
        response.setStatus(500);
        return modelAndView;
    }

    @ExceptionHandler(InterruptedException.class)
    public ModelAndView handleInterruptedException(Exception ex, HttpServletResponse response) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error");
        modelAndView.addObject("message",
                "It took too long time to process the options, no SVG is created. Make sure your javascript is correct");
        response.setStatus(500);
        return modelAndView;
    }

    @ExceptionHandler(ServletException.class)
    public ModelAndView handleServletException(Exception ex, HttpServletResponse response) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("message", ex.getMessage());
        response.setStatus(500);
        return modelAndView;
    }
}