/* * RHQ Management Platform * Copyright (C) 2014 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package org.rhq.common.wildfly; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringReader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import javax.xml.namespace.QName; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; /** * A simple parser of the Wildfly patch files. Can be used only to find a limited set of metadata about the patches. * * @author Lukas Krejci * @since 4.13 */ public final class PatchParser { private static final String PATCHES_NAMESPACE_URI = "urn:jboss:patch:bundle:1.0"; private static final String PATCH_NAMESPACE_URI_REGEXP = "urn:jboss:patch:1[.][012]"; private static final Pattern NAMESPACE_PATTERN = Pattern.compile(PATCH_NAMESPACE_URI_REGEXP); private PatchParser() { } /** * Tries to find if the provided input stream is a Wildfly patch file and return information about the patch parsed * out of it. This method assumes that the provided input stream is the contents of a ZIP file. * <p/> * The stream is not closed after this method returns. * * @param patchContents the contents of the patch file * @param captureDescriptors whether to store the full contents of the patch XML descriptors in the returned info * * @return the info about the patch or null if the stream is not a patch file * * @throws IOException on IO error or if the stream is not in a valid ZIP format * @throws XMLStreamException on error parsing XML patch descriptors (patch.xml or patches.xml files inside the * ZIP) */ public static PatchInfo parse(InputStream patchContents, boolean captureDescriptors) throws IOException, XMLStreamException { ZipInputStream zip = new ZipInputStream(patchContents); Map<String, Patch> foundPatches = new HashMap<String, Patch>(); ParsedPatchesXml patchesXml = null; PatchInfo ret = null; ZipEntry entry; while ((entry = zip.getNextEntry()) != null) { String name = entry.getName().toLowerCase(); if ("patch.xml".equals(name)) { ret = new PatchInfo(parsePatchXml(zip, captureDescriptors)); } else if ("patches.xml".equals(name)) { patchesXml = parsePatchesXml(zip, captureDescriptors); } else if (name.endsWith(".zip")) { // try to parse proactively so that we have the picture of all patches in the zip file even if we get // entries of those patches before we parse "patches.xml". This is the only way to do it in one pass // through the provided input stream... try { PatchInfo p = parse(zip, captureDescriptors); if (p != null && p.is(Patch.class)) { //important to use the exact name, not the lowercase variant foundPatches.put(entry.getName(), p.as(Patch.class)); } } catch (IllegalArgumentException e) { //ignore, this might not be a patch file after all... } catch (XMLStreamException e) { //ignore, this might not be a patch file after all... } } } if (patchesXml != null) { List<PatchBundle.Element> elements = new ArrayList<PatchBundle.Element>(patchesXml.elements.size()); for (String fileName : patchesXml.elements) { elements.add(new PatchBundle.Element(fileName, foundPatches.get(fileName))); } ret = new PatchInfo(new PatchBundle(elements, patchesXml.contents)); } return ret; } private static ParsedPatchesXml parsePatchesXml(InputStream patchesXmlStream, boolean captureContents) throws IllegalArgumentException, XMLStreamException, IOException { String contents = null; XMLStreamReader rdr; if (captureContents) { contents = slurp(patchesXmlStream, "UTF-8"); rdr = XMLInputFactory.newInstance().createXMLStreamReader(new StringReader(contents)); } else { rdr = XMLInputFactory.newInstance().createXMLStreamReader(patchesXmlStream, "UTF-8"); } try { List<String> elements = new ArrayList<String>(); boolean foundPatches = false; boolean foundElements = false; while (rdr.hasNext()) { int eventType = rdr.next(); if (eventType != XMLStreamConstants.START_ELEMENT) { continue; } QName name = rdr.getName(); if (PATCHES_NAMESPACE_URI.equals(name.getNamespaceURI())) { if ("patches".equals(name.getLocalPart())) { if (foundElements) { throw new IllegalArgumentException("Not a Wildfly patch."); } foundPatches = true; } else if (foundPatches && "element".equals(name.getLocalPart())) { elements.add(getAttributeValue(rdr, "", "path")); } } foundElements = true; } return new ParsedPatchesXml(contents, elements); } finally { rdr.close(); } } private static Patch parsePatchXml(InputStream patchXmlStream, boolean captureContents) throws IllegalArgumentException, IOException, XMLStreamException { String contents = null; XMLStreamReader rdr; if (captureContents) { contents = slurp(patchXmlStream, "UTF-8"); rdr = XMLInputFactory.newInstance().createXMLStreamReader(new StringReader(contents)); } else { rdr = XMLInputFactory.newInstance().createXMLStreamReader(patchXmlStream, "UTF-8"); } try { boolean foundPatch = false; boolean foundElements = false; String patchId = null; Patch.Type patchType = null; String identityName = null; String version = null; String description = null; while (rdr.hasNext()) { int eventType = rdr.next(); if (eventType != XMLStreamConstants.START_ELEMENT) { continue; } QName name = rdr.getName(); if (!foundPatch) { if (foundElements) { throw new IllegalArgumentException("Not a Wildfly patch"); } foundPatch = NAMESPACE_PATTERN.matcher(name.getNamespaceURI()).matches() && "patch".equals(name.getLocalPart()); patchId = getAttributeValue(rdr, "", "id"); } else { if (NAMESPACE_PATTERN.matcher(name.getNamespaceURI()).matches()) { if ("upgrade".equals(name.getLocalPart())) { patchType = Patch.Type.CUMULATIVE; identityName = getAttributeValue(rdr, "", "name"); version = getAttributeValue(rdr, "", "to-version"); } else if ("no-upgrade".equals(name.getLocalPart())) { patchType = Patch.Type.ONE_OFF; identityName = getAttributeValue(rdr, "", "name"); version = getAttributeValue(rdr, "", "version"); } else if ("description".equals(name.getLocalPart())) { while (rdr.hasNext()) { eventType = rdr.next(); switch (eventType) { case XMLStreamConstants.CDATA: case XMLStreamConstants.CHARACTERS: description = rdr.getText(); break; } if (description != null) { break; } } } else if ("element".equals(name.getLocalPart())) { //we've found enough info... break out of the reader loop break; } } } foundElements = true; } return new Patch(patchId, patchType, identityName, version, description, contents); } finally { rdr.close(); } } private static String getAttributeValue(XMLStreamReader rdr, String namespaceURI, String name) { for (int i = 0; i < rdr.getAttributeCount(); ++i) { QName qname = rdr.getAttributeName(i); if (namespaceURI.equals(qname.getNamespaceURI()) && name.equals(qname.getLocalPart())) { return rdr.getAttributeValue(i); } } return null; } private static String slurp(InputStream stream, String charsetName) throws IOException { StringBuilder bld = new StringBuilder(); InputStreamReader rdr = new InputStreamReader(stream, charsetName); char[] buffer = new char[1024]; int cnt; while ((cnt = rdr.read(buffer)) != -1) { bld.append(buffer, 0, cnt); } return bld.toString(); } private static final class ParsedPatchesXml { String contents; List<String> elements; private ParsedPatchesXml(String contents, List<String> elements) { this.contents = contents; this.elements = elements; } } }