org.jamwiki.utils.WikiUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.jamwiki.utils.WikiUtil.java

Source

/**
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, version 2.1, dated February 1999.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the latest version of the GNU Lesser General
 * Public License as published by the Free Software Foundation;
 *
 * This program 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 program (LICENSE.txt); if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package org.jamwiki.utils;

import info.bliki.gae.db.GAEDataHandler;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.jamwiki.DataAccessException;
import org.jamwiki.DataHandler;
import org.jamwiki.Environment;
import org.jamwiki.WikiBase;
import org.jamwiki.WikiException;
import org.jamwiki.WikiMessage;
import org.jamwiki.WikiVersion;
import org.jamwiki.model.Role;
import org.jamwiki.model.Topic;
import org.jamwiki.model.VirtualWiki;

/**
 * This class provides a variety of general utility methods for handling
 * wiki-specific functionality such as retrieving topics from the URL.
 */
public class WikiUtil {

    private static final WikiLogger logger = WikiLogger.getLogger(WikiUtil.class.getName());

    /** webapp context path, initialized from JAMWikiFilter. */
    public static String WEBAPP_CONTEXT_PATH = null;
    private static Pattern INVALID_ROLE_NAME_PATTERN = null;
    private static Pattern INVALID_TOPIC_NAME_PATTERN = null;
    private static Pattern VALID_USER_LOGIN_PATTERN = null;
    public static final String PARAMETER_TOPIC = "topic";
    public static final String PARAMETER_VIRTUAL_WIKI = "virtualWiki";
    public static final String PARAMETER_WATCHLIST = "watchlist";

    static {
        try {
            INVALID_ROLE_NAME_PATTERN = Pattern
                    .compile(Environment.getValue(Environment.PROP_PATTERN_INVALID_ROLE_NAME));
            INVALID_TOPIC_NAME_PATTERN = Pattern
                    .compile(Environment.getValue(Environment.PROP_PATTERN_INVALID_TOPIC_NAME));
            VALID_USER_LOGIN_PATTERN = Pattern
                    .compile(Environment.getValue(Environment.PROP_PATTERN_VALID_USER_LOGIN));
        } catch (PatternSyntaxException e) {
            logger.severe("Unable to compile pattern", e);
        }
    }

    /**
     * Create a pagination object based on parameters found in the current
     * request.
     * 
     * @param request
     *          The servlet request object.
     * @return A Pagination object constructed from parameters found in the
     *         request object.
     */
    public static Pagination buildPagination(HttpServletRequest request) {
        int num = Environment.getIntValue(Environment.PROP_RECENT_CHANGES_NUM);
        if (request.getParameter("num") != null) {
            try {
                num = Integer.parseInt(request.getParameter("num"));
            } catch (NumberFormatException e) {
                // invalid number
            }
        }
        int offset = 0;
        if (request.getParameter("offset") != null) {
            try {
                offset = Integer.parseInt(request.getParameter("offset"));
            } catch (NumberFormatException e) {
                // invalid number
            }
        }
        return new Pagination(num, offset);
    }

    /**
     * Utility method to retrieve an instance of the current data handler.
     * 
     * @return An instance of the current data handler.
     * @throws IOException
     *           Thrown if a data handler instance can not be instantiated.
     */
    public static DataHandler dataHandlerInstance() throws IOException {
        return new GAEDataHandler();
    }

    /**
     * Convert a topic name or other value into a value suitable for use as a
     * file name.  This method replaces spaces with underscores, and then URL
     * encodes the value.
     *
     * @param name The value that is to be encoded for use as a file name.
     * @return The encoded value.
     */
    public static String encodeForFilename(String name) {
        if (StringUtils.isBlank(name)) {
            throw new IllegalArgumentException("File name not specified in encodeForFilename");
        }
        // replace spaces with underscores
        String result = Utilities.encodeTopicName(name);
        // URL encode the rest of the name
        try {
            result = URLEncoder.encode(result, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            // this should never happen
            throw new IllegalStateException("Unsupporting encoding UTF-8");
        }
        return result;
    }

    /**
     * Given an article name, return the appropriate comments topic article name.
     * For example, if the article name is "Topic" then the return value is
     * "Comments:Topic".
     *
     * @param name The article name from which a comments article name is to
     *  be constructed.
     * @return The comments article name for the article name.
     */
    public static String extractCommentsLink(String name) {
        if (StringUtils.isBlank(name)) {
            throw new IllegalArgumentException("Topic name must not be empty in extractCommentsLink");
        }
        WikiLink wikiLink = LinkUtil.parseWikiLink(name);
        if (StringUtils.isBlank(wikiLink.getNamespace())) {
            return NamespaceHandler.NAMESPACE_COMMENTS + NamespaceHandler.NAMESPACE_SEPARATOR + name;
        }
        String namespace = wikiLink.getNamespace();
        String commentsNamespace = NamespaceHandler.getCommentsNamespace(namespace);
        return (!StringUtils.isBlank(commentsNamespace))
                ? commentsNamespace + NamespaceHandler.NAMESPACE_SEPARATOR + wikiLink.getArticle()
                : NamespaceHandler.NAMESPACE_COMMENTS + NamespaceHandler.NAMESPACE_SEPARATOR
                        + wikiLink.getArticle();
    }

    /**
     * Given an article name, extract an appropriate topic article name.  For
     * example, if the article name is "Comments:Topic" then the return value
     * is "Topic".
     *
     * @param name The article name from which a topic article name is to be
     *  constructed.
     * @return The topic article name for the article name.
     */
    public static String extractTopicLink(String name) {
        if (StringUtils.isBlank(name)) {
            throw new IllegalArgumentException("Topic name must not be empty in extractTopicLink");
        }
        WikiLink wikiLink = LinkUtil.parseWikiLink(name);
        if (StringUtils.isBlank(wikiLink.getNamespace())) {
            return name;
        }
        String namespace = wikiLink.getNamespace();
        String mainNamespace = NamespaceHandler.getMainNamespace(namespace);
        return (!StringUtils.isBlank(mainNamespace))
                ? mainNamespace + NamespaceHandler.NAMESPACE_SEPARATOR + wikiLink.getArticle()
                : wikiLink.getArticle();
    }

    /**
     * Determine the URL for the default virtual wiki topic, not including the application server context.
     */
    public static String findDefaultVirtualWikiUrl(String virtualWikiName) {
        if (StringUtils.isBlank(virtualWikiName)) {
            virtualWikiName = WikiBase.DEFAULT_VWIKI;
        }
        String target = Environment.getValue(Environment.PROP_BASE_DEFAULT_TOPIC);
        try {
            VirtualWiki virtualWiki = WikiBase.getDataHandler().lookupVirtualWiki(virtualWikiName);
            target = virtualWiki.getDefaultTopicName();
        } catch (DataAccessException e) {
            logger.warning("Unable to retrieve default topic for virtual wiki", e);
        }
        return "/" + virtualWikiName + "/" + target;
    }

    /**
      * Given a topic type, determine the namespace name.
      *
      * @param topicType The topic type.
      * @return The namespace that matches the topic type.
      */
    public static String findNamespaceForTopicType(int topicType) {
        switch (topicType) {
        case Topic.TYPE_IMAGE:
        case Topic.TYPE_FILE:
            return NamespaceHandler.NAMESPACE_IMAGE;
        case Topic.TYPE_CATEGORY:
            return NamespaceHandler.NAMESPACE_CATEGORY;
        case Topic.TYPE_SYSTEM_FILE:
            return NamespaceHandler.NAMESPACE_JAMWIKI;
        case Topic.TYPE_TEMPLATE:
            return NamespaceHandler.NAMESPACE_TEMPLATE;
        default:
            return "";
        }
    }

    /**
      * Given a namespace name, determine the topic type.
      *
      * @param namespace The namespace name.
      * @return The topic type that matches the namespace.
      */
    public static int findTopicTypeForNamespace(String namespace) {
        if (namespace != null) {
            if (namespace.equals(NamespaceHandler.NAMESPACE_CATEGORY)) {
                return Topic.TYPE_CATEGORY;
            }
            if (namespace.equals(NamespaceHandler.NAMESPACE_TEMPLATE)) {
                return Topic.TYPE_TEMPLATE;
            }
            if (namespace.equals(NamespaceHandler.NAMESPACE_JAMWIKI)) {
                return Topic.TYPE_SYSTEM_FILE;
            }
            if (namespace.equals(NamespaceHandler.NAMESPACE_IMAGE)) {
                // FIXME - handle TYPE_FILE
                return Topic.TYPE_IMAGE;
            }
        }
        return Topic.TYPE_ARTICLE;
    }

    /**
     * Return the URL of the index page for the wiki.
     * 
     * @throws DataAccessException
     *           Thrown if any error occurs while retrieving data.
     */
    public static String getBaseUrl() throws DataAccessException {
        String url = Environment.getValue(Environment.PROP_SERVER_URL);
        url += LinkUtil.buildTopicUrl(WEBAPP_CONTEXT_PATH, WikiBase.DEFAULT_VWIKI,
                Environment.getValue(Environment.PROP_BASE_DEFAULT_TOPIC), true);
        return url;
    }

    /**
     * Retrieve a parameter from the servlet request. This method works around
     * some issues encountered when retrieving non-ASCII values from URL
     * parameters.
     * 
     * @param request
     *          The servlet request object.
     * @param name
     *          The parameter name to be retrieved.
     * @param decodeUnderlines
     *          Set to <code>true</code> if underlines should be automatically
     *          converted to spaces.
     * @return The decoded parameter value retrieved from the request.
     */
    public static String getParameterFromRequest(HttpServletRequest request, String name,
            boolean decodeUnderlines) {
        String value = null;
        if (request.getMethod().equalsIgnoreCase("GET")) {
            // parameters passed via the URL are URL encoded, so request.getParameter
            // may
            // not interpret non-ASCII characters properly. This code attempts to work
            // around that issue by manually decoding. yes, this is ugly and it would
            // be
            // great if someone could eventually make it unnecessary.
            String query = request.getQueryString();
            if (StringUtils.isBlank(query)) {
                return null;
            }
            String prefix = name + "=";
            int pos = query.indexOf(prefix);
            if (pos != -1 && (pos + prefix.length()) < query.length()) {
                value = query.substring(pos + prefix.length());
                if (value.indexOf('&') != -1) {
                    value = value.substring(0, value.indexOf('&'));
                }
            }
            return Utilities.decodeAndEscapeTopicName(value, decodeUnderlines);
        }
        value = request.getParameter(name);
        if (value == null) {
            value = (String) request.getAttribute(name);
        }
        if (value == null) {
            return null;
        }
        return Utilities.decodeTopicName(value, decodeUnderlines);
    }

    /**
     * Retrieve a topic name from the servlet request. This method will retrieve a
     * request parameter matching the PARAMETER_TOPIC value, and will decode it
     * appropriately.
     * 
     * @param request
     *          The servlet request object.
     * @return The decoded topic name retrieved from the request.
     */
    public static String getTopicFromRequest(HttpServletRequest request) {
        return WikiUtil.getParameterFromRequest(request, WikiUtil.PARAMETER_TOPIC, true);
    }

    /**
     * Retrieve a topic name from the request URI. This method will retrieve the
     * portion of the URI that follows the virtual wiki and decode it
     * appropriately.
     * 
     * @param request
     *          The servlet request object.
     * @return The decoded topic name retrieved from the URI.
     */
    public static String getTopicFromURI(HttpServletRequest request) {
        // skip one directory, which is the virutal wiki
        String topic = retrieveDirectoriesFromURI(request, 1);
        if (topic == null) {
            logger.warning("No topic in URL: " + request.getRequestURI());
            return null;
        }
        int pos = topic.indexOf('#');
        if (pos != -1) {
            // strip everything after and including '#'
            if (pos == 0) {
                logger.warning("No topic in URL: " + request.getRequestURI());
                return null;
            }
            topic = topic.substring(0, pos);
        }
        pos = topic.indexOf('?');
        if (pos != -1) {
            // strip everything after and including '?'
            if (pos == 0) {
                logger.warning("No topic in URL: " + request.getRequestURI());
                return null;
            }
            topic = topic.substring(0, pos);
        }
        pos = topic.indexOf(';');
        if (pos != -1) {
            // some servlet containers return parameters of the form
            // ";jsessionid=1234" when getRequestURI is called.
            if (pos == 0) {
                logger.warning("No topic in URL: " + request.getRequestURI());
                return null;
            }
            topic = topic.substring(0, pos);
        }
        if (!StringUtils.isBlank(topic)) {
            topic = Utilities.decodeAndEscapeTopicName(topic, true);
        }
        return topic;
    }

    /**
     * Retrieve a virtual wiki name from the servlet request. This method will
     * retrieve a request parameter matching the PARAMETER_VIRTUAL_WIKI value, and
     * will decode it appropriately.
     * 
     * @param request
     *          The servlet request object.
     * @return The decoded virtual wiki name retrieved from the request.
     */
    public static String getVirtualWikiFromRequest(HttpServletRequest request) {
        String virtualWiki = request.getParameter(WikiUtil.PARAMETER_VIRTUAL_WIKI);
        if (virtualWiki == null) {
            virtualWiki = (String) request.getAttribute(WikiUtil.PARAMETER_VIRTUAL_WIKI);
        }
        if (virtualWiki == null || virtualWiki.length() == 0) {
            return WikiBase.DEFAULT_VWIKI;
        }
        //    if (virtualWiki == null) {
        //      return null;
        //    }
        return Utilities.decodeTopicName(virtualWiki, true);
    }

    /**
     * Retrieve a virtual wiki name from the request URI. This method will
     * retrieve the portion of the URI that immediately follows the servlet
     * context and decode it appropriately.
     * 
     * @param request
     *          The servlet request object.
     * @return The decoded virtual wiki name retrieved from the URI.
     */
    public static String getVirtualWikiFromURI(HttpServletRequest request) {
        String uri = retrieveDirectoriesFromURI(request, 0);
        if (StringUtils.isBlank(uri)) {
            logger.info("No virtual wiki found in URL: " + request.getRequestURI());
            return null;
        }
        // default the virtual wiki to the URI since the user may have accessed a
        // URL of
        // the form /context/virtualwiki with no trailing slash
        String virtualWiki = uri;
        int slashIndex = uri.indexOf('/');
        if (slashIndex != -1) {
            virtualWiki = uri.substring(0, slashIndex);
        }
        return Utilities.decodeAndEscapeTopicName(virtualWiki, true);
    }

    /**
     * Given a topic name, determine if that name corresponds to a comments
     * page.
     *
     * @param topicName The topic name (non-null) to examine to determine if it
     *  is a comments page or not.
     * @return <code>true</code> if the page is a comments page, <code>false</code>
     *  otherwise.
     */
    public static boolean isCommentsPage(String topicName) {
        WikiLink wikiLink = LinkUtil.parseWikiLink(topicName);
        if (StringUtils.isBlank(wikiLink.getNamespace())) {
            return false;
        }
        String namespace = wikiLink.getNamespace();
        if (namespace.equals(NamespaceHandler.NAMESPACE_SPECIAL)) {
            return false;
        }
        String commentNamespace = NamespaceHandler.getCommentsNamespace(namespace);
        return namespace.equals(commentNamespace);
    }

    /**
     * Determine if the system properties file exists and has been initialized.
     * This method is primarily used to determine whether or not to display
     * the system setup page or not.
     *
     * @return <code>true</code> if the properties file has NOT been initialized,
     *  <code>false</code> otherwise.
     */
    public static boolean isFirstUse() {
        return !Environment.getBooleanValue(Environment.PROP_BASE_INITIALIZED);
    }

    /**
     * Determine if the system code has been upgraded from the configured system
     * version.  Thus if the system is upgraded, this method returns <code>true</code>
     *
     * @return <code>true</code> if the system has been upgraded, <code>false</code>
     *  otherwise.
     */
    public static boolean isUpgrade() {
        if (WikiUtil.isFirstUse()) {
            return false;
        }
        WikiVersion oldVersion = new WikiVersion(Environment.getValue(Environment.PROP_BASE_WIKI_VERSION));
        WikiVersion currentVersion = new WikiVersion(WikiVersion.CURRENT_WIKI_VERSION);
        return false; //oldVersion.before(currentVersion);
    }

    /**
     * Utility method for reading special topic values from files and returning
     * the file contents.
     *
     * @param locale The locale for the user viewing the special page.
     * @param pageName The name of the special page being retrieved.
     */
    public static String readSpecialPage(Locale locale, String pageName) throws IOException {
        String contents = null;
        String filename = null;
        String language = null;
        String country = null;
        if (locale != null) {
            language = locale.getLanguage();
            country = locale.getCountry();
        }
        String subdirectory = "";
        if (!StringUtils.isBlank(language) && !StringUtils.isBlank(country)) {
            try {
                subdirectory = new File(WikiBase.SPECIAL_PAGE_DIR, language + "_" + country).getPath();
                filename = new File(subdirectory, WikiUtil.encodeForFilename(pageName) + ".txt").getPath();
                contents = Utilities.readFile(filename);
            } catch (IOException e) {
                logger.info("File " + filename + " does not exist");
            }
        }
        if (contents == null && !StringUtils.isBlank(language)) {
            try {
                subdirectory = new File(WikiBase.SPECIAL_PAGE_DIR, language).getPath();
                filename = new File(subdirectory, WikiUtil.encodeForFilename(pageName) + ".txt").getPath();
                contents = Utilities.readFile(filename);
            } catch (IOException e) {
                logger.info("File " + filename + " does not exist");
            }
        }
        if (contents == null) {
            try {
                subdirectory = new File(WikiBase.SPECIAL_PAGE_DIR).getPath();
                filename = new File(subdirectory, WikiUtil.encodeForFilename(pageName) + ".txt").getPath();
                contents = Utilities.readFile(filename);
            } catch (IOException e) {
                logger.warning("File " + filename + " could not be read", e);
                throw e;
            }
        }
        return contents;
    }

    /**
     * Utility method for retrieving values from the URI. This method will attempt
     * to properly convert the URI encoding, and then offers a way to return
     * directories after the initial context directory. For example, if the URI is
     * "/context/first/second/third" and this method is called with a skipCount of
     * 1, the return value is "second/third".
     * 
     * @param request
     *          The servlet request object.
     * @param skipCount
     *          The number of directories to skip.
     * @return A UTF-8 encoded portion of the URL that skips the web application
     *         context and skipCount directories, or <code>null</code> if the
     *         number of directories is less than skipCount.
     */
    private static String retrieveDirectoriesFromURI(HttpServletRequest request, int skipCount) {
        String uri = request.getRequestURI().trim();
        // FIXME - needs testing on other platforms
        uri = Utilities.convertEncoding(uri, "ISO-8859-1", "UTF-8");
        String contextPath = request.getContextPath().trim();
        if (StringUtils.isBlank(uri) || contextPath == null) {
            return null;
        }
        // make sure there are no instances of "//" in the URL
        uri = uri.replaceAll("(/){2,}", "/");
        if (uri.length() <= contextPath.length()) {
            return null;
        }
        uri = uri.substring(contextPath.length() + 1);
        int i = 0;
        while (i < skipCount) {
            int slashIndex = uri.indexOf('/');
            if (slashIndex == -1) {
                return null;
            }
            uri = uri.substring(slashIndex + 1);
            i++;
        }
        return uri;
    }

    /**
     * Utility method for determining if a topic name is valid for use on the Wiki,
     * meaning that it is not empty and does not contain any invalid characters.
     *
     * @param name The topic name to validate.
     * @throws WikiException Thrown if the user name is invalid.
     */
    public static void validateTopicName(String name) throws WikiException {
        if (StringUtils.isBlank(name)) {
            throw new WikiException(new WikiMessage("common.exception.notopic"));
        }
        if (PseudoTopicHandler.isPseudoTopic(name)) {
            throw new WikiException(new WikiMessage("common.exception.pseudotopic", name));
        }
        WikiLink wikiLink = LinkUtil.parseWikiLink(name);
        String namespace = StringUtils.trimToNull(wikiLink.getNamespace());
        String article = StringUtils.trimToNull(wikiLink.getArticle());
        if (StringUtils.startsWith(namespace, "/") || StringUtils.startsWith(article, "/")) {
            throw new WikiException(new WikiMessage("common.exception.name", name));
        }
        if (namespace != null && namespace.toLowerCase().equals(NamespaceHandler.NAMESPACE_SPECIAL.toLowerCase())) {
            throw new WikiException(new WikiMessage("common.exception.name", name));
        }
        Matcher m = WikiUtil.INVALID_TOPIC_NAME_PATTERN.matcher(name);
        if (m.find()) {
            throw new WikiException(new WikiMessage("common.exception.name", name));
        }
    }

    /**
     * Utility method for determining if the parameters of a Role are valid
     * or not.
     *
     * @param role The Role to validate.
     * @throws WikiException Thrown if the role is invalid.
     */
    public static void validateRole(Role role) throws WikiException {
        Matcher m = WikiUtil.INVALID_ROLE_NAME_PATTERN.matcher(role.getAuthority());
        if (!m.matches()) {
            throw new WikiException(new WikiMessage("roles.error.name", role.getAuthority()));
        }
        if (!StringUtils.isBlank(role.getDescription()) && role.getDescription().length() > 200) {
            throw new WikiException(new WikiMessage("roles.error.description"));
        }
        // FIXME - throw a user-friendly error if the role name is already in use
    }

    /**
     * Utility method for determining if a password is valid for use on the wiki.
     *
     * @param password The password value.
     * @param confirmPassword Passwords must be entered twice to avoid tying errors.
     *  This field represents the confirmed password entry.
     */
    public static void validatePassword(String password, String confirmPassword) throws WikiException {
        if (StringUtils.isBlank(password)) {
            throw new WikiException(new WikiMessage("error.newpasswordempty"));
        }
        if (StringUtils.isBlank(confirmPassword)) {
            throw new WikiException(new WikiMessage("error.passwordconfirm"));
        }
        if (!password.equals(confirmPassword)) {
            throw new WikiException(new WikiMessage("admin.message.passwordsnomatch"));
        }
    }

    /**
     * Utility method for determining if a username is valid for use on the Wiki,
     * meaning that it is not empty and does not contain any invalid characters.
     *
     * @param name The username to validate.
     * @throws WikiException Thrown if the user name is invalid.
     */
    public static void validateUserName(String name) throws WikiException {
        if (StringUtils.isBlank(name)) {
            throw new WikiException(new WikiMessage("error.loginempty"));
        }
        Matcher m = WikiUtil.VALID_USER_LOGIN_PATTERN.matcher(name);
        if (!m.matches()) {
            throw new WikiException(new WikiMessage("common.exception.name", name));
        }
    }
}