001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.util.file; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.util.Collections; 022import java.util.HashSet; 023import java.util.Set; 024 025import javax.servlet.FilterConfig; 026import javax.servlet.ServletContext; 027import javax.xml.parsers.DocumentBuilder; 028import javax.xml.parsers.DocumentBuilderFactory; 029import javax.xml.parsers.ParserConfigurationException; 030 031import org.apache.wicket.util.xml.CustomEntityResolver; 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034import org.w3c.dom.Document; 035import org.w3c.dom.Node; 036import org.w3c.dom.NodeList; 037import org.xml.sax.SAXException; 038 039/** 040 * A utility class providing helper methods in dealing with web.xml 041 * 042 * @author jcompagner 043 * @author Juergen Donnerstag 044 */ 045public class WebXmlFile 046{ 047 private static final Logger log = LoggerFactory.getLogger(WebXmlFile.class); 048 049 /** 050 * Construct. 051 */ 052 public WebXmlFile() 053 { 054 } 055 056 /** 057 * Gets unique Wicket filter path via FilterConfig 058 * 059 * @param isServlet 060 * true if Servlet, false if Filter 061 * @param filterConfig 062 * @return Filter path retrieved from "url-pattern". Null if not found or error occurred 063 */ 064 public final String getUniqueFilterPath(final boolean isServlet, final FilterConfig filterConfig) 065 { 066 String filterName = filterConfig.getFilterName(); 067 Set<String> paths = getFilterPath(isServlet, filterConfig.getServletContext(), filterName); 068 return uniquePath(paths, isServlet, filterName); 069 } 070 071 /** 072 * Gets Wicket filter path via ServletContext and the filter name 073 * 074 * @param isServlet 075 * true if Servlet, false if Filter 076 * @param servletContext 077 * @param filterName 078 * @return Filter paths retrieved from "url-pattern" 079 */ 080 public final Set<String> getFilterPath(final boolean isServlet, 081 final ServletContext servletContext, final String filterName) 082 { 083 InputStream is = servletContext.getResourceAsStream("/WEB-INF/web.xml"); 084 if (is != null) 085 { 086 try 087 { 088 return getFilterPath(isServlet, filterName, is); 089 } 090 catch (ParserConfigurationException | SAXException | IOException ex) 091 { 092 log.error("Error reading servlet/filter path from web.xml", ex); 093 } 094 catch (SecurityException e) 095 { 096 // Swallow this at INFO. 097 log.info("Couldn't read web.xml to automatically pick up servlet/filter path: " + 098 e.getMessage()); 099 } 100 } 101 return Collections.emptySet(); 102 } 103 104 /** 105 * Gets unique filter path via filter name and InputStream. 106 * 107 * @param isServlet 108 * true if Servlet, false if Filter 109 * @param filterName 110 * @param is 111 * The web.xml file 112 * @return Filter path retrieved from "url-pattern". Null if not found. 113 * @throws ParserConfigurationException 114 * @throws IOException 115 * @throws SAXException 116 * 117 * @see #getFilterPath(boolean, String, java.io.InputStream) 118 */ 119 public final String getUniqueFilterPath(final boolean isServlet, final String filterName, 120 final InputStream is) throws ParserConfigurationException, SAXException, IOException 121 { 122 return uniquePath(getFilterPath(isServlet, filterName, is), isServlet, filterName); 123 } 124 125 /** 126 * return unique path from set of paths 127 * 128 * @param paths 129 * <code>null</code> if set was empty, unique path if set was of length == 1 130 * @param isServlet 131 * @param filterName 132 * @return unique path 133 * @throws RuntimeException 134 * in case length > 1 135 */ 136 private String uniquePath(final Set<String> paths, final boolean isServlet, 137 final String filterName) 138 { 139 if (paths.size() > 1) 140 { 141 StringBuilder err = new StringBuilder(); 142 err.append("web.xml: expected one "); 143 err.append(isServlet ? "servlet" : "filter"); 144 err.append(" path for ["); 145 err.append(filterName); 146 err.append("] but found multiple:"); 147 148 for (String path : paths) 149 { 150 err.append(" [").append(path).append(']'); 151 } 152 throw new RuntimeException(err.toString()); 153 } 154 155 if (paths.size() == 1) 156 { 157 return paths.iterator().next(); 158 } 159 return null; 160 } 161 162 /** 163 * Gets Wicket filter path via filter name and InputStream. The InputStream is assumed to be an 164 * web.xml file. 165 * <p> 166 * A typical Wicket web.xml entry looks like: 167 * 168 * <pre> 169 * <code> 170 * <filter> 171 * <filter-name>HelloWorldApplication</filter-name> 172 * <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class> 173 * <init-param> 174 * <param-name>applicationClassName</param-name> 175 * <param-value>org.apache.wicket.examples.helloworld.HelloWorldApplication</param-value> 176 * </init-param> 177 * </filter> 178 * 179 * <filter-mapping> 180 * <filter-name>HelloWorldApplication</filter-name> 181 * <url-pattern>/helloworld/*</url-pattern> 182 * <dispatcher>REQUEST</dispatcher> 183 * <dispatcher>INCLUDE</dispatcher> 184 * </filter-mapping> 185 * </code> 186 * </pre> 187 * 188 * @param isServlet 189 * true if Servlet, false if Filter 190 * @param filterName 191 * @param is 192 * The web.xml file 193 * @return Filter paths retrieved from "url-pattern". 194 * @throws ParserConfigurationException 195 * @throws IOException 196 * @throws SAXException 197 */ 198 public final Set<String> getFilterPath(final boolean isServlet, final String filterName, 199 final InputStream is) throws ParserConfigurationException, SAXException, IOException 200 { 201 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 202 DocumentBuilder builder = factory.newDocumentBuilder(); 203 204 // try to pull DTD from local set of entities 205 builder.setEntityResolver(CustomEntityResolver.getPreloaded()); 206 Document document = builder.parse(is); 207 208 String tag = (isServlet ? "servlet" : "filter"); 209 String mapping = tag + "-mapping"; 210 String name = tag + "-name"; 211 212 Set<String> urlPatterns = getFilterPaths(filterName, mapping, name, 213 document.getChildNodes()); 214 215 if (urlPatterns.size() == 0) 216 { 217 log.warn("web.xml: No url-pattern found for '{}' with name '{}'", tag, filterName); 218 } 219 220 if (log.isInfoEnabled()) 221 { 222 StringBuilder msg = new StringBuilder(); 223 msg.append("web.xml: url mapping found for ") 224 .append(tag) 225 .append(" with name ") 226 .append(filterName) 227 .append(':'); 228 229 for (String urlPattern : urlPatterns) 230 { 231 msg.append(" ["); 232 msg.append(urlPattern); 233 msg.append(']'); 234 } 235 log.info(msg.toString()); 236 } 237 Set<String> stripped = new HashSet<>(urlPatterns.size()); 238 239 for (String urlPattern : urlPatterns) 240 { 241 stripped.add(urlPattern.substring(1, urlPattern.length() - 1)); 242 } 243 return stripped; 244 } 245 246 /** 247 * Iterate through all children of 'node' and search for a node with name "filterName". Return 248 * the value of node "url-pattern" if "filterName" was found. 249 * 250 * @param filterName 251 * @param name 252 * @param node 253 * @return value of node "url-pattern" 254 */ 255 private Set<String> getFilterPaths(final String filterName, final String name, final Node node) 256 { 257 Set<String> paths = new HashSet<>(); 258 String foundUrlPattern = null; 259 String foundFilterName = null; 260 261 for (int i = 0; i < node.getChildNodes().getLength(); ++i) 262 { 263 Node n = node.getChildNodes().item(i); 264 265 if (name.equals(n.getNodeName())) 266 { 267 foundFilterName = n.getTextContent(); 268 } 269 else if ("url-pattern".equals(n.getNodeName())) 270 { 271 foundUrlPattern = n.getTextContent(); 272 } 273 274 if (foundFilterName != null) 275 { 276 foundFilterName = foundFilterName.trim(); 277 } 278 279 if (filterName.equals(foundFilterName)) 280 { 281 if (foundUrlPattern != null) 282 { 283 paths.add(foundUrlPattern.trim()); 284 } 285 } 286 } 287 return paths; 288 } 289 290 /** 291 * Find a node with name 'mapping' within 'nodeList' and if found continue to search amongst its 292 * children for a node with 'filterName' and "url-pattern' 293 * 294 * 295 * @param filterName 296 * @param mapping 297 * @param name 298 * @param nodeList 299 * @return The value assigned to node "url-pattern" 300 */ 301 private Set<String> getFilterPaths(final String filterName, final String mapping, 302 final String name, final NodeList nodeList) 303 { 304 Set<String> paths = new HashSet<>(1); 305 306 for (int i = 0; i < nodeList.getLength(); ++i) 307 { 308 Node node = nodeList.item(i); 309 310 if (mapping.equals(node.getNodeName())) 311 { 312 paths.addAll(getFilterPaths(filterName, name, node)); 313 } 314 else 315 { 316 paths.addAll(getFilterPaths(filterName, mapping, name, node.getChildNodes())); 317 } 318 } 319 return paths; 320 } 321}