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