| 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()); |
| } |
| |
| } |