2 * Copyright (C) 2009 The Android Open Source Project
\r
4 * Licensed under the Apache License, Version 2.0 (the "License");
\r
5 * you may not use this file except in compliance with the License.
\r
6 * You may obtain a copy of the License at
\r
8 * http://www.apache.org/licenses/LICENSE-2.0
\r
10 * Unless required by applicable law or agreed to in writing, software
\r
11 * distributed under the License is distributed on an "AS IS" BASIS,
\r
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
13 * See the License for the specific language governing permissions and
\r
14 * limitations under the License.
\r
17 package com.android.sdklib.internal.repository;
\r
19 import com.android.annotations.Nullable;
\r
20 import com.android.annotations.VisibleForTesting;
\r
21 import com.android.annotations.VisibleForTesting.Visibility;
\r
22 import com.android.sdklib.internal.repository.UrlOpener.CanceledByUserException;
\r
23 import com.android.sdklib.repository.RepoConstants;
\r
24 import com.android.sdklib.repository.SdkAddonConstants;
\r
25 import com.android.sdklib.repository.SdkRepoConstants;
\r
27 import org.w3c.dom.Document;
\r
28 import org.w3c.dom.NamedNodeMap;
\r
29 import org.w3c.dom.Node;
\r
30 import org.xml.sax.ErrorHandler;
\r
31 import org.xml.sax.InputSource;
\r
32 import org.xml.sax.SAXException;
\r
33 import org.xml.sax.SAXParseException;
\r
35 import java.io.ByteArrayInputStream;
\r
36 import java.io.FileNotFoundException;
\r
37 import java.io.IOException;
\r
38 import java.io.InputStream;
\r
39 import java.net.MalformedURLException;
\r
40 import java.net.URL;
\r
41 import java.util.ArrayList;
\r
42 import java.util.Arrays;
\r
43 import java.util.HashMap;
\r
44 import java.util.regex.Matcher;
\r
45 import java.util.regex.Pattern;
\r
47 import javax.net.ssl.SSLKeyException;
\r
48 import javax.xml.XMLConstants;
\r
49 import javax.xml.parsers.DocumentBuilder;
\r
50 import javax.xml.parsers.DocumentBuilderFactory;
\r
51 import javax.xml.parsers.ParserConfigurationException;
\r
52 import javax.xml.transform.stream.StreamSource;
\r
53 import javax.xml.validation.Schema;
\r
54 import javax.xml.validation.SchemaFactory;
\r
55 import javax.xml.validation.Validator;
\r
58 * An sdk-addon or sdk-repository source, i.e. a download site.
\r
59 * It may be a full repository or an add-on only repository.
\r
60 * A repository describes one or {@link Package}s available for download.
\r
62 public abstract class SdkSource implements IDescription, Comparable<SdkSource> {
\r
64 private String mUrl;
\r
66 private Package[] mPackages;
\r
67 private String mDescription;
\r
68 private String mFetchError;
\r
69 private final String mUiName;
\r
72 * Constructs a new source for the given repository URL.
\r
73 * @param url The source URL. Cannot be null. If the URL ends with a /, the default
\r
74 * repository.xml filename will be appended automatically.
\r
75 * @param uiName The UI-visible name of the source. Can be null.
\r
77 public SdkSource(String url, String uiName) {
\r
79 // URLs should not be null and should not have whitespace.
\r
85 // if the URL ends with a /, it must be "directory" resource,
\r
86 // in which case we automatically add the default file that will
\r
87 // looked for. This way it will be obvious to the user which
\r
88 // resource we are actually trying to fetch.
\r
89 if (url.endsWith("/")) { //$NON-NLS-1$
\r
90 String[] names = getDefaultXmlFileUrls();
\r
91 if (names.length > 0) {
\r
98 setDefaultDescription();
\r
102 * Returns true if this is an addon source.
\r
103 * We only load addons and extras from these sources.
\r
105 public abstract boolean isAddonSource();
\r
108 * Returns the basename of the default URLs to try to download the
\r
110 * E.g. this is typically SdkRepoConstants.URL_DEFAULT_XML_FILE
\r
111 * or SdkAddonConstants.URL_DEFAULT_XML_FILE
\r
113 protected abstract String[] getDefaultXmlFileUrls();
\r
115 /** Returns SdkRepoConstants.NS_LATEST_VERSION or SdkAddonConstants.NS_LATEST_VERSION. */
\r
116 protected abstract int getNsLatestVersion();
\r
118 /** Returns SdkRepoConstants.NS_URI or SdkAddonConstants.NS_URI. */
\r
119 protected abstract String getNsUri();
\r
121 /** Returns SdkRepoConstants.NS_PATTERN or SdkAddonConstants.NS_PATTERN. */
\r
122 protected abstract String getNsPattern();
\r
124 /** Returns SdkRepoConstants.getSchemaUri() or SdkAddonConstants.getSchemaUri(). */
\r
125 protected abstract String getSchemaUri(int version);
\r
127 /* Returns SdkRepoConstants.NODE_SDK_REPOSITORY or SdkAddonConstants.NODE_SDK_ADDON. */
\r
128 protected abstract String getRootElementName();
\r
130 /** Returns SdkRepoConstants.getXsdStream() or SdkAddonConstants.getXsdStream(). */
\r
131 protected abstract InputStream getXsdStream(int version);
\r
134 * In case we fail to load an XML, examine the XML to see if it matches a <b>future</b>
\r
135 * schema that as at least a <code>tools</code> node that we could load to update the
\r
138 * @param xml The input XML stream. Can be null.
\r
139 * @return Null on failure, otherwise returns an XML DOM with just the tools we
\r
140 * need to update this SDK Manager.
\r
141 * @null Can return null on failure.
\r
143 protected abstract Document findAlternateToolsXml(@Nullable InputStream xml)
\r
144 throws IOException;
\r
147 * Two repo source are equal if they have the same URL.
\r
150 public boolean equals(Object obj) {
\r
151 if (obj instanceof SdkSource) {
\r
152 SdkSource rs = (SdkSource) obj;
\r
153 return rs.getUrl().equals(this.getUrl());
\r
159 public int hashCode() {
\r
160 return mUrl.hashCode();
\r
164 * Implementation of the {@link Comparable} interface.
\r
165 * Simply compares the URL using the string's default ordering.
\r
167 public int compareTo(SdkSource rhs) {
\r
168 return this.getUrl().compareTo(rhs.getUrl());
\r
172 * Returns the UI-visible name of the source. Can be null.
\r
174 public String getUiName() {
\r
178 /** Returns the URL of the XML file for this source. */
\r
179 public String getUrl() {
\r
184 * Returns the list of known packages found by the last call to load().
\r
185 * This is null when the source hasn't been loaded yet.
\r
187 public Package[] getPackages() {
\r
191 @VisibleForTesting(visibility=Visibility.PRIVATE)
\r
192 protected void setPackages(Package[] packages) {
\r
193 mPackages = packages;
\r
195 if (mPackages != null) {
\r
196 // Order the packages.
\r
197 Arrays.sort(mPackages, null);
\r
202 * Clear the internal packages list. After this call, {@link #getPackages()} will return
\r
203 * null till load() is called.
\r
205 public void clearPackages() {
\r
210 * Returns the short description of the source, if not null.
\r
211 * Otherwise returns the default Object toString result.
\r
213 * This is mostly helpful for debugging.
\r
214 * For UI display, use the {@link IDescription} interface.
\r
217 public String toString() {
\r
218 String s = getShortDescription();
\r
222 return super.toString();
\r
225 public String getShortDescription() {
\r
227 if (mUiName != null && mUiName.length() > 0) {
\r
229 String host = "malformed URL";
\r
232 URL u = new URL(mUrl);
\r
233 host = u.getHost();
\r
234 } catch (MalformedURLException e) {
\r
237 return String.format("%1$s (%2$s)", mUiName, host);
\r
243 public String getLongDescription() {
\r
244 // Note: in a normal workflow, mDescription is filled by setDefaultDescription().
\r
245 // However for packages made by unit tests or such, this can be null.
\r
246 return mDescription == null ? "" : mDescription; //$NON-NLS-1$
\r
250 * Returns the last fetch error description.
\r
251 * If there was no error, returns null.
\r
253 public String getFetchError() {
\r
254 return mFetchError;
\r
258 * Tries to fetch the repository index for the given URL.
\r
260 public void load(ITaskMonitor monitor, boolean forceHttp) {
\r
262 monitor.setProgressMax(7);
\r
264 setDefaultDescription();
\r
268 url = url.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
\r
271 monitor.setDescription("Fetching URL: %1$s", url);
\r
272 monitor.incProgress(1);
\r
274 mFetchError = null;
\r
275 Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
\r
276 String[] validationError = new String[] { null };
\r
277 Exception[] exception = new Exception[] { null };
\r
278 Document validatedDoc = null;
\r
279 boolean usingAlternateXml = false;
\r
280 boolean usingAlternateUrl = false;
\r
281 String validatedUri = null;
\r
283 String[] defaultNames = getDefaultXmlFileUrls();
\r
284 String firstDefaultName = defaultNames.length > 0 ? defaultNames[0] : "";
\r
286 InputStream xml = fetchUrl(url, monitor.createSubMonitor(1), exception);
\r
288 int version = getXmlSchemaVersion(xml);
\r
289 if (version == 0) {
\r
294 // FIXME: this is a quick fix to support an alternate upgrade path.
\r
295 // The whole logic below needs to be updated.
\r
296 if (xml == null && defaultNames.length > 0) {
\r
297 ITaskMonitor subMonitor = monitor.createSubMonitor(1);
\r
298 subMonitor.setProgressMax(defaultNames.length);
\r
300 String baseUrl = url;
\r
301 if (!baseUrl.endsWith("/")) {
\r
302 int pos = baseUrl.lastIndexOf('/');
\r
304 baseUrl = baseUrl.substring(0, pos + 1);
\r
308 for(String name : defaultNames) {
\r
309 String newUrl = baseUrl + name;
\r
310 if (newUrl.equals(url)) {
\r
313 xml = fetchUrl(newUrl, subMonitor.createSubMonitor(1), exception);
\r
315 int version = getXmlSchemaVersion(xml);
\r
316 if (version == 0) {
\r
320 subMonitor.incProgress(
\r
321 subMonitor.getProgressMax() - subMonitor.getProgress());
\r
327 monitor.incProgress(1);
\r
330 // If the original URL can't be fetched
\r
331 // and the URL doesn't explicitly end with our filename
\r
332 // and it wasn't an HTTP authentication operation canceled by the user
\r
333 // then make another tentative after changing the URL.
\r
335 && !url.endsWith(firstDefaultName)
\r
336 && !(exception[0] instanceof CanceledByUserException)) {
\r
337 if (!url.endsWith("/")) { //$NON-NLS-1$
\r
338 url += "/"; //$NON-NLS-1$
\r
340 url += firstDefaultName;
\r
342 xml = fetchUrl(url, monitor.createSubMonitor(1), exception);
\r
343 usingAlternateUrl = true;
\r
345 monitor.incProgress(1);
\r
348 // FIXME this needs to revisited.
\r
350 monitor.setDescription("Validate XML: %1$s", url);
\r
352 ITaskMonitor subMonitor = monitor.createSubMonitor(2);
\r
353 subMonitor.setProgressMax(2);
\r
354 for (int tryOtherUrl = 0; tryOtherUrl < 2; tryOtherUrl++) {
\r
355 // Explore the XML to find the potential XML schema version
\r
356 int version = getXmlSchemaVersion(xml);
\r
358 if (version >= 1 && version <= getNsLatestVersion()) {
\r
359 // This should be a version we can handle. Try to validate it
\r
360 // and report any error as invalid XML syntax,
\r
362 String uri = validateXml(xml, url, version, validationError, validatorFound);
\r
364 // Validation was successful
\r
365 validatedDoc = getDocument(xml, monitor);
\r
366 validatedUri = uri;
\r
368 if (usingAlternateUrl && validatedDoc != null) {
\r
369 // If the second tentative succeeded, indicate it in the console
\r
370 // with the URL that worked.
\r
371 monitor.log("Repository found at %1$s", url);
\r
373 // Keep the modified URL
\r
376 } else if (validatorFound[0].equals(Boolean.FALSE)) {
\r
377 // Validation failed because this JVM lacks a proper XML Validator
\r
378 mFetchError = validationError[0];
\r
380 // We got a validator but validation failed. We know there's
\r
381 // what looks like a suitable root element with a suitable XMLNS
\r
382 // so it must be a genuine error of an XML not conforming to the schema.
\r
384 } else if (version > getNsLatestVersion()) {
\r
385 // The schema used is more recent than what is supported by this tool.
\r
386 // Tell the user to upgrade, pointing him to the right version of the tool
\r
390 validatedDoc = findAlternateToolsXml(xml);
\r
391 } catch (IOException e) {
\r
392 // Failed, will be handled below.
\r
394 if (validatedDoc != null) {
\r
395 validationError[0] = null; // remove error from XML validation
\r
396 validatedUri = getNsUri();
\r
397 usingAlternateXml = true;
\r
400 } else if (version < 1 && tryOtherUrl == 0 && !usingAlternateUrl) {
\r
401 // This is obviously not one of our documents.
\r
402 mFetchError = String.format(
\r
403 "Failed to validate the XML for the repository at URL '%1$s'",
\r
406 // If we haven't already tried the alternate URL, let's do it now.
\r
407 // We don't capture any fetch exception that happen during the second
\r
408 // fetch in order to avoid hidding any previous fetch errors.
\r
409 if (!url.endsWith(firstDefaultName)) {
\r
410 if (!url.endsWith("/")) { //$NON-NLS-1$
\r
411 url += "/"; //$NON-NLS-1$
\r
413 url += firstDefaultName;
\r
415 xml = fetchUrl(url, subMonitor.createSubMonitor(1), null /* outException */);
\r
416 subMonitor.incProgress(1);
\r
417 // Loop to try the alternative document
\r
419 usingAlternateUrl = true;
\r
423 } else if (version < 1 && usingAlternateUrl && mFetchError == null) {
\r
424 // The alternate URL is obviously not a valid XML either.
\r
425 // We only report the error if we failed to produce one earlier.
\r
426 mFetchError = String.format(
\r
427 "Failed to validate the XML for the repository at URL '%1$s'",
\r
431 // If we get here either we succeeded or we ran out of alternatives.
\r
436 // If any exception was handled during the URL fetch, display it now.
\r
437 if (exception[0] != null) {
\r
438 mFetchError = "Failed to fetch URL";
\r
440 String reason = null;
\r
441 if (exception[0] instanceof FileNotFoundException) {
\r
442 // FNF has no useful getMessage, so we need to special handle it.
\r
443 reason = "File not found";
\r
444 mFetchError += ": " + reason;
\r
445 } else if (exception[0] instanceof SSLKeyException) {
\r
446 // That's a common error and we have a pref for it.
\r
447 reason = "HTTPS SSL error. You might want to force download through HTTP in the settings.";
\r
448 mFetchError += ": HTTPS SSL error";
\r
449 } else if (exception[0].getMessage() != null) {
\r
450 reason = exception[0].getMessage();
\r
452 // We don't know what's wrong. Let's give the exception class at least.
\r
453 reason = String.format("Unknown (%1$s)", exception[0].getClass().getName());
\r
456 monitor.logError("Failed to fetch URL %1$s, reason: %2$s", url, reason);
\r
459 if (validationError[0] != null) {
\r
460 monitor.logError("%s", validationError[0]); //$NON-NLS-1$
\r
463 // Stop here if we failed to validate the XML. We don't want to load it.
\r
464 if (validatedDoc == null) {
\r
468 if (usingAlternateXml) {
\r
469 // We found something using the "alternate" XML schema (that is the one made up
\r
470 // to support schema upgrades). That means the user can only install the tools
\r
471 // and needs to upgrade them before it download more stuff.
\r
473 // Is the manager running from inside ADT?
\r
474 // We check that com.android.ide.eclipse.adt.AdtPlugin exists using reflection.
\r
476 boolean isADT = false;
\r
478 Class<?> adt = Class.forName("com.android.ide.eclipse.adt.AdtPlugin"); //$NON-NLS-1$
\r
479 isADT = (adt != null);
\r
480 } catch (ClassNotFoundException e) {
\r
486 info = "This repository requires a more recent version of ADT. Please update the Eclipse Android plugin.";
\r
487 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.";
\r
490 info = "This repository requires a more recent version of the Tools. Please update.";
\r
491 mDescription = "This repository requires a more recent version of the Tools.\nYou must update it before you can see other new packages.";
\r
494 mFetchError = mFetchError == null ? info : mFetchError + ". " + info;
\r
497 monitor.incProgress(1);
\r
500 monitor.setDescription("Parse XML: %1$s", url);
\r
501 monitor.incProgress(1);
\r
502 parsePackages(validatedDoc, validatedUri, monitor);
\r
503 if (mPackages == null || mPackages.length == 0) {
\r
504 mDescription += "\nNo packages found.";
\r
505 } else if (mPackages.length == 1) {
\r
506 mDescription += "\nOne package found.";
\r
508 mDescription += String.format("\n%1$d packages found.", mPackages.length);
\r
513 monitor.incProgress(1);
\r
516 private void setDefaultDescription() {
\r
517 if (isAddonSource()) {
\r
520 if (mUiName != null) {
\r
521 desc += "Add-on Provider: " + mUiName;
\r
524 desc += "Add-on URL: " + mUrl;
\r
526 mDescription = desc;
\r
528 mDescription = String.format("SDK Source: %1$s", mUrl);
\r
533 * Fetches the document at the given URL and returns it as a string. Returns
\r
534 * null if anything wrong happens and write errors to the monitor.
\r
535 * References: <br/>
\r
538 * @param urlString The URL to load, as a string.
\r
539 * @param monitor {@link ITaskMonitor} related to this URL.
\r
540 * @param outException If non null, where to store any exception that
\r
541 * happens during the fetch.
\r
542 * @see UrlOpener UrlOpener, which handles all URL logic.
\r
544 private InputStream fetchUrl(String urlString, ITaskMonitor monitor, Exception[] outException) {
\r
547 InputStream is = null;
\r
551 byte[] result = new byte[inc];
\r
554 is = UrlOpener.openUrl(urlString, monitor);
\r
557 while ((n = is.read(result, curr, result.length - curr)) != -1) {
\r
559 if (curr == result.length) {
\r
560 byte[] temp = new byte[curr + inc];
\r
561 System.arraycopy(result, 0, temp, 0, curr);
\r
566 return new ByteArrayInputStream(result, 0, curr);
\r
572 } catch (IOException e) {
\r
578 } catch (Exception e) {
\r
579 if (outException != null) {
\r
580 outException[0] = e;
\r
588 * Validates this XML against one of the requested SDK Repository schemas.
\r
589 * If the XML was correctly validated, returns the schema that worked.
\r
590 * If it doesn't validate, returns null and stores the error in outError[0].
\r
591 * If we can't find a validator, returns null and set validatorFound[0] to false.
\r
593 @VisibleForTesting(visibility=Visibility.PRIVATE)
\r
594 protected String validateXml(InputStream xml, String url, int version,
\r
595 String[] outError, Boolean[] validatorFound) {
\r
602 Validator validator = getValidator(version);
\r
604 if (validator == null) {
\r
605 validatorFound[0] = Boolean.FALSE;
\r
606 outError[0] = String.format(
\r
607 "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.",
\r
612 validatorFound[0] = Boolean.TRUE;
\r
614 // Reset the stream if it supports that operation.
\r
617 // Validation throws a bunch of possible Exceptions on failure.
\r
618 validator.validate(new StreamSource(xml));
\r
619 return getSchemaUri(version);
\r
621 } catch (SAXParseException e) {
\r
622 outError[0] = String.format(
\r
623 "XML verification failed for %1$s.\nLine %2$d:%3$d, Error: %4$s",
\r
626 e.getColumnNumber(),
\r
629 } catch (Exception e) {
\r
630 outError[0] = String.format(
\r
631 "XML verification failed for %1$s.\nError: %2$s",
\r
639 * Manually parses the root element of the XML to extract the schema version
\r
640 * at the end of the xmlns:sdk="http://schemas.android.com/sdk/android/repository/$N"
\r
643 * @return 1..{@link SdkRepoConstants#NS_LATEST_VERSION} for a valid schema version
\r
644 * or 0 if no schema could be found.
\r
646 @VisibleForTesting(visibility=Visibility.PRIVATE)
\r
647 protected int getXmlSchemaVersion(InputStream xml) {
\r
652 // Get an XML document
\r
653 Document doc = null;
\r
657 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
\r
658 factory.setIgnoringComments(false);
\r
659 factory.setValidating(false);
\r
661 // Parse the old document using a non namespace aware builder
\r
662 factory.setNamespaceAware(false);
\r
663 DocumentBuilder builder = factory.newDocumentBuilder();
\r
665 // We don't want the default handler which prints errors to stderr.
\r
666 builder.setErrorHandler(new ErrorHandler() {
\r
667 public void warning(SAXParseException e) throws SAXException {
\r
670 public void fatalError(SAXParseException e) throws SAXException {
\r
673 public void error(SAXParseException e) throws SAXException {
\r
678 doc = builder.parse(xml);
\r
680 // Prepare a new document using a namespace aware builder
\r
681 factory.setNamespaceAware(true);
\r
682 builder = factory.newDocumentBuilder();
\r
684 } catch (Exception e) {
\r
685 // Failed to reset XML stream
\r
686 // Failed to get builder factor
\r
687 // Failed to create XML document builder
\r
688 // Failed to parse XML document
\r
689 // Failed to read XML document
\r
696 // Check the root element is an XML with at least the following properties:
\r
697 // <sdk:sdk-repository
\r
698 // xmlns:sdk="http://schemas.android.com/sdk/android/repository/$N">
\r
700 // Note that we don't have namespace support enabled, we just do it manually.
\r
702 Pattern nsPattern = Pattern.compile(getNsPattern());
\r
704 String prefix = null;
\r
705 for (Node child = doc.getFirstChild(); child != null; child = child.getNextSibling()) {
\r
706 if (child.getNodeType() == Node.ELEMENT_NODE) {
\r
708 String name = child.getNodeName();
\r
709 int pos = name.indexOf(':');
\r
710 if (pos > 0 && pos < name.length() - 1) {
\r
711 prefix = name.substring(0, pos);
\r
712 name = name.substring(pos + 1);
\r
714 if (getRootElementName().equals(name)) {
\r
715 NamedNodeMap attrs = child.getAttributes();
\r
716 String xmlns = "xmlns"; //$NON-NLS-1$
\r
717 if (prefix != null) {
\r
718 xmlns += ":" + prefix; //$NON-NLS-1$
\r
720 Node attr = attrs.getNamedItem(xmlns);
\r
721 if (attr != null) {
\r
722 String uri = attr.getNodeValue();
\r
724 Matcher m = nsPattern.matcher(uri);
\r
726 String version = m.group(1);
\r
728 return Integer.parseInt(version);
\r
729 } catch (NumberFormatException e) {
\r
743 * Helper method that returns a validator for our XSD, or null if the current Java
\r
744 * implementation can't process XSD schemas.
\r
746 * @param version The version of the XML Schema.
\r
747 * See {@link SdkRepoConstants#getXsdStream(int)}
\r
749 private Validator getValidator(int version) throws SAXException {
\r
750 InputStream xsdStream = getXsdStream(version);
\r
751 SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
\r
753 if (factory == null) {
\r
757 // This may throw a SAX Exception if the schema itself is not a valid XSD
\r
758 Schema schema = factory.newSchema(new StreamSource(xsdStream));
\r
760 Validator validator = schema == null ? null : schema.newValidator();
\r
762 // We don't want the default handler, which by default dumps errors to stderr.
\r
763 validator.setErrorHandler(new ErrorHandler() {
\r
764 public void warning(SAXParseException e) throws SAXException {
\r
767 public void fatalError(SAXParseException e) throws SAXException {
\r
770 public void error(SAXParseException e) throws SAXException {
\r
779 * Parse all packages defined in the SDK Repository XML and creates
\r
780 * a new mPackages array with them.
\r
782 @VisibleForTesting(visibility=Visibility.PRIVATE)
\r
783 protected boolean parsePackages(Document doc, String nsUri, ITaskMonitor monitor) {
\r
785 Node root = getFirstChild(doc, nsUri, getRootElementName());
\r
786 if (root != null) {
\r
788 ArrayList<Package> packages = new ArrayList<Package>();
\r
790 // Parse license definitions
\r
791 HashMap<String, String> licenses = new HashMap<String, String>();
\r
792 for (Node child = root.getFirstChild();
\r
794 child = child.getNextSibling()) {
\r
795 if (child.getNodeType() == Node.ELEMENT_NODE &&
\r
796 nsUri.equals(child.getNamespaceURI()) &&
\r
797 child.getLocalName().equals(RepoConstants.NODE_LICENSE)) {
\r
798 Node id = child.getAttributes().getNamedItem(RepoConstants.ATTR_ID);
\r
800 licenses.put(id.getNodeValue(), child.getTextContent());
\r
806 for (Node child = root.getFirstChild();
\r
808 child = child.getNextSibling()) {
\r
809 if (child.getNodeType() == Node.ELEMENT_NODE &&
\r
810 nsUri.equals(child.getNamespaceURI())) {
\r
811 String name = child.getLocalName();
\r
815 // We can load addon and extra packages from all sources, either
\r
816 // internal or user sources.
\r
817 if (SdkAddonConstants.NODE_ADD_ON.equals(name)) {
\r
818 p = new AddonPackage(this, child, nsUri, licenses);
\r
820 } else if (RepoConstants.NODE_EXTRA.equals(name)) {
\r
821 p = new ExtraPackage(this, child, nsUri, licenses);
\r
823 } else if (!isAddonSource()) {
\r
824 // We only load platform, doc and tool packages from internal
\r
825 // sources, never from user sources.
\r
826 if (SdkRepoConstants.NODE_PLATFORM.equals(name)) {
\r
827 p = new PlatformPackage(this, child, nsUri, licenses);
\r
828 } else if (SdkRepoConstants.NODE_DOC.equals(name)) {
\r
829 p = new DocPackage(this, child, nsUri, licenses);
\r
830 } else if (SdkRepoConstants.NODE_TOOL.equals(name)) {
\r
831 p = new ToolPackage(this, child, nsUri, licenses);
\r
832 } else if (SdkRepoConstants.NODE_PLATFORM_TOOL.equals(name)) {
\r
833 p = new PlatformToolPackage(this, child, nsUri, licenses);
\r
834 } else if (SdkRepoConstants.NODE_SAMPLE.equals(name)) {
\r
835 p = new SamplePackage(this, child, nsUri, licenses);
\r
836 } else if (SdkRepoConstants.NODE_SYSTEM_IMAGE.equals(name)) {
\r
837 p = new SystemImagePackage(this, child, nsUri, licenses);
\r
838 } else if (SdkRepoConstants.NODE_SOURCE.equals(name)) {
\r
839 p = new SourcePackage(this, child, nsUri, licenses);
\r
845 monitor.logVerbose("Found %1$s", p.getShortDescription());
\r
847 } catch (Exception e) {
\r
848 // Ignore invalid packages
\r
849 monitor.logError("Ignoring invalid %1$s element: %2$s", name, e.toString());
\r
854 setPackages(packages.toArray(new Package[packages.size()]));
\r
863 * Returns the first child element with the given XML local name.
\r
864 * If xmlLocalName is null, returns the very first child element.
\r
866 private Node getFirstChild(Node node, String nsUri, String xmlLocalName) {
\r
868 for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
\r
869 if (child.getNodeType() == Node.ELEMENT_NODE &&
\r
870 nsUri.equals(child.getNamespaceURI())) {
\r
871 if (xmlLocalName == null || child.getLocalName().equals(xmlLocalName)) {
\r
881 * Takes an XML document as a string as parameter and returns a DOM for it.
\r
883 * On error, returns null and prints a (hopefully) useful message on the monitor.
\r
885 @VisibleForTesting(visibility=Visibility.PRIVATE)
\r
886 protected Document getDocument(InputStream xml, ITaskMonitor monitor) {
\r
888 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
\r
889 factory.setIgnoringComments(true);
\r
890 factory.setNamespaceAware(true);
\r
892 DocumentBuilder builder = factory.newDocumentBuilder();
\r
894 Document doc = builder.parse(new InputSource(xml));
\r
897 } catch (ParserConfigurationException e) {
\r
898 monitor.logError("Failed to create XML document builder");
\r
900 } catch (SAXException e) {
\r
901 monitor.logError("Failed to parse XML document");
\r
903 } catch (IOException e) {
\r
904 monitor.logError("Failed to read XML document");
\r