/******************************************************************************* * * Copyright (c) 2004-2010 Oracle Corporation. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * *******************************************************************************/ package hudson.tools; import hudson.FilePath; import hudson.model.DownloadService.Downloadable; import hudson.model.Node; import hudson.model.TaskListener; import net.sf.json.JSONObject; import org.kohsuke.stapler.DataBoundConstructor; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.net.URL; /** * Partial convenience implementation of {@link ToolInstaller} that just * downloads an archive from the URL and extracts it. * * <p> Each instance of this is configured to download from a specific URL * identified by an ID. * * @author Kohsuke Kawaguchi * @since 1.308 */ public abstract class DownloadFromUrlInstaller extends ToolInstaller { //TODO: review and check whether we can do it private public final String id; @DataBoundConstructor protected DownloadFromUrlInstaller(String id) { // this installer implementation is designed for platform independent binary, // and as such we don't provide the label support super(null); this.id = id; } public String getId() { return id; } /** * Checks if the specified expected location already contains the installed * version of the tool. * * This check needs to run fairly efficiently. The current implementation * uses the souce URL of {@link Installable}, based on the assumption that * released bits do not change its content. */ protected boolean isUpToDate(FilePath expectedLocation, Installable i) throws IOException, InterruptedException { FilePath marker = expectedLocation.child(".installedFrom"); return marker.exists() && marker.readToString().equals(i.url); } /** * Gets the {@link Installable} identified by {@link #id}. * * @return null if no such ID is found. */ public Installable getInstallable() throws IOException { for (Installable i : ((DescriptorImpl<?>) getDescriptor()).getInstallables()) { if (id.equals(i.id)) { return i; } } return null; } public FilePath performInstallation(ToolInstallation tool, Node node, TaskListener log) throws IOException, InterruptedException { FilePath expected = preferredLocation(tool, node); Installable inst = getInstallable(); if (inst == null) { log.getLogger().println("Invalid tool ID " + id); return expected; } if (isUpToDate(expected, inst)) { return expected; } if (expected.installIfNecessaryFrom(new URL(inst.url), log, "Unpacking " + inst.url + " to " + expected + " on " + node.getDisplayName())) { expected.child(".timestamp").delete(); // we don't use the timestamp FilePath base = findPullUpDirectory(expected); if (base != null && base != expected) { base.moveAllChildrenTo(expected); } // leave a record for the next up-to-date check expected.child(".installedFrom").write(inst.url, "UTF-8"); expected.act(new ZipExtractionInstaller.ChmodRecAPlusX()); } return expected; } /** * Often an archive contains an extra top-level directory that's unnecessary * when extracted on the disk into the expected location. If your * installation sources provide that kind of archives, override this method * to find the real root location. * * <p> The caller will "pull up" the discovered real root by throw away the * intermediate directory, so that the user-configured "tool home" directory * contains the right files. * * <p> The default implementation applies some heuristics to auto-determine * if the pull up is necessary. This should work for typical archive files. * * @param root The directory that contains the extracted archive. This * directory contains nothing but the extracted archive. For example, if the * user installed * http://archive.apache.org/dist/ant/binaries/jakarta-ant-1.1.zip , this * directory would contain a single directory "jakarta-ant". * * @return Return the real top directory inside {@code root} that contains * the meat. In the above example, <tt>root.child("jakarta-ant")</tt> should * be returned. If there's no directory to pull up, return null. */ protected FilePath findPullUpDirectory(FilePath root) throws IOException, InterruptedException { // if the directory just contains one directory and that alone, assume that's the pull up subject // otherwise leave it as is. List<FilePath> children = root.list(); if (children.size() != 1) { return null; } if (children.get(0).isDirectory()) { return children.get(0); } return null; } public static abstract class DescriptorImpl<T extends DownloadFromUrlInstaller> extends ToolInstallerDescriptor<T> { @SuppressWarnings("deprecation") // intentionally adding dynamic item here protected DescriptorImpl() { Downloadable.all().add(createDownloadable()); } protected Downloadable createDownloadable() { return new Downloadable(getId()); } /** * This ID needs to be unique, and needs to match the ID token in the * JSON update file. <p> By default we use the fully-qualified class * name of the {@link DownloadFromUrlInstaller} subtype. */ public String getId() { return clazz.getName().replace('$', '.'); } /** * List of installable tools. * * <p> The UI uses this information to populate the drop-down. Subtypes * can override this method if it wants to change the way the list is * filled. * * @return never null. */ public List<? extends Installable> getInstallables() throws IOException { JSONObject d = Downloadable.get(getId()).getData(); if (d == null) { return Collections.emptyList(); } return Arrays.asList(((InstallableList) JSONObject.toBean(d, InstallableList.class)).list); } } /** * Used for JSON databinding to parse the obtained list. */ public static class InstallableList { // initialize with an empty array just in case JSON doesn't have the list field (which shouldn't happen.) //TODO: review and check whether we can do it private public Installable[] list = new Installable[0]; public Installable[] getList() { return list; } public void setList(Installable[] list) { this.list = list; } } /** * Downloadable and installable tool. */ public static class Installable { //TODO: review and check whether we can do it private /** * Used internally to uniquely identify the name. */ public String id; /** * This is the human readable name. */ public String name; /** * URL. */ public String url; public String getId() { return id; } public String getName() { return name; } public String getUrl() { return url; } public void setId(String id) { this.id = id; } public void setName(String name) { this.name = name; } public void setUrl(String url) { this.url = url; } } }