1 /* 2 * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package jdk.incubator.jpackage.internal; 26 27 import java.io.FileInputStream; 28 import java.io.IOException; 29 import java.nio.file.Path; 30 import java.util.List; 31 import java.util.ArrayList; 32 import java.util.Map; 33 import javax.xml.parsers.DocumentBuilder; 34 import javax.xml.parsers.DocumentBuilderFactory; 35 import javax.xml.parsers.ParserConfigurationException; 36 import javax.xml.xpath.XPath; 37 import javax.xml.xpath.XPathConstants; 38 import javax.xml.xpath.XPathExpressionException; 39 import javax.xml.xpath.XPathFactory; 40 import org.w3c.dom.Document; 41 import org.w3c.dom.NodeList; 42 import org.xml.sax.SAXException; 43 44 import static jdk.incubator.jpackage.internal.StandardBundlerParam.*; 45 46 public class AppImageFile { 47 48 // These values will be loaded from AppImage xml file. 49 private final String creatorVersion; 50 private final String creatorPlatform; 51 private final String launcherName; 52 private final List<String> addLauncherNames; 53 54 private final static String FILENAME = ".jpackage.xml"; 55 56 private final static Map<Platform, String> PLATFORM_LABELS = Map.of( 57 Platform.LINUX, "linux", Platform.WINDOWS, "windows", Platform.MAC, 58 "macOS"); 59 60 61 private AppImageFile() { 62 this(null, null, null, null); 63 } 64 65 private AppImageFile(String launcherName, List<String> addLauncherNames, 66 String creatorVersion, String creatorPlatform) { 67 this.launcherName = launcherName; 68 this.addLauncherNames = addLauncherNames; 69 this.creatorVersion = creatorVersion; 70 this.creatorPlatform = creatorPlatform; 71 } 72 73 /** 74 * Returns list of additional launchers configured for the application. 75 * Each item in the list is not null or empty string. 76 * Returns empty list for application without additional launchers. 77 */ 78 List<String> getAddLauncherNames() { 79 return addLauncherNames; 80 } 81 82 /** 83 * Returns main application launcher name. Never returns null or empty value. 84 */ 85 String getLauncherName() { 86 return launcherName; 87 } 88 89 void verifyCompatible() throws ConfigException { 90 // Just do nothing for now. 91 } 92 93 /** 94 * Returns path to application image info file. 95 * @param appImageDir - path to application image 96 */ 97 public static Path getPathInAppImage(Path appImageDir) { 98 return appImageDir.resolve(FILENAME); 99 } 100 101 /** 102 * Saves file with application image info in application image. 103 * @param appImageDir - path to application image 104 * @throws IOException 105 */ 106 static void save(Path appImageDir, Map<String, Object> params) 107 throws IOException { 108 IOUtils.createXml(getPathInAppImage(appImageDir), xml -> { 109 xml.writeStartElement("jpackage-state"); 110 xml.writeAttribute("version", getVersion()); 111 xml.writeAttribute("platform", getPlatform()); 112 113 xml.writeStartElement("app-version"); 114 xml.writeCharacters(VERSION.fetchFrom(params)); 115 xml.writeEndElement(); 116 117 xml.writeStartElement("main-launcher"); 118 xml.writeCharacters(APP_NAME.fetchFrom(params)); 119 xml.writeEndElement(); 120 121 List<Map<String, ? super Object>> addLaunchers = 122 ADD_LAUNCHERS.fetchFrom(params); 123 124 for (int i = 0; i < addLaunchers.size(); i++) { 125 Map<String, ? super Object> sl = addLaunchers.get(i); 126 xml.writeStartElement("add-launcher"); 127 xml.writeCharacters(APP_NAME.fetchFrom(sl)); 128 xml.writeEndElement(); 129 } 130 }); 131 } 132 133 /** 134 * Loads application image info from application image. 135 * @param appImageDir - path to application image 136 * @return valid info about application image or null 137 * @throws IOException 138 */ 139 static AppImageFile load(Path appImageDir) throws IOException { 140 try { 141 Document doc = readXml(appImageDir); 142 143 XPath xPath = XPathFactory.newInstance().newXPath(); 144 145 String mainLauncher = xpathQueryNullable(xPath, 146 "/jpackage-state/main-launcher/text()", doc); 147 if (mainLauncher == null) { 148 // No main launcher, this is fatal. 149 return new AppImageFile(); 150 } 151 152 List<String> addLaunchers = new ArrayList<String>(); 153 154 String platform = xpathQueryNullable(xPath, 155 "/jpackage-state/@platform", doc); 156 157 String version = xpathQueryNullable(xPath, 158 "/jpackage-state/@version", doc); 159 160 NodeList launcherNameNodes = (NodeList) xPath.evaluate( 161 "/jpackage-state/add-launcher/text()", doc, 162 XPathConstants.NODESET); 163 164 for (int i = 0; i != launcherNameNodes.getLength(); i++) { 165 addLaunchers.add(launcherNameNodes.item(i).getNodeValue()); 166 } 167 168 AppImageFile file = new AppImageFile( 169 mainLauncher, addLaunchers, version, platform); 170 if (!file.isValid()) { 171 file = new AppImageFile(); 172 } 173 return file; 174 } catch (XPathExpressionException ex) { 175 // This should never happen as XPath expressions should be correct 176 throw new RuntimeException(ex); 177 } 178 } 179 180 public static Document readXml(Path appImageDir) throws IOException { 181 try { 182 Path path = getPathInAppImage(appImageDir); 183 184 DocumentBuilderFactory dbf = 185 DocumentBuilderFactory.newDefaultInstance(); 186 dbf.setFeature( 187 "http://apache.org/xml/features/nonvalidating/load-external-dtd", 188 false); 189 DocumentBuilder b = dbf.newDocumentBuilder(); 190 return b.parse(new FileInputStream(path.toFile())); 191 } catch (ParserConfigurationException | SAXException ex) { 192 // Let caller sort this out 193 throw new IOException(ex); 194 } 195 } 196 197 /** 198 * Returns list of launcher names configured for the application. 199 * The first item in the returned list is main launcher name. 200 * Following items in the list are names of additional launchers. 201 */ 202 static List<String> getLauncherNames(Path appImageDir, 203 Map<String, ? super Object> params) { 204 List<String> launchers = new ArrayList<>(); 205 try { 206 AppImageFile appImageInfo = AppImageFile.load(appImageDir); 207 if (appImageInfo != null) { 208 launchers.add(appImageInfo.getLauncherName()); 209 launchers.addAll(appImageInfo.getAddLauncherNames()); 210 return launchers; 211 } 212 } catch (IOException ioe) { 213 Log.verbose(ioe); 214 } 215 216 launchers.add(APP_NAME.fetchFrom(params)); 217 ADD_LAUNCHERS.fetchFrom(params).stream().map(APP_NAME::fetchFrom).forEach( 218 launchers::add); 219 return launchers; 220 } 221 222 private static String xpathQueryNullable(XPath xPath, String xpathExpr, 223 Document xml) throws XPathExpressionException { 224 NodeList nodes = (NodeList) xPath.evaluate(xpathExpr, xml, 225 XPathConstants.NODESET); 226 if (nodes != null && nodes.getLength() > 0) { 227 return nodes.item(0).getNodeValue(); 228 } 229 return null; 230 } 231 232 private static String getVersion() { 233 return System.getProperty("java.version"); 234 } 235 236 private static String getPlatform() { 237 return PLATFORM_LABELS.get(Platform.getPlatform()); 238 } 239 240 private boolean isValid() { 241 if (launcherName == null || launcherName.length() == 0 || 242 addLauncherNames.indexOf("") != -1) { 243 // Some launchers have empty names. This is invalid. 244 return false; 245 } 246 247 // Add more validation. 248 249 return true; 250 } 251 252 }