1 /******************************************************************************* 2 * Copyright (c) 2005, 2017 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 * G&H Softwareentwicklung GmbH - internationalization implementation (bug 150933) 14 * Michael Seele - remove offline-allowed (bug 153403) 15 *******************************************************************************/ 16 17 package org.eclipse.pde.internal.build.tasks; 18 19 import java.io.*; 20 import java.util.*; 21 import java.util.zip.ZipEntry; 22 import java.util.zip.ZipFile; 23 import javax.xml.parsers.*; 24 import org.xml.sax.*; 25 import org.xml.sax.helpers.DefaultHandler; 26 27 /** 28 * 29 * @since 3.1 30 */ 31 public class JNLPGenerator extends DefaultHandler { 32 33 private SAXParser parser; 34 private final File featureRoot; 35 36 private final String codebase; 37 private final String j2se; 38 39 /** 40 * id = ??? 41 * version = jnlp.version 42 * label = information.title 43 * provider-name = information.vendor 44 * image = information.icon 45 * feature.description = information.description 46 * feature.includes = extension 47 * feature.plugin = jar 48 */ 49 private final static SAXParserFactory parserFactory = SAXParserFactory.newInstance(); 50 private PrintWriter out; 51 private String destination; 52 private String provider; 53 private String label; 54 private String version; 55 private String id; 56 private String description; 57 private boolean resourceWritten = false; 58 private String currentOS = null; 59 private String currentArch = null; 60 private Locale locale = null; 61 private PropertyResourceBundle nlsBundle = null; 62 private final boolean generateOfflineAllowed; 63 private Config[] configs; 64 65 /** 66 * For testing purposes only. 67 */ main(String[] args)68 public static void main(String[] args) { 69 JNLPGenerator generator = new JNLPGenerator(args[0], args[1], args[2], args[3]); 70 generator.process(); 71 } 72 73 /** 74 * Constructs a feature parser. 75 */ JNLPGenerator(String feature, String destination, String codebase, String j2se)76 public JNLPGenerator(String feature, String destination, String codebase, String j2se) { 77 this(feature, destination, codebase, j2se, Locale.getDefault(), true, null); 78 } 79 80 /** 81 * Constructs a feature parser. 82 */ JNLPGenerator(String feature, String destination, String codebase, String j2se, Locale locale, boolean generateOfflineAllowed, String configs)83 public JNLPGenerator(String feature, String destination, String codebase, String j2se, Locale locale, boolean generateOfflineAllowed, String configs) { 84 super(); 85 this.featureRoot = new File(feature); 86 this.destination = destination; 87 this.codebase = codebase; 88 this.j2se = j2se; 89 this.locale = locale; 90 this.generateOfflineAllowed = generateOfflineAllowed; 91 try { 92 parserFactory.setNamespaceAware(true); 93 parser = parserFactory.newSAXParser(); 94 } catch (ParserConfigurationException e) { 95 System.out.println(e); 96 } catch (SAXException e) { 97 System.out.println(e); 98 } 99 setConfigInfo(configs); 100 } 101 102 /** 103 * Parses the specified url and constructs a feature 104 */ process()105 public void process() { 106 InputStream in = null; 107 final String FEATURE_XML = "feature.xml"; //$NON-NLS-1$ 108 109 try { 110 ZipFile featureArchive = null; 111 InputStream nlsStream = null; 112 if (featureRoot.isFile()) { 113 featureArchive = new ZipFile(featureRoot); 114 nlsStream = getNLSStream(featureArchive); 115 ZipEntry featureXML = featureArchive.getEntry(FEATURE_XML); 116 in = featureArchive.getInputStream(featureXML); 117 } else { 118 nlsStream = getNLSStream(this.featureRoot); 119 in = new BufferedInputStream(new FileInputStream(new File(featureRoot, FEATURE_XML))); 120 } 121 try { 122 if (nlsStream != null) { 123 nlsBundle = new PropertyResourceBundle(nlsStream); 124 nlsStream.close(); 125 } 126 } catch (IOException e) { 127 // do nothing 128 } 129 try { 130 parser.parse(new InputSource(in), this); 131 writeResourceEpilogue(); 132 writeEpilogue(); 133 } catch (SAXException e) { 134 //Ignore the exception 135 } finally { 136 in.close(); 137 if (out != null) 138 out.close(); 139 if (featureArchive != null) 140 featureArchive.close(); 141 } 142 } catch (IOException e) { 143 //Ignore the exception 144 } 145 } 146 147 /** 148 * Search for nls properties files and return the stream if files are found. 149 * First try to load the default properties file, then one with the default 150 * locale settings and if nothing matches, return the stream of the first 151 * properties file found. 152 */ getNLSStream(File root)153 private InputStream getNLSStream(File root) { 154 String appendix = ".properties"; //$NON-NLS-1$ 155 String[] potentials = createNLSPotentials(); 156 157 Map<String, File> validEntries = new HashMap<>(); 158 File[] files = root.listFiles(); 159 for (int i = 0; i < files.length; i++) { 160 String filename = files[i].getName(); 161 if (filename.endsWith(appendix)) { 162 validEntries.put(filename, files[i]); 163 } 164 } 165 InputStream stream = null; 166 if (validEntries.size() > 0) { 167 for (int i = 0; i < potentials.length; i++) { 168 File file = validEntries.get(potentials[i]); 169 if (file != null) { 170 try { 171 stream = new BufferedInputStream(new FileInputStream(file)); 172 break; 173 } catch (IOException e) { 174 // do nothing 175 } 176 } 177 } 178 if (stream == null) { 179 File file = validEntries.values().iterator().next(); 180 try { 181 stream = new BufferedInputStream(new FileInputStream(file)); 182 } catch (IOException e) { 183 // do nothing 184 } 185 } 186 } 187 return stream; 188 } 189 190 /** 191 * Search for nls properties files and return the stream if files are found. 192 * First try to load the default properties file, then one with the default 193 * locale settings and if nothing matches, return the stream of the first 194 * founded properties file. 195 */ getNLSStream(ZipFile featureArchive)196 private InputStream getNLSStream(ZipFile featureArchive) { 197 String appendix = ".properties"; //$NON-NLS-1$ 198 String[] potentials = createNLSPotentials(); 199 200 Map<String, ZipEntry> validEntries = new HashMap<>(); 201 for (Enumeration<? extends ZipEntry> enumeration = featureArchive.entries(); enumeration.hasMoreElements();) { 202 ZipEntry entry = enumeration.nextElement(); 203 String entryName = entry.getName(); 204 if (entryName.endsWith(appendix)) { 205 validEntries.put(entryName, entry); 206 } 207 } 208 InputStream stream = null; 209 if (validEntries.size() > 0) { 210 for (int i = 0; i < potentials.length; i++) { 211 ZipEntry entry = validEntries.get(potentials[i]); 212 if (entry != null) { 213 try { 214 stream = featureArchive.getInputStream(entry); 215 break; 216 } catch (IOException e) { 217 // do nothing 218 } 219 } 220 } 221 if (stream == null) { 222 ZipEntry entry = validEntries.values().iterator().next(); 223 try { 224 stream = featureArchive.getInputStream(entry); 225 } catch (IOException e) { 226 // do nothing 227 } 228 } 229 } 230 return stream; 231 } 232 createNLSPotentials()233 private String[] createNLSPotentials() { 234 String suffix = "feature"; //$NON-NLS-1$ 235 String appendix = ".properties"; //$NON-NLS-1$ 236 237 String language = locale.getLanguage(); 238 String country = locale.getCountry(); 239 String variant = locale.getVariant(); 240 241 String potential1 = '_' + language + '_' + country + '_' + variant; 242 String potential2 = '_' + language + '_' + country; 243 String potential3 = '_' + language; 244 String potential4 = ""; //$NON-NLS-1$ 245 246 String[] potentials = new String[] {potential1, potential2, potential3, potential4}; 247 for (int i = 0; i < potentials.length; i++) { 248 potentials[i] = suffix + potentials[i] + appendix; 249 } 250 return potentials; 251 } 252 253 @Override startElement(String uri, String localName, String qName, Attributes attributes)254 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { 255 try { 256 if ("feature".equals(localName)) { //$NON-NLS-1$ 257 processFeature(attributes); 258 } else if ("includes".equals(localName)) { //$NON-NLS-1$ 259 processIncludes(attributes); 260 } else if ("description".equals(localName)) { //$NON-NLS-1$ 261 processDescription(attributes); 262 } else if ("plugin".equals(localName)) { //$NON-NLS-1$ 263 processPlugin(attributes); 264 } 265 } catch (IOException e) { 266 throw new SAXException(e); 267 } 268 } 269 processPlugin(Attributes attributes)270 private void processPlugin(Attributes attributes) throws IOException { 271 writePrologue(); 272 String pluginId = attributes.getValue("id"); //$NON-NLS-1$ 273 String pluginVersion = attributes.getValue("version"); //$NON-NLS-1$ 274 String os = attributes.getValue("os"); //$NON-NLS-1$ 275 String ws = attributes.getValue("ws"); //$NON-NLS-1$ 276 String arch = attributes.getValue("arch"); //$NON-NLS-1$ 277 if (isValidEnvironment(os, ws, arch)) { 278 writeResourcePrologue(os, ws, arch); 279 out.println("\t\t<jar href=\"plugins/" + pluginId + "_" + pluginVersion + ".jar\"/>"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ 280 } 281 } 282 writeResourceEpilogue()283 private void writeResourceEpilogue() { 284 if (!resourceWritten) 285 return; 286 out.println("\t</resources>"); //$NON-NLS-1$ 287 resourceWritten = false; 288 currentOS = null; 289 } 290 writeResourcePrologue(String os, String ws, String arch)291 private void writeResourcePrologue(String os, String ws, String arch) { 292 if (os == null) 293 os = ws; 294 os = convertOS(os); 295 arch = convertArch(arch); 296 if (resourceWritten && osMatch(os) && archMatch(arch)) 297 return; 298 if (resourceWritten) 299 writeResourceEpilogue(); 300 out.println("\t<resources" + (os == null ? "" : " os=\"" + os + "\"") + (arch == null ? "" : " arch=\"" + arch + "\"") + ">"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$//$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$//$NON-NLS-7$ //$NON-NLS-8$ 301 resourceWritten = true; 302 currentOS = os; 303 currentArch = arch; 304 } 305 convertOS(String os)306 private String convertOS(String os) { 307 if (os == null) 308 return null; 309 if ("freebsd".equalsIgnoreCase(os)) //$NON-NLS-1$ 310 return "FreeBSD"; //$NON-NLS-1$ 311 if ("win32".equalsIgnoreCase(os)) //$NON-NLS-1$ 312 return "Windows"; //$NON-NLS-1$ 313 if ("macosx".equalsIgnoreCase(os)) //$NON-NLS-1$ 314 return "Mac"; //$NON-NLS-1$ 315 if ("linux".equalsIgnoreCase(os)) //$NON-NLS-1$ 316 return "Linux"; //$NON-NLS-1$ 317 if ("solaris".equalsIgnoreCase(os)) //$NON-NLS-1$ 318 return "Solaris"; //$NON-NLS-1$ 319 if ("hpux".equalsIgnoreCase(os)) //$NON-NLS-1$ 320 return "HP-UX"; //$NON-NLS-1$ 321 if ("aix".equalsIgnoreCase(os)) //$NON-NLS-1$ 322 return "AIX"; //$NON-NLS-1$ 323 return os; 324 } 325 osMatch(String os)326 private boolean osMatch(String os) { 327 if (os == currentOS) 328 return true; 329 if (os == null) 330 return false; 331 return os.equals(currentOS); 332 } 333 convertArch(String arch)334 private String convertArch(String arch) { 335 if (arch == null) 336 return null; 337 338 if ("x86_64".equals(arch))//$NON-NLS-1$ 339 return "x86_64"; //$NON-NLS-1$ 340 341 return arch; 342 } 343 archMatch(String arch)344 private boolean archMatch(String arch) { 345 if (arch == currentOS) 346 return true; 347 if (arch == null) 348 return false; 349 return arch.equals(currentArch); 350 } 351 processDescription(Attributes attributes)352 private void processDescription(Attributes attributes) { 353 // ignoring for now 354 } 355 processIncludes(Attributes attributes)356 private void processIncludes(Attributes attributes) throws IOException { 357 writePrologue(); 358 String inclusionId = attributes.getValue("id"); //$NON-NLS-1$ 359 String inclusionVersion = attributes.getValue("version"); //$NON-NLS-1$ 360 String name = attributes.getValue("name"); //$NON-NLS-1$ 361 String os = attributes.getValue("os"); //$NON-NLS-1$ 362 String ws = attributes.getValue("ws"); //$NON-NLS-1$ 363 String arch = attributes.getValue("arch"); //$NON-NLS-1$ 364 if (isValidEnvironment(os, ws, arch)) { 365 writeResourcePrologue(os, ws, arch); 366 out.print("\t\t<extension ");//$NON-NLS-1$ 367 if (name != null) 368 out.print("name=\"" + name + "\" "); //$NON-NLS-1$ //$NON-NLS-2$ 369 if (inclusionId != null) { 370 out.print("href=\"features/" + inclusionId); //$NON-NLS-1$ 371 if (inclusionVersion != null) 372 out.print('_' + inclusionVersion); 373 out.print(".jnlp\" "); //$NON-NLS-1$ 374 } 375 out.println("/>"); //$NON-NLS-1$ 376 } 377 } 378 processFeature(Attributes attributes)379 private void processFeature(Attributes attributes) { 380 id = attributes.getValue("id"); //$NON-NLS-1$ 381 version = attributes.getValue("version"); //$NON-NLS-1$ 382 label = processNLS(attributes.getValue("label")); //$NON-NLS-1$ 383 provider = processNLS(attributes.getValue("provider-name")); //$NON-NLS-1$ 384 } 385 386 /** 387 * Search for a human readable string in the feature.properties file(s) if 388 * the given string is a translateable key. 389 * 390 * @param string a translateable key or a normal string(nothing is done) 391 * 392 * @return a translateabled string or the given string if it is not a 393 * translateable key 394 */ processNLS(String string)395 private String processNLS(String string) { 396 if (string == null) 397 return null; 398 string = string.trim(); 399 if (!string.startsWith("%")) { //$NON-NLS-1$ 400 return string; 401 } 402 if (string.startsWith("%%")) { //$NON-NLS-1$ 403 return string.substring(1); 404 } 405 int index = string.indexOf(" "); //$NON-NLS-1$ 406 String key = index == -1 ? string : string.substring(0, index); 407 String dflt = index == -1 ? string : string.substring(index + 1); 408 if (nlsBundle == null) { 409 return dflt; 410 } 411 try { 412 return nlsBundle.getString(key.substring(1)); 413 } catch (MissingResourceException e) { 414 return dflt; 415 } 416 } 417 writePrologue()418 private void writePrologue() throws IOException { 419 if (out != null) 420 return; 421 if (destination == null) { 422 featureRoot.getParentFile(); 423 destination = featureRoot.getParent() + '/'; 424 } 425 if (destination.endsWith("/") || destination.endsWith("\\")) //$NON-NLS-1$ //$NON-NLS-2$ 426 destination = new File(featureRoot.getParentFile(), id + "_" + version + ".jnlp").getAbsolutePath(); //$NON-NLS-1$ //$NON-NLS-2$ 427 out = new PrintWriter(new BufferedOutputStream(new FileOutputStream(destination))); 428 writePrologue(); 429 out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); //$NON-NLS-1$ 430 out.print("<jnlp spec=\"1.0+\" "); //$NON-NLS-1$ 431 if (codebase != null) 432 out.print("codebase=\"" + codebase); //$NON-NLS-1$ 433 out.println("\">"); //$NON-NLS-1$ 434 out.println("\t<information>"); //$NON-NLS-1$ 435 if (label != null) 436 out.println("\t\t<title>" + label + "</title>"); //$NON-NLS-1$ //$NON-NLS-2$ 437 if (provider != null) 438 out.println("\t\t<vendor>" + provider + "</vendor>"); //$NON-NLS-1$ //$NON-NLS-2$ 439 if (description != null) 440 out.println("\t\t<description>" + description + "</description>"); //$NON-NLS-1$ //$NON-NLS-2$ 441 if (generateOfflineAllowed) 442 out.println("\t\t<offline-allowed/>"); //$NON-NLS-1$ 443 out.println("\t</information>"); //$NON-NLS-1$ 444 out.println("\t<security>"); //$NON-NLS-1$ 445 out.println("\t\t<all-permissions/>"); //$NON-NLS-1$ 446 out.println("\t</security>"); //$NON-NLS-1$ 447 out.println("\t<component-desc/>"); //$NON-NLS-1$ 448 out.println("\t<resources>"); //$NON-NLS-1$ 449 out.println("\t\t<j2se version=\"" + j2se + "\" />"); //$NON-NLS-1$ //$NON-NLS-2$ 450 out.println("\t</resources>"); //$NON-NLS-1$ 451 } 452 writeEpilogue()453 private void writeEpilogue() { 454 out.println("</jnlp>"); //$NON-NLS-1$ 455 } 456 isMatching(String candidateValues, String siteValues)457 private boolean isMatching(String candidateValues, String siteValues) { 458 if (candidateValues == null) 459 return true; 460 if (siteValues == null) 461 return false; 462 if ("*".equals(candidateValues)) //$NON-NLS-1$ 463 return true; 464 if ("".equals(candidateValues)) //$NON-NLS-1$ 465 return true; 466 StringTokenizer siteTokens = new StringTokenizer(siteValues, ","); //$NON-NLS-1$ 467 //$NON-NLS-1$ 468 while (siteTokens.hasMoreTokens()) { 469 StringTokenizer candidateTokens = new StringTokenizer(candidateValues, ","); //$NON-NLS-1$ 470 String siteValue = siteTokens.nextToken(); 471 while (candidateTokens.hasMoreTokens()) { 472 if (siteValue.equalsIgnoreCase(candidateTokens.nextToken())) 473 return true; 474 } 475 } 476 return false; 477 } 478 isValidEnvironment(String os, String ws, String arch)479 private boolean isValidEnvironment(String os, String ws, String arch) { 480 if (configs.length == 0) 481 return true; 482 for (int i = 0; i < configs.length; i++) { 483 if (isMatching(os, configs[i].getOs()) && isMatching(ws, configs[i].getWs()) && isMatching(arch, configs[i].getArch())) 484 return true; 485 } 486 return false; 487 } 488 setConfigInfo(String spec)489 private void setConfigInfo(String spec) { 490 if (spec != null && spec.startsWith("$")) { //$NON-NLS-1$ 491 configs = new Config[0]; 492 return; 493 } 494 if (spec == null) { 495 configs = new Config[] {Config.genericConfig()}; 496 return; 497 } 498 StringTokenizer tokens = new StringTokenizer(spec, "&"); //$NON-NLS-1$ 499 int configNbr = tokens.countTokens(); 500 ArrayList<Config> configInfos = new ArrayList<>(configNbr); 501 while (tokens.hasMoreElements()) { 502 String aConfig = tokens.nextToken(); 503 StringTokenizer configTokens = new StringTokenizer(aConfig, ","); //$NON-NLS-1$ 504 if (configTokens.countTokens() == 3) { 505 Config toAdd = new Config(configTokens.nextToken().trim(), configTokens.nextToken().trim(), configTokens.nextToken().trim()); 506 if (toAdd.equals(Config.genericConfig())) 507 toAdd = Config.genericConfig(); 508 configInfos.add(toAdd); 509 } 510 } 511 if (configInfos.size() == 0) 512 configInfos.add(Config.genericConfig()); 513 configs = configInfos.toArray(new Config[configInfos.size()]); 514 } 515 } 516