net.sf.j2ep.UrlRewritingOutputStream.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.j2ep.UrlRewritingOutputStream.java

Source

/*
 * Copyright 2000,2004 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.sf.j2ep;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;

import net.sf.j2ep.model.Server;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * A wrapper for the default output stream. This class will make sure all data
 * being sent is cached by the stream and can rewrite all the links.
 * 
 * @author Anders Nyman
 */
public final class UrlRewritingOutputStream extends ServletOutputStream {

    /**
     * The stream we are wrapping, is the original response stream.
     */
    private ServletOutputStream originalStream;

    /**
     * Stream that is written to, works as a buffer for the response stream.
     */
    private ByteArrayOutputStream stream;

    /**
     * The server, needed when we rewrite absolute links.
     */
    private String ownHostName;

    /**
     * The contextPath, needed when we rewrite links.
     */
    private String contextPath;

    /**
     * The servers.
     */
    private ServerChain serverChain;

    /**
     * Regex matching links in the HTML.
     */
    private static Pattern linkPattern = Pattern.compile(
            "\\b(url\\s*[=\\:]|ajaxurl\\s*=|href\\s*=|src\\s*=|action\\s*=|url\\()\\s*([\"\'])(([^/]+://)([^/<>]+))?([^\"\'>]*)[\"\']",
            Pattern.CASE_INSENSITIVE | Pattern.CANON_EQ);

    /**
     * Logging element supplied by commons-logging.
     */
    private static Log log;

    /**
     * Basic constructor.
     * 
     * @param originalStream
     *            The stream we are wrapping
     */
    public UrlRewritingOutputStream(ServletOutputStream originalStream, String ownHostName, String contextPath,
            ServerChain serverChain) {
        this.originalStream = originalStream;
        this.ownHostName = ownHostName;
        this.contextPath = contextPath;
        this.serverChain = serverChain;
        log = LogFactory.getLog(UrlRewritingOutputStream.class);

        stream = new ByteArrayOutputStream();
    }

    /**
     * @see java.io.OutputStream#write(int)
     */
    public void write(int b) throws IOException {
        stream.write(b);
    }

    /**
     * @see java.io.OutputStream#write(byte[], int, int)
     */
    public void write(byte[] b, int off, int len) throws IOException {
        stream.write(b, off, len);
    }

    /**
     * @see java.io.OutputStream#write(byte[])
     */
    public void write(byte[] b) throws IOException {
        stream.write(b);
    }

    /**
     * Processes the stream looking for links, all links found are rewritten.
     * After this the stream is written to the response.
     * 
     * @param server
     *            The server that we are using for this request.
     * @throws IOException
     *             Is thrown when there is a problem with the streams
     */
    public void rewrite(Server server) throws IOException {
        /*
         * Using regex can be quite harsh sometimes so here is how the regex
         * trying to find links works
         * 
         * \\b(href=|src=|action=|url\\()([\"\']) This part is the
         * identification of links, matching something like href=", href=' and
         * href=
         * 
         * (([^/]+://)([^/<>]+))? This is to identify absolute paths. A link
         * doesn't have to be absolute therefor there is a ?.
         * 
         * ([^\"\'>]*) This is the link
         * 
         * [\"\'] Ending " or '
         * 
         * $1 - link type, e.g. href= $2 - ", ' or whitespace $3 - The entire
         * http://www.server.com if present $4 - The protocol, e.g http:// or
         * ftp:// $5 - The host name, e.g. www.server.com $6 - The link
         */
        StringBuffer page = new StringBuffer();

        Matcher matcher = linkPattern.matcher(stream.toString());

        while (matcher.find()) {

            String link = matcher.group(6).replaceAll("\\$", "\\\\\\$");
            if (link.length() == 0) {
                link = "/";
            }

            String rewritten = null;
            if (matcher.group(4) != null) {
                rewritten = handleExternalLink(matcher, link);
            } else if (link.startsWith("/")) {
                rewritten = handleLocalLink(server, matcher, link);
            }

            if (rewritten != null) {
                if (log.isDebugEnabled()) {
                    log.debug("Found link " + link + " >> " + rewritten);
                }
                matcher.appendReplacement(page, rewritten);
            }
        }

        matcher.appendTail(page);
        originalStream.write(stream.toByteArray());
    }

    public void rewriteRss(Server server) throws IOException {
        String streamString = stream.toString();
        if (streamString.startsWith("<?xml")) {
            String originalPath = server.getDomainName() + server.getPath();
            if (!originalPath.startsWith("/")) {
                originalPath = "/" + originalPath;
            }
            if (originalPath.endsWith("/")) {
                originalPath = originalPath.substring(0, originalPath.length() - 1);
            }
            String replacement = this.ownHostName + this.contextPath;
            if (!replacement.startsWith("/")) {
                replacement = "/" + replacement;
            }
            if (replacement.endsWith("/")) {
                replacement = replacement.substring(0, replacement.length() - 1);
            }
            originalStream.write(streamString.replaceAll(originalPath, replacement).getBytes());
        } else {
            originalStream.write(streamString.getBytes());
        }
    }

    /**
     * Rewrites a absolute path starting with a protocol e.g.
     * http://www.server.com/index.html
     * 
     * @param matcher
     *            The matcher used for this link
     * @param link
     *            The part of the link after the domain name
     * @return The link now rewritten
     */
    private String handleExternalLink(Matcher matcher, String link) {
        String location = matcher.group(5) + link;
        Server matchingServer = serverChain.getServerMapped(location);

        if (matchingServer != null) {
            link = link.substring(matchingServer.getPath().length());
            link = matchingServer.getRule().revert(link);
            String type = matcher.group(1);
            String separator = matcher.group(2);
            String protocol = matcher.group(4);
            return type + separator + protocol + ownHostName + contextPath + link + separator;
        } else {
            return null;
        }
    }

    /**
     * 
     * @param server
     *            The current server we are using for this page
     * @param matcher
     *            The matcher used for this link
     * @param link
     *            The original link
     * @return The rewritten link
     */
    private String handleLocalLink(Server server, Matcher matcher, String link) {
        String serverDir = server.getPath();

        if (serverDir.equals("") || link.startsWith(serverDir + "/")) {
            link = server.getRule().revert(link.substring(serverDir.length()));
            String type = matcher.group(1);
            String separator = matcher.group(2);
            return type + separator + contextPath + link + separator;
        } else {
            return null;
        }
    }

    /**
     * @see java.io.Closeable#close()
     */
    public void close() throws IOException {
        stream.close();
    }

    @Override
    public boolean isReady() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void setWriteListener(WriteListener arg0) {
        // TODO Auto-generated method stub

    }

}