View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.shared.dependency.analyzer.dependencyclasses;
20  
21  import javax.inject.Named;
22  import javax.inject.Singleton;
23  import javax.xml.XMLConstants;
24  import javax.xml.parsers.DocumentBuilder;
25  import javax.xml.parsers.DocumentBuilderFactory;
26  import javax.xml.parsers.ParserConfigurationException;
27  
28  import java.io.File;
29  import java.io.IOException;
30  import java.util.ArrayList;
31  import java.util.Arrays;
32  import java.util.Collections;
33  import java.util.List;
34  import java.util.Optional;
35  import java.util.Set;
36  import java.util.stream.Collectors;
37  
38  import org.apache.maven.model.Plugin;
39  import org.apache.maven.project.MavenProject;
40  import org.apache.maven.shared.dependency.analyzer.ClassesPatterns;
41  import org.apache.maven.shared.dependency.analyzer.DependencyUsage;
42  import org.apache.maven.shared.dependency.analyzer.MainDependencyClassesProvider;
43  import org.codehaus.plexus.util.xml.Xpp3Dom;
44  import org.slf4j.Logger;
45  import org.slf4j.LoggerFactory;
46  import org.w3c.dom.Document;
47  import org.w3c.dom.Node;
48  import org.w3c.dom.NodeList;
49  import org.xml.sax.SAXException;
50  
51  /**
52   * Implementation of {@link MainDependencyClassesProvider} for web applications.
53   */
54  @Named
55  @Singleton
56  class WarMainDependencyClassesProvider implements MainDependencyClassesProvider {
57  
58      private static final Logger LOGGER = LoggerFactory.getLogger(WarMainDependencyClassesProvider.class);
59  
60      private static final List<String> WEB_XML_NAMESPACES = Arrays.asList(
61              "https://jakarta.ee/xml/ns/jakartaee", // Jakarta EE 9+
62              "http://xmlns.jcp.org/xml/ns/javaee", // Java EE 7–8
63              "http://java.sun.com/xml/ns/javaee" // Java EE 5–6
64              );
65  
66      @Override
67      public Set<DependencyUsage> getDependencyClasses(MavenProject project, ClassesPatterns excludedClasses)
68              throws IOException {
69          if (!"war".equals(project.getPackaging())) {
70              return Collections.emptySet();
71          }
72  
73          File webXml = findWebXml(project);
74          if (webXml == null) {
75              LOGGER.debug("No web.xml found for project {}", project);
76              return Collections.emptySet();
77          }
78  
79          if (!webXml.isFile()) {
80              LOGGER.debug("{} is not a file in project {}", webXml, project);
81              return Collections.emptySet();
82          }
83  
84          try {
85              return processWebXml(webXml, excludedClasses);
86          } catch (SAXException | ParserConfigurationException e) {
87              LOGGER.warn("Error parsing web.xml file {}: {}", webXml, e.getMessage());
88              return Collections.emptySet();
89          }
90      }
91  
92      private File findWebXml(MavenProject project) {
93          // standard location
94          File webXmlFile = new File(project.getBasedir(), "src/main/webapp/WEB-INF/web.xml");
95          if (webXmlFile.isFile()) {
96              return webXmlFile;
97          }
98  
99          // check maven-war-plugin configuration for custom location of web.xml
100         Plugin plugin = project.getBuild().getPluginsAsMap().get("org.apache.maven.plugins:maven-war-plugin");
101         if (plugin == null) {
102             // should not happen as we are in a war project
103             LOGGER.debug("No war plugin found for project {}", project);
104             return null;
105         }
106 
107         return Optional.ofNullable(plugin.getConfiguration())
108                 .map(Xpp3Dom.class::cast)
109                 .map(config -> config.getChild("webXml"))
110                 .map(Xpp3Dom::getValue)
111                 .map(path -> new File(project.getBasedir(), path))
112                 .orElse(null);
113     }
114 
115     private Set<DependencyUsage> processWebXml(File webXml, ClassesPatterns excludedClasses)
116             throws IOException, SAXException, ParserConfigurationException {
117 
118         DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
119         documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
120         documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
121         documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
122         documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
123         documentBuilderFactory.setExpandEntityReferences(false);
124         documentBuilderFactory.setNamespaceAware(true);
125 
126         DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
127 
128         Document doc = documentBuilder.parse(webXml);
129 
130         List<String> classes = new ArrayList<>();
131 
132         processClassesFromTags(doc, classes, "filter-class");
133         processClassesFromTags(doc, classes, "listener-class");
134         processClassesFromTags(doc, classes, "servlet-class");
135 
136         return classes.stream()
137                 .filter(className -> !excludedClasses.isMatch(className))
138                 .map(className -> new DependencyUsage(className, webXml.toString()))
139                 .collect(Collectors.toSet());
140     }
141 
142     private void processClassesFromTags(Document doc, List<String> classes, String tagName) {
143         for (String namespace : WEB_XML_NAMESPACES) {
144             NodeList tags = doc.getElementsByTagNameNS(namespace, tagName);
145             for (int i = 0; i < tags.getLength(); i++) {
146                 Node node = tags.item(i);
147                 Optional.ofNullable(node.getTextContent())
148                         .map(String::trim)
149                         .filter(s -> !s.isEmpty())
150                         .ifPresent(classes::add);
151             }
152 
153             if (tags.getLength() > 0) {
154                 // if we found tags in this namespace, no need to check further namespaces
155                 return;
156             }
157         }
158     }
159 }