Java tutorial
/* * The MIT License * * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Brian Westrich, Martin Eigenbrodt * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package hudson.plugins.downstream_ext; import hudson.Extension; import hudson.Launcher; import hudson.Util; import hudson.matrix.MatrixAggregatable; import hudson.matrix.MatrixAggregator; import hudson.matrix.MatrixConfiguration; import hudson.matrix.MatrixBuild; import hudson.matrix.MatrixProject; import hudson.model.AutoCompletionCandidates; import hudson.model.BuildListener; import hudson.model.DependecyDeclarer; import hudson.model.DependencyGraph; import hudson.model.Item; import hudson.model.ItemGroup; import hudson.model.Items; import hudson.model.Result; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.Job; import hudson.model.Project; import hudson.model.TopLevelItem; import hudson.model.listeners.ItemListener; import hudson.plugins.downstream_ext.DownstreamTrigger.DescriptorImpl.ItemListenerImpl; import hudson.tasks.BuildStepMonitor; import hudson.tasks.Messages; import hudson.tasks.Notifier; import hudson.tasks.Publisher; import hudson.tasks.BuildTrigger; import hudson.util.FormValidation; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.StringTokenizer; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import jenkins.model.Jenkins; import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; /** * Triggers builds of other projects. * * This class was inspired by {@link BuildTrigger} (rev. 21890) - * but has changed significantly in the mean time. */ @SuppressWarnings("rawtypes") public class DownstreamTrigger extends Notifier implements DependecyDeclarer, MatrixAggregatable { private static final Logger LOGGER = Logger.getLogger(DownstreamTrigger.class.getName()); /** * Comma-separated list of other projects as specified in configuration. This could include regex names too. */ private String specifiedChildProjects; /** * Comma-separated list of other projects to be scheduled. */ private String childProjects; /** * Threshold status to trigger other builds. */ private Result threshold = Result.SUCCESS; /** * Defines how the result {@link #threshold} should * be evaluated. * @since 1.3 */ private Strategy thresholdStrategy; private final boolean onlyIfSCMChanges; private final boolean onlyIfLocalSCMChanges; /** * Defines if for Matrix jobs the downstream job should only be triggered once. * Default is to trigger for each child configuration of the Matrix parent. * * @since 1.6 * @deprecated replaced by matrixTrigger */ @Deprecated private transient Boolean triggerOnlyOnceWhenMatrixEnds; /** * Defines when to trigger downstream builds for matrix upstream jobs. * * @see {@link MatrixTrigger} * @since 1.6 */ private MatrixTrigger matrixTrigger; private static final ConcurrentHashMap<AbstractProject<?, ?>, Executor> executors = new ConcurrentHashMap<AbstractProject<?, ?>, Executor>(); @DataBoundConstructor public DownstreamTrigger(String childProjects, String threshold, boolean onlyIfSCMChanges, boolean onlyIfLocalSCMChanges, String strategy, String matrixTrigger) { this(childProjects, resultFromString(threshold), onlyIfSCMChanges, onlyIfLocalSCMChanges, Strategy.valueOf(strategy), matrixTrigger != null ? MatrixTrigger.valueOf(matrixTrigger) : null); } public DownstreamTrigger(String childProjects, Result threshold, boolean onlyIfSCMChanges, boolean onlyIfLocalSCMChanges, Strategy strategy, MatrixTrigger matrixTrigger) { if (childProjects == null) throw new IllegalArgumentException(); this.specifiedChildProjects = childProjects; this.childProjects = resolveRegex(childProjects); this.threshold = threshold; this.onlyIfSCMChanges = onlyIfSCMChanges; this.onlyIfLocalSCMChanges = onlyIfLocalSCMChanges; this.thresholdStrategy = strategy; this.matrixTrigger = matrixTrigger; } private String resolveRegex(String projects) { if (Jenkins.getInstance() == null) { return projects; } List<String> providedList = Arrays.asList(projects.split(",")); List<String> projectsList = new ArrayList<String>(); for (String name : providedList) { Pattern pattern = Pattern.compile(name); for (TopLevelItem item : Jenkins.getInstance().getItems()) { String itemName = item.getName(); if (pattern.matcher(itemName).matches() && !projectsList.contains(item.getName())) { projectsList.add(item.getName()); } } } StringBuilder sb = new StringBuilder(); for (String s : projectsList) { sb.append(s).append(","); } return sb.length() > 0 ? sb.deleteCharAt(sb.length() - 1).toString() : ""; } private static Result resultFromString(String s) { Result result = Result.fromString(s); // fromString returns FAILURE for unknown strings instead of // IllegalArgumentException. Don't know why the author thought that this // is useful ... if (!result.toString().equals(s)) { throw new IllegalArgumentException("Unknown result type '" + s + "'"); } return result; } public String getChildProjectsValue() { return specifiedChildProjects; } public Result getThreshold() { if (threshold == null) return Result.SUCCESS; else return threshold; } public boolean isOnlyIfSCMChanges() { return onlyIfSCMChanges; } public boolean isOnlyIfLocalSCMChanges() { return onlyIfLocalSCMChanges; } public MatrixTrigger getMatrixTrigger() { return this.matrixTrigger; } /** * @deprecated * Use {@link #getChildProjects(ItemGroup)} */ public List<AbstractProject> getChildProjects() { return Items.fromNameList(childProjects, AbstractProject.class); } public List<AbstractProject> getChildProjects(ItemGroup context) { return Items.fromNameList(context, childProjects, AbstractProject.class); } public Strategy getStrategy() { return this.thresholdStrategy; } public BuildStepMonitor getRequiredMonitorService() { return BuildStepMonitor.NONE; } @Override public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) { // nothing to do here. Everything happens in buildDependencyGraph return true; } /** * {@inheritDoc} */ public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) { ItemGroup context = owner.getParent(); for (AbstractProject downstream : getChildProjects(context)) { graph.addDependency(new DownstreamDependency(owner, downstream, this)); } // workaround for problems with Matrix projects // see https://issues.jenkins-ci.org/browse/JENKINS-5508 if (this.matrixTrigger != null && (this.matrixTrigger == MatrixTrigger.ONLY_CONFIGURATIONS || this.matrixTrigger == MatrixTrigger.BOTH)) { if (owner instanceof MatrixProject) { MatrixProject proj = (MatrixProject) owner; Collection<MatrixConfiguration> activeConfigurations = proj.getActiveConfigurations(); for (MatrixConfiguration conf : activeConfigurations) { for (AbstractProject downstream : getChildProjects(context)) { graph.addDependency(new DownstreamDependency(conf, downstream, this)); } } } } } @Override public boolean needsToRunAfterFinalized() { return true; } /** * Called from {@link ItemListenerImpl} when a job is renamed. * * @return true * if this {@link DownstreamTrigger} is changed and needs to be saved. */ public boolean onJobRenamed(String oldName, String newName) { // quick test if (!childProjects.contains(oldName)) return false; boolean changed = false; // we need to do this per string, since old Project object is already gone. String[] projects = childProjects.split(","); for (int i = 0; i < projects.length; i++) { if (projects[i].trim().equals(oldName)) { projects[i] = newName; changed = true; } } if (changed) { StringBuilder b = new StringBuilder(); for (String p : projects) { if (b.length() > 0) b.append(','); b.append(p); } childProjects = b.toString(); } return changed; } public static void executeForProject(AbstractProject<?, ?> project, Runnable run) { Executor executor = executors.get(project); if (executor == null) { executor = Executors.newSingleThreadExecutor(); Executor old = executors.putIfAbsent(project, executor); if (old != null) { executor = old; } } executor.execute(run); } private Object readResolve() { if (thresholdStrategy == null) { // set to the single strategy used in downstream-ext <= 1.2 thresholdStrategy = Strategy.AND_HIGHER; } if (this.triggerOnlyOnceWhenMatrixEnds != null) { if (this.triggerOnlyOnceWhenMatrixEnds.booleanValue()) { this.matrixTrigger = MatrixTrigger.ONLY_PARENT; } else { this.matrixTrigger = MatrixTrigger.ONLY_CONFIGURATIONS; } } return this; } @Extension // for some reason when running mvn from commandline the build fails, // if the class names are not fully qualified here!? public static class DescriptorImpl extends hudson.tasks.BuildStepDescriptor<hudson.tasks.Publisher> { public static final String[] THRESHOLD_VALUES = { Result.SUCCESS.toString(), Result.UNSTABLE.toString(), Result.FAILURE.toString(), Result.ABORTED.toString() }; public static final String[] MATRIX_TRIGGER_VALUES; static { MatrixTrigger[] values = MatrixTrigger.values(); MATRIX_TRIGGER_VALUES = new String[values.length]; for (int i = 0; i < values.length; i++) { MATRIX_TRIGGER_VALUES[i] = values[i].toString(); } } public static final Strategy[] STRATEGY_VALUES = Strategy.values(); @Override public boolean isApplicable(Class<? extends AbstractProject> jobType) { return true; } @Override public String getDisplayName() { return hudson.plugins.downstream_ext.Messages.DownstreamTrigger_DisplayName(); } /** * Form validation method. */ public FormValidation doCheck(@AncestorInPath Item project, @QueryParameter String value) { // Require CONFIGURE permission on this project if (!project.hasPermission(Item.CONFIGURE)) return FormValidation.ok(); StringTokenizer tokens = new StringTokenizer(Util.fixNull(value), ","); boolean hasProjects = false; while (tokens.hasMoreTokens()) { String projectName = tokens.nextToken().trim(); if (StringUtils.isNotBlank(projectName)) { Item item = Jenkins.getInstance().getItem(projectName, project, Item.class); if (item == null) item = matchingItem(projectName); if (item == null) return FormValidation.error(Messages.BuildTrigger_NoSuchProject(projectName, AbstractProject .findNearest(projectName, project.getParent()).getRelativeNameFrom(project))); if (!(item instanceof AbstractProject)) return FormValidation.error(Messages.BuildTrigger_NotBuildable(projectName)); hasProjects = true; } } if (!hasProjects) { return FormValidation.error(Messages.BuildTrigger_NoProjectSpecified()); } return FormValidation.ok(); } /** * Tries to find a matching project assuming name as regex expression. */ private static TopLevelItem matchingItem(String name) { Pattern pattern = Pattern.compile(name); for (TopLevelItem item : Jenkins.getInstance().getItems()) { if (pattern.matcher(item.getName()).matches()) { return item; } } return null; } public AutoCompletionCandidates doAutoCompleteChildProjects(@QueryParameter String value) { AutoCompletionCandidates candidates = new AutoCompletionCandidates(); List<Job> jobs = Jenkins.getInstance().getItems(Job.class); for (Job job : jobs) { if (job.getFullName().startsWith(value)) { if (job.hasPermission(Item.READ)) { candidates.add(job.getFullName()); } } } return candidates; } @Override public Publisher newInstance(StaplerRequest req, JSONObject formData) throws FormException { String matrixTrigger = formData.has("matrixTrigger") ? formData.getString("matrixTrigger") : null; return new DownstreamTrigger(formData.getString("childProjects"), formData.getString("threshold"), formData.has("onlyIfSCMChanges") && formData.getBoolean("onlyIfSCMChanges"), formData.has("onlyIfLocalSCMChanges") && formData.getBoolean("onlyIfLocalSCMChanges"), formData.getString("strategy"), matrixTrigger); } public boolean isMatrixProject(AbstractProject project) { return project instanceof MatrixProject; } @Extension public static class ItemListenerImpl extends ItemListener { @Override public void onRenamed(Item item, String oldName, String newName) { // update DownstreamTrigger of other projects that point to this object. // can't we generalize this? for (Project<?, ?> p : Jenkins.getInstance().getAllItems(Project.class)) { DownstreamTrigger t = p.getPublishersList().get(DownstreamTrigger.class); if (t != null) { if (t.onJobRenamed(oldName, newName)) { try { p.save(); } catch (IOException e) { LOGGER.log(Level.WARNING, "Failed to persist project setting during rename from " + oldName + " to " + newName, e); } } } } } @Override public void onDeleted(Item item) { executors.remove(item); } } } public enum Strategy { AND_HIGHER("equal or over") { @Override public boolean evaluate(Result threshold, Result actualResult) { return actualResult.isBetterOrEqualTo(threshold); } }, EXACT("equal") { @Override public boolean evaluate(Result threshold, Result actualResult) { return actualResult.equals(threshold); } }, AND_LOWER("equal or under") { @Override public boolean evaluate(Result threshold, Result actualResult) { return actualResult.isWorseOrEqualTo(threshold); } }; public final String displayName; Strategy(String displayName) { this.displayName = displayName; } public String getDisplayName() { return this.displayName; } public abstract boolean evaluate(Result threshold, Result actualResult); } /** * This method is invoked only by matrix projects and is used to allow a matrix job to fire a * downstream job only when it ends, instead of starting them for every matrix configuration. * */ public MatrixAggregator createAggregator(MatrixBuild build, Launcher launcher, BuildListener listener) { return new MatrixAggregator(build, launcher, listener) { @Override public boolean endBuild() throws InterruptedException, IOException { if (matrixTrigger != null && (matrixTrigger == MatrixTrigger.ONLY_PARENT || matrixTrigger == MatrixTrigger.BOTH)) { // trigger downstream job once return hudson.tasks.BuildTrigger.execute(build, listener); } return true; } }; } }