PageRenderTime 59ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/server/src/main/java/com/paypal/selion/grid/FileDownloader.java

https://gitlab.com/CORP-RESELLER/SeLion
Java | 326 lines | 190 code | 40 blank | 96 comment | 25 complexity | 3f639ee4739f87528cb3df9fa965d846 MD5 | raw file
  1. /*-------------------------------------------------------------------------------------------------------------------*\
  2. | Copyright (C) 2014-2016 PayPal |
  3. | |
  4. | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance |
  5. | with the License. |
  6. | |
  7. | You may obtain a copy of the License at |
  8. | |
  9. | http://www.apache.org/licenses/LICENSE-2.0 |
  10. | |
  11. | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed |
  12. | on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for |
  13. | the specific language governing permissions and limitations under the License. |
  14. \*-------------------------------------------------------------------------------------------------------------------*/
  15. package com.paypal.selion.grid;
  16. import java.io.File;
  17. import java.io.IOException;
  18. import java.net.URL;
  19. import java.security.MessageDigest;
  20. import java.security.NoSuchAlgorithmException;
  21. import java.util.ArrayList;
  22. import java.util.Arrays;
  23. import java.util.Iterator;
  24. import java.util.List;
  25. import java.util.logging.Level;
  26. import org.apache.commons.codec.binary.Hex;
  27. import org.apache.commons.compress.archivers.ArchiveStreamFactory;
  28. import org.apache.commons.io.FileUtils;
  29. import org.apache.commons.lang.StringUtils;
  30. import org.openqa.selenium.Platform;
  31. import com.google.common.base.Preconditions;
  32. import com.paypal.selion.SeLionConstants;
  33. import com.paypal.selion.grid.ArtifactDetails.URLChecksumEntity;
  34. import com.paypal.selion.grid.RunnableLauncher.InstanceType;
  35. import com.paypal.selion.logging.SeLionGridLogger;
  36. import com.paypal.selion.pojos.SeLionGridConstants;
  37. /**
  38. * File downloader is used to clean up files already downloaded and download all the files specified in the
  39. * download.json file
  40. */
  41. final class FileDownloader {
  42. private static final SeLionGridLogger LOGGER = SeLionGridLogger.getLogger(FileDownloader.class);
  43. private static List<String> files = new ArrayList<String>();
  44. private static long lastModifiedTime;
  45. private static final List<String> SUPPORTED_TYPES = Arrays.asList(ArchiveStreamFactory.ZIP, ArchiveStreamFactory.TAR,
  46. ArchiveStreamFactory.JAR, "bz2", "msi", "gz");
  47. private static final File DOWNLOAD_FILE = new File(SeLionGridConstants.DOWNLOAD_JSON_FILE);
  48. private FileDownloader() {
  49. // Utility class. Hide the constructor
  50. }
  51. /**
  52. * Cleanup all the files already downloaded within the same JVM process. Automatically called internally.
  53. */
  54. static void cleanup() {
  55. LOGGER.entering();
  56. for (String temp : files) {
  57. new File(temp).delete();
  58. }
  59. // Cleaning up the files list
  60. files.clear();
  61. LOGGER.exiting();
  62. }
  63. /**
  64. * Check download.json and download files based on artifact names. Returns without downloading if it detects a
  65. * last modified time stamp is unchanged from the last check. Cleans up previous downloads from the same JVM
  66. *
  67. * @param artifactNames
  68. * the artifact names to download
  69. */
  70. static void checkForDownloads(List<String> artifactNames) {
  71. checkForDownloads(artifactNames, true);
  72. }
  73. /**
  74. * Check download.json and download files based on artifact names. Cleans up previous downloads from the same
  75. * JVM
  76. *
  77. * @param artifactNames
  78. * the artifact names to download
  79. * @param checkTimeStamp
  80. * whether to check the last modified time stamp of the downlaod.json file. Returns immediately on
  81. * subsequent calls if <code>true</code> and last modified is unchanged.
  82. */
  83. static void checkForDownloads(List<String> artifactNames, boolean checkTimeStamp) {
  84. checkForDownloads(artifactNames, true, true);
  85. }
  86. /**
  87. * Check download.json and download files based on artifact names
  88. *
  89. * @param artifactNames
  90. * the artifact names to download
  91. * @param checkTimeStamp
  92. * whether to check the last modified time stamp of the downlaod.json file. Returns immediately on
  93. * subsequent calls if <code>true</code> and last modified is unchanged.
  94. * @param cleanup
  95. * whether to cleanup previous downloads from a previous call to
  96. * checkForDownloads in the same JVM
  97. */
  98. static void checkForDownloads(List<String> artifactNames, boolean checkTimeStamp, boolean cleanup) {
  99. LOGGER.entering();
  100. if (checkTimeStamp && (lastModifiedTime == DOWNLOAD_FILE.lastModified())) {
  101. return;
  102. }
  103. lastModifiedTime = DOWNLOAD_FILE.lastModified();
  104. if (cleanup) {
  105. cleanup();
  106. }
  107. List<URLChecksumEntity> artifactDetails = new ArrayList<ArtifactDetails.URLChecksumEntity>();
  108. try {
  109. artifactDetails = ArtifactDetails.getArtifactDetailsForCurrentPlatformByNames(DOWNLOAD_FILE, artifactNames);
  110. } catch (IOException e) {
  111. LOGGER.log(Level.SEVERE, "Unable to open download.json file", e);
  112. throw new RuntimeException(e);
  113. }
  114. downloadAndExtractArtifacts(artifactDetails);
  115. LOGGER.exiting();
  116. }
  117. /**
  118. * Check download.json and download files based on {@link InstanceType}. Returns without downloading if it detects a
  119. * last modified time stamp is unchanged from the last check. Cleans up previous downloads from the same JVM
  120. *
  121. * @param instanceType
  122. * the {@link InstanceType} to process downloads for
  123. */
  124. static void checkForDownloads(InstanceType instanceType) {
  125. checkForDownloads(instanceType, true);
  126. }
  127. /**
  128. * Check download.json and download files based on {@link InstanceType}. Cleans up previous downloads from the same
  129. * JVM
  130. *
  131. * @param instanceType
  132. * the {@link InstanceType} to process downloads for
  133. * @param checkTimeStamp
  134. * whether to check the last modified time stamp of the downlaod.json file. Returns immediately on
  135. * subsequent calls if <code>true</code> and last modified is unchanged.
  136. */
  137. static void checkForDownloads(InstanceType instanceType, boolean checkTimeStamp) {
  138. checkForDownloads(instanceType, checkTimeStamp, true);
  139. }
  140. /**
  141. * Check download.json and download files based on {@link InstanceType}
  142. *
  143. * @param instanceType
  144. * the {@link InstanceType} to process downlaods for
  145. * @param checkTimeStamp
  146. * whether to check the last modified time stamp of the downlaod.json file. Returns immediately on
  147. * subsequent calls if <code>true</code> and last modified is unchanged.
  148. * @param cleanup
  149. * whether to cleanup previous downloads from a previous call to
  150. * checkForDownloads in the same JVM
  151. */
  152. static void checkForDownloads(InstanceType instanceType, boolean checkTimeStamp, boolean cleanup) {
  153. LOGGER.entering();
  154. if (checkTimeStamp && (lastModifiedTime == DOWNLOAD_FILE.lastModified())) {
  155. return;
  156. }
  157. lastModifiedTime = DOWNLOAD_FILE.lastModified();
  158. if (cleanup) {
  159. cleanup();
  160. }
  161. List<URLChecksumEntity> artifactDetails = new ArrayList<ArtifactDetails.URLChecksumEntity>();
  162. try {
  163. artifactDetails = ArtifactDetails.getArtifactDetailsForCurrentPlatformByRole(DOWNLOAD_FILE, instanceType);
  164. } catch (IOException e) {
  165. LOGGER.log(Level.SEVERE, "Unable to open download.json file", e);
  166. throw new RuntimeException(e);
  167. }
  168. downloadAndExtractArtifacts(artifactDetails);
  169. LOGGER.exiting();
  170. }
  171. private static void downloadAndExtractArtifacts(List<URLChecksumEntity> artifactDetails) {
  172. LOGGER.fine("Current Platform: " + Platform.getCurrent());
  173. for (Iterator<URLChecksumEntity> iterator = artifactDetails.iterator(); iterator.hasNext();) {
  174. URLChecksumEntity entity = (URLChecksumEntity) iterator.next();
  175. String url = entity.getUrl().getValue();
  176. String checksum = entity.getChecksum().getValue();
  177. StringBuilder msg = new StringBuilder();
  178. msg.append("Downloading from URL: ").append(url).append("...");
  179. msg.append("[").append(checksum).append("] will be used for checksum validation.");
  180. LOGGER.fine(msg.toString());
  181. String result;
  182. while ((result = downloadFile(url, checksum)) == null) {
  183. // TODO: Need to add a measurable wait to skip downloading after 'n' tries
  184. LOGGER.warning("Error downloading the file " + url + ". Retrying....");
  185. }
  186. files.add(result);
  187. if (result.endsWith(".msi")) {
  188. String extractedExeFile = FileExtractor.extractMsi(result);
  189. files.add(extractedExeFile);
  190. }
  191. if (!result.endsWith(".jar") && !result.endsWith(".msi")) {
  192. List<String> extractedFileList = FileExtractor.extractArchive(result);
  193. files.addAll(extractedFileList);
  194. }
  195. }
  196. LOGGER.fine("Files after download and extract: " + files.toString());
  197. }
  198. private static boolean checkLocalFile(String filename, String checksum, String algorithm) {
  199. MessageDigest md = null;
  200. StringBuilder sb = new StringBuilder("");
  201. try {
  202. md = MessageDigest.getInstance(algorithm);
  203. } catch (NoSuchAlgorithmException e1) {
  204. // NOSONAR
  205. }
  206. try {
  207. byte[] mdbytes = md.digest(FileUtils.readFileToByteArray(new File(filename)));
  208. sb.append(Hex.encodeHexString(mdbytes));
  209. } catch (IOException e) {
  210. LOGGER.log(Level.SEVERE, e.getMessage(), e);
  211. }
  212. if (checksum.equals(sb.toString())) {
  213. LOGGER.fine("checksum matched for " + filename);
  214. return true;
  215. }
  216. LOGGER.fine("checksum did not match for " + filename);
  217. return false;
  218. }
  219. private static String decideFilePath(String fileName) {
  220. if (fileName.endsWith(".jar")) {
  221. return new StringBuilder().append(SeLionConstants.SELION_HOME_DIR).append(fileName).toString();
  222. } else {
  223. // Encountered a archive type: at this point it is sure the valid archive types come in
  224. return new StringBuilder().append(SeLionGridConstants.DOWNLOADS_DIR).append(fileName).toString();
  225. }
  226. }
  227. private static String downloadFile(String url, String checksum, String algorithm) {
  228. Preconditions.checkArgument(StringUtils.isNotBlank(algorithm), "Invalid Algorithm: Cannot be null or empty");
  229. String filename = decideFilePath(url.substring(url.lastIndexOf("/") + 1));
  230. if (new File(filename).exists()) {
  231. // local file exist. no need to download
  232. if (checkLocalFile(filename, checksum, algorithm)) {
  233. return filename;
  234. }
  235. }
  236. LOGGER.info("Downloading from " + url + " with checksum " + checksum + "[" + algorithm + "]");
  237. try {
  238. FileUtils.copyURLToFile(new URL(url), new File(filename), 10000, 60000);
  239. } catch (IOException e) {
  240. LOGGER.log(Level.SEVERE, e.getMessage(), e);
  241. }
  242. if (checkLocalFile(filename, checksum, algorithm)) {
  243. return filename;
  244. }
  245. return null;
  246. }
  247. /**
  248. * Download a file from the specified url
  249. *
  250. * @param artifactUrl
  251. * url of the file to be downloaded.
  252. * @param checksum
  253. * checksum to downloaded file.
  254. * @return the downloaded file path.
  255. */
  256. static String downloadFile(String artifactUrl, String checksum) {
  257. LOGGER.entering(new Object[] { artifactUrl, checksum });
  258. Preconditions.checkArgument(StringUtils.isNotBlank(artifactUrl), "Invalid URL: Cannot be null or empty");
  259. Preconditions.checkArgument(StringUtils.isNotBlank(checksum), "Invalid CheckSum: Cannot be null or empty");
  260. // Making sure only the files supported go through the download and extraction.
  261. isValidFileType(artifactUrl);
  262. String algorithm = null;
  263. if (isValidSHA1(checksum)) {
  264. algorithm = "SHA1";
  265. } else if (isValidMD5(checksum)) {
  266. algorithm = "MD5";
  267. }
  268. String result = downloadFile(artifactUrl, checksum, algorithm);
  269. LOGGER.exiting(result);
  270. return result;
  271. }
  272. private static boolean isValidSHA1(String s) {
  273. return s.matches("[a-fA-F0-9]{40}");
  274. }
  275. private static boolean isValidMD5(String s) {
  276. return s.matches("[a-fA-F0-9]{32}");
  277. }
  278. private static void isValidFileType(String url) {
  279. // Obtaining only the file extension
  280. String fileType = url.substring(url.lastIndexOf('.') + 1);
  281. if (!SUPPORTED_TYPES.contains(fileType)) {
  282. throw new UnsupportedOperationException("Unsupported file format: " + fileType
  283. + ". Supported file types are .zip, .tar, .msi and bz2");
  284. }
  285. }
  286. }