package org.jboss.arquillian.drone.webdriver.binary.handler; import java.io.File; import java.net.URL; import java.util.logging.Logger; import org.jboss.arquillian.drone.webdriver.binary.BinaryFilesUtils; import org.jboss.arquillian.drone.webdriver.binary.downloading.Downloader; import org.jboss.arquillian.drone.webdriver.binary.downloading.ExternalBinary; import org.jboss.arquillian.drone.webdriver.binary.downloading.source.ExternalBinarySource; import org.jboss.arquillian.drone.webdriver.utils.Constants; import org.jboss.arquillian.drone.webdriver.utils.PropertySecurityAction; import org.jboss.arquillian.drone.webdriver.utils.Validate; import org.openqa.selenium.remote.DesiredCapabilities; /** * Class that handles system properties, properties stored in capabilities, downloading, extracting and setting binaries * as executable. */ public abstract class AbstractBinaryHandler implements BinaryHandler { /** * Capability property that sets downloading on or off. By default it is on */ public static final String DOWNLOAD_BINARIES_PROPERTY = "downloadBinaries"; private Logger log = Logger.getLogger(this.getClass().toString()); /** * Checks system properties and capabilities, whether a path to binary is already set there * (see {@link AbstractBinaryHandler#getSystemBinaryProperty()} and {@link AbstractBinaryHandler#getBinaryProperty()} * ). * If not and the downloading is not set off ({@link AbstractBinaryHandler#DOWNLOAD_BINARIES_PROPERTY}), then the * binary is downloaded. Resulting binary is then set as system property that is returned by the method * {@link AbstractBinaryHandler#getSystemBinaryProperty()} * * @param performExecutableValidations * If it should be checked whether the binary points to an executable file. * * @return Path to the binary */ @Override public String checkAndSetBinary(boolean performExecutableValidations) { String binary = PropertySecurityAction.getProperty(getSystemBinaryProperty()); if (Validate.empty(binary)) { binary = PropertySecurityAction.getProperty(getBinaryProperty()); } if (Validate.empty(binary) && !Validate.empty(getBinaryProperty())) { binary = (String) getCapabilities().getCapability(getBinaryProperty()); } if (Validate.empty(binary)) { String downloadBinaries = (String) getCapabilities().getCapability(DOWNLOAD_BINARIES_PROPERTY); if (Validate.empty(downloadBinaries) || (!downloadBinaries.toLowerCase().trim().equals("false") && !downloadBinaries.toLowerCase().trim().equals("no"))) { try { binary = downloadAndPrepare().toString(); } catch (Exception e) { throw new IllegalStateException( "Something bad happened when Drone was trying to download and prepare a binary. " + "For more information see the cause.", e); } } } setBinaryAsSystemProperty(performExecutableValidations, binary); return binary; } /** * Sets binary as a system property that is returned by method * {@link AbstractBinaryHandler#getSystemBinaryProperty()} * * @param performExecutableValidations * If it should be checked whether the binary points to an executable file. * @param binary * Path to the binary */ protected void setBinaryAsSystemProperty(boolean performExecutableValidations, String binary) { if (Validate.nonEmpty(binary) && Validate.nonEmpty(getSystemBinaryProperty())) { if (performExecutableValidations) { Validate.isExecutable(binary, "The binary must point to an executable file, " + binary); } PropertySecurityAction.setProperty(getSystemBinaryProperty(), binary); } } /** * This method consist of four steps: * <br/> * <h3>1. Checking properties</h3> * In the first step it checks capabilities if there is set either url a binary should be downloaded from or * a desired binary version. For more information see methods {@link AbstractBinaryHandler#getUrlToDownloadProperty()} * and * {@link AbstractBinaryHandler#getDesiredVersionProperty()}. * <p> * <h3>2. Downloading</h3> * If the url is set then the binary is downloaded from the given url. * <p> * If there is set only the desired version, then a binary with the specified version is downloaded using an external * binary source ({@link AbstractBinaryHandler#getExternalBinarySource()}) * </p> * <p> * If there is set neither a url nor a desired version, then a binary with the latest version is downloaded using * the external binary source. * </p> * <p> * <p> * Directory where the downloaded file is stored depends on set properties. If there is set the desired version, * or if the latest version is downloaded then the file is stored in: * <br/> * <code>$HOME/.arquillian/drone/ + </code>{@link AbstractBinaryHandler#getArquillianCacheSubdirectory()}<code> * + / + version * </code> * <br/> * If the version is not set, then the file is stored in: <code>target/drone/downloaded</code> * </p> * <p> * <h3>3. Extraction/copy</h3> * When the file is downloaded then it is expected that in most cases it is an archive (zip, tar) file. If the file * is * an archive then it is extracted; if the file is not an archive file, then it is copied. The targeted directory * for this operation is: * <br/> * <code>target/drone/md5hash(downloaded_file)/</code> * <p> * <h3>4. Setting as executable</h3> * In the last step the extracted/copied file is set as an executalbe file. * * @return An executable binary that was extracted/copied from the downloaded file * * @throws Exception * If anything bad happens */ public File downloadAndPrepare() throws Exception { String url = null; if (!Validate.empty(getUrlToDownloadProperty())) { url = (String) getCapabilities().getCapability(getUrlToDownloadProperty()); } String desiredVersion = null; if (!Validate.empty(getDesiredVersionProperty())) { desiredVersion = (String) getCapabilities().getCapability(getDesiredVersionProperty()); } if (Validate.nonEmpty(url)) { if (Validate.empty(desiredVersion)) { return downloadAndPrepare(null, url); } else { return downloadAndPrepare(createAndGetCacheDirectory(desiredVersion), url); } } if (getExternalBinarySource() == null) { return null; } ExternalBinary release = null; if (Validate.nonEmpty(desiredVersion)) { release = getExternalBinarySource().getReleaseForVersion(desiredVersion); } else { release = getExternalBinarySource().getLatestRelease(); } return downloadAndPrepare(createAndGetCacheDirectory(release.getVersion()), release.getUrl()); } /** * Takes care of all steps but the first one of the method {@link AbstractBinaryHandler#downloadAndPrepare()} * * @param targetDir * A directory where a downloaded binary should be stored * @param from * A url a binary should be downloaded from * * @return An executable binary that was extracted/copied from the downloaded file * * @throws Exception * If anything bad happens */ protected File downloadAndPrepare(File targetDir, String from) throws Exception { return downloadAndPrepare(targetDir, new URL(from)); } /** * Takes care of all steps but the first one of the method {@link AbstractBinaryHandler#downloadAndPrepare()} * * @param targetDir * A directory where a downloaded binary should be stored * @param from * A url a binary should be downloaded from * * @return An executable binary that was extracted/copied from the downloaded file * * @throws Exception * If anything bad happens */ protected File downloadAndPrepare(File targetDir, URL from) throws Exception { File downloaded = Downloader.download(targetDir, from); File extraction = BinaryFilesUtils.extract(downloaded); File[] files = extraction.listFiles(file -> file.isFile()); if (files == null || files.length == 0) { throw new IllegalStateException( "The number of extracted files in the directory " + extraction + " is 0. There is no file to use"); } return markAsExecutable(files[0]); } /** * Sets the given binary to be executable (if it is not already set) * * @param binaryFile * A binary file that should be set to be executable * * @return the given binary file set to be executable */ protected File markAsExecutable(File binaryFile) { if (!Validate.executable(binaryFile.getAbsolutePath())) { log.info("marking binary file: " + binaryFile.getPath() + " as executable"); try { binaryFile.setExecutable(true); } catch (SecurityException se) { log.severe("The downloaded binary: " + binaryFile + " could not be set as executable. This may cause additional problems."); } } return binaryFile; } private File createAndGetCacheDirectory(String subdirectory) { String dirPath = Constants.ARQUILLIAN_DRONE_CACHE_DIRECTORY + getArquillianCacheSubdirectory() + (subdirectory == null ? "" : File.separator + subdirectory); File dir = new File(dirPath); dir.mkdirs(); return dir; } /** * This method should return a capability property name which a path to an executable binary could be stored under * * @return A capability property name which a path to an executable binary could be stored under */ protected abstract String getBinaryProperty(); /** * This method should return a system property name which a path to an executable binary should be stored under * * @return A system property name which a path to an executable binary should be stored under */ public abstract String getSystemBinaryProperty(); /** * Name of the subdirectory that should be used for this binary handler in the Drone cache directory * <code>($HOME/.arquillian/drone)</code> * * @return Name of the subdirectory that should be used for this binary handler in the Drone cache directory */ protected abstract String getArquillianCacheSubdirectory(); /** * This method should return a capability property name which a desired version of a binary could be stored under * * @return A capability property name under a desired version of a binary could be stored under */ protected abstract String getDesiredVersionProperty(); /** * This method should return a capability property name which a url pointing to a desired binary could be stored under * * @return A capability property name which a url pointing to a desired binary could be stored under */ protected abstract String getUrlToDownloadProperty(); /** * This method should return an instance of an {@link ExternalBinary} that should be used for retrieving available * releases of a binary * * @return An instance of an {@link ExternalBinary} that should be used for retrieving available releases of a binary */ protected abstract ExternalBinarySource getExternalBinarySource(); /** * This method should return a desired capabilities with stored properties * * @return A desired capabilities with stored properties */ protected abstract DesiredCapabilities getCapabilities(); }