blob: 2180dafe90178097de6ff5bee0be739760e179ef [file] [log] [blame]
package org.apache.archiva.xml;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.apache.archiva.repository.storage.StorageAsset;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.*;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
/**
* XMLReader - a set of common xml utility methods for reading content out of an xml file.
*/
public class XMLReader
{
private URL xmlUrl;
private String documentType;
private Document document;
private Map<String, String> namespaceMap = new HashMap<>();
private Map<String, String> reverseNamespaceMap = new HashMap<>();
private class NamespaceCtx implements NamespaceContext {
@Override
public String getNamespaceURI(String prefix) {
return namespaceMap.get(prefix);
}
@Override
public String getPrefix(String namespaceURI) {
return reverseNamespaceMap.get(namespaceURI);
}
@Override
public Iterator getPrefixes(String namespaceURI) {
return namespaceMap.keySet().iterator();
}
}
public XMLReader( String type, Path file )
throws XMLException
{
initWithFile( type, file );
}
private void initWithFile( String type, Path file) throws XMLException {
if ( !Files.exists(file) )
{
throw new XMLException( "file does not exist: " + file.toAbsolutePath() );
}
if ( !Files.isRegularFile(file) )
{
throw new XMLException( "path is not a file: " + file.toAbsolutePath() );
}
if ( !Files.isReadable(file) )
{
throw new XMLException( "Cannot read xml file due to permissions: " + file.toAbsolutePath() );
}
try
{
initWithUrl( type, file.toUri().toURL() );
}
catch ( MalformedURLException e )
{
throw new XMLException( "Unable to translate file " + file + " to URL: " + e.getMessage(), e );
}
}
public XMLReader( String type, StorageAsset asset) throws XMLException
{
if (asset.isFileBased()) {
initWithFile( type, asset.getFilePath( ) );
} else {
URI uri = asset.getStorage( ).getLocation( ).resolve( asset.getPath( ) );
try(InputStream in = asset.getReadStream()) {
initWithStream( type, uri.toURL( ), in );
}
catch ( IOException e )
{
throw new XMLException( "Could not open asset stream of " + uri + ": " + e.getMessage( ), e );
}
}
}
public XMLReader( String type, URL url )
throws XMLException
{
initWithUrl( type, url );
}
private void initWithUrl( String type, URL url ) throws XMLException {
try(InputStream in = url.openStream()) {
initWithStream( type, url, in );
}
catch ( IOException e )
{
throw new XMLException( "Could not open url " + url + ": " + e.getMessage( ), e );
}
}
private void initWithStream( String type, URL url, InputStream in )
throws XMLException
{
this.documentType = type;
this.xmlUrl = url;
// SAXReader reader = new SAXReader();
try (Reader reader = new LatinEntityResolutionReader(new BufferedReader(new InputStreamReader(in, "UTF-8"))))
{
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
dbf.setExpandEntityReferences(false);
dbf.setValidating(false);
// dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD,"false");
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING,true);
// dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
DocumentBuilder db = dbf.newDocumentBuilder();
// To suppress error output at System.err
db.setErrorHandler(new ErrorHandler() {
@Override
public void warning(SAXParseException exception) throws SAXException {
}
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
});
this.document = db.parse(new InputSource(reader));
}
catch ( IOException e )
{
throw new XMLException( "Unable to open stream to " + url + ": " + e.getMessage(), e );
} catch (ParserConfigurationException e) {
throw new XMLException("Unable to start parser "+e.getMessage());
} catch (SAXException e) {
throw new XMLException("Unable to parse file "+e.getMessage());
}
Element root = this.document.getDocumentElement();
if ( root == null )
{
throw new XMLException( "Invalid " + documentType + " xml: root element is null." );
}
if ( !StringUtils.equals( root.getLocalName(), documentType ) )
{
throw new XMLException(
"Invalid " + documentType + " xml: Unexpected root element <" + root.getLocalName() + ">, expected <"
+ documentType + ">" + root.getNodeName() );
}
}
public String getDefaultNamespaceURI()
{
String namespace = this.document.getNamespaceURI();
return namespace;
}
public void addNamespaceMapping( String elementName, String uri )
{
this.namespaceMap.put( elementName, uri );
}
public Element getElement( String xpathExpr )
throws XMLException
{
XPathExpression xpath = null;
try {
xpath = createXPath( xpathExpr );
Object evaluated = xpath.evaluate( document, XPathConstants.NODE);
if ( evaluated == null )
{
return null;
}
if ( evaluated instanceof Element )
{
return (Element) evaluated;
}
else
{
// Unknown evaluated type.
throw new XMLException( ".getElement( Expr: " + xpathExpr + " ) resulted in non-Element type -> ("
+ evaluated.getClass().getName() + ") " + evaluated );
}
} catch (XPathExpressionException e) {
throw new XMLException("Could not parse xpath expression");
}
}
private XPathExpression createXPath(String xpathExpr ) throws XPathExpressionException {
XPath xpath = XPathFactory.newInstance().newXPath();
if ( !this.namespaceMap.isEmpty() )
{
xpath.setNamespaceContext(new NamespaceCtx());
}
return xpath.compile(xpathExpr);
}
public boolean hasElement( String xpathExpr )
throws XMLException
{
XPathExpression xpath = null;
try {
xpath = createXPath( xpathExpr );
Object evaluated = xpath.evaluate( document, XPathConstants.NODE );
if ( evaluated == null )
{
return false;
}
return true;
} catch (XPathExpressionException e) {
throw new XMLException("Could not create xpath expression");
}
}
/**
* Remove namespaces from entire document.
*/
public void removeNamespaces()
{
removeNamespaces( this.document.getDocumentElement() );
}
/**
* Remove namespaces from element recursively.
*/
@SuppressWarnings("unchecked")
public void removeNamespaces( Node elem )
{
if (elem.getNodeType() == Node.ELEMENT_NODE || elem.getNodeType() == Node.ATTRIBUTE_NODE) {
document.renameNode(elem, null, elem.getLocalName());
Node n;
NodeList nodeList = elem.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
n = nodeList.item(i);
removeNamespaces(n);
}
}
}
public String getElementText( Node context, String xpathExpr )
throws XMLException
{
XPathExpression xpath = null;
try {
xpath = createXPath( xpathExpr );
Object evaluated = xpath.evaluate( context, XPathConstants.NODE );
if ( evaluated == null )
{
return null;
}
if ( evaluated instanceof Element )
{
Element evalElem = (Element) evaluated;
return XmlUtil.getText(evalElem);
}
else
{
// Unknown evaluated type.
throw new XMLException( ".getElementText( Node, Expr: " + xpathExpr + " ) resulted in non-Element type -> ("
+ evaluated.getClass().getName() + ") " + evaluated );
}
} catch (XPathExpressionException e) {
throw new XMLException("Could not parse xpath expression");
}
}
public String getElementText( String xpathExpr )
throws XMLException
{
return getElementText(document, xpathExpr);
}
@SuppressWarnings("unchecked")
public List<Node> getElementList( String xpathExpr )
throws XMLException
{
XPathExpression xpath = null;
try {
xpath = createXPath( xpathExpr );
Object evaluated = xpath.evaluate( document, XPathConstants.NODESET);
if ( evaluated == null )
{
return Collections.emptyList();
}
NodeList nl = (NodeList) evaluated;
List<Node> nodeList = new ArrayList<>();
for (int i = 0 ; i<nl.getLength(); i++) {
nodeList.add(nl.item(i));
}
return nodeList;
} catch (XPathExpressionException e) {
throw new XMLException("Could not parse xpath expression");
}
}
public List<String> getElementListText( String xpathExpr )
throws XMLException
{
List<Node> elemList = getElementList( xpathExpr );
if ( elemList == null )
{
return null;
}
return elemList.stream().filter(n -> n instanceof Element).map(n -> XmlUtil.getText(n)).collect(Collectors.toList());
}
}