1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.sdklib.internal.repository; 18 19 import com.android.sdklib.internal.repository.Archive.Arch; 20 import com.android.sdklib.internal.repository.Archive.Os; 21 import com.android.sdklib.repository.SdkRepository; 22 23 import org.w3c.dom.Document; 24 import org.w3c.dom.Element; 25 import org.w3c.dom.NamedNodeMap; 26 import org.w3c.dom.Node; 27 import org.xml.sax.InputSource; 28 import org.xml.sax.SAXException; 29 30 import java.io.ByteArrayInputStream; 31 import java.io.FileNotFoundException; 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.net.URL; 35 import java.util.ArrayList; 36 import java.util.HashMap; 37 import java.util.regex.Pattern; 38 39 import javax.net.ssl.SSLKeyException; 40 import javax.xml.XMLConstants; 41 import javax.xml.parsers.DocumentBuilder; 42 import javax.xml.parsers.DocumentBuilderFactory; 43 import javax.xml.parsers.ParserConfigurationException; 44 import javax.xml.transform.stream.StreamSource; 45 import javax.xml.validation.Schema; 46 import javax.xml.validation.SchemaFactory; 47 import javax.xml.validation.Validator; 48 49 /** 50 * An sdk-repository source, i.e. a download site. 51 * It may be a full repository or an add-on only repository. 52 * A repository describes one or {@link Package}s available for download. 53 */ 54 public class RepoSource implements IDescription { 55 56 private String mUrl; 57 private final boolean mUserSource; 58 59 private Package[] mPackages; 60 private String mDescription; 61 private String mFetchError; 62 63 /** 64 * Constructs a new source for the given repository URL. 65 * @param url The source URL. Cannot be null. If the URL ends with a /, the default 66 * repository.xml filename will be appended automatically. 67 * @param userSource True if this a user source (add-ons & packages only.) 68 */ RepoSource(String url, boolean userSource)69 public RepoSource(String url, boolean userSource) { 70 71 // if the URL ends with a /, it must be "directory" resource, 72 // in which case we automatically add the default file that will 73 // looked for. This way it will be obvious to the user which 74 // resource we are actually trying to fetch. 75 if (url.endsWith("/")) { //$NON-NLS-1$ 76 url += SdkRepository.URL_DEFAULT_XML_FILE; 77 } 78 79 mUrl = url; 80 mUserSource = userSource; 81 setDefaultDescription(); 82 } 83 84 /** 85 * Two repo source are equal if they have the same userSource flag and the same URL. 86 */ 87 @Override equals(Object obj)88 public boolean equals(Object obj) { 89 if (obj instanceof RepoSource) { 90 RepoSource rs = (RepoSource) obj; 91 return rs.isUserSource() == this.isUserSource() && rs.getUrl().equals(this.getUrl()); 92 } 93 return false; 94 } 95 96 @Override hashCode()97 public int hashCode() { 98 return mUrl.hashCode() ^ Boolean.valueOf(mUserSource).hashCode(); 99 } 100 101 /** Returns true if this is a user source. We only load addon and extra packages 102 * from a user source and ignore the rest. */ isUserSource()103 public boolean isUserSource() { 104 return mUserSource; 105 } 106 107 /** Returns the URL of the repository.xml file for this source. */ getUrl()108 public String getUrl() { 109 return mUrl; 110 } 111 112 /** 113 * Returns the list of known packages found by the last call to load(). 114 * This is null when the source hasn't been loaded yet. 115 */ getPackages()116 public Package[] getPackages() { 117 return mPackages; 118 } 119 120 /** 121 * Clear the internal packages list. After this call, {@link #getPackages()} will return 122 * null till load() is called. 123 */ clearPackages()124 public void clearPackages() { 125 mPackages = null; 126 } 127 getShortDescription()128 public String getShortDescription() { 129 return mUrl; 130 } 131 getLongDescription()132 public String getLongDescription() { 133 return mDescription == null ? "" : mDescription; //$NON-NLS-1$ 134 } 135 136 /** 137 * Returns the last fetch error description. 138 * If there was no error, returns null. 139 */ getFetchError()140 public String getFetchError() { 141 return mFetchError; 142 } 143 144 /** 145 * Tries to fetch the repository index for the given URL. 146 */ load(ITaskMonitor monitor, boolean forceHttp)147 public void load(ITaskMonitor monitor, boolean forceHttp) { 148 149 monitor.setProgressMax(4); 150 151 setDefaultDescription(); 152 153 String url = mUrl; 154 if (forceHttp) { 155 url = url.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$ 156 } 157 158 monitor.setDescription("Fetching %1$s", url); 159 monitor.incProgress(1); 160 161 mFetchError = null; 162 Exception[] exception = new Exception[] { null }; 163 ByteArrayInputStream xml = fetchUrl(url, exception); 164 Document validatedDoc = null; 165 String validatedUri = null; 166 if (xml != null) { 167 monitor.setDescription("Validate XML"); 168 String uri = validateXml(xml, url, monitor); 169 if (uri != null) { 170 validatedDoc = getDocument(xml, monitor); 171 validatedUri = uri; 172 } else { 173 validatedDoc = findAlternateToolsXml(xml); 174 validatedUri = SdkRepository.NS_SDK_REPOSITORY; 175 } 176 } 177 178 // If we failed the first time and the URL doesn't explicitly end with 179 // our filename, make another tentative after changing the URL. 180 if (validatedDoc == null && !url.endsWith(SdkRepository.URL_DEFAULT_XML_FILE)) { 181 if (!url.endsWith("/")) { //$NON-NLS-1$ 182 url += "/"; //$NON-NLS-1$ 183 } 184 url += SdkRepository.URL_DEFAULT_XML_FILE; 185 186 xml = fetchUrl(url, exception); 187 if (xml != null) { 188 String uri = validateXml(xml, url, monitor); 189 if (uri != null) { 190 validatedDoc = getDocument(xml, monitor); 191 validatedUri = uri; 192 } else { 193 validatedDoc = findAlternateToolsXml(xml); 194 validatedUri = SdkRepository.NS_SDK_REPOSITORY; 195 } 196 } 197 198 if (validatedDoc != null) { 199 // If the second tentative succeeded, indicate it in the console 200 // with the URL that worked. 201 monitor.setResult("Repository found at %1$s", url); 202 203 // Keep the modified URL 204 mUrl = url; 205 } 206 } 207 208 // If any exception was handled during the URL fetch, display it now. 209 if (exception[0] != null) { 210 mFetchError = "Failed to fetch URL"; 211 212 String reason = null; 213 if (exception[0] instanceof FileNotFoundException) { 214 // FNF has no useful getMessage, so we need to special handle it. 215 reason = "File not found"; 216 mFetchError += ": " + reason; 217 } else if (exception[0] instanceof SSLKeyException) { 218 // That's a common error and we have a pref for it. 219 reason = "HTTPS SSL error. You might want to force download through HTTP in the settings."; 220 mFetchError += ": HTTPS SSL error"; 221 } else if (exception[0].getMessage() != null) { 222 reason = exception[0].getMessage(); 223 } else { 224 // We don't know what's wrong. Let's give the exception class at least. 225 reason = String.format("Unknown (%1$s)", exception[0].getClass().getName()); 226 } 227 228 monitor.setResult("Failed to fetch URL %1$s, reason: %2$s", url, reason); 229 } 230 231 // Stop here if we failed to validate the XML. We don't want to load it. 232 if (validatedDoc == null) { 233 return; 234 } 235 236 monitor.incProgress(1); 237 238 if (xml != null) { 239 monitor.setDescription("Parse XML"); 240 monitor.incProgress(1); 241 parsePackages(validatedDoc, validatedUri, monitor); 242 if (mPackages == null || mPackages.length == 0) { 243 mDescription += "\nNo packages found."; 244 } else if (mPackages.length == 1) { 245 mDescription += "\nOne package found."; 246 } else { 247 mDescription += String.format("\n%1$d packages found.", mPackages.length); 248 } 249 } 250 251 // done 252 monitor.incProgress(1); 253 } 254 setDefaultDescription()255 private void setDefaultDescription() { 256 if (mUserSource) { 257 mDescription = String.format("Add-on Source: %1$s", mUrl); 258 } else { 259 mDescription = String.format("SDK Source: %1$s", mUrl); 260 } 261 } 262 263 /** 264 * Fetches the document at the given URL and returns it as a string. 265 * Returns null if anything wrong happens and write errors to the monitor. 266 * 267 * References: 268 * Java URL Connection: http://java.sun.com/docs/books/tutorial/networking/urls/readingWriting.html 269 * Java URL Reader: http://java.sun.com/docs/books/tutorial/networking/urls/readingURL.html 270 * Java set Proxy: http://java.sun.com/docs/books/tutorial/networking/urls/_setProxy.html 271 */ fetchUrl(String urlString, Exception[] outException)272 private ByteArrayInputStream fetchUrl(String urlString, Exception[] outException) { 273 URL url; 274 try { 275 url = new URL(urlString); 276 277 InputStream is = null; 278 279 int inc = 65536; 280 int curr = 0; 281 byte[] result = new byte[inc]; 282 283 try { 284 is = url.openStream(); 285 286 int n; 287 while ((n = is.read(result, curr, result.length - curr)) != -1) { 288 curr += n; 289 if (curr == result.length) { 290 byte[] temp = new byte[curr + inc]; 291 System.arraycopy(result, 0, temp, 0, curr); 292 result = temp; 293 } 294 } 295 296 return new ByteArrayInputStream(result, 0, curr); 297 298 } finally { 299 if (is != null) { 300 try { 301 is.close(); 302 } catch (IOException e) { 303 // pass 304 } 305 } 306 } 307 308 } catch (Exception e) { 309 outException[0] = e; 310 } 311 312 return null; 313 } 314 315 /** 316 * Validates this XML against one of the possible SDK Repository schema, starting 317 * by the most recent one. 318 * If the XML was correctly validated, returns the schema that worked. 319 * If no schema validated the XML, returns null. 320 */ validateXml(ByteArrayInputStream xml, String url, ITaskMonitor monitor)321 private String validateXml(ByteArrayInputStream xml, String url, ITaskMonitor monitor) { 322 323 String lastError = null; 324 String extraError = null; 325 for (int version = SdkRepository.NS_LATEST_VERSION; version >= 1; version--) { 326 try { 327 Validator validator = getValidator(version); 328 329 if (validator == null) { 330 lastError = "XML verification failed for %1$s.\nNo suitable XML Schema Validator could be found in your Java environment. Please consider updating your version of Java."; 331 continue; 332 } 333 334 xml.reset(); 335 // Validation throws a bunch of possible Exceptions on failure. 336 validator.validate(new StreamSource(xml)); 337 return SdkRepository.getSchemaUri(version); 338 339 } catch (Exception e) { 340 lastError = "XML verification failed for %1$s.\nError: %2$s"; 341 extraError = e.getMessage(); 342 if (extraError == null) { 343 extraError = e.getClass().getName(); 344 } 345 } 346 } 347 348 if (lastError != null) { 349 monitor.setResult(lastError, url, extraError); 350 } 351 return null; 352 } 353 354 /** 355 * The purpose of this method is to support forward evolution of our schema. 356 * <p/> 357 * At this point, we know that xml does not point to any schema that this version of 358 * the tool know how to process, so it's not one of the possible 1..N versions of our 359 * XSD schema. 360 * <p/> 361 * We thus try to interpret the byte stream as a possible XML stream. It may not be 362 * one at all in the first place. If it looks anything line an XML schema, we try to 363 * find its <tool> elements. If we find any, we recreate a suitable document 364 * that conforms to what we expect from our XSD schema with only those elements. 365 * To be valid, the <tool> element must have at least one <archive> 366 * compatible with this platform. 367 * 368 * If we don't find anything suitable, we drop the whole thing. 369 * 370 * @param xml The input XML stream. Can be null. 371 * @return Either a new XML document conforming to our schema with at least one <tool> 372 * element or null. 373 */ findAlternateToolsXml(InputStream xml)374 protected Document findAlternateToolsXml(InputStream xml) { 375 // Note: protected for unit-test access 376 377 if (xml == null) { 378 return null; 379 } 380 381 // Reset the stream if it supports that operation. 382 // At runtime we use a ByteArrayInputStream which can be reset; however for unit tests 383 // we use a FileInputStream that doesn't support resetting and is read-once. 384 try { 385 xml.reset(); 386 } catch (IOException e1) { 387 // ignore if not supported 388 } 389 390 // Get an XML document 391 392 Document oldDoc = null; 393 Document newDoc = null; 394 try { 395 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 396 factory.setIgnoringComments(false); 397 factory.setValidating(false); 398 399 // Parse the old document using a non namespace aware builder 400 factory.setNamespaceAware(false); 401 DocumentBuilder builder = factory.newDocumentBuilder(); 402 oldDoc = builder.parse(xml); 403 404 // Prepare a new document using a namespace aware builder 405 factory.setNamespaceAware(true); 406 builder = factory.newDocumentBuilder(); 407 newDoc = builder.newDocument(); 408 409 } catch (Exception e) { 410 // Failed to get builder factor 411 // Failed to create XML document builder 412 // Failed to parse XML document 413 // Failed to read XML document 414 } 415 416 if (oldDoc == null || newDoc == null) { 417 return null; 418 } 419 420 421 // Check the root element is an xsd-schema with at least the following properties: 422 // <sdk:sdk-repository 423 // xmlns:sdk="http://schemas.android.com/sdk/android/repository/$N"> 424 // 425 // Note that we don't have namespace support enabled, we just do it manually. 426 427 Pattern nsPattern = Pattern.compile(SdkRepository.NS_SDK_REPOSITORY_PATTERN); 428 429 Node oldRoot = null; 430 String prefix = null; 431 for (Node child = oldDoc.getFirstChild(); child != null; child = child.getNextSibling()) { 432 if (child.getNodeType() == Node.ELEMENT_NODE) { 433 prefix = null; 434 String name = child.getNodeName(); 435 int pos = name.indexOf(':'); 436 if (pos > 0 && pos < name.length() - 1) { 437 prefix = name.substring(0, pos); 438 name = name.substring(pos + 1); 439 } 440 if (SdkRepository.NODE_SDK_REPOSITORY.equals(name)) { 441 NamedNodeMap attrs = child.getAttributes(); 442 String xmlns = "xmlns"; //$NON-NLS-1$ 443 if (prefix != null) { 444 xmlns += ":" + prefix; //$NON-NLS-1$ 445 } 446 Node attr = attrs.getNamedItem(xmlns); 447 if (attr != null) { 448 String uri = attr.getNodeValue(); 449 if (uri != null && nsPattern.matcher(uri).matches()) { 450 oldRoot = child; 451 break; 452 } 453 } 454 } 455 } 456 } 457 458 // we must have found the root node, and it must have an XML namespace prefix. 459 if (oldRoot == null || prefix == null || prefix.length() == 0) { 460 return null; 461 } 462 463 final String ns = SdkRepository.NS_SDK_REPOSITORY; 464 Element newRoot = newDoc.createElementNS(ns, SdkRepository.NODE_SDK_REPOSITORY); 465 newRoot.setPrefix(prefix); 466 newDoc.appendChild(newRoot); 467 int numTool = 0; 468 469 // Find an inner <tool> node and extract its required parameters 470 471 Node tool = null; 472 while ((tool = findChild(oldRoot, tool, prefix, SdkRepository.NODE_TOOL)) != null) { 473 // To be valid, the tool element must have: 474 // - a <revision> element with a number 475 // - an optional <uses-license> node, which we'll skip right now. 476 // (if we add it later, we must find the license declaration element too) 477 // - an <archives> element with one or more <archive> elements inside 478 // - one of the <archive> elements must have an "os" and "arch" attributes 479 // compatible with the current platform. Only keep the first such element found. 480 // - the <archive> element must contain a <size>, a <checksum> and a <url>. 481 482 try { 483 Node revision = findChild(tool, null, prefix, SdkRepository.NODE_REVISION); 484 Node archives = findChild(tool, null, prefix, SdkRepository.NODE_ARCHIVES); 485 486 if (revision == null || archives == null) { 487 continue; 488 } 489 490 int rev = 0; 491 try { 492 String content = revision.getTextContent(); 493 content = content.trim(); 494 rev = Integer.parseInt(content); 495 if (rev < 1) { 496 continue; 497 } 498 } catch (NumberFormatException ignore) { 499 continue; 500 } 501 502 Element newTool = newDoc.createElementNS(ns, SdkRepository.NODE_TOOL); 503 newTool.setPrefix(prefix); 504 appendChild(newTool, ns, prefix, 505 SdkRepository.NODE_REVISION, Integer.toString(rev)); 506 Element newArchives = appendChild(newTool, ns, prefix, 507 SdkRepository.NODE_ARCHIVES, null); 508 int numArchives = 0; 509 510 Node archive = null; 511 while ((archive = findChild(archives, 512 archive, 513 prefix, 514 SdkRepository.NODE_ARCHIVE)) != null) { 515 try { 516 Os os = (Os) XmlParserUtils.getEnumAttribute(archive, 517 SdkRepository.ATTR_OS, 518 Os.values(), 519 null /*default*/); 520 Arch arch = (Arch) XmlParserUtils.getEnumAttribute(archive, 521 SdkRepository.ATTR_ARCH, 522 Arch.values(), 523 Arch.ANY); 524 if (os == null || !os.isCompatible() || 525 arch == null || !arch.isCompatible()) { 526 continue; 527 } 528 529 Node node = findChild(archive, null, prefix, SdkRepository.NODE_URL); 530 String url = node == null ? null : node.getTextContent().trim(); 531 if (url == null || url.length() == 0) { 532 continue; 533 } 534 535 node = findChild(archive, null, prefix, SdkRepository.NODE_SIZE); 536 long size = 0; 537 try { 538 size = Long.parseLong(node.getTextContent()); 539 } catch (Exception e) { 540 // pass 541 } 542 if (size < 1) { 543 continue; 544 } 545 546 node = findChild(archive, null, prefix, SdkRepository.NODE_CHECKSUM); 547 // double check that the checksum element contains a type=sha1 attribute 548 if (node == null) { 549 continue; 550 } 551 NamedNodeMap attrs = node.getAttributes(); 552 Node typeNode = attrs.getNamedItem(SdkRepository.ATTR_TYPE); 553 if (typeNode == null || 554 !SdkRepository.ATTR_TYPE.equals(typeNode.getNodeName()) || 555 !SdkRepository.SHA1_TYPE.equals(typeNode.getNodeValue())) { 556 continue; 557 } 558 String sha1 = node == null ? null : node.getTextContent().trim(); 559 if (sha1 == null || sha1.length() != SdkRepository.SHA1_CHECKSUM_LEN) { 560 continue; 561 } 562 563 // Use that archive for the new tool element 564 Element ar = appendChild(newArchives, ns, prefix, 565 SdkRepository.NODE_ARCHIVE, null); 566 ar.setAttributeNS(ns, SdkRepository.ATTR_OS, os.getXmlName()); 567 ar.setAttributeNS(ns, SdkRepository.ATTR_ARCH, arch.getXmlName()); 568 569 appendChild(ar, ns, prefix, SdkRepository.NODE_URL, url); 570 appendChild(ar, ns, prefix, SdkRepository.NODE_SIZE, Long.toString(size)); 571 Element cs = appendChild(ar, ns, prefix, SdkRepository.NODE_CHECKSUM, sha1); 572 cs.setAttributeNS(ns, SdkRepository.ATTR_TYPE, SdkRepository.SHA1_TYPE); 573 574 numArchives++; 575 576 } catch (Exception ignore1) { 577 // pass 578 } 579 } // while <archive> 580 581 if (numArchives > 0) { 582 newRoot.appendChild(newTool); 583 numTool++; 584 } 585 } catch (Exception ignore2) { 586 // pass 587 } 588 } // while <tool> 589 590 return numTool > 0 ? newDoc : null; 591 } 592 593 /** 594 * Helper method used by {@link #findAlternateToolsXml(InputStream)} to find a given 595 * element child in a root XML node. 596 */ findChild(Node rootNode, Node after, String prefix, String nodeName)597 private Node findChild(Node rootNode, Node after, String prefix, String nodeName) { 598 nodeName = prefix + ":" + nodeName; 599 Node child = after == null ? rootNode.getFirstChild() : after.getNextSibling(); 600 for(; child != null; child = child.getNextSibling()) { 601 if (child.getNodeType() == Node.ELEMENT_NODE && nodeName.equals(child.getNodeName())) { 602 return child; 603 } 604 } 605 return null; 606 } 607 608 /** 609 * Helper method used by {@link #findAlternateToolsXml(InputStream)} to create a new 610 * XML element into a parent element. 611 */ appendChild(Element rootNode, String namespaceUri, String prefix, String nodeName, String nodeValue)612 private Element appendChild(Element rootNode, String namespaceUri, 613 String prefix, String nodeName, 614 String nodeValue) { 615 Element node = rootNode.getOwnerDocument().createElementNS(namespaceUri, nodeName); 616 node.setPrefix(prefix); 617 if (nodeValue != null) { 618 node.setTextContent(nodeValue); 619 } 620 rootNode.appendChild(node); 621 return node; 622 } 623 624 625 /** 626 * Helper method that returns a validator for our XSD, or null if the current Java 627 * implementation can't process XSD schemas. 628 * 629 * @param version The version of the XML Schema. 630 * See {@link SdkRepository#getXsdStream(int)} 631 */ getValidator(int version)632 private Validator getValidator(int version) throws SAXException { 633 InputStream xsdStream = SdkRepository.getXsdStream(version); 634 SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 635 636 if (factory == null) { 637 return null; 638 } 639 640 // This may throw a SAX Exception if the schema itself is not a valid XSD 641 Schema schema = factory.newSchema(new StreamSource(xsdStream)); 642 643 Validator validator = schema == null ? null : schema.newValidator(); 644 645 return validator; 646 } 647 648 649 /** 650 * Parse all packages defined in the SDK Repository XML and creates 651 * a new mPackages array with them. 652 */ parsePackages(Document doc, String nsUri, ITaskMonitor monitor)653 protected boolean parsePackages(Document doc, String nsUri, ITaskMonitor monitor) { 654 // protected for unit-test acces 655 656 assert doc != null; 657 658 Node root = getFirstChild(doc, nsUri, SdkRepository.NODE_SDK_REPOSITORY); 659 if (root != null) { 660 661 ArrayList<Package> packages = new ArrayList<Package>(); 662 663 // Parse license definitions 664 HashMap<String, String> licenses = new HashMap<String, String>(); 665 for (Node child = root.getFirstChild(); 666 child != null; 667 child = child.getNextSibling()) { 668 if (child.getNodeType() == Node.ELEMENT_NODE && 669 nsUri.equals(child.getNamespaceURI()) && 670 child.getLocalName().equals(SdkRepository.NODE_LICENSE)) { 671 Node id = child.getAttributes().getNamedItem(SdkRepository.ATTR_ID); 672 if (id != null) { 673 licenses.put(id.getNodeValue(), child.getTextContent()); 674 } 675 } 676 } 677 678 // Parse packages 679 for (Node child = root.getFirstChild(); 680 child != null; 681 child = child.getNextSibling()) { 682 if (child.getNodeType() == Node.ELEMENT_NODE && 683 nsUri.equals(child.getNamespaceURI())) { 684 String name = child.getLocalName(); 685 Package p = null; 686 687 try { 688 // We can load addon and extra packages from all sources, either 689 // internal or user sources. 690 if (SdkRepository.NODE_ADD_ON.equals(name)) { 691 p = new AddonPackage(this, child, licenses); 692 693 } else if (SdkRepository.NODE_EXTRA.equals(name)) { 694 p = new ExtraPackage(this, child, licenses); 695 696 } else if (!mUserSource) { 697 // We only load platform, doc and tool packages from internal 698 // sources, never from user sources. 699 if (SdkRepository.NODE_PLATFORM.equals(name)) { 700 p = new PlatformPackage(this, child, licenses); 701 } else if (SdkRepository.NODE_DOC.equals(name)) { 702 p = new DocPackage(this, child, licenses); 703 } else if (SdkRepository.NODE_TOOL.equals(name)) { 704 p = new ToolPackage(this, child, licenses); 705 } 706 } 707 708 if (p != null) { 709 packages.add(p); 710 monitor.setDescription("Found %1$s", p.getShortDescription()); 711 } 712 } catch (Exception e) { 713 // Ignore invalid packages 714 } 715 } 716 } 717 718 mPackages = packages.toArray(new Package[packages.size()]); 719 720 return true; 721 } 722 723 return false; 724 } 725 726 /** 727 * Returns the first child element with the given XML local name. 728 * If xmlLocalName is null, returns the very first child element. 729 */ getFirstChild(Node node, String nsUri, String xmlLocalName)730 private Node getFirstChild(Node node, String nsUri, String xmlLocalName) { 731 732 for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) { 733 if (child.getNodeType() == Node.ELEMENT_NODE && 734 nsUri.equals(child.getNamespaceURI())) { 735 if (xmlLocalName == null || child.getLocalName().equals(xmlLocalName)) { 736 return child; 737 } 738 } 739 } 740 741 return null; 742 } 743 744 /** 745 * Takes an XML document as a string as parameter and returns a DOM for it. 746 * 747 * On error, returns null and prints a (hopefully) useful message on the monitor. 748 */ getDocument(ByteArrayInputStream xml, ITaskMonitor monitor)749 private Document getDocument(ByteArrayInputStream xml, ITaskMonitor monitor) { 750 try { 751 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 752 factory.setIgnoringComments(true); 753 factory.setNamespaceAware(true); 754 755 DocumentBuilder builder = factory.newDocumentBuilder(); 756 xml.reset(); 757 Document doc = builder.parse(new InputSource(xml)); 758 759 return doc; 760 } catch (ParserConfigurationException e) { 761 monitor.setResult("Failed to create XML document builder"); 762 763 } catch (SAXException e) { 764 monitor.setResult("Failed to parse XML document"); 765 766 } catch (IOException e) { 767 monitor.setResult("Failed to read XML document"); 768 } 769 770 return null; 771 } 772 } 773