1 /* 2 * Copyright (c) 2014, 2016 Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 */ 5 package com.oracle.appbundlers.utils; 6 7 import static com.oracle.appbundlers.utils.Config.CONFIG_INSTANCE; 8 import static java.lang.String.format; 9 import static java.util.Arrays.asList; 10 import static java.util.stream.Collectors.joining; 11 12 import java.io.BufferedWriter; 13 import java.io.File; 14 import java.io.IOException; 15 import java.io.PrintWriter; 16 import java.io.StringWriter; 17 import java.nio.charset.StandardCharsets; 18 import java.nio.file.Files; 19 import java.nio.file.Path; 20 import java.util.Collection; 21 import java.util.HashMap; 22 import java.util.List; 23 import java.util.Map; 24 import java.util.Set; 25 import java.util.concurrent.ExecutionException; 26 import java.util.function.Function; 27 import java.util.logging.Level; 28 import java.util.logging.Logger; 29 import java.util.stream.Collectors; 30 31 import javax.xml.parsers.DocumentBuilder; 32 import javax.xml.parsers.DocumentBuilderFactory; 33 import javax.xml.parsers.ParserConfigurationException; 34 import javax.xml.transform.OutputKeys; 35 import javax.xml.transform.Transformer; 36 import javax.xml.transform.TransformerException; 37 import javax.xml.transform.TransformerFactory; 38 import javax.xml.transform.dom.DOMSource; 39 import javax.xml.transform.stream.StreamResult; 40 41 import org.w3c.dom.DOMException; 42 import org.w3c.dom.Document; 43 import org.w3c.dom.Element; 44 45 import com.oracle.appbundlers.utils.installers.AbstractBundlerUtils; 46 import com.oracle.tools.packager.ConfigException; 47 import com.oracle.tools.packager.RelativeFileSet; 48 import com.oracle.tools.packager.UnsupportedPlatformException; 49 import com.sun.javafx.tools.packager.bundlers.BundleParams; 50 51 /** 52 * @author Andrei Eremeev <andrei.eremeev@oracle.com> 53 */ 54 public class AntBundlingManager extends BundlingManager { 55 56 private static final Logger LOG = Logger 57 .getLogger(AntBundlingManager.class.getName()); 58 59 @SuppressWarnings("serial") 60 private final static Map<String, Location> toAntEntry = new HashMap<String, Location>() { 61 { 62 put(APP_RESOURCES, new Location("fx:resources", "")); 63 put(LICENSE_FILE, new Location("fx:resources", "")); 64 put(APPLICATION_CLASS, new Location("fx:application", "mainClass")); 65 put(IDENTIFIER, new Location("fx:application", "id")); 66 put(VERSION, new Location("fx:application", "version")); 67 put(APP_NAME, 68 new Location("fx:application", "name")); 69 put(VENDOR, new Location("fx:info", "vendor")); 70 put(TITLE, new Location("fx:info", "title")); 71 put(DESCRIPTION, new Location("fx:info", "description")); 72 put(ICON, new Location("fx:info", "")); 73 put(EMAIL, new Location("fx:info", "email")); 74 put(COPYRIGHT, new Location("fx:info", "copyright")); 75 put(LICENSE_TYPE, new Location("fx:info", "license")); 76 put(CATEGORY, new Location("fx:info", "category")); 77 put(SHORTCUT_HINT, new Location("fx:preferences", "shortcut")); 78 put(MENU_HINT, new Location("fx:preferences", "menu")); 79 put(SYSTEM_WIDE, new Location("fx:preferences", "install")); 80 put(BundleParams.PARAM_RUNTIME, 81 new Location("fx:platform", "baseDir")); 82 put(JVM_OPTIONS, new Location("fx:platform", "")); 83 put(JVM_PROPERTIES, new Location("fx:platform", "")); 84 put(USER_JVM_OPTIONS, new Location("fx:platform", "")); 85 put(ARGUMENTS, new Location("fx:application", "")); 86 put(FILE_ASSOCIATIONS, new Location("fx:info", "")); 87 put(SECONDARY_LAUNCHERS, Location.DUMMY); 88 put(STRIP_NATIVE_COMMANDS, new Location("fx:runtime", STRIP_NATIVE_COMMANDS)); 89 put(ADD_MODS, new Location("fx:runtime", "")); 90 put(LIMIT_MODS, new Location("fx:runtime","")); 91 put(MODULEPATH, new Location("fx:runtime","")); 92 put(MAIN_MODULE, new Location("fx:application", MAIN_MODULE)); 93 } 94 }; 95 96 public AntBundlingManager(AbstractBundlerUtils bundler) { 97 super(bundler); 98 } 99 100 @Override 101 public boolean validate(Map<String, Object> params) 102 throws UnsupportedPlatformException, ConfigException { 103 return true; 104 } 105 106 private Data createDocument() 107 throws DOMException, ParserConfigurationException { 108 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 109 DocumentBuilder builder = factory.newDocumentBuilder(); 110 Document document = builder.newDocument(); 111 Element project = document.createElement("project"); 112 project.setAttribute("default", "fx-deploy"); 113 project.setAttribute("xmlns:fx", "javafx:com.sun.javafx.tools.ant"); 114 document.appendChild(project); 115 116 Element taskDef = document.createElement("taskdef"); 117 taskDef.setAttribute("resource", "com/sun/javafx/tools/ant/antlib.xml"); 118 taskDef.setAttribute("uri", "javafx:com.sun.javafx.tools.ant"); 119 taskDef.setAttribute("classpath", CONFIG_INSTANCE.getAntJavaFx()); 120 project.appendChild(taskDef); 121 122 Element target = document.createElement("target"); 123 target.setAttribute("name", "fx-deploy"); 124 project.appendChild(target); 125 return new Data(document, target); 126 } 127 128 private String documentToXml(Document document) 129 throws TransformerException { 130 TransformerFactory factory = TransformerFactory.newInstance(); 131 factory.setAttribute("indent-number", 4); 132 Transformer transformer = factory.newTransformer(); 133 DOMSource source = new DOMSource(document); 134 StringWriter writer = new StringWriter(); 135 StreamResult result = new StreamResult(new PrintWriter(writer)); 136 transformer.setOutputProperty(OutputKeys.METHOD, "xml"); 137 transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 138 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); 139 transformer.transform(source, result); 140 return writer.toString(); 141 } 142 143 @SuppressWarnings("unchecked") 144 private void appendToFXDeploy(Document document, Element fxDeploy, 145 Map<String, Object> params) throws IOException { 146 final Map<String, Element> ant = toAntEntry.values().stream() 147 .filter(location -> location != Location.DUMMY) 148 .map(location -> location.element).distinct().collect(Collectors 149 .toMap(Function.identity(), document::createElement)); 150 ant.forEach((str, element) -> fxDeploy.appendChild(element)); 151 for (Map.Entry<String, Object> entry : params.entrySet()) { 152 String key = entry.getKey(); 153 Object value = entry.getValue(); 154 System.out.println("key = " + key); 155 System.out.println("value = " + value); 156 Location location = toAntEntry.get(key); 157 if (location != null) { 158 Element parentEl = ant.get(location.element); 159 switch (key) { 160 case "appResources": { 161 RelativeFileSet relFileSet = (RelativeFileSet) value; 162 Element e = document.createElement("fx:fileset"); 163 e.setAttribute("dir", 164 relFileSet.getBaseDirectory().getAbsolutePath()); 165 e.setAttribute("includes", relFileSet.getIncludedFiles() 166 .stream().collect(joining(","))); 167 parentEl.appendChild(e); 168 break; 169 } 170 case "licenseFile": { 171 String file = (String) value; 172 Element e = document.createElement("fx:fileset"); 173 RelativeFileSet relFileSet = null; 174 relFileSet = com.oracle.tools.packager.StandardBundlerParam.APP_RESOURCES.fetchFrom(params); 175 e.setAttribute("dir", 176 relFileSet.getBaseDirectory().getAbsolutePath()); 177 e.setAttribute("includes", file); 178 e.setAttribute("type", "license"); 179 parentEl.appendChild(e); 180 break; 181 } 182 case "icon": { 183 File icon = (File) value; 184 Element e = document.createElement("fx:icon"); 185 e.setAttribute("href", icon.getAbsolutePath()); 186 parentEl.appendChild(e); 187 break; 188 } 189 case "jvmOptions": { 190 createJvmOptionsEntries(document, parentEl, value); 191 break; 192 } 193 case "jvmProperties": { 194 createJvmPropertiesEntries(document, parentEl, value); 195 break; 196 } 197 case "userJvmOptions": { 198 createUserJvmOptionsEntries(document, parentEl, value); 199 break; 200 } 201 case "secondaryLaunchers": { 202 List<Map<String, Object>> launchers = (List<Map<String, Object>>) value; 203 for (Map<String, Object> properties : launchers) { 204 Element launcherEl = document 205 .createElement("fx:secondaryLauncher"); 206 207 for (Map.Entry<String, Object> keyVal : properties 208 .entrySet()) { 209 210 switch (keyVal.getKey()) { 211 case "jvmOptions": { 212 createJvmOptionsEntries(document, launcherEl, 213 keyVal.getValue()); 214 break; 215 } 216 case "jvmProperties": { 217 createJvmPropertiesEntries(document, launcherEl, 218 keyVal.getValue()); 219 break; 220 } 221 case "userJvmOptions": { 222 createUserJvmOptionsEntries(document, 223 launcherEl, keyVal.getValue()); 224 break; 225 } 226 case "arguments": { 227 createArgumentEntries(document, launcherEl, 228 keyVal.getValue()); 229 break; 230 } 231 232 case MAIN_MODULE: 233 launcherEl.setAttribute(MAIN_MODULE, (String) keyVal.getValue()); 234 break; 235 236 case APPLICATION_CLASS: 237 launcherEl.setAttribute(location.attribute, (String) keyVal.getValue()); 238 break; 239 240 default: 241 launcherEl.appendChild( 242 createBundleArgumentEntry(document, 243 keyVal.getKey(), 244 keyVal.getValue())); 245 } 246 } 247 248 fxDeploy.appendChild(launcherEl); 249 } 250 break; 251 } 252 case "runtime": { 253 RelativeFileSet relFileSet = (RelativeFileSet) value; 254 parentEl.setAttribute(location.attribute, 255 relFileSet.getBaseDirectory().getAbsolutePath()); 256 break; 257 } 258 case "arguments": { 259 createArgumentEntries(document, parentEl, value); 260 break; 261 } 262 case "fileAssociations": { 263 List<Map<String, Object>> associations = (List<Map<String, Object>>) value; 264 for (Map<String, Object> association : associations) { 265 Element el = document.createElement("fx:association"); 266 List<String> extensions = (List<String>) association 267 .get(FA_EXTENSIONS); 268 List<String> contentTypes = (List<String>) association 269 .get(FA_CONTENT_TYPE); 270 String description = (String) association 271 .get(FA_DESCRIPTION); 272 el.setAttribute("extension", extensions.stream() 273 .collect(Collectors.joining(" "))); 274 el.setAttribute("mimetype", contentTypes.stream() 275 .collect(Collectors.joining(" "))); 276 if (description != null) { 277 el.setAttribute("description", description); 278 } 279 File icon = (File) association.get(FA_ICON); 280 if (icon != null) { 281 el.setAttribute("icon", icon.getAbsolutePath()); 282 } 283 parentEl.appendChild(el); 284 } 285 break; 286 } 287 288 case STRIP_NATIVE_COMMANDS: 289 parentEl.setAttribute(STRIP_NATIVE_COMMANDS, 290 value.toString()); 291 break; 292 case ADD_MODS: 293 Element addModsElement = document 294 .createElement("fx:" + ADD_MODS); 295 addModsElement.setAttribute("value", getValueAsString(value)); 296 parentEl.appendChild(addModsElement); 297 break; 298 case LIMIT_MODS: 299 Element limitModsElement = document 300 .createElement("fx:" + LIMIT_MODS); 301 limitModsElement.setAttribute("value", getValueAsString(value)); 302 parentEl.appendChild(limitModsElement); 303 break; 304 case MODULEPATH: 305 Element modulePath = document 306 .createElement("fx:" + MODULEPATH); 307 modulePath.setAttribute("value", getValueAsString(value)); 308 parentEl.appendChild(modulePath); 309 break; 310 case MAIN_MODULE: 311 if(value instanceof String) { 312 parentEl.setAttribute(MAIN_MODULE, ((String) value).split("/")[0]); 313 parentEl.setAttribute("mainClass", ((String) value).split("/")[1]); 314 } 315 break; 316 default: 317 checkValue(value); 318 parentEl.setAttribute(location.attribute, value.toString()); 319 break; 320 } 321 } else { 322 fxDeploy.appendChild( 323 createBundleArgumentEntry(document, key, value)); 324 } 325 } 326 } 327 328 @SuppressWarnings({ "unchecked", "rawtypes" }) 329 private String getValueAsString(Object value) { 330 String actualValue = null; 331 if (value instanceof String) { 332 actualValue = value.toString(); 333 } else if (value instanceof List) { 334 actualValue = String.join(",", (List) value); 335 } else if (value instanceof Set) { 336 actualValue = String.join(",", (Set) value); 337 } else if (value instanceof Object) { 338 actualValue = value.toString(); 339 } 340 return actualValue; 341 } 342 343 private Element createBundleArgumentEntry(Document document, String argName, 344 Object value) throws IOException { 345 Element bundleArgument = document.createElement("fx:bundleArgument"); 346 String argValue; 347 if ("mainJar".equals(argName)) { 348 RelativeFileSet fileSet = (RelativeFileSet) value; 349 argValue = fileSet.getIncludedFiles().iterator().next(); 350 } else { 351 checkValue(value); 352 argValue = value.toString(); 353 } 354 bundleArgument.setAttribute("arg", argName); 355 bundleArgument.setAttribute("value", argValue); 356 357 return bundleArgument; 358 } 359 360 @SuppressWarnings("unchecked") 361 private void createJvmOptionsEntries(Document document, Element parentEl, 362 Object value) { 363 Collection<String> col = (Collection<String>) value; 364 col.forEach(arg -> { 365 Element e = document.createElement("fx:jvmarg"); 366 e.setAttribute("value", arg); 367 parentEl.appendChild(e); 368 }); 369 } 370 371 @SuppressWarnings("unchecked") 372 private void createJvmPropertiesEntries(Document document, Element parentEl, 373 Object value) { 374 Map<String, String> properties = (Map<String, String>) value; 375 properties.forEach((k, v) -> { 376 Element e = document.createElement("fx:property"); 377 e.setAttribute("name", k); 378 e.setAttribute("value", v); 379 parentEl.appendChild(e); 380 }); 381 } 382 383 @SuppressWarnings("unchecked") 384 private void createUserJvmOptionsEntries(Document document, 385 Element parentEl, Object value) { 386 Map<String, String> properties = (Map<String, String>) value; 387 properties.forEach((k, v) -> { 388 Element e = document.createElement("fx:jvmuserarg"); 389 e.setAttribute("name", k); 390 e.setAttribute("value", v); 391 parentEl.appendChild(e); 392 }); 393 } 394 395 @SuppressWarnings("unchecked") 396 private void createArgumentEntries(Document document, Element parentEl, 397 Object value) { 398 List<String> arguments = (List<String>) value; 399 for (String arg : arguments) { 400 Element e = document.createElement("fx:argument"); 401 e.setTextContent(arg); 402 parentEl.appendChild(e); 403 } 404 } 405 406 private void checkValue(Object value) throws IOException { 407 if (!(value instanceof String) && !(value instanceof Boolean) 408 && !(value instanceof File)) { 409 throw new IOException(format("Value is not mapped : %s, %s", 410 value.getClass(), value.toString())); 411 } 412 } 413 414 private Element fxDeploy(Document document, Map<String, Object> params, 415 File file) throws IOException { 416 Element fxDeploy = document.createElement("fx:deploy"); 417 String bundleType = getBundler().getBundleType(); 418 fxDeploy.setAttribute("nativeBundles", 419 "image".equalsIgnoreCase(bundleType) ? "image" 420 : getBundler().getID()); 421 if (!file.getName().equals("bundles")) { 422 throw new IllegalArgumentException( 423 "Invalid bundle directory : " + file); 424 } 425 fxDeploy.setAttribute("outdir", file.getParentFile().getAbsolutePath()); 426 fxDeploy.setAttribute("outfile", "test"); 427 fxDeploy.setAttribute("verbose", "true"); 428 appendToFXDeploy(document, fxDeploy, params); 429 return fxDeploy; 430 } 431 432 private void writeToFile(Path file, String buildXml) throws IOException { 433 try (BufferedWriter writer = Files.newBufferedWriter(file, 434 StandardCharsets.UTF_8)) { 435 writer.append(buildXml); 436 } 437 } 438 439 @Override 440 public File execute(Map<String, Object> params, File file) 441 throws IOException { 442 try { 443 Data data = createDocument(); 444 Element fxDeploy = fxDeploy(data.document, params, file); 445 data.fxDeployTarget.appendChild(fxDeploy); 446 Path buildXmlFile = Utils.getTempDir().resolve("build.xml"); 447 String buildXml = documentToXml(data.document); 448 // TODO: WTF? 449 LOG.log(Level.INFO, "build.xml:\n{0}\n", buildXml); 450 writeToFile(buildXmlFile, buildXml); 451 452 final List<String> command = asList(CONFIG_INSTANCE.antExec(), "-f", 453 buildXmlFile.toString()); 454 455 @SuppressWarnings("serial") 456 ProcessOutput process = Utils.runCommand(command, 457 /* verbose = */ true, 458 /* timeout = */ CONFIG_INSTANCE.getInstallTimeout(), 459 new HashMap<String, String>() { 460 { 461 put("JAVA_HOME", CONFIG_INSTANCE.getJavaHome()); 462 } 463 }); 464 if (process.isTimeoutExceeded()) { 465 throw new IOException( 466 "The command " + command + " hasn't finished in " 467 + CONFIG_INSTANCE.getInstallTimeout() 468 + " milliseconds"); 469 } 470 471 if (process.exitCode() != 0) { 472 throw new IOException( 473 "Process finished with not zero exit code"); 474 } 475 // TODO: Proper exception handle? 476 return file; 477 } catch (DOMException | ParserConfigurationException 478 | TransformerException | ExecutionException e) { 479 throw new IOException(e); 480 } 481 } 482 483 @Override 484 public String getShortName() { 485 return "ant"; 486 } 487 488 private static class Location { 489 490 public static final Location DUMMY = new Location("dummy", "dummy"); 491 492 public final String element; 493 public final String attribute; 494 495 public Location(String element, String attribute) { 496 this.element = element; 497 this.attribute = attribute; 498 } 499 } 500 501 private static class Data { 502 503 public final Document document; 504 public final Element fxDeployTarget; 505 506 public Data(Document document, Element fxDeploy) { 507 this.document = document; 508 this.fxDeployTarget = fxDeploy; 509 } 510 } 511 }