Java tutorial
/** * 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)); } } }