1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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
94 File webXmlFile = new File(project.getBasedir(), "src/main/webapp/WEB-INF/web.xml");
95 if (webXmlFile.isFile()) {
96 return webXmlFile;
97 }
98
99
100 Plugin plugin = project.getBuild().getPluginsAsMap().get("org.apache.maven.plugins:maven-war-plugin");
101 if (plugin == null) {
102
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
155 return;
156 }
157 }
158 }
159 }