/*
* Copyright (c) 2012-2014 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.systemservices.impl.upgrade;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import java.io.*;
import java.net.*;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.*;
import javax.net.ssl.*;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLEditorKit;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import com.emc.storageos.services.util.AlertsLogger;
import com.emc.storageos.svcs.errorhandling.resources.APIException;
import com.emc.storageos.svcs.errorhandling.resources.BadRequestException;
import com.emc.storageos.systemservices.exceptions.SyssvcException;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import com.emc.storageos.services.util.Strings;
import com.emc.storageos.coordinator.client.model.SoftwareVersion;
import static com.emc.storageos.coordinator.client.model.Constants.*;
import com.emc.storageos.coordinator.exceptions.InvalidSoftwareVersionException;
import com.emc.storageos.systemservices.exceptions.RemoteRepositoryException;
import com.emc.storageos.systemservices.impl.upgrade.beans.SoftwareUpdate;
import com.emc.storageos.services.util.Waiter;
public class RemoteRepository {
private static final Logger _log = LoggerFactory.getLogger(RemoteRepository.class);
private URL _repo;
private Proxy _proxy;
private int _timeout; // connect and read timeout
private boolean _disabled = false;
// List of image URLS
List<URL> imageUrls = new ArrayList<URL>();
// thread pool used to execute the new version check
private static final int FIXED_THREAD_POOL_SIZE = 5;
// create instance and invoke private constructor
private static ExecutorService _executorService = null;
private String _ssohost;
private String _username;
private String _password;
private String _ctsession;
private SSLSocketFactory _sslSocketFactory;
private final int MAXIMUM_REDIRECT_ALLOWED = 10;
private static volatile CoordinatorClientExt _coordinator;
private static volatile RemoteRepositoryCacheUpdate _remoteRepositoryCacheUpdate;
// constants for remote repository
private final static String SYSTEM_UPDATE_REPO = "system_update_repo";
private final static String SYSTEM_UPDATE_PROXY = "system_update_proxy";
private final static String SYSTEM_UPDATE_USERNAME = "system_update_username";
private final static String SYSTEM_UPDATE_PASSWORD = "system_update_password"; // NOSONAR
// ("squid:S2068 Suppressing sonar violation of hard-coded password")
private final static String SYSTEM_UPDATE_CHECK_FREQUENCY_HOURS = "system_update_check_frequency_hours";
// connect + read timeout constant
private final static int SYSTEM_UPDATE_REPO_TIMEOUT = 30000;
private static final String EMC_SSO_AUTH_SERVICE_PROTOCOL = "https";
private static final String EMC_SSO_AUTH_SERVICE_HOST = "sso.emc.com";
private static final String EMC_SSO_AUTH_SERVICE_TESTHOST = "sso-tst.emc.com";
private static final String EMC_SSO_DOWNLOAD_SERVICE_HOST = "download.emc.com";
private static final String EMC_SSO_DOWNLOAD_SERVICE_TESTHOST = "download-tst.emc.com";
private static final String EMC_SSO_AUTH_SERVICE_URLPATH = "/authRest/service/auth.json";
private static final String EMC_SSO_AUTH_SERVICE_LOGIN_POST_CONTENT = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><user><password>{0}</password><username>{1}</username></user>";
/**
* Create singleton instance of ExecutorsService with a fixed thread pool.
*
* @return ExecutorService
*/
private synchronized static ExecutorService getExecutorServiceInstance() {
if (_executorService == null) {
_executorService = Executors.newFixedThreadPool(FIXED_THREAD_POOL_SIZE);
}
return _executorService;
}
/***
*
* @param repoUrl - URL to the Software Update Repository
* @param repoProxy - Proxy to access the Software Update Repository
* @param password
* @param username
* @param timeout - Connect and read timeout
*/
private RemoteRepository(String repoUrl, String repoProxy, String username, String password, int timeout) {
_username = username;
_password = password;
_timeout = timeout;
_proxy = Proxy.NO_PROXY;
if (null == repoUrl || repoUrl.isEmpty()) {
_disabled = true;
} else {
try {
_repo = new URL(repoUrl);
initializeSslContext();
} catch (MalformedURLException e) {
_log.error("Error in RemoteRepository Constructor: MalformedUrl found for remote repository URL " + repoUrl);
_repo = null;
} catch (KeyManagementException e) {
throw APIException.internalServerErrors.initializeSSLContentError();
} catch (NoSuchAlgorithmException e) {
throw APIException.internalServerErrors.initializeSSLContentError();
}
if (repoProxy != null && !repoProxy.isEmpty()) {
try {
URL repoProxyUrl = new URL(repoProxy);
_proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(repoProxyUrl.getHost(), repoProxyUrl.getPort()));
} catch (MalformedURLException e) {
_log.error("Error in RemoteRepository Constructor: MalformedUrl found for proxy URL " + repoProxy);
} catch (IllegalArgumentException e) {
_log.error("Error in RemoteRepository Constructor: Illegal argument for proxy URL {}, {}", repoProxy, e.getMessage());
}
}
}
}
/**
* Get an instance of the remote repository from the coordinator
*
* @return remote repository instance
* @throws Exception
*/
public static RemoteRepository getInstance() throws Exception {
Map<String, String> propInfo = _coordinator.getPropertyInfo().getProperties();
return new RemoteRepository(propInfo.get(SYSTEM_UPDATE_REPO),
propInfo.get(SYSTEM_UPDATE_PROXY),
propInfo.get(SYSTEM_UPDATE_USERNAME),
propInfo.get(SYSTEM_UPDATE_PASSWORD),
SYSTEM_UPDATE_REPO_TIMEOUT);
}
public static void setCoordinator(CoordinatorClientExt coordinator) {
_coordinator = coordinator;
}
/**
* Get the cached list of software versions. Return an empty list if the cache is null
*
* @return cached list of software versions
* @throws Exception
*/
public static Map<SoftwareVersion, List<SoftwareVersion>> getCachedSoftwareVersions() throws Exception {
RemoteRepositoryCache cachedSoftwareVersions = _coordinator.getTargetInfo(RemoteRepositoryCache.class);
return cachedSoftwareVersions == null ? Collections.<SoftwareVersion, List<SoftwareVersion>> emptyMap() : cachedSoftwareVersions
.getCachedVersions();
}
@Override
public String toString() {
return MessageFormat.format("repo={0} proxy={1}", _repo, _proxy);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
RemoteRepository other = (RemoteRepository) obj;
if (_proxy == null) {
if (other._proxy != null) {
return false;
}
} else if (!_proxy.equals(other._proxy)) {
return false;
}
if (_repo == null) {
if (other._repo != null) {
return false;
}
} else if (!_repo.equals(other._repo)) {
return false;
}
return true;
}
@Override
public int hashCode() {
return Objects.hash(_proxy, _repo);
}
/***
* Get an InputStream to the URL within the Software Update Repository
* Use the HTTP proxy if needed.
*
* @param version - Version number
* @return InputStream - Opened input stream to the image
*/
public InputStream getImageInputStream(final SoftwareVersion version) throws RemoteRepositoryException {
try {
return getImageInputStream(parseRepository().get(version));
} catch (RemoteRepositoryException e) {
throw e;
} catch (Exception e) {
throw SyssvcException.syssvcExceptions.remoteRepoError(MessageFormat.format(
"Failed to open an input stream for version={0}: {1}", version, e));
}
}
/**
* Get the size of the image file of a version
*
* @param version
*/
public int checkVersionSize(final SoftwareVersion version) {
URL imageUrl = getImageURL(version);
HttpURLConnection urlConnection = invokeRequest(imageUrl);
return urlConnection.getContentLength();
}
/**
* Check that a version can be downloaded
*
* @param version
*/
public void checkVersionDownloadable(final SoftwareVersion version) {
URL imageUrl = getImageURL(version);
HttpURLConnection urlConnection;
InputStream is;
try {
urlConnection = invokeRequest(imageUrl);
is = urlConnection.getInputStream();
} catch (RemoteRepositoryException e) {
throw e;
} catch (Exception e) {
// log the exception then throw a bad request exception instead
_log.error("Caught an exception trying to access " + version.toString() + " at url " + imageUrl.toString(), e);
throw BadRequestException.badRequests.invalidImageUrl(version.toString(), imageUrl.toString());
}
// The URL is valid. Check that a binary file will be downloaded.
try {
// Throw an exception if the content is html
// the content should be a binary type so it
// is safe to assume the content is an error
// page.
if (null != urlConnection.getContentType() && urlConnection.getContentType().contains("html")) {
// try to grab some bytes and dump it to the logs
// in case the page has some detailed information
byte[] buffer = new byte[256];
try {
if (is.read(buffer) > -1) {
_log.error("Downloaded error page when attempting to get version " + version.toString() +
" from url " + imageUrl.toString() + " error page contents: " +
new String(buffer, "UTF-8"));
}
} catch (Exception e) {
// ignore exceptions trying to get detailed content
}
throw BadRequestException.badRequests.downloadFailed(version.toString(), imageUrl.toString());
}
} finally {
try {
is.close();
} catch (Exception e) {
// ignore errors trying to close the input stream
}
}
}
/***
* Get an InputStream to the URL within the Software Update Repository
* Use the HTTP proxy if needed.
*
* @param url - A URL within the remote repository
* @return InputStream - Opened input stream to the image
*/
public InputStream getImageInputStream(final URL url) throws RemoteRepositoryException {
try {
return invokeRequest(url).getInputStream();
} catch (RemoteRepositoryException e) {
throw e;
} catch (Exception e) {
throw SyssvcException.syssvcExceptions.remoteRepoError(MessageFormat.format(
"Failed to open an input stream for url={0} proxy={1}: {2}", url, _proxy, e));
}
}
/**
* Get URL for a software version image in the repository
*
* @param version ViPR software version
* @return URL of the version image in the repository
*/
public URL getImageURL(final SoftwareVersion version) {
return parseRepository().get(version);
}
/***
*
* @return Map<SoftwareVersion, URL> containg a list of the available versions and their URLs
*/
public List<SoftwareVersion> getVersions() throws RemoteRepositoryException {
if (_disabled) {
_log.debug("Check for software versions is disabled.");
return new ArrayList<SoftwareVersion>();
}
final Map<SoftwareVersion, URL> versions = parseRepository();
_log.info("Getting available versions from url: {}", _repo);
_log.debug("Available versions: {}", Strings.repr(versions));
return new ArrayList<SoftwareVersion>(versions.keySet());
}
/**
* Read a remote repository. Return an input stream and the content-type
*
* @return RepositoryContent object that contains an input stream of the content and the type of content
* @throws RemoteRepositoryException
*/
private RepositoryContent readRepository(URL repo) throws RemoteRepositoryException {
return readRepository(repo, 0); // There was 0 redirects at the initial redirect
}
/**
* Read a remote repository. Return an input stream and the content-type
*
* @param The url of the repository to read
* @param A redirect counter to keep track of the number of redirects. There is a upper limit on the redirects
* @return RepositoryContent object that contains an input stream of the content and the type of content
* @throws RemoteRepositoryException
*/
private RepositoryContent readRepository(URL repo, int redirectCount) throws RemoteRepositoryException {
try {
_log.debug("Repository URL is: " + repo.toString());
HttpURLConnection httpCon = prepareConnection(repo);
httpCon.setInstanceFollowRedirects(false);
httpCon.addRequestProperty("User-Agent", "Mozilla");
if (SoftwareUpdate.isCatalogServer(repo)) {
writePostContent(httpCon, SoftwareUpdate.getCatalogPostContent(repo));
} else {
httpCon.connect();
_log.debug("The return code of the connection is: " + httpCon.getResponseCode());
if (httpCon.getResponseCode() == HttpURLConnection.HTTP_MOVED_PERM
|| httpCon.getResponseCode() == HttpURLConnection.HTTP_MOVED_TEMP) {
redirectCount++;
if (redirectCount > MAXIMUM_REDIRECT_ALLOWED) {
throw SyssvcException.syssvcExceptions.remoteRepoError("Too many redirects! Quit connection!");
}
URL forwardedTo = new URL(httpCon.getHeaderField("Location"));
_log.info("Connecting to URL " + repo.toString() + " redirected to " + forwardedTo);
return readRepository(forwardedTo, redirectCount);
}
}
return new RepositoryContent(httpCon.getContentType(), httpCon.getInputStream(), repo);
} catch (Exception e) {
throw SyssvcException.syssvcExceptions.remoteRepoError(MessageFormat.format("Failed to read repository {0} ({1})", _repo, e));
}
}
// Parse the remote repository into a Map<SoftwareVersion, URL>
//
private Map<SoftwareVersion, URL> parseRepository() throws RemoteRepositoryException {
RepositoryContent repositoryContent = readRepository(_repo);
_log.debug("Parsing repository URL: the URL after redirections is " + repositoryContent.getRepoURL());
try {
if (repositoryContent.getContentType().toLowerCase().contains("text/xml")) {
return parseCatalog(readCatalog(readInputStream(repositoryContent.getContentStream())));
} else {
return parseDirectory(repositoryContent.getRepoURL(), readInputStream(repositoryContent.getContentStream()));
}
} catch (RemoteRepositoryException e) {
throw e;
} catch (Exception e) {
throw SyssvcException.syssvcExceptions.remoteRepoError(
MessageFormat.format("Failed to parse the remote repository {0} input={1} ({2})",
_repo, Strings.repr(repositoryContent), e));
} finally {
try {
if (null != repositoryContent.getContentStream()) {
repositoryContent.getContentStream().close();
}
} catch (IOException e) {
_log.error("Failed to close input stream: " + e);
}
}
}
/**
* @param is InputStream of the remote directory to read
* @return the remote repository directory as a string
* @throws RemoteRepositoryException
* @throws IOException
*/
private String readInputStream(InputStream is) throws RemoteRepositoryException, IOException {
StringBuilder input = new StringBuilder();
Reader in = new InputStreamReader(is, "UTF-8");
char[] buffer = new char[0x10000];
while (true) {
int read = in.read(buffer, 0, buffer.length);
if (read <= 0) {
break;
}
input.append(buffer, 0, read);
}
in.close();
return input.toString();
}
/**
* Read an EMC catalog containing ViPR software updates
*
* @param input InputStream of the catalog
* @return the catalog as a string
* @throws SAXException
* @throws IOException
* @throws ParserConfigurationException
* @throws DOMException
* @throws XPathExpressionException
* @throws RemoteRepositoryException
*/
private String readCatalog(String input) throws SAXException, IOException, ParserConfigurationException, DOMException,
XPathExpressionException, RemoteRepositoryException {
_log.debug("Reading catalog: {}", input);
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
dbFactory.setExpandEntityReferences(false);
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(new InputSource(new StringReader(input)));
XPath xPath = XPathFactory.newInstance().newXPath();
Element downloadUpdatesElement = (Element) xPath.compile("//downloadUpdatesOUT").evaluate(doc, XPathConstants.NODE);
if (null == downloadUpdatesElement) {
throw SyssvcException.syssvcExceptions.remoteRepoError(MessageFormat.format("Invalid catalog recieved from {0} catalog: {1}",
_repo, input));
}
String hasErrors = downloadUpdatesElement.getElementsByTagName("hasErrors").item(0).getTextContent();
if (Boolean.parseBoolean(hasErrors)) {
String errorMessage = downloadUpdatesElement.getElementsByTagName("errorString").item(0).getTextContent();
throw SyssvcException.syssvcExceptions.remoteRepoError(MessageFormat.format("Error receiving catalog from {0} error: {1}",
_repo, errorMessage));
}
String encodedCatalog = downloadUpdatesElement.getElementsByTagName("encodedXML").item(0).getTextContent();
return new String(Base64.decodeBase64(encodedCatalog.getBytes("UTF-8")), "UTF-8");
}
/**
* Parse the remote repository directory string
*
* @param input the remote repository directory string representation
* @return a map of software versions and file URLs
* @throws IOException
* @throws RemoteRepositoryException
*/
private Map<SoftwareVersion, URL> parseDirectory(URL url, String input)
throws IOException, RemoteRepositoryException {
Map<SoftwareVersion, URL> versions = new HashMap<SoftwareVersion, URL>();
ParserCallback callback = new ParserCallback(url, versions);
new HTMLEditor().getParser().parse(new StringReader(input.toString()), callback, true);
return versions;
}
/**
* Parse the EMC software update string representation
*
* @param input the EMC software catalog string representation
* @return a map of software version to remote file URLs
* @throws ParserConfigurationException
* @throws SAXException
* @throws IOException
* @throws XPathExpressionException
* @throws InvalidSoftwareVersionException
* @throws MalformedURLException
* @throws RemoteRepositoryException
*/
private Map<SoftwareVersion, URL> parseCatalog(String input)
throws ParserConfigurationException, SAXException, IOException,
XPathExpressionException, InvalidSoftwareVersionException,
MalformedURLException, RemoteRepositoryException {
Map<SoftwareVersion, URL> versions = new HashMap<SoftwareVersion, URL>();
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(new InputSource(new StringReader(input)));
XPath xPath = XPathFactory.newInstance().newXPath();
NodeList fileList = (NodeList) xPath.compile("//File").evaluate(doc, XPathConstants.NODESET);
for (int fileItr = 0; fileItr < fileList.getLength(); fileItr++) {
Node fileNode = fileList.item(fileItr);
Element element = (Element) fileNode;
Node nameNode = element.getAttributeNode("Name");
if (null != nameNode) {
String fileName = nameNode.getNodeValue();
if (fileName.endsWith(SOFTWARE_IMAGE_SUFFIX)) {
String fileVersion = fileName.replace(SOFTWARE_IMAGE_SUFFIX,"");
Node urlNode = element.getAttributeNode("URL");
String fileUrl = urlNode.getNodeValue();
versions.put(new SoftwareVersion(fileVersion), new URL(fileUrl));
}
}
}
if (versions.isEmpty()) {
throw SyssvcException.syssvcExceptions.remoteRepoError("Empty remote repository: " + _repo);
}
return versions;
}
/**
* Send a post request with content to the specified connection
*
* @param connection connection to URL
* @param postContent content to post
* @throws Exception
*/
private void writePostContent(HttpURLConnection connection, String postContent) throws Exception {
connection.setRequestMethod("POST");
connection.addRequestProperty("Content-Type",
"application/xml; charset=utf-8");
// set the output and input to true
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setAllowUserInteraction(false);
// set the content length
DataOutputStream dstream = null;
try {
connection.connect();
dstream = new DataOutputStream(connection.getOutputStream());
// write the post content
dstream.writeBytes(postContent);
dstream.flush();
} finally {
// flush the stream
if (dstream != null) {
try {
dstream.close();
} catch (Exception ex) {
_log.error("Exception while closing the stream." +
" Exception: " + ex,
"WebClient._writePostContent()");
}
}
}
}
public static void startRemoteRepositoryCacheUpdate() {
if (!_coordinator.isControlNode()) {
return;
}
if (null == _remoteRepositoryCacheUpdate) {
_remoteRepositoryCacheUpdate = new RemoteRepositoryCacheUpdate();
getExecutorServiceInstance().submit(_remoteRepositoryCacheUpdate);
} else {
throw new IllegalStateException("New version check thread is already started");
}
}
public static void wakeupNewVersionCheck() {
if (null == _remoteRepositoryCacheUpdate) {
startRemoteRepositoryCacheUpdate();
} else {
_remoteRepositoryCacheUpdate.wakeup();
}
}
public static void stopRemoteRepositoryCacheUpdate() {
if (null != _remoteRepositoryCacheUpdate) {
_remoteRepositoryCacheUpdate.stop();
}
}
/**
* Class that is used to refresh the list of new versions in the remote repository.
* The check is executed on frequency that can be changed by the user. A cached list of new
* versions is updated in the coordinator whenever the check is successfully executed.
*/
private static class RemoteRepositoryCacheUpdate implements Runnable {
private static final long DEFAULT_FREQUENCY = 24 * 60 * 60 * 1000; // 24 hours
private static final long MINIMUM_FREQUENCY = 60 * 60 * 1000; // 1 hour
private static final long WAIT = 5 * 1000;
boolean _run = true;
private final Waiter _waiter = new Waiter();
private RemoteRepositoryCacheUpdate() {
}
@Override
public void run() {
while (_run) {
if (_coordinator.getNewVersionLock()) {
try {
Map<String, String> propInfo = _coordinator.getPropertyInfo().getProperties();
long softwareVersionCheckFrequencyMillis = DEFAULT_FREQUENCY;
if (null != propInfo.get(SYSTEM_UPDATE_CHECK_FREQUENCY_HOURS)) { // use the configured check frequency
int frequencyHours = Integer.parseInt(propInfo.get(SYSTEM_UPDATE_CHECK_FREQUENCY_HOURS));
if (frequencyHours < TimeUnit.MILLISECONDS.toHours(MINIMUM_FREQUENCY)) {
_log.warn("Software version check frequency cannot be less than {} hour",
TimeUnit.MILLISECONDS.toHours(MINIMUM_FREQUENCY));
softwareVersionCheckFrequencyMillis = MINIMUM_FREQUENCY;
} else {
softwareVersionCheckFrequencyMillis = TimeUnit.HOURS.toMillis(frequencyHours);
}
}
RemoteRepository remoteRepository = getInstance();
RemoteRepositoryCache remoteRepositoryCache = _coordinator.getTargetInfo(RemoteRepositoryCache.class); // get cached
// repository
// info
boolean bDoCheck = false; // a flag indicating if a reread of the repository is needed
long now = System.currentTimeMillis();
if (null == remoteRepositoryCache) {
bDoCheck = true;
_log.debug("Performing new version check because there is no new software version cached");
} else if (!remoteRepositoryCache.getRepositoryInfo().equals(remoteRepository.toString())) {
bDoCheck = true;
_log.debug("Performing new version check because remote repository info changed");
} else {
if (now - remoteRepositoryCache.getLastVersionCheck() > softwareVersionCheckFrequencyMillis) {
bDoCheck = true;
_log.debug("Performing new version check because a check hasn't been performed in at least {} hours",
TimeUnit.MILLISECONDS.toHours(softwareVersionCheckFrequencyMillis));
}
}
if (bDoCheck) {
String repository = remoteRepository.toString();
Map<SoftwareVersion, List<SoftwareVersion>> softwareVersionMap;
try {
softwareVersionMap = remoteRepository.getVersionsWithMetadata();
_log.info("Got the versions with metadata from repo: " + softwareVersionMap);
} catch (Exception e) {
AlertsLogger.getAlertsLogger().error(
"Failed to get the list of software versions from remote repository: " + e.getMessage()
+ " please verify ViPR's Internet connection and check if the repository URL("
+ remoteRepository.toString() + ") is correct.");
// Set the list to empty if it didn't exist or if whatever used to be cached
// is from a different repository.
// That way we can still update the time the check was last performed
// so we're not churning this check on a bad repository configuration
if (null != remoteRepositoryCache
&& remoteRepositoryCache.getRepositoryInfo().equals(remoteRepository.toString())) {
softwareVersionMap = remoteRepositoryCache.getCachedVersions();
} else {
softwareVersionMap = Collections.<SoftwareVersion, List<SoftwareVersion>> emptyMap();
}
}
_log.debug("Caching software versions: {}", softwareVersionMap);
_coordinator.setTargetInfo(new RemoteRepositoryCache(softwareVersionMap, now, repository), false);
} else {
_log.debug("No version check necessary");
}
} catch (Exception e) {
_log.error("Get new software versions failed: ", e);
} finally {
_coordinator.releaseNewVersionLock();
}
} else {
_log.debug("Failed to get new software versions check lock");
}
_waiter.sleep(WAIT);
}
}
private void stop() {
_run = false;
wakeup();
}
private void wakeup() {
_waiter.wakeup();
}
}
/**
* Invoke a request to the URL
* For images in download-tst.emc.com, using sso-tst.emc.com as auth source
* For images in download.emc.com, using sso.emc.com as auth source
*
* @param url
* @return a connection to the URL that was sent the request
* @throws RemoteRepositoryException
*/
private HttpURLConnection invokeRequest(URL url) throws RemoteRepositoryException {
if (url.getProtocol().equalsIgnoreCase(EMC_SSO_AUTH_SERVICE_PROTOCOL) &&
url.getHost().equalsIgnoreCase(EMC_SSO_DOWNLOAD_SERVICE_TESTHOST)) {
_ssohost = EMC_SSO_AUTH_SERVICE_TESTHOST;
} else if (url.getProtocol().equalsIgnoreCase(EMC_SSO_AUTH_SERVICE_PROTOCOL) &&
url.getHost().equalsIgnoreCase(EMC_SSO_DOWNLOAD_SERVICE_HOST)) {
_ssohost = EMC_SSO_AUTH_SERVICE_HOST;
} else {
_ssohost = null;
}
if (_ssohost != null) {
login();
}
return connectImage(url);
}
/**
* The EMC SSO service (https://sso.emc.com) authenticates uses and issues authentication
* tokens that enable access to other EMC services, in particular to the EMC download repository (https://download.emc.com).
* Request:
* URI: http://sso.emc.com/authRest/service/auth.json
* Format: XML
* HTTP Method: POST
* Request Body for customer, partner or lite users:
* <?xml version="1.0" encoding="UTF-8"
* standalone="yes"?><user><password>##########</password><username>johndoe@acme.com</username></user>
* Request Body for employee
* <?xml version="1.0" encoding="UTF-8" standalone="yes"?><user><password>pin+fob/softtoken</password><username>emp nt</username></user>
*
* Response:
* 1. Example for successful response:
* {
* "object": {
* "authResult": {
* "status":"SUCCESS",
* "operation":"VALID_USER",
* "token":"AAAAAgABAFBLtr+WcJAh+DJ1Q2GXYiH0PC5+Txuscy1+pU7TRpAcUoyfhNwB55DZwPCZlQwgVpyY+vaOYNblApcSOZ+hEWFzIxj1JtII/ozshY+33ddafg==",
* "userProps":[{"propName":"LAST_NAME","propValue":"[TestFour]"},{"propName":"GIVEN_NAME","propValue":"[AlphaFour]"},{"propName":"UID",
* "propValue":"[1110000003]"},{"propName":"EMC_IDENTITY","propValue":"[C]"}]
* }
* },
* "serviceFault":null
* }
* 2. Example for failure response:
* {
* "object": {
* "authResult": {
* "status": "FAILED",
* "operation": "INVALID_USERNAME_PASSWORD",
* "token": null,
* "userProps": null
* }
* },
* "serviceFault":null
* }
*
* @param username
* @param password
*/
private void login() {
_log.info("{} is trying to login ...", _username);
try {
URL url = new URL(EMC_SSO_AUTH_SERVICE_PROTOCOL, _ssohost, EMC_SSO_AUTH_SERVICE_URLPATH);
HttpURLConnection httpCon = prepareConnection(url);
httpCon.setInstanceFollowRedirects(false);
String loginContent = SoftwareUpdate.getDownloadLoginContent(_username, _password);
writePostContent(httpCon, loginContent);
InputStream in = httpCon.getInputStream();
if (in == null) {
throw new IllegalArgumentException("in is null");
}
// use InputStreamReader to read charset encoding gracefully
BufferedReader rd = new BufferedReader(new InputStreamReader(in));
String line;
StringBuffer response = new StringBuffer();
while ((line = rd.readLine()) != null) {
response.append(line);
response.append('\r');
}
rd.close();
String s = response.toString();
_log.debug("Response body: " + s);
JSONParser parser = new JSONParser();
JSONObject obj = (JSONObject) parser.parse(s);
JSONObject authObj = (JSONObject) obj.get("object");
JSONObject authResultObj = (JSONObject) authObj.get("authResult");
if (authResultObj.get("status").toString().equalsIgnoreCase("SUCCESS")) {
_log.info("{} login EMC SSO service successfully", _username);
if (authResultObj.get("token") != null) {
_ctsession = authResultObj.get("token").toString();
_log.debug("From EMC SSO service, user {} obtained CTSESSION cookie: {}", _username, _ctsession);
} else {
throw new IllegalArgumentException("Failed to parse ctsession token as expected");
}
} else if (authResultObj.get("status").toString().equalsIgnoreCase("FAILED")) {
JSONObject serviceFaultObj = (JSONObject) obj.get("serviceFault");
String errstr = "";
if (serviceFaultObj != null) {
errstr = "Please contact with EMC customer support. EMC SSO service failed:" + serviceFaultObj.toString();
} else {
String operation = authResultObj.get("operation").toString();
errstr = "EMC SSO authentication result is " + operation;
}
_log.error(errstr);
throw SyssvcException.syssvcExceptions.remoteRepoError(errstr);
}
} catch (Exception e) {
throw SyssvcException.syssvcExceptions.remoteRepoError(
MessageFormat.format("User {0} failed to login {1}: {2}", _username, EMC_SSO_AUTH_SERVICE_URLPATH, e));
}
}
/**
* Connect with remote target url for download
* Client needs to read the token from returned JSON object for login request.
* Token aka CTSESSION is one of the important attribute to access any application or rest services that is protected behind RSA.
* For Example: if support zone rest needs to be accessed then use the auth rest service read the token and set the cookie header with
* the token as shown
* For download request:
* URI: image url
* Format: XML
* HTTP Method: GET
* HTTP Header: CTSESSION="token"
*
* @param url
* @return HttpURLConnection
* @throws RemoteRepositoryException
*/
private HttpURLConnection connectImage(URL url) throws RemoteRepositoryException {
try {
_log.info("Connecting to URL: {}", url.toString());
HttpURLConnection httpCon = prepareConnection(url);
httpCon.setInstanceFollowRedirects(false);
String cookie = "CTSESSION=" + _ctsession;
httpCon.setRequestProperty("Cookie", cookie);
httpCon.setRequestMethod("GET");
httpCon.connect();
if (httpCon.getResponseCode() != HttpURLConnection.HTTP_OK) {
_log.info("connect image request return {}", httpCon.getResponseCode());
throw new IllegalArgumentException("Http error code:" + httpCon.getResponseCode());
}
_log.info("Image is located successfully and its size is " + httpCon.getContentLength());
return httpCon;
} catch (Exception e) {
throw SyssvcException.syssvcExceptions.remoteRepoError(
MessageFormat.format("User {0} failed to connect with remote image {1}: {2}", _username, url.toString(), e));
}
}
/**
* Initialize the SSL context for connecting to a remote repository
*
* @throws NoSuchAlgorithmException
* @throws KeyManagementException
*/
private void initializeSslContext() throws NoSuchAlgorithmException, KeyManagementException {
// Create a trust manager that does not validate certificate chains
final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
@Override
public void checkClientTrusted(final X509Certificate[] chain, final String authType) {
}
@Override
public void checkServerTrusted(final X509Certificate[] chain, final String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
} };
// Install the all-trusting trust manager
SSLContext sslContext;
sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
// Create an ssl socket factory with our all-trusting manager
_sslSocketFactory = sslContext.getSocketFactory();
}
/**
* Open a URL connection and set the SSL context factory if necessary
*
* @param url
* @return a connection to the URL
* @throws IOException
*/
private HttpURLConnection prepareConnection(URL url) throws IOException {
HttpURLConnection connection;
connection = (HttpURLConnection) url.openConnection(_proxy);
connection.setConnectTimeout(_timeout);
connection.setReadTimeout(_timeout);
if (url.getProtocol().equalsIgnoreCase(EMC_SSO_AUTH_SERVICE_PROTOCOL)) {
((HttpsURLConnection) connection).setSSLSocketFactory(_sslSocketFactory);
}
return connection;
}
/**
* Class to hold the remote repository input stream and content type
*
*/
private class RepositoryContent {
private String _contentType;
private InputStream _contentStream;
private URL _repoURL;
public URL getRepoURL() {
return _repoURL;
}
public RepositoryContent(String contentType, InputStream contentStream, URL repoURL) {
_contentType = contentType;
_contentStream = contentStream;
_repoURL = repoURL;
}
public String getContentType() {
return _contentType;
}
public InputStream getContentStream() {
return _contentStream;
}
}
// Parser callback used by parseDirectory()
//
private class ParserCallback extends HTMLEditorKit.ParserCallback {
private URL _url;
private Map<SoftwareVersion, URL> _versionsMap;
public ParserCallback(URL url, Map<SoftwareVersion, URL> versionsMap) {
_url = url;
_versionsMap = versionsMap;
}
@Override
public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
if (t == HTML.Tag.A) {
// Extract the
final String href = (String) a.getAttribute(HTML.Attribute.HREF);
final String logMsg = "getVersions(): href=" + href;
try {
URL subdirUrl = new URL(_url, href);
String[] subdirFileParts = subdirUrl.getFile().split("/");
SoftwareVersion version = new SoftwareVersion(subdirFileParts[subdirFileParts.length - 1]);
URL imageUrl = new URL(subdirUrl, version.toString() + SOFTWARE_IMAGE_SUFFIX);
_log.debug(logMsg + " version=" + version + " url=" + imageUrl);
_versionsMap.put(version, imageUrl);
} catch (Exception e) {
_log.debug("Ignored. " + logMsg + ": " + e);
}
}
}
}
private class HTMLEditor extends HTMLEditorKit {
public HTMLEditorKit.Parser getParser() {
return super.getParser();
}
}
public List<SoftwareVersion> getUpgradeFromVersions(SoftwareVersion version) throws Exception {
RemoteRepositoryCache remoteRepositoryCache = _coordinator.getTargetInfo(RemoteRepositoryCache.class); // get cached repository info
Map<SoftwareVersion, List<SoftwareVersion>> cachedVersionMap = remoteRepositoryCache.getCachedVersions();
if (cachedVersionMap.containsKey(version)) {
return cachedVersionMap.get(version);
}
// The target version is not cached, need to recache the repository
String repository = this.toString();
Map<SoftwareVersion, List<SoftwareVersion>> softwareVersionMap;
try {
softwareVersionMap = this.getVersionsWithMetadata();
_log.info("Got the versions with metadata from repo: " + softwareVersionMap);
} catch (Exception e) {
AlertsLogger.getAlertsLogger().error(
"Failed to get the list of software versions from remote repository: " + e.getMessage()
+ " please verify ViPR's Internet connection and check if the repository URL(" + this.toString()
+ ") is correct.");
// Set the list to empty if it didn't exist or if whatever used to be cached
// is from a different repository.
// That way we can still update the time the check was last performed
// so we're not churning this check on a bad repository configuration
if (null != remoteRepositoryCache && remoteRepositoryCache.getRepositoryInfo().equals(repository)) {
softwareVersionMap = remoteRepositoryCache.getCachedVersions();
} else {
softwareVersionMap = Collections.<SoftwareVersion, List<SoftwareVersion>> emptyMap();
}
}
_log.debug("Caching software versions: {}", softwareVersionMap);
_coordinator.setTargetInfo(new RemoteRepositoryCache(softwareVersionMap, System.currentTimeMillis(), repository), false);
if (!softwareVersionMap.containsKey(version)) {
// log the exception then throw a bad request exception instead
_log.error("Version: " + version.toString() + "does not exist!");
throw BadRequestException.badRequests.versionNotExist(version.toString());
}
return softwareVersionMap.get(version);
}
public Map<SoftwareVersion, List<SoftwareVersion>> getVersionsWithMetadata() {
RepositoryContent repositoryContent = readRepository(_repo);
try {
if (repositoryContent.getContentType().toLowerCase().contains("text/xml")) {
return getVersionsWithMetadataCatalog(readCatalog(readInputStream(repositoryContent.getContentStream())));
} else {
return getVersionsWithMetadataDirectory();
}
} catch (RemoteRepositoryException e) {
throw e;
} catch (Exception e) {
throw SyssvcException.syssvcExceptions.remoteRepoError(
MessageFormat.format("Failed to parse the remote repository {0} input={1} ({2})",
_repo, Strings.repr(repositoryContent), e));
} finally {
try {
if (null != repositoryContent.getContentStream()) {
repositoryContent.getContentStream().close();
}
} catch (IOException e) {
_log.error("Failed to close input stream: " + e);
}
}
}
private Map<SoftwareVersion, List<SoftwareVersion>> getVersionsWithMetadataCatalog(
String catalogString) throws ParserConfigurationException, SAXException, IOException,
XPathExpressionException, InvalidSoftwareVersionException,
MalformedURLException, RemoteRepositoryException {
Map<SoftwareVersion, List<SoftwareVersion>> map = new HashMap<SoftwareVersion, List<SoftwareVersion>>();
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(new InputSource(new StringReader(catalogString)));
XPath xPath = XPathFactory.newInstance().newXPath();
NodeList fileList = (NodeList) xPath.compile("//File").evaluate(doc, XPathConstants.NODESET);
for (int fileItr = 0; fileItr < fileList.getLength(); fileItr++) {
Node fileNode = fileList.item(fileItr);
Element element = (Element) fileNode;
Node nameNode = element.getAttributeNode("Name");
if (null != nameNode) {
String fileName = nameNode.getNodeValue();
if (fileName.endsWith(SOFTWARE_IMAGE_SUFFIX)) {
String fileVersion = fileName.replace(SOFTWARE_IMAGE_SUFFIX,"");
Node catalogInfoNode = element.getAttributeNode("CatalogInfo");
String catalogInfo = catalogInfoNode.getNodeValue();
SoftwareVersion tempVersion = new SoftwareVersion(fileVersion);
List<SoftwareVersion> tempList = new ArrayList<SoftwareVersion>();
if (!catalogInfo.equals("")) {
String upgradeFromInfoRaw = null;
for (String s : catalogInfo.split(",")) { // key-value pairs are separated by comma
if (s.startsWith("upgradeFromVersions=")) {
upgradeFromInfoRaw = s;
break;
}
}
String upgradeFromInfo = upgradeFromInfoRaw.split("=")[1]; // only need the value
for (String versionStr : upgradeFromInfo.split(";")) { // versions are separated by semicolon
tempList.add(new SoftwareVersion(versionStr));
}
}
map.put(tempVersion, tempList);
}
}
}
return map;
}
private Map<SoftwareVersion, List<SoftwareVersion>> getVersionsWithMetadataDirectory()
throws IOException, RemoteRepositoryException, MalformedURLException {
RepositoryContent content = readRepository(_repo);
URL redirectedURL = content.getRepoURL();
Map<SoftwareVersion, List<SoftwareVersion>> versions = new HashMap<SoftwareVersion, List<SoftwareVersion>>();
DirectoryMetaDataCallback callback = new DirectoryMetaDataCallback(redirectedURL, versions);
new HTMLEditor().getParser().parse(new StringReader(readInputStream(content.getContentStream())), callback, true);
return versions;
}
// Parser callback used by parseDirectory()
//
private class DirectoryMetaDataCallback extends HTMLEditorKit.ParserCallback {
private URL _url;
private Map<SoftwareVersion, List<SoftwareVersion>> _versionsMap;
public DirectoryMetaDataCallback(URL url, Map<SoftwareVersion, List<SoftwareVersion>> versionsMap) {
_versionsMap = versionsMap;
_url = url;
}
@Override
public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
if (t == HTML.Tag.A) {
// Extract the
final String href = (String) a.getAttribute(HTML.Attribute.HREF);
final String logMsg = "getVersionsWithMetadataCatalogDirectoryRepo(): href=" + href;
try {
URL subdirUrl = new URL(_url, href);
String[] subdirFileParts = subdirUrl.getFile().split("/");
SoftwareVersion version = new SoftwareVersion(subdirFileParts[subdirFileParts.length - 1]);
URL metadataFileUrl = new URL(subdirUrl, "vipr.md");
HttpURLConnection httpCon = prepareConnection(metadataFileUrl);
httpCon.setRequestMethod("GET");
List<SoftwareVersion> tempList = new ArrayList<SoftwareVersion>();
if (httpCon.getResponseCode() == HttpURLConnection.HTTP_OK) {
InputStream is = httpCon.getInputStream();
StringBuilder input = new StringBuilder();
Reader in = new InputStreamReader(is, "UTF-8");
while (true) {
char[] buffer = new char[0x10000];
int read = in.read(buffer, 0, buffer.length);
if (read <= 0) {
break;
}
input.append(buffer, 0, read);
}
_log.info("The meta data file is: " + input.toString());
for (String s : input.toString().split("\n")) {
if (s.startsWith("upgrade_from:")) {
if (s.trim().endsWith(":")) {
break;
}
for (String versionStr : s.substring(13).split(",")) {
tempList.add(new SoftwareVersion(versionStr));
}
break;
}
}
}
_versionsMap.put(version, tempList);
} catch (Exception e) {
_log.debug("Ignored. " + logMsg + ": " + e);
}
}
}
}
public List<String> getUpgradeFromVersionsString(SoftwareVersion version) throws Exception {
RepositoryContent repositoryContent = readRepository(_repo);
try {
if (repositoryContent.getContentType().toLowerCase().contains("text/xml")) {
return getUpgradeFromVersionsFromCatalogRepo(readCatalog(readInputStream(repositoryContent.getContentStream())), version);
} else {
return getUpgradeFromVersionsFromDirectoryRepo(version);
}
} catch (RemoteRepositoryException e) {
throw e;
} catch (Exception e) {
throw SyssvcException.syssvcExceptions.remoteRepoError(
MessageFormat.format("Failed to parse the remote repository {0} input={1} ({2})",
_repo, Strings.repr(repositoryContent), e));
} finally {
try {
if (null != repositoryContent.getContentStream()) {
repositoryContent.getContentStream().close();
}
} catch (IOException e) {
_log.error("Failed to close input stream: " + e);
}
}
}
private List<String> getUpgradeFromVersionsFromCatalogRepo(String input, SoftwareVersion version) throws ParserConfigurationException,
SAXException, IOException,
XPathExpressionException, InvalidSoftwareVersionException,
MalformedURLException, RemoteRepositoryException {
List<String> versions = new ArrayList<String>();
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(new InputSource(new StringReader(input)));
XPath xPath = XPathFactory.newInstance().newXPath();
NodeList fileList = (NodeList) xPath.compile("//File").evaluate(doc, XPathConstants.NODESET);
OUTER: for (int fileItr = 0; fileItr < fileList.getLength(); fileItr++) {
Node fileNode = fileList.item(fileItr);
Element element = (Element) fileNode;
Node nameNode = element.getAttributeNode("Name");
if (null != nameNode) {
String fileName = nameNode.getNodeValue();
if (fileName.endsWith(SOFTWARE_IMAGE_SUFFIX)) {
String fileVersion = fileName.replace(SOFTWARE_IMAGE_SUFFIX,"");
if (new SoftwareVersion(fileVersion).equals(version)) {
// Only find the node for image file of that particular version
Node catalogInfoNode = element.getAttributeNode("CatalogInfo");
String catalogInfo = catalogInfoNode.getNodeValue();
if (catalogInfo == null) {
return versions;
}
String upgradeFromInfoRaw = null;
for (String s : catalogInfo.split(",")) { // key-value pairs are separated by comma
if (s.startsWith("upgradeFromVersions=")) {
upgradeFromInfoRaw = s;
break;
}
}
String upgradeFromInfo = upgradeFromInfoRaw.split("=")[1]; // The format is
// "upgradeableFromVersion=version1;version2". We don't
// need the key and the equal sign
for (String v : upgradeFromInfo.split(";")) {
versions.add(v);
}
break OUTER;
}
}
}
}
return versions;
}
private List<String> getUpgradeFromVersionsFromDirectoryRepo(
SoftwareVersion version) throws IOException, RemoteRepositoryException, MalformedURLException {
URL redirectedURL = readRepository(_repo).getRepoURL();
URL metadataFileURL = new URL(redirectedURL, version.toString().substring(5) + "/vipr.md");
HttpURLConnection httpCon = null;
httpCon = prepareConnection(metadataFileURL);
httpCon.setRequestMethod("GET");
if (httpCon.getResponseCode() == HttpURLConnection.HTTP_OK) {
for (String s : readInputStream(httpCon.getInputStream()).split("\n")) {
if (s.startsWith("upgrade_from:")) {
if (!s.trim().endsWith(":")) {
return Arrays.asList(s.substring(13).split(","));
}
break;
}
}
}
return new ArrayList<String>();
}
/**
* Get a list of new versions that can be installed from the remote repository
*
* @param remoteVersions versions available in the remote repository
* @param forceInstall whether we should show versions available for force install
* @param localCurrent current target version
* @param localVersions currently installed versions
* @param prefix log prefix
*/
public List<SoftwareVersion> findInstallableVersions(
final boolean forceInstall,
final SoftwareVersion localCurrent,
final List<SoftwareVersion> localVersions) {
List<SoftwareVersion> newVersionList = findNewVersions(localCurrent, localVersions, forceInstall);
if (newVersionList.isEmpty()) {
return newVersionList;
}
RepositoryContent repositoryContent = readRepository(_repo);
try {
if (repositoryContent.getContentType().toLowerCase().contains("text/xml")) {
return finInstallableVersionsFromCatalogRepo(readCatalog(readInputStream(repositoryContent.getContentStream())),
newVersionList, localVersions);
} else {
return findInstallableVersionsFromDirectoryRepo(newVersionList, localVersions);
}
} catch (RemoteRepositoryException e) {
throw e;
} catch (Exception e) {
throw SyssvcException.syssvcExceptions.remoteRepoError(
MessageFormat.format("Failed to parse the remote repository {0} input={1} ({2})",
_repo, Strings.repr(repositoryContent), e));
} finally {
try {
if (null != repositoryContent.getContentStream()) {
repositoryContent.getContentStream().close();
}
} catch (IOException e) {
_log.error("Failed to close input stream: " + e);
}
}
}
private List<SoftwareVersion> findNewVersions(final SoftwareVersion localCurrent,
final List<SoftwareVersion> localVersions, final boolean forceInstall) {
List<SoftwareVersion> newVersions = new ArrayList<SoftwareVersion>();
List<SoftwareVersion> allVersions = new ArrayList<SoftwareVersion>(getVersions());
Collections.sort(allVersions);
Collections.reverse(allVersions);
_log.debug("Test if a version is new:");
ToInstallLoop: for (SoftwareVersion v : allVersions) {
// skip version lower than current version
if (!forceInstall && localCurrent.compareTo(v) > 0) {
_log.debug(" try=" + v +
": lower than or equal to current version. Skipping.");
continue;
}
// skip local versions
for (SoftwareVersion version : localVersions) {
if (version.compareTo(v) == 0) {
_log.debug(" try=" + v +
": already downloaded {}. Skipping.", version);
continue ToInstallLoop;
}
}
_log.debug(" try=" + v +
": new version.");
newVersions.add(v);
}
return newVersions;
}
private List<SoftwareVersion> finInstallableVersionsFromCatalogRepo(String catalogString,
List<SoftwareVersion> newVersionList, List<SoftwareVersion> localVersions) throws ParserConfigurationException, SAXException,
IOException,
XPathExpressionException, InvalidSoftwareVersionException,
MalformedURLException, RemoteRepositoryException {
List<SoftwareVersion> validVersions = new ArrayList<SoftwareVersion>();
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(new InputSource(new StringReader(catalogString)));
XPath xPath = XPathFactory.newInstance().newXPath();
NodeList fileList = (NodeList) xPath.compile("//File").evaluate(doc, XPathConstants.NODESET);
OUTER: for (int fileItr = 0; fileItr < fileList.getLength(); fileItr++) {
Node fileNode = fileList.item(fileItr);
Element element = (Element) fileNode;
Node nameNode = element.getAttributeNode("Name");
if (null != nameNode) {
String fileName = nameNode.getNodeValue();
if (fileName.endsWith(SOFTWARE_IMAGE_SUFFIX)) {
String fileVersion = fileName.replace(SOFTWARE_IMAGE_SUFFIX,"");
SoftwareVersion tempVersion = new SoftwareVersion(fileVersion);
if (newVersionList.contains(tempVersion)) {
Node catalogInfoNode = element.getAttributeNode("CatalogInfo");
String catalogInfo = catalogInfoNode.getNodeValue();
if (catalogInfo.equals(""))
{
continue; // Ignore the version that doesn't have the upgradeFromVersion metadata
}
String upgradeFromInfoRaw = catalogInfo.split(",")[0]; // key-value pairs are separated by comma
String upgradeFromInfo = upgradeFromInfoRaw.split("=")[1]; // only need the value
for (String versionStr : upgradeFromInfo.split(";")) { // versions are separated by semicolon
for (SoftwareVersion v : localVersions) {
if (new SoftwareVersion(versionStr).weakEquals(v)) { // wild card is used in the upgradeFromVersions list,
// need use weakEquals
validVersions.add(tempVersion);
continue OUTER;
}
}
}
}
}
}
}
return validVersions;
}
private List<SoftwareVersion> findInstallableVersionsFromDirectoryRepo(
List<SoftwareVersion> newVersionList,
List<SoftwareVersion> localVersions) throws IOException, RemoteRepositoryException, MalformedURLException {
// TODO
return null;
}
}