org.xwiki.platform.svg.internal.BatikSVGRasterizer.java Source code

Java tutorial

Introduction

Here is the source code for org.xwiki.platform.svg.internal.BatikSVGRasterizer.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 org.xwiki.platform.svg.internal;

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

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.servlet.http.HttpServletResponse;

import org.apache.batik.transcoder.SVGAbstractTranscoder;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.xwiki.component.annotation.Component;
import org.xwiki.container.Container;
import org.xwiki.container.servlet.ServletResponse;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.DocumentReferenceResolver;
import org.xwiki.platform.svg.SVGRasterizer;
import org.xwiki.resource.temporary.TemporaryResourceReference;
import org.xwiki.resource.temporary.TemporaryResourceStore;

/**
 * The straight-forward implementation of the {@link SVGRasterizer} role using Batik.
 *
 * @version $Id: a9c633386274128f20755bc440b321cbe70092ad $
 * @since 8.0M1
 */
@Component
@Singleton
public class BatikSVGRasterizer implements SVGRasterizer {
    private static final String TEMP_DIR_NAME = "svg";

    private static final String RASTER_FILE_EXTENSION = ".png";

    @Inject
    private Logger logger;

    @Inject
    private TemporaryResourceStore temporaryResourceStore;

    @Inject
    @Named("current")
    private DocumentReferenceResolver<String> currentDocumentResolver;

    @Inject
    private Container container;

    @Override
    public File rasterizeToTemporaryFile(String content, int width, int height) throws IOException {
        String fileName = getTemporaryFileName(content, width, height);
        TemporaryResourceReference reference = new TemporaryResourceReference(TEMP_DIR_NAME, fileName, null);
        File out = this.temporaryResourceStore.getTemporaryFile(reference);
        if (rasterizeToFile(content, out, width, height)) {
            return out;
        }
        return null;
    }

    @Override
    public TemporaryResourceReference rasterizeToTemporaryResource(String content, int width, int height)
            throws IOException {
        return rasterizeToTemporaryResource(content, width, height, getCurrentDocument());
    }

    @Override
    public TemporaryResourceReference rasterizeToTemporaryResource(String content, int width, int height,
            DocumentReference targetContext) throws IOException {
        String fileName = getTemporaryFileName(content, width, height);
        TemporaryResourceReference reference = new TemporaryResourceReference(TEMP_DIR_NAME, fileName,
                targetContext);
        File out = this.temporaryResourceStore.getTemporaryFile(reference);
        if (rasterizeToFile(content, out, width, height)) {
            return reference;
        }
        return null;
    }

    @Override
    public void rasterizeToResponse(String content, int width, int height) throws IOException {
        if (!(this.container.getResponse() instanceof ServletResponse)) {
            return;
        }
        HttpServletResponse response = ((ServletResponse) this.container.getResponse()).getHttpServletResponse();
        File result = rasterizeToTemporaryFile(content, width, height);
        if (result == null) {
            return;
        }
        response.setContentLength((int) result.length());
        response.setContentType("image/png");
        OutputStream os = response.getOutputStream();
        FileUtils.copyFile(result, os);
        os.flush();
    }

    private boolean rasterizeToFile(String content, File out, int width, int height) throws IOException {
        if (!out.getParentFile().exists() && !out.getParentFile().mkdirs()) {
            this.logger.debug("Failed to create temporary folder [{}].", out.getParentFile().getAbsolutePath());
            return false;
        } else if (out.exists() && out.isFile()) {
            this.logger.debug("Reusing existing temporary raster image: {}", out.getAbsolutePath());
            return true;
        } else {
            try (OutputStream fout = new FileOutputStream(out)) {
                this.logger.debug("Rasterizing to temp file: {}", out.getAbsolutePath());
                TranscoderInput input = new TranscoderInput(new StringReader(content));
                TranscoderOutput output = new TranscoderOutput(fout);
                boolean success = rasterize(input, output, width, height);
                if (!success) {
                    out.delete();
                }
                return success;
            }
        }
    }

    private boolean rasterize(TranscoderInput input, TranscoderOutput output, int width, int height) {
        PNGTranscoder transcoder = new PNGTranscoder();

        if (width > 0) {
            transcoder.addTranscodingHint(SVGAbstractTranscoder.KEY_WIDTH, Float.valueOf(width));
        }
        if (height > 0) {
            transcoder.addTranscodingHint(SVGAbstractTranscoder.KEY_HEIGHT, Float.valueOf(height));
        }

        // Set maximum width and height to 8k to avoid DoS attacks
        transcoder.addTranscodingHint(SVGAbstractTranscoder.KEY_MAX_WIDTH, Float.valueOf(8192));
        transcoder.addTranscodingHint(SVGAbstractTranscoder.KEY_MAX_HEIGHT, Float.valueOf(8192));

        try {
            transcoder.transcode(input, output);
            return true;
        } catch (TranscoderException ex) {
            this.logger.warn("Failed to rasterize SVG image: {}", ExceptionUtils.getRootCauseMessage(ex));
        }
        return false;
    }

    private String getTemporaryFileName(String content, int width, int height) {
        return Math.abs(content.hashCode()) + RASTER_FILE_EXTENSION;
    }

    private DocumentReference getCurrentDocument() {
        return this.currentDocumentResolver.resolve("");
    }
}