Package hudson.ivy

Source Code of hudson.ivy.IvyBuildTrigger

/**
* Copyright 2007-2011 hibou, Martin Ficker, Jeffrey Metcalf, Timothy Bingaman
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package hudson.ivy;

import hudson.CopyOnWrite;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Cause;
import hudson.model.DependecyDeclarer;
import hudson.model.DependencyGraph;
import hudson.model.Hudson;
import hudson.model.Item;
import hudson.model.Project;
import hudson.model.Result;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Notifier;
import hudson.tasks.Publisher;
import hudson.util.FormValidation;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletException;

import net.sf.json.JSONObject;

import org.apache.ivy.Ivy;
import org.apache.ivy.Ivy.IvyCallback;
import org.apache.ivy.core.IvyContext;
import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
import org.apache.ivy.core.module.id.ModuleId;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry;
import org.apache.ivy.core.settings.IvySettings;

import org.apache.ivy.plugins.version.VersionMatcher;
import org.apache.ivy.util.Message;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

/**
* Trigger the build of other project based on the Ivy dependency management system.
*
* @author jmetcalf@dev.java.net
* @author martinficker@dev.java.net
* @author hibou@dev.java.net
* @author tbingaman@dev.java.net
*/
@SuppressWarnings("unchecked")
public class IvyBuildTrigger extends Notifier implements DependecyDeclarer {

    /**
     * The name of a copy of the ivy file relative to the projects root dir since the
     * workspace may not always be accessible.
     */
    private static final String BACKUP_IVY_FILE_NAME = "ivy.xml";
    /**
     * The name of a copy of the ivy properties file relative to the projects root dir since the
     * workspace may not always be accessible.
     */
    private static final String BACKUP_IVY_PROPERTIES_NAME = "ivy.properties";

    private static final Logger LOGGER = Logger.getLogger(IvyBuildTrigger.class.getName());

    /**
     * The name of the ivy file relative to the workspace as configured by the user.
     */
    private String ivyFile = "ivy.xml";
    /**
     * The name of the ivy properties file relative to the workspace as configured by the user.
     */
  private final String ivyProperties;

    /**
     * Identifies {@link IvyConfiguration} to be used.
     */
    private final String ivyConfName;
   
    private final boolean triggerWhenUnstable;
   
    private final boolean useUpstreamParameters;

   
    /**
     * The last modified time of the backup copy of the ivy file on the master.
     */
    private transient long lastmodified = 0;

    /**
     * The Ivy ModuleDescriptor for the project associated with this instance.
     */
    private transient ModuleDescriptor moduleDescriptor;

    /**
     * The Ivy VersionMatcher configured from the Ivy settings being used by this instance.
     */
    private transient VersionMatcher versionMatcher;

    /**
     * Set the Message Implementation for ivy to avoid logging to err
     */
    static {
        Message.setDefaultLogger(new IvyMessageImpl());
    }

    /**
     * Constructor
     *
     * @param ivyFile
     *            the ivy.xml file path within the workspace
     * @param ivyConfName
     *            the Ivy configuration name to use
     * @param ivyProperties
     *            the Ivy properties file path within the workspace
     * @param triggerWhenUnstable
     *            true if this build should be triggered even when an upstream build in Unstable.
     *            false if this build should be triggered only when an upstream build is Successful.
     * @param passPropertiesDownstream          
     */
    @DataBoundConstructor
    public IvyBuildTrigger(final String ivyFile, final String ivyConfName, final String ivyPropertiesFile, final boolean triggerWhenUnstable, final boolean useUpstreamParameters) {
        this.ivyFile = ivyFile;
        this.ivyConfName = ivyConfName;
        this.ivyProperties = ivyPropertiesFile;
        this.triggerWhenUnstable = triggerWhenUnstable;
        this.useUpstreamParameters = useUpstreamParameters;
    }

    /**
     * Constructor
     *
     * @param ivyFile
     *            the ivy.xml file path within the workspace
     * @param ivyConfName
     *            the Ivy configuration name to use
     * @deprecated use {@link #IvyBuildTrigger(String, String, boolean)} instead
     */
    @Deprecated
    public IvyBuildTrigger(final String ivyFile, final String ivyConfName) {
        this(ivyFile, ivyConfName, null, false, false);
    }

  public String getIvyPropertiesFile() {
    return ivyProperties;
  }
    /**
     *
     * @return the ivy.xml file path within the workspace
     */
    public String getIvyFile() {
        return ivyFile;
    }

    /**
     *
     * @return the Ivy configuration name used
     */
    public String getIvyConfName() {
        return ivyConfName;
    }

    /**
     *
     * @return the {@link IvyConfiguration} from the {@link #ivyConfName}
     */
    private IvyConfiguration getIvyConfiguration() {
        IvyConfiguration conf = null;
        if (ivyConfName != null) {
            for (IvyConfiguration i : DESCRIPTOR.getConfigurations()) {
                if (i.getName().equals(ivyConfName)) {
                    conf = i;
                    break;
                }
            }
        }
        return conf;
    }

    public boolean isTriggerWhenUnstable() {
        return triggerWhenUnstable;
    }

    public boolean isUseUpstreamParameters()
    {
        return useUpstreamParameters;
    }
   
    /**
     *
     * @return the Ivy instance based on the {@link #ivyConfName}
     *
     * @throws ParseException
     * @throws IOException
     */
    public Ivy getIvy(File localFilePath, String propertyFile) {
        Message.setDefaultLogger(new IvyMessageImpl());
        IvyConfiguration ivyConf = getIvyConfiguration();
            try {
                IvySettings ivySettings = new IvySettings();
                if (propertyFile != null) {
                  File f = new File(localFilePath,propertyFile);
                  ivySettings.loadProperties(f);
                  LOGGER.log(Level.INFO,"Configured Ivy using custom properties " + f);
                }
                if (ivyConf != null) {
                    ivySettings.load(new File(ivyConf.getIvyConfPath()));
                    LOGGER.log(Level.INFO,"Configured Ivy using custom settings " + ivyConf.getIvyConfPath());
                } else {
                    ivySettings.loadDefault();
                    LOGGER.log(Level.INFO,"Configured Ivy using default 2.1 settings");
                }
                return Ivy.newInstance(ivySettings);
            }
            catch (Exception e) {
                LOGGER.log(Level.SEVERE,"Error while reading the default Ivy 2.1 settings: " + e.getMessage(),e);
            }
            return null;
    }

    /**
     * Get the last computed Ivy module descriptor created from the ivy.xml of
     * this trigger.
     *
     * @param p the project this Trigger belongs to
     * @return the Ivy module descriptor
     */
    private ModuleDescriptor getModuleDescriptor(AbstractProject p) {
        if (moduleDescriptor == null) {
            moduleDescriptor = recomputeModuleDescriptor(p.getSomeBuildWithWorkspace());
        }
        return moduleDescriptor;
    }

    /**
     * Will only copy the file from the repository if its last modified time
     * exceeds what the instance thinks is the last recorded modified time of
     * the localFile, which is the local backup ivy file copy.  For this
     * to operate properly for remoting circumstances, the master and slave instances
     * must be reasonably time synchronized.
     *
     * @param workspace Workspace root Directory
     * @param localFile The local file to be copied to
     * @return  true iff the file was actually copied
     * @throws IOException   If unable to access/copy the workspace ivy file
     * @throws InterruptedException  If interrupted while accessing the workspace ivy file
     */
    private boolean copyFileFromWorkspaceIfNecessary(FilePath workspace, String fileToCopy, File localFile, String localDestFile) throws IOException, InterruptedException {
        boolean copied = false;
        if (workspace != null) { // Unless the workspace is non-null we can not copy a new ivy file
        FilePath f = workspace.child(fileToCopy);
        // Copy the ivy file from the workspace (possibly at a slave) to the projects dir (at Master)
        FilePath backupCopy = new FilePath(localFile).child(localDestFile);
        long flastModified = f.lastModified();
        if (flastModified == 0l) throw new FileNotFoundException("Can't stat file " + f);
        if (flastModified > lastmodified) {
          f.copyTo(backupCopy);
          localFile.setLastModified(flastModified);
          copied = true;
          LOGGER.info("Copied the workspace file "+fileToCopy+" to backup "+localFile.getCanonicalFile().toString()+"/"+localDestFile);
        }
        }
        return copied;
    }

    /**
     * Force the creation of the module descriptor.
     *
     * @param  b  a build this trigger belongs to
     */
    private ModuleDescriptor recomputeModuleDescriptor(AbstractBuild<?,?> b) {
        // The build may be null if no build with a workspace was found
        if (b == null) {
            return null;
        }
        LOGGER.fine("Recomputing Moduledescriptor for Project "+b.getProject().getFullDisplayName());

    final File destDir = b.getProject().getRootDir();
   
    String propertyFileToLoadIntoIvy = null;
   
    String propertyFile = getIvyPropertiesFile();
    if (propertyFile != null && !propertyFile.trim().isEmpty()) {
      try {
              copyFileFromWorkspaceIfNecessary(b.getWorkspace(), propertyFile, destDir, BACKUP_IVY_PROPERTIES_NAME);
              propertyFileToLoadIntoIvy = BACKUP_IVY_PROPERTIES_NAME;
          }
          catch (IOException e) {
              LOGGER.log(Level.WARNING, "Failed to access the workspace ivy properties file '"+propertyFile+"'", e);
              LOGGER.log(Level.WARNING, "Removing ModuleDescriptor");
              return null;
          }
          catch (InterruptedException e) {
              LOGGER.log(Level.WARNING, "Interupted while accessing the workspace ivy properties file '"+propertyFile+"'", e);
              File ivyP = new File(destDir, BACKUP_IVY_PROPERTIES_NAME);
              if (ivyP.canRead()) {
                LOGGER.log(Level.WARNING, "Will try to use use existing ivy properties backup");
                propertyFileToLoadIntoIvy = BACKUP_IVY_PROPERTIES_NAME;
            }
          }
    }

    Ivy ivy = getIvy(destDir, propertyFileToLoadIntoIvy);
        if (ivy == null) {
            return null;
        }

        versionMatcher = ivy.getSettings().getVersionMatcher();

    String ivyDesc = getIvyFile();
        File ivyF = null;
       
    if (ivyDesc != null && !ivyDesc.trim().isEmpty()) {
          ivyF = new File(destDir, BACKUP_IVY_FILE_NAME);
      try {
              copyFileFromWorkspaceIfNecessary(b.getWorkspace(), ivyDesc, destDir, BACKUP_IVY_FILE_NAME);
          }
          catch (IOException e) {
              LOGGER.log(Level.WARNING, "Failed to access the workspace ivy file '"+ivyDesc+"'", e);
              LOGGER.log(Level.WARNING, "Removing ModuleDescriptor");
              return null;
          }
          catch (InterruptedException e) {
              LOGGER.log(Level.WARNING, "Interupted while accessing the workspace ivy file '"+ivyDesc+"'", e);
              if (ivyF.canRead()) LOGGER.log(Level.WARNING, "Will try to use use existing ivy file backup");
          }
    }

        final File fivyF = ivyF;

        // Calculate ModuleDescriptor from the backup copy
        if (fivyF == null || !fivyF.canRead()) {
            LOGGER.log(Level.WARNING, "Cannot read ivy file backup...removing ModuleDescriptor");
            return null;
        }

        if (moduleDescriptor == null || fivyF.lastModified() > lastmodified) {
            lastmodified = fivyF.lastModified();
            return (ModuleDescriptor) ivy.execute(new IvyCallback(){
                public Object doInIvyContext(Ivy ivy, IvyContext context) {
                    try {
                        return  ModuleDescriptorParserRegistry.getInstance().parseDescriptor(ivy.getSettings(),
                            fivyF.toURI().toURL(), ivy.getSettings().doValidate());
                    } catch (MalformedURLException e) {
                        LOGGER.log(Level.WARNING, "The URL is malformed : " + fivyF, e);
                        return null;
                    } catch (ParseException e) {
                        LOGGER.log(Level.WARNING, "Parsing error while reading the ivy file " + fivyF, e);
                        return null;
                    } catch (IOException e) {
                        LOGGER.log(Level.WARNING, "I/O error while reading the ivy file " + fivyF, e);
                        return null;
                    }
                }
            });
        }
  return null;
    }

    /**
     * Set the Ivy ModuleDescriptor for this instance.  If the descriptor
     * has changed, then invalidate the descriptor project map and
     * rebuild the dependency graph.
     *
     * @param d  The ModuleDescriptor to set.
     */
    private void setModuleDescriptor(ModuleDescriptor d) {
        ModuleDescriptor old = moduleDescriptor;
        moduleDescriptor = d;
        if (old == moduleDescriptor) return;
        if ((old==null) || !old.equals(moduleDescriptor)) {
            DESCRIPTOR.invalidateProjectMap();
            Hudson.getInstance().rebuildDependencyGraph();
         }
    }

    /**
     * The BuildStep perform implementation.  It performs a conditional recomputation of the project ModuleDescriptor.
     *
     * @return  always returns true so the build can continue
     */
    @Override
    public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
        setModuleDescriptor(recomputeModuleDescriptor(build));
        return true;
    }

    /**
     * IvyBuildTrigger is only responsible for helping to build dependency graphs based on Ivy settings and configuration.
     * It never alters build status and its execution time should not be included in build time.
     *
     * @return  always returns true meaning that the perform method will not run until the build is marked complete
     * @see #perform(AbstractBuild, Launcher, BuildListener)
     */
    @Override
    public boolean needsToRunAfterFinalized() {
        return true;
    }

    public BuildStepMonitor getRequiredMonitorService() {
        return BuildStepMonitor.BUILD;
    }

    /**
     * Build the dependency graph based on the Ivy ModuleDescriptor for this trigger.
     *
     * @param   owner  the project this trigger belongs to
     * @param   graph  the DependencyGraph to which computed dependencies are added
     */
    public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) {
        ModuleDescriptor md;
        md = getModuleDescriptor(owner);
        if (md == null) {
            return;
        }

        // Get All Dependencies from ivy.
        // Map them to corresponding  hudson projects

        DependencyDescriptor[] deps = md.getDependencies();

        for (DependencyDescriptor depDesc : deps) {
            ModuleId id = depDesc.getDependencyId();
            ModuleRevisionId rid = depDesc.getDependencyRevisionId();

            List<AbstractProject> possibleDeps = DESCRIPTOR.getProjectsFor (id);
            for (AbstractProject p : possibleDeps) {
                // ignore disabled Projects
                if (p.isDisabled()) p = null;
                // Such a project might not exist
                if (p != null && p instanceof Project) {
                    if (captures(rid, (Project) p)) {
                        graph.addDependency(new IvyThresholdDependency(p, owner, triggerWhenUnstable ? Result.UNSTABLE : Result.SUCCESS, useUpstreamParameters));
                    }
                }
            }
        }
    }

    /**
     * Determine if a dependency ModuleRevisionId captures the candidate project.
     *
     * @param depRevId   The ModuleRevisionId of the dependency
     * @param candidate  A candidate Project that is tested against the depRevId
     * @return  true iff the candidate is captured according to the rules
     */
    private boolean captures(ModuleRevisionId depRevId, Project candidate) {
        IvyBuildTrigger t = (IvyBuildTrigger) candidate.getPublisher(DESCRIPTOR);
        boolean captures = (t != null); // check again in case candidate reconfigured against race condition
        if (captures && DESCRIPTOR.isExtendedVersionMatching()) {
            ModuleDescriptor cmd = t.getModuleDescriptor(candidate);
            ModuleRevisionId cmrid = cmd.getModuleRevisionId();

            VersionMatcher matcher = versionMatcher;

            // A null VersionMatcher means something is really wrong and we should not proceed to add the dependency
            captures = (matcher != null && matcher.isDynamic(depRevId));
            if (captures) {
                String dbranch = depRevId.getBranch();
                String cbranch = cmrid.getBranch();

                // Best Practice assumes you are using branch in your Ivy repository patterns before revision.
                // Otherwise you will need to use pattern-based (not latest.*) dynamic dependency revisions
                // to guarantee you will not run into Ivy resolve errors during builds.
                if (dbranch != null) {
                    captures = dbranch.equals(cbranch);
                }
                else {
                    captures = (cbranch == null);
                }
                if (captures) {
                    captures = matcher.accept(depRevId, cmrid);
                    if (captures && matcher.needModuleDescriptor(depRevId, cmrid)) {
                        captures = matcher.accept(depRevId, cmd);
                    }
                }
            }
        }
        return captures;
    }

    /**
     * Container for the Ivy configuration.
     * Note that configurations are now called settings in Ivy 2.0
     * to avoid confusion with dependency configurations.
     */
    public static class IvyConfiguration {

        private String name;

        private String ivyConfPath;

        /**
         * Contructor
         *
         * @param name
         *            the name of the configuration
         * @param ivyConfPath
         *            the full path to the ivy configuration file
         */
        public IvyConfiguration(String name, String ivyConfPath) {
            this.name = name;
            this.ivyConfPath = ivyConfPath;
        }

        /**
         *
         * @return the full path to the ivy configuration file
         */
        public String getIvyConfPath() {
            return ivyConfPath;
        }

        /**
         *
         * @return the name of the configuration
         */
        public String getName() {
            return name;
        }

        /**
         *
         * @return true if the configuration file exists
         */
        public boolean getExists() {
            return new File(ivyConfPath).exists();
        }
    }

    /**
     * The descriptor of this trigger.  It is annotated as an Extension so Hudson can automatically register this
     * instance as associated with IvyBuildTrigger.
     */
    @Extension
    public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();

    /**
     * The descriptor implementation of this trigger
     */
    public static final class DescriptorImpl extends BuildStepDescriptor<Publisher> {

        /**
         * Note that configurations are now called settings in Ivy 2.0
         * to avoid confusion with dependency configurations.
         */
        @CopyOnWrite
        private volatile IvyConfiguration[] configurations = new IvyConfiguration[0];

        /**
         * A map of Ivy ModuleIds against the associated AbstractProject instances.
         */
        @CopyOnWrite
        private transient volatile Map<ModuleId, List<AbstractProject>> projectMap=null;

        /**
         * This indicates if extendedVersionMatching should be used to help restrict
         * project dependency mapping.
         */
        private volatile boolean extendedVersionMatching = false;

        /**
         * Default constructor just loads any serialized configuration.
         */
        DescriptorImpl() {
            load();
        }

        /**
         * Return a List of AbstractProjects that have an IvyBuildtrigger configured with an
         * ivy file Matching the given ModuleID.  This method returns an empty list rather
         * than null when there are no matching projects.
         *
         * @param id    The Module Id to search for
         * @return a List of Matching Projects
         */
        private List<AbstractProject> getProjectsFor(ModuleId searchId) {
            if (projectMap == null) calculateProjectMap();
            List<AbstractProject> result = projectMap.get(searchId);
            if (result == null) result = Collections.emptyList();
            return result;
        }

        /**
         * This method should be called if the cached Project Map may be invalid.
         * Reasons for this might be:
         * <ul>
         <li> An Ivy File has Changed
         *  <li> A Project was renamed
         * </ul>
         */
        private void invalidateProjectMap() {
            projectMap = null;
        }

        /**
         * Calculate the map of projects to Ivy ModuleId.
         */
        private void calculateProjectMap() {
            List<Project> projects = Hudson.getInstance().getAllItems(Project.class);
            Map<ModuleId, List<AbstractProject>> projectMap = new HashMap<ModuleId, List<AbstractProject>>();
            for (Project<?, ?> p : projects) {
                if (p.isDisabled()) {
                    continue;
                }
                IvyBuildTrigger t = (IvyBuildTrigger) p.getPublisher(DESCRIPTOR);
                if (t != null) {
                    ModuleDescriptor m;

                    try {
                        m = t.getModuleDescriptor(p);
                    } catch (Exception e) { // This does sometimes fail with an exception instead of returning null for an offline slave
                        LOGGER.log(Level.WARNING,"Calculating the ModuleDescriptor failed for project "+p.getFullDisplayName(),e);
                        m =  null;
                    }

                    if (m != null) {
                        ModuleId id = m.getModuleRevisionId().getModuleId();
                        List<AbstractProject> list = projectMap.get (id);
                        if (list == null) {
                            list = new ArrayList<AbstractProject> ();
                        }
                        list.add(p);
                        projectMap.put(id, list);
                    }
                }
            }
            this.projectMap = projectMap;
        }

        /**
         *
         * @return every existing configuration
         */
        public IvyConfiguration[] getConfigurations() {
            return configurations;
        }

        /**
         *
         * @return  true if extended version matching is being used.
         */
        public boolean isExtendedVersionMatching() {
            return extendedVersionMatching;
        }

        /**
         * Implement the Descritor's display name.
         */
        public String getDisplayName() {
            return Messages.IvyBuildTrigger_DisplayName();
        }

        /**
         * Configure the Descriptor from a GUI request.
         */
        @Override
        public boolean configure(StaplerRequest req, JSONObject json) {
            boolean r = true;

            int i;
            String[] names = req.getParameterValues("ivy_name");
            String[] paths = req.getParameterValues("ivy_conf_path");
            String vm = req.getParameter("ivy_version_matching");

            IvyConfiguration[] confs;

            if (names != null && paths != null) {
                int len = Math.min(names.length, paths.length);
                confs = new IvyConfiguration[len];
                for (i = 0; i < len; i++) {
                    if (Util.nullify(names[i]) == null) {
                        continue;
                    }
                    if (Util.nullify(paths[i]) == null) {
                        continue;
                    }
                    confs[i] = new IvyConfiguration(names[i], paths[i]);
                }
            } else {
                confs = new IvyConfiguration[0];
            }
            LOGGER.info("IvyConfigurations: " + confs.length);

            this.configurations = confs;
            this.extendedVersionMatching = (vm != null);

            save();
            invalidateProjectMap();
            Hudson.getInstance().rebuildDependencyGraph();
            return r;
        }

        /**
         * Simulate what would happen internally if a successful build occurred on an IvyBuildTrigger managed project.
         * This provides a hook to start builds on downstream projects if a triggering event outside of Hudson occurred.
         * One such triggering event could be publish of a non-integration (milestone/release) build of the IvyBuildTrigger
         * managed project code to the Ivy repository that is visible to your build system.
         *
         * The StaplerRequest parameter must include request parameters <code>org</code> and <code>name</code>
         * which respectively represent the Ivy module descriptor attributes <code>organisation</code> and <code>module</code>.
         * Optional request parameters that can be passed on the StaplerRequest include <code>branch</code> and <code>rev</code>
         * which respectively represent the Ivy module descriptor attributes <code>branch</code> and <code>revision</code>.
         * These values are used to match against the ModuleDescriptor of Hudson projects using the IvyBuildTrigger.  In the
         * case that more than one project matches, it is the first match that will win, and only that project will have
         * its downstream dependencies scheduled for builds.  Therefore the caller is wise to provide the most
         * information in the request to ensure the best possible match.  If the trigger descriptor is set to use extended
         * version matching, then at least one of the optional <code>rev</code> or <code>branch</code> is required on the request.
         *
         * Note this event trigger does not actually build the matched project in Hudson.  It just schedules builds on downstream
         * dependent projects.  Successfully executing this event trigger requires global {@link Item#BUILD} permission on a
         * secured Hudson instance.
         *
         * @author jmetcalf@dev.java.net
         * @param req  The StaplerRequest
         * @param rsp  The StaplerResponse
         * @throws IOException    IOException on the servlet call
         * @throws ServletException   ServletException on the servlet call
         * @see #isExtendedVersionMatching()
         */
        public void doHandleExternalTrigger(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
            String org = Util.fixEmptyAndTrim(req.getParameter("org"));
            String name = Util.fixEmptyAndTrim(req.getParameter("name"));
            String branch = Util.fixEmptyAndTrim(req.getParameter("branch"));
            String rev = Util.fixEmptyAndTrim(req.getParameter("rev"));

            Hudson.getInstance().getACL().checkPermission(Item.BUILD);

            if (org == null || name == null)
                throw new IllegalArgumentException("doHandleExternalTrigger requires the org and name parameters");

            if (extendedVersionMatching && branch == null && rev == null)
                throw new IllegalArgumentException("doHandleExternalTrigger requires rev or branch when using extended revision matching");

            ModuleId id = ModuleId.newInstance(org, name);
            String ivylabel = ModuleRevisionId.newInstance(org, name, branch, Util.fixNull(rev)).toString()// Used only in reporting.
            List<AbstractProject> downstream = Collections.emptyList();

            List<AbstractProject> candidates = getProjectsFor(id);
            for (AbstractProject candidate : candidates) {

                // Don't try to identify downstream dependencies of a disabled project.
                // This is more consistent when simulating Hudson dependency behavior,
                // i.e. a disabled parent could not have been built to initiate the trigger.
                if (candidate.isDisabled() || !(candidate instanceof Project)) continue;

                Project p = (Project) candidate;
                IvyBuildTrigger t = (IvyBuildTrigger) p.getPublisher(DESCRIPTOR);

                if (t != null) {
                    if (extendedVersionMatching) {
                        ModuleDescriptor md = t.getModuleDescriptor(p);
                        ModuleRevisionId mdrid = md.getModuleRevisionId();
                        String mdbranch = mdrid.getBranch();
                        if (branch != null && branch.equals(mdbranch) == false)
                            continue;
                        if (branch == null && mdbranch != null)
                            continue;
                        if (rev != null && rev.equals(mdrid.getRevision()) == false)
                            continue;
                    }
                    downstream = Hudson.getInstance().getDependencyGraph().getDownstream(p);
                    break;
                }
            }
            for (AbstractProject down : downstream) {
                if (down.isDisabled() == false) {
                    down.scheduleBuild(new UserCause(ivylabel));
                }
            }
        }

        /**
         * Check that the Ivy configuration file exists.
         *
         * @param req
         *            the file path
         */
        public FormValidation doCheckIvyConf(@QueryParameter final String value) {
            // this can be used to check the existence of a file on the server,
            // so needs to be protected
            if (!Hudson.getInstance().hasPermission(Hudson.ADMINISTER)) return FormValidation.ok();
            if (Util.fixEmpty(value) == null) {
                return FormValidation.error(Messages.IvyBuildTrigger_CheckIvyConf_PathRequiredError());
            }
            File f = new File(value);
            if (!f.isFile()) {
                return FormValidation.error(Messages.IvyBuildTrigger_CheckIvyConf_PathNotFileError(f));
            }

            return FormValidation.ok();
        }

        /**
         * Check that the workspace relative path to the ivy.xml file was entered and conforms to basic expectations.
         * This code cannot check for file existence since the file never exists for new projects that need to check
         * out the ivy file from source control. Under this condition the validator would always fail for first time
         * configuration which can be confusing.  The Ivy file might also be on a remote slave making checking for
         * existence at this level more difficult.
         *
         * @param value
         *            the relative path
         */
        public FormValidation doCheckIvyFile(@QueryParameter final String value) {
            if (!Hudson.getInstance().hasPermission(Hudson.ADMINISTER)) return FormValidation.ok();
            if (Util.fixEmpty(value) == null) {
                return FormValidation.error(Messages.IvyBuildTrigger_CheckIvyFile_PathRequiredError());
            }
            if (new File(value).isAbsolute()) {
                return FormValidation.error(Messages.IvyBuildTrigger_CheckIvyFile_PathAbsoluteError());
            }

            return FormValidation.ok();
        }

        /**
         * Check if this Descriptor is applicable to the job type.  Currently on instances of Project are supported.
         *
         * @param   jobType   the AbstractProject type to be checked.
         * @return  true iff the project type can apply this Descriptor
         * @see  hudson.model.Project
         */
        public boolean isApplicable(Class<? extends AbstractProject> jobType) {
            return Project.class.isAssignableFrom(jobType);
        }
    }

    /**
     * This cause is used when triggering downstream builds from the external event trigger.
     *
     * @author jmetcalf@dev.java.net
     * @see #doHandleExternalTrigger(StaplerRequest, StaplerResponse)
     */
    public static class UserCause extends Cause.UserCause {

        private String ivylabel;

        /**
         * Constructor
         *
         * @param ivylabel  The Ivy ModuleRevisionId label computed from the StaplerRequest.
         */
        public UserCause(String ivylabel) {
            this.ivylabel = ivylabel;
        }

        /**
         *
         * @return the Ivy ModuleRevisionId label associated with the cause.
         */
        public String getIvylabel() {
            return ivylabel;
        }

        /**
         *
         * @return  the short description of the cause for display purposes.
         */
        @Override
        public String getShortDescription() {
            return Messages.IvyBuildTrigger_UserCause_ShortDescription(getIvylabel(), getUserName());
        }
    }
}
TOP

Related Classes of hudson.ivy.IvyBuildTrigger

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.