1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.common.xsl;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.UncheckedIOException;
25 import java.nio.file.FileVisitResult;
26 import java.nio.file.Files;
27 import java.nio.file.Path;
28 import java.nio.file.SimpleFileVisitor;
29 import java.nio.file.attribute.BasicFileAttributes;
30 import java.util.ArrayList;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.LinkedList;
34 import java.util.List;
35 import java.util.Locale;
36 import java.util.Map;
37 import java.util.Map.Entry;
38 import java.util.Set;
39 import java.util.function.Consumer;
40 import java.util.function.Predicate;
41 import java.util.zip.ZipEntry;
42 import java.util.zip.ZipInputStream;
43
44 import javax.xml.transform.TransformerException;
45
46 import org.apache.logging.log4j.LogManager;
47 import org.apache.logging.log4j.Logger;
48 import org.jdom2.Element;
49 import org.jdom2.filter.Filters;
50 import org.jdom2.util.IteratorIterable;
51 import org.mycore.common.MCRConstants;
52 import org.mycore.common.config.MCRConfigurationDir;
53 import org.mycore.common.content.MCRJDOMContent;
54 import org.mycore.common.xml.MCRURIResolver;
55 import org.mycore.frontend.servlets.MCRServlet;
56 import org.mycore.frontend.servlets.MCRServletJob;
57 import org.xml.sax.SAXException;
58
59
60
61
62
63
64
65
66 public final class MCRXSLInfoServlet extends MCRServlet {
67
68 private static final Logger LOGGER = LogManager.getLogger(MCRXSLInfoServlet.class);
69
70 private final Map<String, Stylesheet> stylesheets = new HashMap<>();
71
72 private final Set<String> unknown = new HashSet<>();
73
74 protected void doGetPost(MCRServletJob job) throws Exception {
75 if ("true".equals(job.getRequest().getParameter("reload"))) {
76 stylesheets.clear();
77 }
78
79 if (stylesheets.isEmpty()) {
80 LOGGER.info("Collecting stylesheet information....");
81 findInConfigDir();
82 findXSLinClassesDir();
83 findXSLinLibJars();
84 inspectStylesheets();
85 handleUnknownStylesheets();
86 }
87
88 buildOutput(job);
89 }
90
91 private void findInConfigDir() throws IOException {
92 final File configDir = MCRConfigurationDir.getConfigurationDirectory();
93 if (configDir == null) {
94 return;
95 }
96 findInConfigResourcesDir(configDir);
97 findInConfigLibDir(configDir);
98 }
99
100 private void findInConfigResourcesDir(File configDir) throws IOException {
101 final Path resources = configDir.toPath().resolve("resources");
102 final Path xslResourcesPath = resources.resolve("xsl");
103 doForFilesRecursive(xslResourcesPath, n -> n.endsWith(".xsl"),
104 file -> foundStylesheet(resources.toUri().relativize(file.toUri()).toString(),
105 configDir.toPath().relativize(file).toString()));
106 }
107
108 private void findInConfigLibDir(File configDir) throws IOException {
109 final Path lib = configDir.toPath().resolve("lib");
110 try {
111 doForFilesRecursive(lib, n -> n.endsWith(".jar"),
112 file -> {
113 try (InputStream in = Files.newInputStream(file)) {
114 findInJarInputStream(configDir.toPath().relativize(file).toString(), in);
115 } catch (IOException e) {
116 throw new UncheckedIOException(e);
117 }
118 });
119 } catch (UncheckedIOException uioe) {
120 throw uioe.getCause();
121 }
122 }
123
124 private void doForFilesRecursive(Path baseDir, Predicate<String> lowerCaseCheck, Consumer<Path> pathConsumer)
125 throws IOException {
126 if (Files.isDirectory(baseDir)) {
127 Files.walkFileTree(baseDir, new SimpleFileVisitor<>() {
128 @Override
129 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
130 if (lowerCaseCheck.test(file.getFileName().toString().toLowerCase(Locale.ROOT))) {
131 pathConsumer.accept(file);
132 }
133 return super.visitFile(file, attrs);
134 }
135 });
136 }
137 }
138
139 private void inspectStylesheets() {
140 for (Entry<String, Stylesheet> entry : stylesheets.entrySet()) {
141 entry.getValue().inspect();
142 }
143 }
144
145 private void handleUnknownStylesheets() {
146 while (!unknown.isEmpty()) {
147 Set<String> list = new HashSet<>(unknown);
148 unknown.clear();
149
150 for (String name : list) {
151 Stylesheet s = new Stylesheet(name);
152 stylesheets.put(name, s);
153 s.inspect();
154 }
155 }
156 }
157
158 private void buildOutput(MCRServletJob job) throws IOException, TransformerException, SAXException {
159 Element output = new Element("stylesheets");
160 for (Entry<String, Stylesheet> entry : stylesheets.entrySet()) {
161 Stylesheet stylesheet = entry.getValue();
162 output.addContent(stylesheet.buildXML());
163 }
164 getLayoutService().doLayout(job.getRequest(), job.getResponse(), new MCRJDOMContent(output));
165 }
166
167 private void findXSLinLibJars() throws IOException {
168 for (String path : diveInto("/WEB-INF/lib/")) {
169 if (path.endsWith(".jar")) {
170 findXSLinJar(path);
171 }
172 }
173 }
174
175 private Set<String> diveInto(String base) {
176 LOGGER.info("Diving into {}...", base);
177 Set<String> paths = getServletContext().getResourcePaths(base);
178
179 Set<String> more = new HashSet<>();
180 if (paths != null) {
181 more.addAll(paths);
182
183 for (String path : paths) {
184 if (path.endsWith("/")) {
185 more.addAll(diveInto(path));
186 }
187 }
188 }
189
190 return more;
191 }
192
193 private void findXSLinJar(String pathOfJarFile) throws IOException {
194 LOGGER.info("Diving into {}...", pathOfJarFile);
195
196 try (InputStream in = getServletContext().getResourceAsStream(pathOfJarFile)) {
197 findInJarInputStream(pathOfJarFile, in);
198 }
199 }
200
201 private void findInJarInputStream(String pathOfJarFile, InputStream in) throws IOException {
202 try (ZipInputStream zis = new ZipInputStream(in)) {
203
204 for (ZipEntry ze = zis.getNextEntry(); ze != null; ze = zis.getNextEntry()) {
205 String name = ze.getName();
206 if (name.startsWith("xsl/") && name.endsWith(".xsl")) {
207 foundStylesheet(name, pathOfJarFile);
208 }
209 zis.closeEntry();
210 }
211 }
212 }
213
214 private void findXSLinClassesDir() {
215 String base = "/WEB-INF/classes/xsl/";
216 for (String path : diveInto(base)) {
217 if (path.endsWith(".xsl")) {
218 foundStylesheet(path, base);
219 }
220 }
221 }
222
223 private void foundStylesheet(String path, String source) {
224 String file = path.substring(path.lastIndexOf("xsl/") + 4);
225 LOGGER.info("Found {} in {}", file, source);
226 Stylesheet stylesheet = getStylesheet(file);
227 if (source.startsWith("/WEB-INF/")) {
228 source = source.substring("/WEB-INF/".length());
229 }
230 stylesheet.origin.add(source);
231 }
232
233 private Stylesheet getStylesheet(String name) {
234 return stylesheets.computeIfAbsent(name, Stylesheet::new);
235 }
236
237 class Stylesheet {
238 String name;
239
240 Set<String> origin = new HashSet<>();
241
242 Set<String> includes = new HashSet<>();
243
244 Set<String> imports = new HashSet<>();
245
246 List<Element> templates = new ArrayList<>();
247
248 Element xsl;
249
250 Stylesheet(String name) {
251 this.name = name;
252 }
253
254 void inspect() {
255 resolveXSL();
256 if (xsl != null) {
257 listTemplates();
258 findIncludes("include", includes);
259 findIncludes("import", imports);
260 }
261 }
262
263 private void resolveXSL() {
264 String uri = "resource:xsl/" + name;
265 resolveXSL(uri);
266 if (xsl == null) {
267 resolveXSL(name);
268 if (xsl != null) {
269 origin.add("URIResolver");
270 }
271 }
272 }
273
274 private void resolveXSL(String uri) {
275 try {
276 xsl = MCRURIResolver.instance().resolve(uri);
277 } catch (Exception ex) {
278 String msg = "Exception resolving stylesheet " + name;
279 LOGGER.warn(msg, ex);
280 }
281 }
282
283 private void findIncludes(String tag, Set<String> set) {
284 List<Element> includes = xsl.getChildren(tag, MCRConstants.XSL_NAMESPACE);
285 for (Element include : includes) {
286 String href = include.getAttributeValue("href");
287 LOGGER.info("{} {}s {}", name, tag, href);
288 set.add(href);
289 if (!stylesheets.containsKey(href)) {
290 unknown.add(href);
291 }
292 }
293 }
294
295 private void listTemplates() {
296 List<Element> list = xsl.getChildren("template", MCRConstants.XSL_NAMESPACE);
297 IteratorIterable<Element> callTemplateElements = xsl
298 .getDescendants(Filters.element("call-template", MCRConstants.XSL_NAMESPACE));
299 LinkedList<Element> templates = new LinkedList<>(list);
300 HashSet<String> callNames = new HashSet<>();
301 for (Element callTemplate : callTemplateElements) {
302 String name = callTemplate.getAttributeValue("name");
303 if (callNames.add(name)) {
304 templates.add(callTemplate);
305 }
306 }
307 for (Element template : templates) {
308 Element copy = template.clone();
309 copy.removeContent();
310 this.templates.add(copy);
311 }
312 }
313
314 Element buildXML() {
315 Element elem = new Element("stylesheet");
316 elem.setAttribute("name", name);
317 addValues(elem, "origin", origin);
318 addValues(elem, "includes", includes);
319 addValues(elem, "imports", imports);
320
321 for (Element template : templates) {
322 elem.addContent(template.clone());
323 }
324
325 return elem;
326 }
327
328 private void addValues(Element parent, String tag, Set<String> set) {
329 for (String value : set) {
330 parent.addContent(new Element(tag).setText(value));
331 }
332 }
333 }
334 }