package org.jboss.arquillian.drone.webdriver.binary.downloading.source; import java.io.StringReader; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.logging.Logger; import java.util.stream.Collectors; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.jboss.arquillian.drone.webdriver.binary.downloading.ExternalBinary; import org.jboss.arquillian.drone.webdriver.utils.HttpClient; import org.jboss.arquillian.drone.webdriver.utils.StringUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; /** * GoogleStorageSource source is an abstract class that helps you to retrieve either latest release * or a release with some version from some specific google storage. * Eg. http://selenium-release.storage.googleapis.com/ for selenium bits or https://chromedriver.storage.googleapis.com/ * for chrome web-drivers */ public abstract class GoogleStorageSource implements ExternalBinarySource { private Logger log = Logger.getLogger(GoogleStorageSource.class.toString()); private HttpClient httpClient; private String storageUrl; private String urlToLatestRelease; private ArrayList<Content> contents; private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); private String latestVersion; /** * @param storageUrl * An url to a google storage from which information about releases should be retrieved from */ public GoogleStorageSource(String storageUrl, HttpClient httpClient) { this.storageUrl = storageUrl; this.httpClient = httpClient; } /** * @param storageUrl * An url to a google storage from which information about releases should be retrieved from * @param urlToLatestRelease * An url where a version of the latest release could be retrieved from */ public GoogleStorageSource(String storageUrl, String urlToLatestRelease, HttpClient httpClient) { this(storageUrl, httpClient); this.urlToLatestRelease = urlToLatestRelease; } @Override public ExternalBinary getLatestRelease() throws Exception { if (urlToLatestRelease != null) { latestVersion = StringUtils.trimMultiline(httpClient.get(urlToLatestRelease).getPayload()); } else { retrieveContents(); } return getReleaseForVersion(latestVersion); } private void retrieveContents() throws Exception { if (contents == null) { contents = new ArrayList<>(); DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); InputSource is = new InputSource(); is.setCharacterStream(new StringReader(httpClient.get(storageUrl).getPayload())); Document doc = db.parse(is); NodeList contentNodes = ((Element) doc.getFirstChild()).getElementsByTagName("Contents"); for (int i = 0; i < contentNodes.getLength(); i++) { Element item = (Element) contentNodes.item(i); Content content = new Content(); String key = getContentOfFirstElement(item, "Key"); if (key.contains("/")) { content.setKey(key); content.setLastModified(getContentOfFirstElement(item, "LastModified")); content.setDirectory(key.substring(0, key.indexOf("/"))); contents.add(content); } } } } private Date parseDate(String date, String key) { try { return dateFormat.parse(date); } catch (ParseException e) { log.warning("Date " + date + " of content " + storageUrl + key + " could not have been parsed. This content will be omitted. See the exception msg: " + e.getMessage()); } return null; } private String getContentOfFirstElement(Element element, String tagName) { NodeList elementsByTagName = element.getElementsByTagName(tagName); if (elementsByTagName.getLength() == 0) { return ""; } return elementsByTagName.item(0).getTextContent(); } @Override public ExternalBinary getReleaseForVersion(String requiredVersion) throws Exception { retrieveContents(); List<Content> matched = contents .stream() .filter(content -> content.getKey().matches(getExpectedKeyRegex(requiredVersion, content.getDirectory()))) .collect(Collectors.toList()); if (matched.size() == 0) { throw new IllegalStateException( "There wasn't found any binary with the key matching regex " + getExpectedKeyRegex(requiredVersion, "directory") + " in the storage: " + storageUrl); } if (requiredVersion != null) { return new ExternalBinary(requiredVersion, storageUrl + matched.get(0).getKey()); } else { Content latestContent = findLatestContent(matched); return new ExternalBinary(latestContent.getDirectory(), storageUrl + latestContent.getKey()); } } private Content findLatestContent(List<Content> matched) { return matched.stream() .sorted((c1, c2) -> { Date c1Date = parseDate(c1.getLastModified(), c1.getKey()); Date c2Date = parseDate(c2.getLastModified(), c2.getKey()); if (c1Date == null) return -1; if (c2Date == null) return 1; return Long.compare(c2Date.getTime(), c1Date.getTime()); }) .findFirst().get(); } /** * It is expected that this abstract method should return a regex that represents a key of an expected binary/file * stored in the google storage. Key consist of <code>directory_name + / + file_name</code>. Keys are visible on * the root of the google storage url (without index.html suffix) * * @param requiredVersion * The required version set using method {@link GoogleStorageSource#getReleaseForVersion}, * or otherwise null * @param directory * The directory for the current binary the regex is being matched against * * @return A regex that represents a key of an expected binary/file stored in the google storage. */ protected abstract String getExpectedKeyRegex(String requiredVersion, String directory); class Content { private String key; private String directory; private String lastModified; public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getLastModified() { return lastModified; } public void setLastModified(String lastModified) { this.lastModified = lastModified; } public String getDirectory() { return directory; } public void setDirectory(String directory) { this.directory = directory; } } }