From a6b075f6520f7bfeb30fbab6d2cf1a17383b2f40 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro <gregd@basistech.com> Date: Tue, 29 Aug 2023 16:54:45 -0400 Subject: [PATCH] work towards module updates --- .../autopsy/apiupdate/ManifestLoader.java | 3 +- .../autopsy/apiupdate/ModuleUpdates.java | 202 ++++++++++++++++-- 2 files changed, 192 insertions(+), 13 deletions(-) diff --git a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ManifestLoader.java b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ManifestLoader.java index e1d1dbd3fe..2b97b3328e 100644 --- a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ManifestLoader.java +++ b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ManifestLoader.java @@ -19,6 +19,7 @@ * @author gregd */ public class ManifestLoader { + private static final String JAR_MANIFEST_REL_PATH = "META-INF/MANIFEST.MF"; public static Attributes loadInputStream(InputStream is) throws IOException { Manifest manifest = new Manifest(is); @@ -32,7 +33,7 @@ public static Attributes loadFromJar(File jarFile) throws IOException { while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); - if ("META-INF/MANIFEST.MF".equalsIgnoreCase(entry.getName())) { + if (JAR_MANIFEST_REL_PATH.equalsIgnoreCase(entry.getName())) { return loadInputStream(zipFile.getInputStream(entry)); } } diff --git a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ModuleUpdates.java b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ModuleUpdates.java index 40ead8e718..ec50af24d7 100644 --- a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ModuleUpdates.java +++ b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ModuleUpdates.java @@ -5,17 +5,37 @@ package org.sleuthkit.autopsy.apiupdate; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.text.MessageFormat; +import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; import java.util.jar.Attributes; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; import org.apache.commons.lang3.StringUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; /** * @@ -27,17 +47,24 @@ public class ModuleUpdates { private static final Pattern SPEC_REGEX = Pattern.compile("^\\s*((?<major>\\d*)\\.)?(?<minor>\\d*)(\\.(?<patch>\\d*))?\\s*$"); private static final String SPEC_KEY = "OpenIDE-Module-Specification-Version"; + private static final String SPEC_PROPS_KEY = "spec.version.base"; private static final String IMPL_KEY = "OpenIDE-Module-Implementation-Version"; private static final String RELEASE_KEY = "OpenIDE-Module"; - private static final Pattern RELEASE_REGEX = Pattern.compile("^\\s*(?<releaseName>.+?)(/(?<releaseNum>\\d*))?\\s*$"); private static final SemVer DEFAULT_SEMVER = new SemVer(1, 0, null); private static final int DEFAULT_VERS_VAL = 1; - private static final String AUTOPSY_PREFIX = "org-sleuthkit-autopsy-"; - + private static final String PROJECT_PROPS_REL_PATH = "nbproject/project.properties"; + private static final String PROJ_XML_REL_PATH = "nbproject/project.xml"; + private static final String MANIFEST_FILE_NAME = "manifest.mf"; + + private static final String PROJ_XML_FMT_STR = "//project/configuration/data/module-dependencies/dependency[code-name-base[contains(text(), '{0}')]]/run-dependency"; + private static final String PROJ_XML_RELEASE_VERS_EL = "release-version"; + private static final String PROJ_XML_SPEC_VERS_EL = "specification-version"; + private static final String PROJ_XML_IMPL_VERS_EL = "implementation-version"; + private static SemVer parseSemVer(String semVerStr, SemVer defaultSemVer, String resourceForLogging) { if (StringUtils.isBlank(semVerStr)) { LOGGER.log(Level.SEVERE, MessageFormat.format("Unable to parse semver for empty string in {0}", resourceForLogging)); @@ -121,12 +148,165 @@ private static void updateVersions() { // implementation += 1 } - private static void setVersions(File srcDir, Map<String, ModuleVersionNumbers> versNums) { + private static Map<String, File> getModuleDirs(File srcDir) throws IOException { + Map<String, File> moduleDirMapping = new HashMap<>(); + for (File dir : srcDir.listFiles((File f) -> f.isDirectory())) { + File manifestFile = dir.toPath().resolve(MANIFEST_FILE_NAME).toFile(); + if (manifestFile.isFile()) { + try (FileInputStream manifestIs = new FileInputStream(manifestFile)) { + Attributes manifestAttrs = ManifestLoader.loadInputStream(manifestIs); + ReleaseVal releaseVal = parseReleaseVers(manifestAttrs.getValue(RELEASE_KEY), manifestFile.getAbsolutePath()); + moduleDirMapping.put(releaseVal.getModuleName(), dir); + } + } + } + return moduleDirMapping; + } + + static void setVersions(File srcDir, Map<String, ModuleVersionNumbers> versNums) { // TODO parse from repo/DIR/manifest.mf release version - Map<String, File> moduleDirs = Stream.of(srcDir.listFiles((File f) -> f.isDirectory())) - .filter(f -> versNums.containsKey((AUTOPSY_PREFIX + f.getName()).toLowerCase())) - .collect(Collectors.toMap(f -> (AUTOPSY_PREFIX + f.getName()).toLowerCase(), f -> f, (f1, f2) -> f1); - // // Spec + Map<String, File> moduleDirs; + try { + moduleDirs = getModuleDirs(srcDir); + } catch (IOException ex) { + LOGGER.log(Level.WARNING, "There was an error getting module directories from source dir: " + srcDir.getAbsolutePath(), ex); + return; + } + + for (Entry<String, File> moduleNameDir : moduleDirs.entrySet()) { + String moduleName = moduleNameDir.getKey(); + File moduleDir = moduleNameDir.getValue(); + ModuleVersionNumbers thisVersNums = versNums.get(moduleName); + + try { + updateProjXml(moduleDir, versNums); + + if (thisVersNums != null) { + updateProjProperties(moduleDir, thisVersNums); + updateManifest(moduleDir, thisVersNums); + } + } catch (Exception ex) { + LOGGER.log(Level.WARNING, "There was an error updating " + moduleDir.getAbsolutePath(), ex); + } + } + } + + private static void updateProjProperties(File moduleDir, ModuleVersionNumbers thisVersNums) throws IOException { + File projectPropsFile = moduleDir.toPath().resolve(PROJECT_PROPS_REL_PATH).toFile(); + if (!projectPropsFile.isFile()) { + LOGGER.log(Level.SEVERE, "No project properties found at " + projectPropsFile.getAbsolutePath()); + return; + } + + Properties projectProps = new Properties(); + try (FileInputStream propsIs = new FileInputStream(projectPropsFile)) { + projectProps.load(propsIs); + } + + String specVal = projectProps.getProperty(SPEC_PROPS_KEY); + if (StringUtils.isNotBlank(specVal)) { + projectProps.setProperty(SPEC_PROPS_KEY, thisVersNums.getSpec().getSemVerStr()); + + try (FileOutputStream propsOut = new FileOutputStream(projectPropsFile)) { + projectProps.store(propsOut, null); + } + } + } + + private static void updateManifest(File moduleDir, ModuleVersionNumbers thisVersNums) throws IOException { + File manifestFile = moduleDir.toPath().resolve(MANIFEST_FILE_NAME).toFile(); + if (!manifestFile.isFile()) { + LOGGER.log(Level.SEVERE, "No manifest file found at " + manifestFile.getAbsolutePath()); + return; + } + + Attributes attributes; + try (FileInputStream manifestIs = new FileInputStream(manifestFile)) { + attributes = ManifestLoader.loadInputStream(manifestIs); + } + + updateAttr(attributes, IMPL_KEY, thisVersNums.getImplementation(), true); + updateAttr(attributes, SPEC_KEY, thisVersNums.getSpec().getSemVerStr(), true); + updateAttr(attributes, RELEASE_KEY, thisVersNums.getRelease().getFullReleaseStr(), true); + } + + private static void updateAttr(Attributes attributes, String key, Object val, boolean updateOnlyIfPresent) { + if (updateOnlyIfPresent && attributes.getValue(key) == null) { + return; + } + + attributes.put(key, val); + } + + private static void updateProjXml(File moduleDir, Map<String, ModuleVersionNumbers> versNums) + throws IOException, ParserConfigurationException, SAXException, XPathExpressionException, TransformerException { + + File projXmlFile = moduleDir.toPath().resolve(PROJ_XML_REL_PATH).toFile(); + if (!projXmlFile.isFile()) { + LOGGER.log(Level.SEVERE, "No project.xml file found at " + projXmlFile.getAbsolutePath()); + return; + } + + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document projectXmlDoc = db.parse(projXmlFile); + + XPath xPath = XPathFactory.newInstance().newXPath(); + + boolean updated = false; + for (Entry<String, ModuleVersionNumbers> updatedModule : versNums.entrySet()) { + String moduleName = updatedModule.getKey(); + ModuleVersionNumbers newVers = updatedModule.getValue(); + Node node = (Node) xPath.compile(MessageFormat.format(PROJ_XML_FMT_STR, moduleName)) + .evaluate(projectXmlDoc, XPathConstants.NODE); + + if (node != null) { + Map<String, String> childElText = new HashMap<>() { + { + put(PROJ_XML_RELEASE_VERS_EL, newVers.getRelease().getReleaseVersion() == null ? "" : newVers.getRelease().getReleaseVersion().toString()); + put(PROJ_XML_IMPL_VERS_EL, Integer.toString(newVers.getImplementation())); + put(PROJ_XML_SPEC_VERS_EL, newVers.getSpec().getSemVerStr()); + } + }; + updated = updateXmlChildrenIfPresent(node, childElText) || updated; + } + } + + if (updated) { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + + // pretty print XML + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + + DOMSource source = new DOMSource(projectXmlDoc); + try (FileOutputStream xmlOut = new FileOutputStream(projXmlFile)) { + StreamResult result = new StreamResult(xmlOut); + transformer.transform(source, result); + } + } + + } + + private static boolean updateXmlChildrenIfPresent(Node parentNode, Map<String, String> childElText) { + NodeList childNodeList = parentNode.getChildNodes(); + boolean changed = false; + for (int i = 0; i < childNodeList.getLength(); i++) { + Node childNode = childNodeList.item(i); + String childNodeEl = childNode.getNodeName(); + String childNodeText = childNode.getTextContent(); + + String newChildNodeText = childElText.get(childNodeEl); + if (newChildNodeText != null && StringUtils.isNotBlank(childNodeText)) { + childNode.setPrefix(newChildNodeText); + changed = true; + } + } + + return changed; + } + + // // Spec // // project.properties // // spec.version.base // // manifest @@ -143,8 +323,6 @@ private static void setVersions(File srcDir, Map<String, ModuleVersionNumbers> v // // project.configuration.data.module-dependencies.dependency.run-dependency: // // specification-version // // release-version - } - public static class ReleaseVal { private final String moduleName; -- GitLab