/*
* 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 join;
import hudson.Extension;
import hudson.Functions;
import hudson.Launcher;
import hudson.Plugin;
import hudson.maven.AbstractMavenProject;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Cause;
import hudson.model.CauseAction;
import hudson.model.Descriptor;
import hudson.model.FreeStyleProject;
import hudson.model.Hudson;
import hudson.model.Item;
import hudson.model.Items;
import hudson.model.Project;
import hudson.model.Run;
import hudson.model.Saveable;
import hudson.model.TaskListener;
import hudson.model.Cause.UpstreamCause;
import hudson.model.listeners.ItemListener;
import hudson.model.listeners.RunListener;
import hudson.tasks.BuildStep;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.BuildTrigger;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import hudson.util.DescribableList;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import join.JoinAction.JoinCause;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
@Extension
public class JoinTrigger extends Recorder {
@Override
public boolean prebuild(AbstractBuild<?, ?> build, BuildListener listener) {
for( BuildStep bs : joinPublishers )
if(!bs.prebuild(build,listener))
return false;
return true;
}
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher,
BuildListener listener) throws InterruptedException, IOException {
BuildTrigger buildTrigger = build.getProject().getPublishersList().get(BuildTrigger.class);
JoinAction joinAction = new JoinAction(this, buildTrigger, tryGetParameterizedDownstreamNames(build));
build.addAction(joinAction);
joinAction.checkPendingDownstream(build, listener);
return true;
}
private ArrayList<String> tryGetParameterizedDownstreamNames(AbstractBuild<?, ?> build) {
ArrayList<String> ret = new ArrayList<String>();
Plugin parameterizedTrigger = Hudson.getInstance().getPlugin("parameterized-trigger");
if (parameterizedTrigger != null) {
hudson.plugins.parameterizedtrigger.BuildTrigger buildTrigger =
build.getProject().getPublishersList().get(hudson.plugins.parameterizedtrigger.BuildTrigger.class);
if (buildTrigger != null) {
for(hudson.plugins.parameterizedtrigger.BuildTriggerConfig config : buildTrigger.getConfigs()) {
for(AbstractProject project : config.getProjectList()) {
if (!project.isDisabled()) {
ret.add(project.getName());
}
}
}
}
}
return ret;
}
private String joinProjects;
private DescribableList<Publisher,Descriptor<Publisher>> joinPublishers =
new DescribableList<Publisher,Descriptor<Publisher>>((Saveable)null);
private boolean evenIfDownstreamUnstable;
public JoinTrigger() {
this(new DescribableList<Publisher, Descriptor<Publisher>>((Saveable)null), "", false);
}
public JoinTrigger(DescribableList<Publisher,Descriptor<Publisher>> publishers,String string, boolean b) {
this.joinProjects = string;
this.evenIfDownstreamUnstable = b;
this.joinPublishers = publishers;
}
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
@Extension
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
public String getDisplayName() {
return "Join Trigger";
}
@Override
public String getHelpFile() {
return "/plugin/join/help/joinTrigger.html";
}
@Override
public JoinTrigger newInstance(StaplerRequest req, JSONObject formData) throws FormException {
LOGGER.finer(formData.toString());
DescribableList<Publisher,Descriptor<Publisher>> publishers =
new DescribableList<Publisher,Descriptor<Publisher>>((Saveable)null);
JSONObject postbuild = formData.optJSONObject("postbuildactions");
if(postbuild != null) {
JSONObject joinPublishers = postbuild.optJSONObject("joinPublishers");
if(joinPublishers != null) {
publishers.rebuild(req, joinPublishers, getApplicableDescriptors());
}
}
JSONObject experimentalpostbuild = formData.optJSONObject("experimentalpostbuildactions");
if(experimentalpostbuild != null) {
JSONObject joinTriggers = experimentalpostbuild.optJSONObject("joinPublishers");
if(joinTriggers != null) {
LOGGER.finest("EPB: " + joinTriggers.toString() + joinTriggers.isArray());
publishers.rebuild(req, joinTriggers, Publisher.all());
}
}
LOGGER.finer("Parsed " + publishers.size() + " publishers");
return new JoinTrigger(publishers,
formData.getString("joinProjectsValue"),
formData.has("evenIfDownstreamUnstable") && formData.getBoolean("evenIfDownstreamUnstable"));
}
@Override
public boolean isApplicable(Class<? extends AbstractProject> jobType) {
boolean freeStyle = FreeStyleProject.class.isAssignableFrom(jobType);
if(freeStyle) {
return true;
}
Plugin mavenProjectPlugin = Hudson.getInstance().getPlugin("maven-plugin");
return mavenProjectPlugin != null && AbstractMavenProject.class.isAssignableFrom(jobType);
}
public boolean showEvenIfUnstableOption(Class<? extends AbstractProject> jobType) {
// UGLY: for promotion process, this option doesn't make sense.
return !jobType.getName().contains("PromotionProcess");
}
public List<Descriptor<Publisher>> getApplicableDescriptors() {
ArrayList<Descriptor<Publisher>> list = new ArrayList<Descriptor<Publisher>>();
Plugin parameterizedTrigger = Hudson.getInstance().getPlugin("parameterized-trigger");
if (parameterizedTrigger != null) {
list.add(Hudson.getInstance().getDescriptorByType(hudson.plugins.parameterizedtrigger.BuildTrigger.DescriptorImpl.class));
}
Plugin copyArchiver = Hudson.getInstance().getPlugin("copyarchiver");
if (copyArchiver != null) {
list.add(Hudson.getInstance().getDescriptorByType(com.thalesgroup.hudson.plugins.copyarchiver.CopyArchiverPublisher.CopyArchiverDescriptor.class));
}
// See issue 4384. Supporting the mailer here breaks its use as the regular post-build action.
//list.add(Hudson.getInstance().getDescriptorByType(hudson.tasks.Mailer.DescriptorImpl.class));
return list;
}
public List<Descriptor<Publisher>> getApplicableExperimentalDescriptors(AbstractProject<?,?> project) {
List<Descriptor<Publisher>> list = Functions.getPublisherDescriptors(project);
LOGGER.finest("publisher count before removing publishers: " + list.size());
// need to prevent infinite recursion, so we remove the Join publisher.
list.remove(Hudson.getInstance().getDescriptorByType(DescriptorImpl.class));
list.removeAll(getApplicableDescriptors());
LOGGER.finest("publisher count after removing publishers: " + list.size());
return list;
}
/**
* Form validation method.
*/
// public FormValidation doCheckChildProjectsValue(@QueryParameter String value ) {
// StringTokenizer tokens = new StringTokenizer(Util.fixNull(value),",");
// while(tokens.hasMoreTokens()) {
// String projectName = tokens.nextToken().trim();
// Item item = Hudson.getInstance().getItemByFullName(projectName,Item.class);
// if(item==null)
// return FormValidation.error(Messages.BuildTrigger_NoSuchProject(projectName,AbstractProject.findNearest(projectName).getName()));
// if(!(item instanceof AbstractProject))
// return FormValidation.error(Messages.BuildTrigger_NotBuildable(projectName));
// }
//
// return FormValidation.ok();
// }
@Extension
public static class RunListenerImpl extends RunListener<Run> {
public RunListenerImpl() {
super(Run.class);
}
@Override
public void onCompleted(Run run, TaskListener listener) {
if(!(run instanceof AbstractBuild)) {
return;
}
AbstractBuild<?,?> abstractBuild = (AbstractBuild<?,?>)run;
listener.getLogger().println("Notifying upstream projects of job completion");
String upstreamProject = null;
int upstreamJobNumber = 0;
CauseAction ca = run.getAction(CauseAction.class);
if(ca == null) {
listener.getLogger().println("Join notifier requires a CauseAction");
return;
}
for(Cause c : ca.getCauses()) {
if(!(c instanceof UpstreamCause)) continue;
if(c instanceof JoinCause) continue;
UpstreamCause uc = (UpstreamCause)c;
notifyJob(abstractBuild, listener, uc.getUpstreamProject(), uc.getUpstreamBuild());
}
return;
}
private void notifyJob(AbstractBuild<?,?> abstractBuild, TaskListener listener, String upstreamProjectName,
int upstreamJobNumber) {
List<AbstractProject> upstreamList = Items.fromNameList(upstreamProjectName,AbstractProject.class);
if(upstreamList.size() != 1) {
listener.getLogger().println("Join notifier cannot find upstream project: " + upstreamProjectName);
return;
}
AbstractProject<?,?> upstreamProject = upstreamList.get(0);
Run upstreamRun = upstreamProject.getBuildByNumber(upstreamJobNumber);
if(upstreamRun == null) {
listener.getLogger().println("Join notifier cannot find upstream run: " + upstreamProjectName + " number " + upstreamJobNumber);
return;
}
if(!(upstreamRun instanceof AbstractBuild)) {
LOGGER.fine("Upstream run is not an AbstractBuild: " + upstreamProjectName + " number " + upstreamJobNumber);
return;
}
AbstractBuild<?,?> upstreamBuild = (AbstractBuild<?,?>)upstreamRun;
JoinAction ja = upstreamRun.getAction(JoinAction.class);
if(ja == null) {
// does not go in the build log, since this is normal for any downstream project that
// runs without the join plugin enabled
LOGGER.finer("Join notifier cannot find upstream JoinAction: " + upstreamProjectName + " number " + upstreamJobNumber);
return;
}
listener.getLogger().println("Notifying upstream of completion: " + upstreamProjectName + " #" + upstreamJobNumber);
ja.downstreamFinished(upstreamBuild, abstractBuild, listener);
}
}
@Extension
public static class ItemListenerImpl extends ItemListener {
@Override
public void onRenamed(Item item, String oldName, String newName) {
// update BuildTrigger of other projects that point to this object.
// can't we generalize this?
for( Project<?,?> p : Hudson.getInstance().getProjects() ) {
BuildTrigger t = p.getPublishersList().get(BuildTrigger.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);
}
}
}
}
}
}
}
private static final Logger LOGGER = Logger.getLogger(JoinTrigger.class.getName());
public boolean usePostBuildActions() {
return containsAnyDescriptor(DescriptorImpl.DESCRIPTOR.getApplicableDescriptors());
}
private boolean containsAnyDescriptor(
List<Descriptor<Publisher>> applicableDescriptors) {
for(Descriptor<Publisher> descriptor : applicableDescriptors) {
if(joinPublishers.contains(descriptor)) {
return true;
}
}
return false;
}
public boolean useExperimentalPostBuildActions(AbstractProject<?,?> project) {
return containsAnyDescriptor(DescriptorImpl.DESCRIPTOR.getApplicableExperimentalDescriptors(project));
}
public String getJoinProjectsValue() {
return joinProjects;
}
public List<AbstractProject> getJoinProjects() {
return Items.fromNameList(joinProjects,AbstractProject.class);
}
public DescribableList<Publisher, Descriptor<Publisher>> getJoinPublishers() {
return joinPublishers;
}
public boolean getEvenIfDownstreamUnstable() {
return this.evenIfDownstreamUnstable;
}
private Object readResolve() {
if(this.joinPublishers == null) {
this.joinPublishers = new DescribableList<Publisher,Descriptor<Publisher>>((Saveable)null);
}
return this;
}
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.NONE;
}
}