com.xpn.xwiki.pdf.impl.PdfURLFactory.java Source code

Java tutorial

Introduction

Here is the source code for com.xpn.xwiki.pdf.impl.PdfURLFactory.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.pdf.impl;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xwiki.model.reference.DocumentReference;

import com.xpn.xwiki.XWiki;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.doc.XWikiAttachment;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.util.Util;
import com.xpn.xwiki.web.XWikiServletURLFactory;

/**
 * Special URL Factory used during exports, which stores referenced attachments and resources on the filesystem, in a
 * temporary folder, so that they can be included in the export result. The returned URLs point to these resources as
 * {@code file://} links, and not as {@code http://} links.
 * 
 * @version $Id: a55e589c5ee9d02c954facfa4270bff976d2a9e4 $
 */
public class PdfURLFactory extends XWikiServletURLFactory {
    /** Logging helper object. */
    private static final Logger LOGGER = LoggerFactory.getLogger(PdfURLFactory.class);

    /** Segment separator used in the collision-free key generation. */
    private static final String SEPARATOR = "/";

    /**
     * {@inheritDoc}
     */
    @Override
    public URL createAttachmentURL(String filename, String space, String name, String action, String querystring,
            String wiki, XWikiContext context) {
        try {
            return getURL(wiki, space, name, filename, null, context);
        } catch (Exception ex) {
            LOGGER.warn("Failed to save image for PDF export", ex);
            return super.createAttachmentURL(filename, space, name, action, null, wiki, context);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public URL createAttachmentRevisionURL(String filename, String space, String name, String revision, String wiki,
            XWikiContext context) {
        try {
            return getURL(wiki, space, name, filename, revision, context);
        } catch (Exception ex) {
            LOGGER.warn("Failed to save image for PDF export: " + ex.getMessage());
            return super.createAttachmentRevisionURL(filename, space, name, revision, wiki, context);
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see com.xpn.xwiki.web.XWikiURLFactory.createSkinURL(String, String, XWikiContext)
     */
    @Override
    public URL createSkinURL(String filename, String skin, XWikiContext context) {
        try {
            Map<String, File> usedFiles = getFileMapping(context);
            String key = getSkinfileKey(filename, skin);
            if (!usedFiles.containsKey(key)) {
                if (!copyResource("/skins/" + skin + '/' + filename, key, usedFiles, context)) {
                    // The resource does not exist, just return a http:// URL
                    return super.createSkinURL(filename, skin, context);
                }
            }
            return usedFiles.get(key).toURI().toURL();
        } catch (Exception ex) {
            // Shouldn't happen
            return super.createSkinURL(filename, skin, context);
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see com.xpn.xwiki.web.XWikiURLFactory.createResourceURL(String, boolean, XWikiContext)
     */
    @Override
    public URL createResourceURL(String filename, boolean forceSkinAction, XWikiContext context) {
        try {
            Map<String, File> usedFiles = getFileMapping(context);
            String key = getResourceKey(filename);
            if (!usedFiles.containsKey(key)) {
                if (!copyResource("/resources/" + filename, key, usedFiles, context)) {
                    return super.createResourceURL(filename, forceSkinAction, context);
                }
            }
            return usedFiles.get(key).toURI().toURL();
        } catch (Exception ex) {
            // Shouldn't happen
            return super.createResourceURL(filename, forceSkinAction, context);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getURL(URL url, XWikiContext context) {
        if (url == null) {
            return "";
        }
        return Util.escapeURL(url.toString());
    }

    /**
     * Store the requested attachment on the filesystem and return a {@code file://} URL where FOP can access that file.
     * 
     * @param wiki the name of the owner document's wiki
     * @param space the name of the owner document's space
     * @param name the name of the owner document
     * @param filename the name of the attachment
     * @param revision an optional attachment version
     * @param context the current request context
     * @return a {@code file://} URL where the attachment has been stored
     * @throws Exception if the attachment can't be retrieved from the database and stored on the filesystem
     */
    private URL getURL(String wiki, String space, String name, String filename, String revision,
            XWikiContext context) throws Exception {
        Map<String, File> usedFiles = getFileMapping(context);
        String key = getAttachmentKey(space, name, filename, revision);
        if (!usedFiles.containsKey(key)) {
            File file = getTemporaryFile(key, context);
            XWikiDocument doc = context.getWiki().getDocument(
                    new DocumentReference(StringUtils.defaultString(wiki, context.getDatabase()), space, name),
                    context);
            XWikiAttachment attachment = doc.getAttachment(filename);
            if (StringUtils.isNotEmpty(revision)) {
                attachment = attachment.getAttachmentRevision(revision, context);
            }
            FileOutputStream fos = new FileOutputStream(file);
            IOUtils.copy(attachment.getContentInputStream(context), fos);
            fos.close();
            usedFiles.put(key, file);
        }
        return usedFiles.get(key).toURI().toURL();
    }

    /**
     * Copy a resource from the filesystem into a temporary file and map this resulting file to the requested resource
     * location.
     * 
     * @param resourceName the name of the file to copy, possibly including a path to it, for example
     *            {@code icons/silk/add.gif}
     * @param key the collision-free identifier of the resource
     * @param usedFiles the mapping of resource keys to temporary files where to put the resulting temporary file
     * @param context the current request context
     * @return {@code true} if copying the resource succeeded and the new temporary file was mapped to the resource key,
     *         {@code false} otherwise
     */
    private boolean copyResource(String resourceName, String key, Map<String, File> usedFiles,
            XWikiContext context) {
        try {
            InputStream data = context.getWiki().getResourceAsStream(resourceName);
            if (data != null) {
                // Copy the resource to a temporary file
                File file = getTemporaryFile(key, context);
                FileOutputStream fos = new FileOutputStream(file);
                IOUtils.copy(data, fos);
                fos.close();
                usedFiles.put(key, file);
                return true;
            }
        } catch (Exception ex) {
            // Can't access the resource, let's hope FOP can handle the http:// URL
        }
        return false;
    }

    /**
     * Computes a safe identifier for an attachment, guaranteed to be collision-free.
     * 
     * @param space the name of the owner document's space
     * @param name the name of the owner document
     * @param filename the name of the attachment
     * @param revision an optional attachment version
     * @return an identifier for this attachment
     */
    private String getAttachmentKey(String space, String name, String filename, String revision) {
        try {
            return "attachment" + SEPARATOR + URLEncoder.encode(space, XWiki.DEFAULT_ENCODING) + SEPARATOR
                    + URLEncoder.encode(name, XWiki.DEFAULT_ENCODING) + SEPARATOR
                    + URLEncoder.encode(filename, XWiki.DEFAULT_ENCODING) + SEPARATOR
                    + URLEncoder.encode(StringUtils.defaultString(revision), XWiki.DEFAULT_ENCODING);
        } catch (UnsupportedEncodingException ex) {
            // This should never happen, UTF-8 is always available
            return space + SEPARATOR + name + SEPARATOR + filename + SEPARATOR
                    + StringUtils.defaultString(revision);
        }
    }

    /**
     * Computes a safe identifier for a resource file, guaranteed to be collision-free.
     * 
     * @param filename the name of the file, possibly including a path to it, for example {@code icons/silk/add.gif}
     * @return an identifier for this file
     */
    private String getResourceKey(String filename) {
        try {
            return "resource" + SEPARATOR + URLEncoder.encode(filename, XWiki.DEFAULT_ENCODING);
        } catch (UnsupportedEncodingException ex) {
            // This should never happen, UTF-8 is always available
            return filename;
        }
    }

    /**
     * Computes a safe identifier for a skin filename, guaranteed to be collision-free.
     * 
     * @param filename the name of the file, possibly including a path to it, for example {@code css/colors/black.css}
     * @param skin the name of the skin where the file is expected to be
     * @return an identifier for this file
     */
    private String getSkinfileKey(String filename, String skin) {
        try {
            return "skin" + SEPARATOR + URLEncoder.encode(skin, XWiki.DEFAULT_ENCODING) + SEPARATOR
                    + URLEncoder.encode(filename, XWiki.DEFAULT_ENCODING);
        } catch (UnsupportedEncodingException ex) {
            // This should never happen, UTF-8 is always available
            return skin + SEPARATOR + filename;
        }
    }

    /**
     * Retrieve the Map that relates resource keys to their corresponding temporary file.
     * 
     * @param context the current request context
     * @return the mapping as it was found in the context (read-write)
     */
    private Map<String, File> getFileMapping(XWikiContext context) {
        @SuppressWarnings("unchecked")
        Map<String, File> usedFiles = (Map<String, File>) context.get("pdfexport-file-mapping");
        return usedFiles;
    }

    /**
     * Create a new temporary file for the given resource key and return it.
     * 
     * @param key the resource key, needed for getting the file extension, if any
     * @param context the current request context
     * @return a new empty file
     * @throws IOException if creating the file fails
     */
    private File getTemporaryFile(String key, XWikiContext context) throws IOException {
        File tempdir = (File) context.get("pdfexportdir");
        return File.createTempFile("pdf", "." + FilenameUtils.getExtension(key), tempdir);
    }
}