package org.springframework.roo.support.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.tuple.Pair;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Helper util class to allow more convenient handling of web.xml file in Web
* projects.
*
* @author Stefan Schmidt
* @since 1.1
*/
public final class WebXmlUtils {
/**
* Enum to define dispatcher
*
* @author Stefan Schmidt
* @since 1.1.1
*/
public static enum Dispatcher {
ERROR, FORWARD, INCLUDE, REQUEST;
}
/**
* Enum to define filter position
*
* @author Stefan Schmidt
* @since 1.1
*/
public static enum FilterPosition {
AFTER, BEFORE, BETWEEN, FIRST, LAST;
}
/**
* Convenience class for passing a web-resource-collection element's details
*
* @since 1.1.1
*/
public static class WebResourceCollection {
private final String description;
private final List<String> httpMethods;
private final List<String> urlPatterns;
private final String webResourceName;
public WebResourceCollection(final String webResourceName, final String description,
final List<String> urlPatterns, final List<String> httpMethods) {
this.webResourceName = webResourceName;
this.description = description;
this.urlPatterns = urlPatterns;
this.httpMethods = httpMethods;
}
public String getDescription() {
return description;
}
public List<String> getHttpMethods() {
return httpMethods;
}
public List<String> getUrlPatterns() {
return urlPatterns;
}
public String getWebResourceName() {
return webResourceName;
}
}
/**
* Value object that holds init-param style information
*
* @author Stefan Schmidt
* @since 1.1
*/
public static class WebXmlParam extends Pair<String, String> {
private static final long serialVersionUID = -1134907409024055399L;
private final String name;
private final String value;
public WebXmlParam(final String name, final String value) {
this.name = name;
this.value = value;
}
@Override
public String getLeft() {
return name;
}
@Override
public String getRight() {
return value;
}
public String getName() {
return getLeft();
}
public String setValue(final String value) {
throw new UnsupportedOperationException();
}
}
private static final String WEB_APP_XPATH = "/web-app/";
private static final String WHITESPACE = "[ \t\r\n]";
private static void addCommentBefore(final Element element, final String comment,
final Document document) {
if (null == XmlUtils.findNode("//comment()[.=' " + comment + " ']",
document.getDocumentElement())) {
document.getDocumentElement().insertBefore(document.createComment(" " + comment + " "),
element);
addLineBreakBefore(element, document);
}
}
/**
* Add a context param to the web.xml document
*
* @param contextParam (required)
* @param document the web.xml document (required)
* @param comment (optional)
*/
public static void addContextParam(final WebXmlParam contextParam, final Document document,
final String comment) {
Validate.notNull(document, "Web XML document required");
Validate.notNull(contextParam, "Context param required");
Element contextParamElement =
XmlUtils.findFirstElement(
WEB_APP_XPATH + "context-param[param-name = '" + contextParam.getName() + "']",
document.getDocumentElement());
if (contextParamElement == null) {
contextParamElement =
new XmlElementBuilder("context-param", document)
.addChild(
new XmlElementBuilder("param-name", document).setText(contextParam.getName())
.build()).build();
insertBetween(contextParamElement, "description[last()]", "filter", document);
if (StringUtils.isNotBlank(comment)) {
addCommentBefore(contextParamElement, comment, document);
}
}
appendChildIfNotPresent(contextParamElement, new XmlElementBuilder("param-value", document)
.setText(contextParam.getValue()).build());
}
/**
* Add error code to web.xml document
*
* @param errorCode (required)
* @param location (required)
* @param document (required)
* @param comment (optional)
*/
public static void addErrorCode(final Integer errorCode, final String location,
final Document document, final String comment) {
Validate.notNull(document, "Web XML document required");
Validate.notNull(errorCode, "Error code required");
Validate.notBlank(location, "Location required");
Element errorPageElement =
XmlUtils.findFirstElement(
WEB_APP_XPATH + "error-page[error-code = '" + errorCode.toString() + "']",
document.getDocumentElement());
if (errorPageElement == null) {
errorPageElement =
new XmlElementBuilder("error-page", document).addChild(
new XmlElementBuilder("error-code", document).setText(errorCode.toString()).build())
.build();
insertBetween(errorPageElement, "welcome-file-list[last()]", "the-end", document);
if (StringUtils.isNotBlank(comment)) {
addCommentBefore(errorPageElement, comment, document);
}
}
appendChildIfNotPresent(errorPageElement,
new XmlElementBuilder("location", document).setText(location).build());
}
/**
* Add exception type to web.xml document
*
* @param exceptionType fully qualified exception type name (required)
* @param location (required)
* @param document (required)
* @param comment (optional)
*/
public static void addExceptionType(final String exceptionType, final String location,
final Document document, final String comment) {
Validate.notNull(document, "Web XML document required");
Validate.notBlank(exceptionType, "Fully qualified exception type name required");
Validate.notBlank(location, "location required");
Element errorPageElement =
XmlUtils.findFirstElement(WEB_APP_XPATH + "error-page[exception-type = '" + exceptionType
+ "']", document.getDocumentElement());
if (errorPageElement == null) {
errorPageElement =
new XmlElementBuilder("error-page", document).addChild(
new XmlElementBuilder("exception-type", document).setText(exceptionType).build())
.build();
insertBetween(errorPageElement, "welcome-file-list[last()]", "the-end", document);
if (StringUtils.isNotBlank(comment)) {
addCommentBefore(errorPageElement, comment, document);
}
}
appendChildIfNotPresent(errorPageElement,
new XmlElementBuilder("location", document).setText(location).build());
}
/**
* Add a new filter definition to web.xml document. The filter will be added
* AFTER (FilterPosition.LAST) all existing filters.
*
* @param filterName (required)
* @param filterClass the fully qualified name of the filter type (required)
* @param urlPattern (required)
* @param document the web.xml document (required)
* @param comment (optional)
* @param initParams a vararg of initial parameters (optional)
*/
public static void addFilter(final String filterName, final String filterClass,
final String urlPattern, final Document document, final String comment,
final WebXmlParam... initParams) {
addFilterAtPosition(FilterPosition.LAST, null, null, filterName, filterClass, urlPattern,
document, comment, initParams);
}
/**
* Add a new filter definition to web.xml document. The filter will be added
* at the FilterPosition specified.
*
* @param filterPosition Filter position (required)
* @param beforeFilterName (optional for filter position FIRST and LAST,
* required for BEFORE and AFTER)
* @param filterName (required)
* @param filterClass the fully qualified name of the filter type (required)
* @param urlPattern (required)
* @param document the web.xml document (required)
* @param comment (optional)
* @param initParams (optional)
* @param dispatchers (optional)
*/
public static void addFilterAtPosition(final FilterPosition filterPosition,
final String afterFilterName, final String beforeFilterName, final String filterName,
final String filterClass, final String urlPattern, final Document document,
final String comment, List<WebXmlParam> initParams, final List<Dispatcher> dispatchers) {
Validate.notNull(document, "Web XML document required");
Validate.notBlank(filterName, "Filter name required");
Validate.notBlank(filterClass, "Filter class required");
Validate.notNull(urlPattern, "Filter URL mapping pattern required");
if (initParams == null) {
initParams = new ArrayList<WebXmlUtils.WebXmlParam>();
}
// Creating filter
Element filterElement =
XmlUtils.findFirstElement(WEB_APP_XPATH + "filter[filter-name = '" + filterName + "']",
document.getDocumentElement());
if (filterElement == null) {
filterElement =
new XmlElementBuilder("filter", document).addChild(
new XmlElementBuilder("filter-name", document).setText(filterName).build()).build();
if (filterPosition.equals(FilterPosition.FIRST)) {
insertBetween(filterElement, "context-param", "filter", document);
} else if (filterPosition.equals(FilterPosition.BEFORE)) {
Validate.notBlank(beforeFilterName,
"The filter position filter name is required when using FilterPosition.BEFORE");
insertBefore(filterElement, "filter[filter-name = '" + beforeFilterName + "']", document);
} else if (filterPosition.equals(FilterPosition.AFTER)) {
Validate.notBlank(afterFilterName,
"The filter position filter name is required when using FilterPosition.AFTER");
insertAfter(filterElement, "filter[filter-name = '" + afterFilterName + "']", document);
} else if (filterPosition.equals(FilterPosition.BETWEEN)) {
Validate.notBlank(beforeFilterName,
"The 'before' filter name is required when using FilterPosition.BETWEEN");
Validate.notBlank(afterFilterName,
"The 'after' filter name is required when using FilterPosition.BETWEEN");
insertBetween(filterElement, "filter[filter-name = '" + afterFilterName + "']",
"filter[filter-name = '" + beforeFilterName + "']", document);
} else {
insertBetween(filterElement, "context-param[last()]", "filter-mapping", document);
}
if (StringUtils.isNotBlank(comment)) {
addCommentBefore(filterElement, comment, document);
}
}
appendChildIfNotPresent(filterElement,
new XmlElementBuilder("filter-class", document).setText(filterClass).build());
for (final WebXmlParam initParam : initParams) {
appendChildIfNotPresent(
filterElement,
new XmlElementBuilder("init-param", document)
.addChild(
new XmlElementBuilder("param-name", document).setText(initParam.getName())
.build())
.addChild(
new XmlElementBuilder("param-value", document).setText(initParam.getValue())
.build()).build());
}
// Creating filter mapping
Element filterMappingElement =
XmlUtils.findFirstElement(WEB_APP_XPATH + "filter-mapping[filter-name = '" + filterName
+ "']", document.getDocumentElement());
if (filterMappingElement == null) {
filterMappingElement =
new XmlElementBuilder("filter-mapping", document).addChild(
new XmlElementBuilder("filter-name", document).setText(filterName).build()).build();
if (filterPosition.equals(FilterPosition.FIRST)) {
insertBetween(filterMappingElement, "filter", "filter-mapping", document);
} else if (filterPosition.equals(FilterPosition.BEFORE)) {
insertBefore(filterMappingElement, "filter-mapping[filter-name = '" + beforeFilterName
+ "']", document);
} else if (filterPosition.equals(FilterPosition.AFTER)) {
insertAfter(filterMappingElement, "filter-mapping[filter-name = '" + beforeFilterName
+ "']", document);
} else if (filterPosition.equals(FilterPosition.BETWEEN)) {
insertBetween(filterMappingElement, "filter-mapping[filter-name = '" + afterFilterName
+ "']", "filter-mapping[filter-name = '" + beforeFilterName + "']", document);
} else {
insertBetween(filterMappingElement, "filter-mapping[last()]", "listener", document);
}
}
appendChildIfNotPresent(filterMappingElement, new XmlElementBuilder("url-pattern", document)
.setText(urlPattern).build());
for (final Dispatcher dispatcher : dispatchers) {
appendChildIfNotPresent(filterMappingElement, new XmlElementBuilder("dispatcher", document)
.setText(dispatcher.name()).build());
}
}
/**
* Add a new filter definition to web.xml document. The filter will be added
* at the FilterPosition specified.
*
* @param filterPosition Filter position (required)
* @param beforeFilterName (optional for filter position FIRST and LAST,
* required for BEFORE and AFTER)
* @param filterName (required)
* @param filterClass the fully qualified name of the filter type (required)
* @param urlPattern (required)
* @param document the web.xml document (required)
* @param comment (optional)
* @param initParams (optional)
*/
public static void addFilterAtPosition(final FilterPosition filterPosition,
final String afterFilterName, final String beforeFilterName, final String filterName,
final String filterClass, final String urlPattern, final Document document,
final String comment, final WebXmlParam... initParams) {
addFilterAtPosition(filterPosition, afterFilterName, beforeFilterName, filterName, filterClass,
urlPattern, document, comment,
initParams == null ? new ArrayList<WebXmlParam>() : Arrays.asList(initParams),
new ArrayList<Dispatcher>());
}
private static void addLineBreakBefore(final Element element, final Document document) {
document.getDocumentElement().insertBefore(document.createTextNode("\n "), element);
}
/**
* Add listener element to web.xml document
*
* @param className the fully qualified name of the listener type (required)
* @param document (required)
* @param comment (optional)
*/
public static void addListener(final String className, final Document document,
final String comment) {
Validate.notNull(document, "Web XML document required");
Validate.notBlank(className, "Class name required");
Element listenerElement =
XmlUtils.findFirstElement(WEB_APP_XPATH + "listener[listener-class = '" + className + "']",
document.getDocumentElement());
if (listenerElement == null) {
listenerElement =
new XmlElementBuilder("listener", document).addChild(
new XmlElementBuilder("listener-class", document).setText(className).build()).build();
insertBetween(listenerElement, "filter-mapping[last()]", "servlet", document);
if (StringUtils.isNotBlank(comment)) {
addCommentBefore(listenerElement, comment, document);
}
}
}
/**
* Add a security constraint to a web.xml document
*
* @param displayName (optional)
* @param webResourceCollections (required)
* @param roleNames (optional)
* @param transportGuarantee (optional)
* @param document (required)
* @param comment (optional)
*/
public static void addSecurityConstraint(final String displayName,
final List<WebResourceCollection> webResourceCollections, final List<String> roleNames,
final String transportGuarantee, final Document document, final String comment) {
Validate.notNull(document, "Web XML document required");
Validate.isTrue(!CollectionUtils.isEmpty(webResourceCollections),
"A security-constraint element must contain at least one web-resource-collection");
Element securityConstraintElement =
XmlUtils.findFirstElement("security-constraint", document.getDocumentElement());
if (securityConstraintElement == null) {
securityConstraintElement = document.createElement("security-constraint");
insertAfter(securityConstraintElement, "session-config[last()]", document);
if (StringUtils.isNotBlank(comment)) {
addCommentBefore(securityConstraintElement, comment, document);
}
}
if (StringUtils.isNotBlank(displayName)) {
appendChildIfNotPresent(securityConstraintElement, new XmlElementBuilder("display-name",
document).setText(displayName).build());
}
for (final WebResourceCollection webResourceCollection : webResourceCollections) {
final XmlElementBuilder webResourceCollectionBuilder =
new XmlElementBuilder("web-resource-collection", document);
Validate
.notBlank(webResourceCollection.getWebResourceName(), "web-resource-name is required");
webResourceCollectionBuilder.addChild(new XmlElementBuilder("web-resource-name", document)
.setText(webResourceCollection.getWebResourceName()).build());
if (StringUtils.isNotBlank(webResourceCollection.getDescription())) {
webResourceCollectionBuilder.addChild(new XmlElementBuilder("description", document)
.setText(webResourceCollection.getWebResourceName()).build());
}
for (final String urlPattern : webResourceCollection.getUrlPatterns()) {
if (StringUtils.isNotBlank(urlPattern)) {
webResourceCollectionBuilder.addChild(new XmlElementBuilder("url-pattern", document)
.setText(urlPattern).build());
}
}
for (final String httpMethod : webResourceCollection.getHttpMethods()) {
if (StringUtils.isNotBlank(httpMethod)) {
webResourceCollectionBuilder.addChild(new XmlElementBuilder("http-method", document)
.setText(httpMethod).build());
}
}
appendChildIfNotPresent(securityConstraintElement, webResourceCollectionBuilder.build());
}
if (roleNames != null && roleNames.size() > 0) {
final XmlElementBuilder authConstraintBuilder =
new XmlElementBuilder("auth-constraint", document);
for (final String roleName : roleNames) {
if (StringUtils.isNotBlank(roleName)) {
authConstraintBuilder.addChild(new XmlElementBuilder("role-name", document).setText(
roleName).build());
}
}
appendChildIfNotPresent(securityConstraintElement, authConstraintBuilder.build());
}
if (StringUtils.isNotBlank(transportGuarantee)) {
final XmlElementBuilder userDataConstraintBuilder =
new XmlElementBuilder("user-data-constraint", document);
userDataConstraintBuilder.addChild(new XmlElementBuilder("transport-guarantee", document)
.setText(transportGuarantee).build());
appendChildIfNotPresent(securityConstraintElement, userDataConstraintBuilder.build());
}
}
/**
* Add servlet element to the web.xml document
*
* @param servletName (required)
* @param className the fully qualified name of the servlet type (required)
* @param urlPattern this can be set to null in which case the servletName
* will be used for mapping (optional)
* @param loadOnStartup (optional)
* @param document (required)
* @param comment (optional)
* @param initParams (optional)
*/
public static void addServlet(final String servletName, final String className,
final String urlPattern, final Integer loadOnStartup, final Document document,
final String comment, final WebXmlParam... initParams) {
Validate.notNull(document, "Web XML document required");
Validate.notBlank(servletName, "Servlet name required");
Validate.notBlank(className, "Fully qualified class name required");
// Create servlet
Element servletElement =
XmlUtils.findFirstElement(WEB_APP_XPATH + "servlet[servlet-name = '" + servletName + "']",
document.getDocumentElement());
if (servletElement == null) {
servletElement =
new XmlElementBuilder("servlet", document).addChild(
new XmlElementBuilder("servlet-name", document).setText(servletName).build()).build();
insertBetween(servletElement, "listener[last()]", "servlet-mapping", document);
if (comment != null && comment.length() > 0) {
addCommentBefore(servletElement, comment, document);
}
}
appendChildIfNotPresent(servletElement, new XmlElementBuilder("servlet-class", document)
.setText(className).build());
for (final WebXmlParam initParam : initParams) {
appendChildIfNotPresent(
servletElement,
new XmlElementBuilder("init-param", document)
.addChild(
new XmlElementBuilder("param-name", document).setText(initParam.getName())
.build())
.addChild(
new XmlElementBuilder("param-value", document).setText(initParam.getValue())
.build()).build());
}
if (loadOnStartup != null) {
appendChildIfNotPresent(servletElement, new XmlElementBuilder("load-on-startup", document)
.setText(loadOnStartup.toString()).build());
}
// Create servlet mapping
Element servletMappingElement =
XmlUtils.findFirstElement(WEB_APP_XPATH + "servlet-mapping[servlet-name = '" + servletName
+ "']", document.getDocumentElement());
if (servletMappingElement == null) {
servletMappingElement =
new XmlElementBuilder("servlet-mapping", document).addChild(
new XmlElementBuilder("servlet-name", document).setText(servletName).build()).build();
insertBetween(servletMappingElement, "servlet[last()]", "session-config", document);
}
if (StringUtils.isNotBlank(urlPattern)) {
appendChildIfNotPresent(servletMappingElement, new XmlElementBuilder("url-pattern", document)
.setText(urlPattern).build());
} else {
appendChildIfNotPresent(servletMappingElement,
new XmlElementBuilder("servlet-name", document).setText(servletName).build());
}
}
/**
* Add a welcome file definition to web.xml document
*
* @param path (required)
* @param document (required)
* @param comment (optional)
*/
public static void addWelcomeFile(final String path, final Document document, final String comment) {
Validate.notNull(document, "Web XML document required");
Validate.notBlank("Path required");
Element welcomeFileElement =
XmlUtils.findFirstElement(WEB_APP_XPATH + "welcome-file-list",
document.getDocumentElement());
if (welcomeFileElement == null) {
welcomeFileElement = document.createElement("welcome-file-list");
insertBetween(welcomeFileElement, "session-config[last()]", "error-page", document);
if (StringUtils.isNotBlank(comment)) {
addCommentBefore(welcomeFileElement, comment, document);
}
}
appendChildIfNotPresent(welcomeFileElement, new XmlElementBuilder("welcome-file", document)
.setText(path).build());
}
/**
* Adds the given child to the given parent if it's not already there
*
* @param parent the parent to which to add a child (required)
* @param child the child to add if not present (required)
*/
private static void appendChildIfNotPresent(final Node parent, final Element child) {
final NodeList existingChildren = parent.getChildNodes();
for (int i = 0; i < existingChildren.getLength(); i++) {
final Node existingChild = existingChildren.item(i);
if (existingChild instanceof Element) {
// Attempt matching of possibly nested structures by using of
// 'getTextContent' as 'isEqualNode' does not match due to line
// returns, etc
// Note, this does not work if child nodes are appearing in a
// different order than expected
if (existingChild.getNodeName().equals(child.getNodeName())
&& existingChild.getTextContent().replaceAll(WHITESPACE, "").trim()
.equals(child.getTextContent().replaceAll(WHITESPACE, ""))) {
// If we found a match, there is no need to append the child
// element
return;
}
}
}
parent.appendChild(child);
}
private static void insertAfter(final Element element, final String afterElementName,
final Document document) {
final Element afterElement =
XmlUtils.findFirstElement(WEB_APP_XPATH + afterElementName, document.getDocumentElement());
if (afterElement != null && afterElement.getNextSibling() != null
&& afterElement.getNextSibling() instanceof Element) {
document.getDocumentElement().insertBefore(element, afterElement.getNextSibling());
addLineBreakBefore(element, document);
addLineBreakBefore(element, document);
return;
}
document.getDocumentElement().appendChild(element);
addLineBreakBefore(element, document);
addLineBreakBefore(element, document);
}
private static void insertBefore(final Element element, final String beforeElementName,
final Document document) {
final Element beforeElement =
XmlUtils.findFirstElement(WEB_APP_XPATH + beforeElementName, document.getDocumentElement());
if (beforeElement != null) {
document.getDocumentElement().insertBefore(element, beforeElement);
addLineBreakBefore(element, document);
addLineBreakBefore(element, document);
return;
}
document.getDocumentElement().appendChild(element);
addLineBreakBefore(element, document);
addLineBreakBefore(element, document);
}
private static void insertBetween(final Element element, final String afterElementName,
final String beforeElementName, final Document document) {
final Element beforeElement =
XmlUtils.findFirstElement(WEB_APP_XPATH + beforeElementName, document.getDocumentElement());
if (beforeElement != null) {
document.getDocumentElement().insertBefore(element, beforeElement);
addLineBreakBefore(element, document);
addLineBreakBefore(element, document);
return;
}
final Element afterElement =
XmlUtils.findFirstElement(WEB_APP_XPATH + afterElementName, document.getDocumentElement());
if (afterElement != null && afterElement.getNextSibling() != null
&& afterElement.getNextSibling() instanceof Element) {
document.getDocumentElement().insertBefore(element, afterElement.getNextSibling());
addLineBreakBefore(element, document);
addLineBreakBefore(element, document);
return;
}
document.getDocumentElement().appendChild(element);
addLineBreakBefore(element, document);
addLineBreakBefore(element, document);
}
/**
* Set the description element in the web.xml document.
*
* @param description (required)
* @param document the web.xml document (required)
* @param comment (optional)
*/
public static void setDescription(final String description, final Document document,
final String comment) {
Validate.notNull(document, "Web XML document required");
Validate.notBlank(description, "Description required");
Element descriptionElement =
XmlUtils.findFirstElement(WEB_APP_XPATH + "description", document.getDocumentElement());
if (descriptionElement == null) {
descriptionElement = document.createElement("description");
insertBetween(descriptionElement, "display-name[last()]", "context-param", document);
if (StringUtils.isNotBlank(comment)) {
addCommentBefore(descriptionElement, comment, document);
}
}
descriptionElement.setTextContent(description);
}
/**
* Set the display-name element in the web.xml document.
*
* @param displayName (required)
* @param document the web.xml document (required)
* @param comment (optional)
*/
public static void setDisplayName(final String displayName, final Document document,
final String comment) {
Validate.notBlank(displayName, "display name required");
Validate.notNull(document, "Web XML document required");
Element displayNameElement =
XmlUtils.findFirstElement(WEB_APP_XPATH + "display-name", document.getDocumentElement());
if (displayNameElement == null) {
displayNameElement = document.createElement("display-name");
insertBetween(displayNameElement, "the-start", "description", document);
if (StringUtils.isNotBlank(comment)) {
addCommentBefore(displayNameElement, comment, document);
}
}
displayNameElement.setTextContent(displayName);
}
/**
* Set session timeout in web.xml document
*
* @param timeout
* @param document (required)
* @param comment (optional)
*/
public static void setSessionTimeout(final int timeout, final Document document,
final String comment) {
Validate.notNull(document, "Web XML document required");
Validate.notNull(timeout, "Timeout required");
Element sessionConfigElement =
XmlUtils.findFirstElement(WEB_APP_XPATH + "session-config", document.getDocumentElement());
if (sessionConfigElement == null) {
sessionConfigElement = document.createElement("session-config");
insertBetween(sessionConfigElement, "servlet-mapping[last()]", "welcome-file-list", document);
if (StringUtils.isNotBlank(comment)) {
addCommentBefore(sessionConfigElement, comment, document);
}
}
appendChildIfNotPresent(sessionConfigElement,
new XmlElementBuilder("session-timeout", document).setText(String.valueOf(timeout)).build());
}
/**
* Constructor is private to prevent instantiation
*/
private WebXmlUtils() {}
}