TagDirImplicitTagLibrary.java
/*
* <license>
* Copyright (c) 2003-2004, Sun Microsystems, Inc.
* Copyright (c) 2022-2026, Web-Legacy
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Sun Microsystems, Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* </license>
*/
package io.github.weblegacy.tlddoc;
import io.github.weblegacy.tlddoc.main.TagLibrary;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Implicit Tag Library for a directory of tag files.
*
* @author mroth
*/
public class TagDirImplicitTagLibrary implements TagLibrary {
/**
* The directory containing the tag files.
*/
private final Path dir;
/**
* Creates a new instance of {@link TagDirImplicitTagLibrary}.
*
* @param dir directory containing the tag files
*/
public TagDirImplicitTagLibrary(Path dir) {
this.dir = dir;
}
/**
* {@inheritDoc}
*/
@Override
public String getPathDescription() {
return dir.toAbsolutePath().toString();
}
/**
* {@inheritDoc}
*/
@Override
public InputStream getResource(String path) throws IOException {
return Utils.backtrackPath(dir, path);
}
/**
* {@inheritDoc}
*/
@Override
public Document getTldDocument(DocumentBuilder documentBuilder) throws IOException,
SAXException, TransformerFactoryConfigurationError, TransformerException {
Document result = documentBuilder.newDocument();
// Determine path from root of web application (this is somewhat of
// a guess):
String path = Utils.pathString(dir);
int index = path.indexOf("/WEB-INF/");
if (index != -1) {
path = path.substring(index);
} else {
path = "unknown";
}
if (!path.endsWith("/")) {
path += "/";
}
// Create root taglib node:
Element taglibElement = createRootTaglibNode(result, path);
// According to the JSP 2.0 specification:
// A <tag-file> element is considered to exist for each tag file in
// this directory, with the following sub-elements:
// - The <name> for each is the filename of the tag file,
// without the .tag extension.
// - The <path> for each is the path of the tag file, relative
// to the root of the web application.
final String p = path;
try (DirectoryStream<Path> files = Files.newDirectoryStream(dir, Utils::isTag)) {
for (Path file : files) {
final Path fn = file.getFileName();
if (fn == null) {
continue;
}
final String fileName = fn.toString();
final String tagName = fileName.substring(0, fileName.lastIndexOf('.'));
final String tagPath = p + fileName;
createTagEntry(result, tagName, tagPath, taglibElement);
}
}
return recreateDocument(documentBuilder, result);
}
/**
* Creates an implicit tag library root node, with default values. Shared by
* WarTagDirImplicitTagLibrary.
*
* @param result XML-document to add new tag-element
* @param path path to the TLD Files
*
* @return new created tag library root node
*/
static Element createRootTaglibNode(Document result, String path) {
Element taglibElement = result.createElementNS(Constants.NS_JAKARTAEE, "taglib");
// JDK 1.4 does not add xmlns for some reason - add it manually:
taglibElement.setAttributeNS("http://www.w3.org/2000/xmlns/",
"xmlns", Constants.NS_JAKARTAEE);
taglibElement.setAttributeNS("http://www.w3.org/2000/xmlns/",
"xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
taglibElement.setAttributeNS(
"http://www.w3.org/2001/XMLSchema-instance",
"xsi:schemaLocation",
Constants.NS_JAKARTAEE
+ " https://jakarta.ee/xml/ns/jakartaee/web-jsptaglibrary_3_0.xsd");
taglibElement.setAttribute("version", "3.0");
result.appendChild(taglibElement);
// Add <description>
Element descriptionElement = result.createElement("description");
descriptionElement.appendChild(result.createTextNode(
"Implicit tag library for tag file directory " + path));
taglibElement.appendChild(descriptionElement);
// Add <tlib-version> of 1.0
Element tlibVersionElement = result.createElement("tlib-version");
tlibVersionElement.appendChild(result.createTextNode("1.0"));
taglibElement.appendChild(tlibVersionElement);
// According to the JSP 2.0 specification, <short-name> is derived
// from the directory name. If the directory is /WEB-INF/tags/, the
// short name is simply tags. Otherwise, the full directory path
// (relative to the web application) is taken, minus the
// /WEB-INF/tags/ prefix. Then, all / characters are replaced
// with -, which yields the short name. Note that short names are
// not guaranteed to be unique.
String shortName;
switch (path) {
case "unknown":
shortName = path;
break;
case "/WEB-INF/tags":
case "/WEB-INF/tags/":
shortName = "tags";
break;
default:
shortName = path;
if (shortName.startsWith("/WEB-INF/tags")) {
shortName = shortName.substring("/WEB-INF/tags".length());
}
if (shortName.startsWith("/")) {
shortName = shortName.substring(1);
}
if (shortName.endsWith("/")) {
shortName = shortName.substring(0, shortName.length() - 1);
}
shortName = shortName.replace('/', '-');
break;
}
Element shortNameElement = result.createElement("short-name");
shortNameElement.appendChild(result.createTextNode(shortName));
taglibElement.appendChild(shortNameElement);
Element uriElement = result.createElement("uri");
uriElement.appendChild(result.createTextNode(path));
taglibElement.appendChild(uriElement);
return taglibElement;
}
/**
* Creates an tag-entry into tag library root node. Shared by WarTagDirImplicitTagLibrary.
*
* @param result XML-document to add new tag-element
* @param tagName name of the tag-entry
* @param tagPath path of the tag-entry
* @param taglibElement tag library root node
*
* @throws DOMException if an XML error has occurred
*/
static void createTagEntry(final Document result, final String tagName, final String tagPath,
Element taglibElement) throws DOMException {
final Element tagFileElement = result.createElement("tag-file");
final Element nameElement = result.createElement("name");
nameElement.appendChild(result.createTextNode(tagName));
tagFileElement.appendChild(nameElement);
final Element pathElement = result.createElement("path");
pathElement.appendChild(result.createTextNode(tagPath));
tagFileElement.appendChild(pathElement);
taglibElement.appendChild(tagFileElement);
}
/**
* Recreates the XML document. JDK 1.4 does not correctly import the node into the tree, so
* simulate reading this entry from a file. There might be a better / more efficient way to do
* this, but this works.
*
* @param documentBuilder {@code DocumentBuilder} to obtain DOM Document for creating an
* XML-document.
* @param result XML-document to recreate
*
* @return the recreated XML-document
*
* @throws IOException if an I/O error has occurred
* @throws SAXException If any parse errors occur.
* @throws TransformerFactoryConfigurationError Thrown in case of {@linkplain
* java.util.ServiceConfigurationError service configuration error} or if the implementation is
* not available or cannot be instantiated.
* @throws TransformerException If an unrecoverable error occurs during the
* course of the transformation.
*/
static Document recreateDocument(final DocumentBuilder documentBuilder, final Document result)
throws IOException, TransformerFactoryConfigurationError, TransformerException,
SAXException {
final StringWriter buffer = new StringWriter();
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.transform(new DOMSource(result), new StreamResult(buffer));
return documentBuilder.parse(new InputSource(new StringReader(buffer.toString())));
}
/**
* {@inheritDoc}
*/
@Override
public void close() throws IOException {
// Nothing to do
}
}