org.ambraproject.web.VirtualJournalContext.java Source code

Java tutorial

Introduction

Here is the source code for org.ambraproject.web.VirtualJournalContext.java

Source

/* $HeadURL::                                                                            $
 * $Id$
 *
 * Copyright (c) 2007-2010 by Public Library of Science
 * http://plos.org
 * http://ambraproject.org
 *
 * 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 org.ambraproject.web;

import org.apache.commons.configuration.Configuration;
import org.ambraproject.configuration.ConfigurationStore;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.File;
import java.net.MalformedURLException;
import java.util.Collection;
import java.util.StringTokenizer;

/**
 * The virtual journal context.
 *
 * Various values relating to the virtual journal context and the current Request.
 * Designed to be put into the Request as an attribute for easy access by the web application.
 * In particular, allows templates full consistent access to the same information that is available
 * to the Java Action.
 *
 * Note that templates do not have full access to the Request, so a Request
 * attribute is used to bridge between the two.
 *
 * Application usage:
 * <pre>
 * VirtualJournalContext requestContent = ServletRequest.getAttribute(PUB_VIRTUALJOURNAL_CONTEXT);
 * String requestJournal = requestContext.getJournal();
 * </pre>
 */
public class VirtualJournalContext {
    /** ServletRequest attribute for the virtual journal context. */
    public static final String PUB_VIRTUALJOURNAL_CONTEXT = "ambra.virtualjournal.context";

    private final String journal;
    private final String mappingPrefix;
    private final String defaultMappingPrefix;
    private final String requestScheme;
    private final int requestPort;
    private final String requestServerName;
    private final String requestContext;
    private final String baseUrl;
    private final String baseHostUrl;
    private final Collection<String> virtualJournals;

    /**
     * Construct an immutable VirtualJournalContext.
     */
    public VirtualJournalContext(final String journal, final String defaultJournal, final String requestScheme,
            final int requestPort, final String requestServerName, final String requestContext,
            final Collection<String> virtualJournals) {

        this.journal = journal;
        this.requestScheme = requestScheme;
        this.requestPort = requestPort;
        this.requestServerName = requestServerName;
        this.requestContext = requestContext;
        this.mappingPrefix = "journals/" + journal;
        this.defaultMappingPrefix = "journals/" + defaultJournal;

        StringBuilder urlBaseValue = new StringBuilder();

        // assume that we're dealing with http or https schemes for now
        // TODO: understand how scheme can be null in request? for now, guard against null
        if (requestScheme != null) {
            urlBaseValue.append(requestScheme).append("://").append(requestServerName);

            //assume that we don't want to put the default ports numbers on the URL
            if (("http".equals(requestScheme.toLowerCase()) && requestPort != 80)
                    || ("https".equals(requestScheme.toLowerCase()) && requestPort != 443)) {
                urlBaseValue.append(":").append(requestPort);
            }
        }
        this.baseHostUrl = urlBaseValue.toString();
        urlBaseValue.append(requestContext);
        this.baseUrl = urlBaseValue.toString();

        this.virtualJournals = virtualJournals;
    }

    private static String[] createMapping(String virtualContextPath, String virtualServletPath,
            String virtualPathInfo) {
        return new String[] { virtualContextPath, virtualServletPath, virtualPathInfo,
                virtualContextPath + virtualServletPath + (virtualPathInfo != null ? virtualPathInfo : "") };
    }

    /**
     * Virtualize URI values.
     *
     * If URI is already prefixed with the mappingPrefix, it is already virtualized.<br/>
     * If URI is prefixed with the virtual journal name, replace with mappingPrefix to
     *   virtualize.<br/>
     * Else, prefix with mappingPrefix to virtualize.
     *
     * @return String[] of mapped {contextPath, servletPath, pathInfo, requestUri}
     */
    public String[] virtualizeUri(final String contextPath, final String servletPath, final String pathInfo) {

        // in tests below, be flexible with servletPath and pathInfo, not all containers adhere tightly
        // to Servlet SRV.4.4.  e.g. prefix could be in servletPath or pathInfo.

        // does URI already contain mappingPrefix?
        if (servletPath != null && servletPath.startsWith(mappingPrefix)
                || pathInfo != null && pathInfo.startsWith(mappingPrefix)) {
            return createMapping(contextPath, servletPath, pathInfo);
        }

        // does URI contain journal name?
        if (servletPath != null && servletPath.startsWith("/" + journal)) {
            return createMapping(contextPath, mappingPrefix + servletPath.substring(journal.length() + 1),
                    pathInfo);
        }
        if (pathInfo != null && pathInfo.startsWith("/" + journal)) {
            return createMapping(contextPath, servletPath,
                    mappingPrefix + pathInfo.substring(journal.length() + 1));
        }

        // need to add mappingPrefix to URI
        if (servletPath != null && servletPath.length() > 0) {
            return createMapping(contextPath, mappingPrefix + servletPath, pathInfo);
        } else {
            return createMapping(contextPath, servletPath, mappingPrefix + pathInfo);
        }
    }

    /**
     * Virtualize URI values to default journal.
     *
     * If URI is already prefixed with the mappingPrefix, replace with default journal mappingPrefix.<br/>
     * If URI is prefixed with the virtual journal name, replace with default journal mappingPrefix.<br/>
     * Else, prefix with default journal mappingPrefix to virtualize.
     *
     * @return String[] of mapped {contextPath, servletPath, pathInfo, requestUri}
     */
    public String[] siteDefaultUri(final String contextPath, final String servletPath, final String pathInfo) {

        // in tests below, be flexible with servletPath and pathInfo, not all containers adhere tightly
        // to Servlet SRV.4.4.  e.g. prefix could be in servletPath or pathInfo.

        // does URI already contain defaultMappingPrefix?
        if (servletPath != null && servletPath.startsWith(defaultMappingPrefix)
                || pathInfo != null && pathInfo.startsWith(defaultMappingPrefix)) {
            return createMapping(contextPath, servletPath, pathInfo);
        }

        // does URI contain mappingPrefix?
        if (servletPath != null && servletPath.startsWith(mappingPrefix)) {
            return createMapping(contextPath, servletPath.replaceFirst(mappingPrefix, defaultMappingPrefix),
                    pathInfo);
        }
        if (pathInfo != null && pathInfo.startsWith(mappingPrefix)) {
            return createMapping(contextPath, servletPath,
                    pathInfo.replaceFirst(mappingPrefix, defaultMappingPrefix));
        }

        // does URI contain journal name?
        if (servletPath != null && servletPath.startsWith("/" + journal)) {
            return createMapping(contextPath, defaultMappingPrefix + servletPath.substring(journal.length() + 1),
                    pathInfo);
        }
        if (pathInfo != null && pathInfo.startsWith("/" + journal)) {
            return createMapping(contextPath, servletPath,
                    defaultMappingPrefix + pathInfo.substring(journal.length() + 1));
        }

        // need to add defaultMappingPrefix to URI
        if (servletPath != null && servletPath.length() > 0) {
            return createMapping(contextPath, defaultMappingPrefix + servletPath, pathInfo);
        } else {
            return createMapping(contextPath, servletPath, defaultMappingPrefix + pathInfo);
        }
    }

    /**
     * Default URI values.
     *
     * If URI is already prefixed with the mappingPrefix, remove it.<br/>
     * If URI is prefixed with the virtual journal name, remove it.<br/>
     * Else, already default values.
     *
     * @return String[] of defaulted {contextPath, servletPath, pathInfo, requestUri}
     */
    public String[] defaultUri(final String contextPath, final String servletPath, final String pathInfo) {

        /*
         * in tests below, be flexible with servletPath and pathInfo, not all containers adhere tightly
         * to Servlet SRV.4.4.  e.g. prefix could be in servletPath or pathInfo.
         */

        // does URI already contain mappingPrefix?
        if (servletPath != null && servletPath.startsWith(mappingPrefix)) {
            return createMapping(contextPath, servletPath.substring(mappingPrefix.length()), pathInfo);
        }
        if (pathInfo != null && pathInfo.startsWith(mappingPrefix)) {
            return createMapping(contextPath, servletPath, pathInfo.substring(mappingPrefix.length()));
        }

        // does URI contain journal name?
        if (servletPath != null && servletPath.startsWith("/" + journal)) {
            return createMapping(contextPath, servletPath.substring(journal.length() + 1), pathInfo);
        }
        if (pathInfo != null && pathInfo.startsWith("/" + journal)) {
            return createMapping(contextPath, servletPath, pathInfo.substring(journal.length() + 1));
        }

        // already defaulted
        return createMapping(contextPath, servletPath, pathInfo);
    }

    /**
     * For a given request, returns a request with paths mapped to the appropriate journal.
     *
     * @param request the original request, with non-journal-relative paths
     * @param configuration configuration object
     * @param servletContext servlet context for the given request
     * @return a request with paths mapped appropriately.  May be the original request passed in, if no changes were
     *     needed.
     * @throws ServletException
     */
    public HttpServletRequest mapRequest(HttpServletRequest request, Configuration configuration,
            ServletContext servletContext) throws ServletException {
        String journal = getJournal();

        if (journal == null)
            return request;

        String cp = request.getContextPath();
        String sp = request.getServletPath();
        String pi = request.getPathInfo();

        // Find resource in journal
        String[] mapped = getMappedPaths(virtualizeUri(cp, sp, pi), configuration, servletContext);

        // Find resource in default journal
        if (mapped == null)
            mapped = getMappedPaths(siteDefaultUri(cp, sp, pi), configuration, servletContext);

        // Find resource in app defaults
        if (mapped == null)
            mapped = getMappedPaths(defaultUri(cp, sp, pi), configuration, servletContext);

        if ((mapped != null) && mapped[3].equals(request.getRequestURI()))
            return request;

        if (mapped == null)
            return request;
        else
            return wrapRequest(request, mapped);
    }

    private String[] getMappedPaths(String[] paths, Configuration configuration, ServletContext servletContext)
            throws ServletException {

        // strip contextPath ("/ambra-webapp") from resource path
        String resource = paths[3].substring(paths[0].length());

        if (resource.startsWith("journals") || resource.startsWith("/journals")) {
            String path = getJournalResourcePath(resource, configuration);
            String fullPath = path.startsWith("/") ? path : "/" + path;

            if (resourceExistsInPath(fullPath)) {
                return new String[] { paths[0], "", fullPath, paths[0] + fullPath };
            }
        } else if (resourceExistsInServletContext(resource, servletContext)) {
            return paths;
        }

        return null;
    }

    /**
     * Path /journal/journal-name/resource changes to /journal/journal-name/webapp/resource
     * @param resource Resource path
     * @return Absolute resource path
     */
    private String getJournalResourcePath(String resource, Configuration configuration) {
        String templatePath = configuration.getString(ConfigurationStore.JOURNAL_TEMPLATE_DIR, null);

        StringTokenizer tokenizer = new StringTokenizer(resource, "/");
        StringBuilder stringBuilder = new StringBuilder();
        boolean addWebapp = false;
        while (tokenizer.hasMoreTokens()) {
            String token = tokenizer.nextToken();
            if (token.equals("journals") && stringBuilder.length() == 0) {
                addWebapp = true;
            }
            stringBuilder.append('/').append(token);
            if (addWebapp && !token.equals("journals")) {
                stringBuilder.append("/webapp");
                addWebapp = false;
            }
        }

        return templatePath + stringBuilder.toString();
    }

    /**
     * Search in servlet context - struts directory
     * @param resource path to resource
     * @return true if resource exists in servlet context
     * @throws ServletException Servlet exception
     */
    private boolean resourceExistsInServletContext(String resource, ServletContext servletContext)
            throws ServletException {
        try {
            return servletContext.getResource(resource) != null;
        } catch (MalformedURLException mre) {
            throw new ServletException("Invalid resource path: " + resource, mre);
        }
    }

    /**
     * Search in file system
     * @param resource path to resource
     * @return true if resource exists in servlet context
     * @throws ServletException Servlet exception
     */
    private boolean resourceExistsInPath(String resource) throws ServletException {
        File file = new File(resource);
        return file.isFile() && file.canRead();
    }

    /**
     * Wrap an HttpServletRequest with arbitrary URI values.
     *
     * @param request the request to wrap
     * @param paths the paths to substitute
     *
     * @return the wrapped request instance
     *
     * @throws IllegalArgumentException DOCUMENT ME!
     */
    public static HttpServletRequest wrapRequest(final HttpServletRequest request, final String[] paths) {
        if ((paths == null) || (paths.length != 4))
            throw new IllegalArgumentException("Invalid path list");

        return new HttpServletRequestWrapper(request) {
            public String getRequestURI() {
                return paths[3];
            }

            public String getContextPath() {
                return paths[0];
            }

            public String getServletPath() {
                return paths[1];
            }

            public String getPathInfo() {
                return paths[2];
            }
        };
    }

    /**
     * Get the virtual journal name.
     *
     * @return Journal name, may be <code>null</code>.
     */
    public String getJournal() {
        return journal;
    }

    /**
     * Get the virtual journal Request scheme.
     *
     * @return Request scheme.
     */
    public String getRequestScheme() {
        return requestScheme;
    }

    /**
     * Get the virtual journal Request port.
     *
     * @return Request port.
     */
    public int getRequestPort() {
        return requestPort;
    }

    /**
     * Get the virtual journal Request server name.
     *
     * @return Request server name.
     */
    public String getRequestServerName() {
        return requestServerName;
    }

    /**
     * Get the virtual journal Request context.
     *
     * @return Request context.
     */
    public String getRequestContext() {
        return requestContext;
    }

    /**
     * Get the base url of the request which consists of the scheme, server name, server port, and
     * context.
     *
     * @return string representing the base request URL
     */
    public String getBaseUrl() {
        return baseUrl;
    }

    /**
     * Get the base host url of the request which consists of the scheme, server name, and server port
     *
     * @return string representing the base host URL
     */
    public String getBaseHostUrl() {
        return baseHostUrl;
    }

    /**
     * Get the virtual journals that are available.
     *
     * @return Available virtual journals.
     */
    public Collection<String> getVirtualJournals() {
        return virtualJournals;
    }

    public String toString() {
        String result = String.format(
                "VirtualJournalContext: journal=%s, defaultMappingPrefix=%s, requestScheme=%s, "
                        + "requestPort=%s, requestServerName=%s, requestContext=%s, virtualJournals=",
                journal, defaultMappingPrefix, requestScheme, requestPort, requestServerName, requestContext);
        for (String s : virtualJournals) {
            result += s + ",";
        }
        return result;
    }
}