/** * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. * * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS * graphic logo is a trademark of OpenMRS Inc. */ package org.openmrs.module.web.filter; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Vector; import org.openmrs.module.Module; import org.openmrs.module.ModuleException; import org.openmrs.module.web.WebModuleUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * This class represents the mapping of a Filter to a collection of Servlets and URLs */ public class ModuleFilterMapping implements Serializable { public static final long serialVersionUID = 1; private static Logger log = LoggerFactory.getLogger(WebModuleUtil.class); // Properties private Module module; private String filterName; private List<String> servletNames = new ArrayList<String>(); private List<String> urlPatterns = new ArrayList<String>(); /** * Default constructor, requires a Module * * @param module - the module to use to construct this ModuleFilterMapping */ public ModuleFilterMapping(Module module) { this.module = module; } /** * @return - the {@link Module} that registered this FilterDefinition */ public Module getModule() { return module; } /** * @param module the {@link Module} */ public void setModule(Module module) { this.module = module; } /** * @return the name of the Filter */ public String getFilterName() { return filterName; } /** * @param filterName the name of the Filter */ public void setFilterName(String filterName) { this.filterName = filterName; } /** * @return a List of all Servlet Names mapped to this Filter */ public List<String> getServletNames() { return servletNames; } /** * @param servletNames a List of all Servlet Names mapped to this filter */ public void setServletNames(List<String> servletNames) { this.servletNames = servletNames; } /** * Adds a Servlet name to the List of those mapped to this filter * * @param servletName - The servlet name to add */ public void addServletName(String servletName) { this.servletNames.add(servletName); } /** * @return - a List of all Url Patterns mapped to this filter */ public List<String> getUrlPatterns() { return urlPatterns; } /** * @param urlPatterns a List of all Url Patterns mapped to this filter */ public void setUrlPatterns(List<String> urlPatterns) { this.urlPatterns = urlPatterns; } /** * Adds a Url pattern to the List of those mapped to this filter * * @param urlPattern - The urlPattern to add */ public void addUrlPattern(String urlPattern) { this.urlPatterns.add(urlPattern); } /** * Return <code>true</code> if the passed Filter passes one or more filter mappings otherwise, * return <code>false</code>. * * @param filterMapping - The {@link ModuleFilterMapping} to check for matching servlets and url * patterns * @param requestPath - The URI of the request to check against the {@link ModuleFilterMapping}, * with the context path already removed (since module filter mappings are relative to the * context path). * @return - true if the given {@link ModuleFilterMapping} matches the passed requestPath For * example: Passing a ModuleFilterMapping containing a urlPattern of "*" would return * true for any requestPath Passing a ModuleFilterMapping containing a urlPattern of * "*.jsp" would return true for any requestPath ending in ".jsp" * @should return false if the requestPath is null * @should return true if the ModuleFilterMapping contains any matching urlPatterns for this * requestPath * @should return true if the ModuleFilterMapping contains any matching servletNames for this * requestPath * @should return false if no matches are found for this requestPath */ public static boolean filterMappingPasses(ModuleFilterMapping filterMapping, String requestPath) { // Return false if url is null if (requestPath == null) { return false; } for (String patternToCheck : filterMapping.getUrlPatterns()) { if (urlPatternMatches(patternToCheck, requestPath)) { return true; } } for (String patternToCheck : filterMapping.getServletNames()) { if (servletNameMatches(patternToCheck, requestPath)) { return true; } } // If none found, return false return false; } /** * Return <code>true</code> if the context-relative request path matches the patternToCheck * otherwise, return <code>false</code>. * * @param patternToCheck String pattern to check * @param requestPath to check * @should return false if the patternToCheck is null * @should return true if the pattern is * * @should return true if the pattern is /* * @should return true if the pattern matches the requestPath exactly * @should return true if the pattern matches everything up to a suffix of /* * @should return true if the pattern matches by extension * @should return false if no pattern matches */ public static boolean urlPatternMatches(String patternToCheck, String requestPath) { // Return false if patternToCheck is null if (patternToCheck == null) { return false; } log.debug("Checking URL <" + requestPath + "> against pattern <" + patternToCheck + ">"); // Match exact or full wildcard if ("*".equals(patternToCheck) || "/*".equals(patternToCheck) || patternToCheck.equals(requestPath)) { return true; } // Match wildcard if (patternToCheck.endsWith("/*")) { int patternLength = patternToCheck.length() - 2; if (patternToCheck.regionMatches(0, requestPath, 0, patternLength)) { if (requestPath.length() == patternLength) { return true; } else if ('/' == requestPath.charAt(patternLength)) { return true; } } return false; } // Case 3 - Extension Match if (patternToCheck.startsWith("*.")) { int slash = requestPath.lastIndexOf('/'); int period = requestPath.lastIndexOf('.'); int reqLen = requestPath.length(); int patLen = patternToCheck.length(); if (slash >= 0 && period > slash && period != reqLen - 1 && reqLen - period == patLen - 1) { return (patternToCheck.regionMatches(2, requestPath, period + 1, patLen - 2)); } } // If no match found by here, return false return false; } /** * Return <code>true</code> if the specified servlet name matches the filterMapping otherwise * return <code>false</code>. * * @param patternToCheck String pattern to check * @param servletName Servlet Name to check * @should return false if the patternToCheck is null * @should return true if the pattern is * * @should return true if the pattern matches the servlet name exactly * @should return false if no pattern matches */ public static boolean servletNameMatches(String patternToCheck, String servletName) { // Return false if servletName is null if (servletName == null) { return false; } log.debug("Checking servlet <" + servletName + "> against pattern <" + patternToCheck + ">"); // Match exact or full wildcard if (("*").equals(patternToCheck) || servletName.equals(patternToCheck)) { return true; } // If none found, return false return false; } /** * Static method to parse through a Module's configuration file and return a List of * ModuleFilterMapping objects for which there are configuration elements. Expected XML Format: * * <pre> * <filter-mapping> * <filter-name>MyFilterName</filter-name> * <url-pattern>The pattern of URLs to match</filter-class> * </filter-mapping> * or * <filter-mapping> * <filter-name>MyFilterName</filter-name> * <servlet-name>The servlet name to match</servlet-name> * </filter-mapping> * </pre> * * @param module - The {@link Module} for which you want to retrieve the defined * {@link ModuleFilterMapping}s * @return - a List of {@link ModuleFilterMapping}s that are defined for the passed * {@link Module} */ public static List<ModuleFilterMapping> retrieveFilterMappings(Module module) throws ModuleException { List<ModuleFilterMapping> mappings = new Vector<ModuleFilterMapping>(); try { Element rootNode = module.getConfig().getDocumentElement(); NodeList mappingNodes = rootNode.getElementsByTagName("filter-mapping"); if (mappingNodes.getLength() > 0) { for (int i = 0; i < mappingNodes.getLength(); i++) { ModuleFilterMapping mapping = new ModuleFilterMapping(module); Node node = mappingNodes.item(i); NodeList configNodes = node.getChildNodes(); for (int j = 0; j < configNodes.getLength(); j++) { Node configNode = configNodes.item(j); if ("filter-name".equals(configNode.getNodeName())) { mapping.setFilterName(configNode.getTextContent()); } else if ("url-pattern".equals(configNode.getNodeName())) { mapping.addUrlPattern(configNode.getTextContent()); } else if ("servlet-name".equals(configNode.getNodeName())) { mapping.addServletName(configNode.getTextContent()); } } mappings.add(mapping); } } } catch (Exception e) { throw new ModuleException("Unable to parse filters in module configuration.", e); } log.debug("Retrieved " + mappings.size() + " filter-mappings for " + module.getModuleId() + ": " + mappings); return mappings; } }