Skip to content

Commit

Permalink
Merge pull request #3 from ikedam/feature/JENKINS-19494_handle_newIns…
Browse files Browse the repository at this point in the history
…tance

[JENKINS-19494] Make flexible publisher to work with publishers with customized Descriptor#newInstance.
  • Loading branch information
ikedam committed Sep 14, 2013
2 parents d921267 + 7b78b88 commit f60940f
Show file tree
Hide file tree
Showing 4 changed files with 251 additions and 11 deletions.
Expand Up @@ -39,16 +39,21 @@
import hudson.model.Hudson;
import hudson.tasks.ArtifactArchiver;
import hudson.tasks.BuildStep;
import net.sf.json.JSONObject;

import org.jenkins_ci.plugins.run_condition.BuildStepRunner;
import org.jenkins_ci.plugins.run_condition.RunCondition;
import org.jenkins_ci.plugins.run_condition.core.AlwaysRun;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import jenkins.model.Jenkins;

public class ConditionalPublisher implements Describable<ConditionalPublisher>, DependecyDeclarer {

private final RunCondition condition;
Expand All @@ -64,6 +69,15 @@ public ConditionalPublisher(final RunCondition condition, final BuildStep publis
this(condition, publisher, runner, false, null, null);
}

/**
* @param condition
* @param publisher
* @param runner
* @param configuredAggregation
* @param aggregationCondition
* @param aggregationRunner
* @see ConditionalPublisherDescriptor#newInstance(StaplerRequest, JSONObject)
*/
@DataBoundConstructor
public ConditionalPublisher(final RunCondition condition, final BuildStep publisher, final BuildStepRunner runner,
boolean configuredAggregation, final RunCondition aggregationCondition, final BuildStepRunner aggregationRunner) {
Expand Down Expand Up @@ -164,6 +178,95 @@ public Descriptor<? extends BuildStep> getDefaultPublisher() {
public boolean isMatrixProject(Object it) {
return (it instanceof MatrixProject);
}

/**
* Build a new instance from parameters a user input in a configuration page.
*
* Usually, it is done by {@link StaplerRequest#bindJSON(Class, JSONObject)},
* and {@link DataBoundConstructor} of classes of posted objects.
*
* But we have to use {@link Descriptor#newInstance(StaplerRequest, JSONObject)}
* for classes without {@link DataBoundConstructor} (such as {@link hudson.tasks.Mailer})
* and classes with {@link Descriptor#newInstance(StaplerRequest, JSONObject)}
* doing different from their constructors with {@link DataBoundConstructor}
* (such as {@link hudson.tasks.junit.JUnitResultArchiver}).
*
* @param req
* @param formData
* @return
* @throws hudson.model.Descriptor.FormException
* @see hudson.model.Descriptor#newInstance(org.kohsuke.stapler.StaplerRequest, net.sf.json.JSONObject)
* @see ConditionalPublisher#ConditionalPublisher(RunCondition, BuildStep, BuildStepRunner, boolean, RunCondition, BuildStepRunner)
*/
@Override
public ConditionalPublisher newInstance(StaplerRequest req, JSONObject formData)
throws hudson.model.Descriptor.FormException {
RunCondition condition = null;
BuildStepRunner runner = null;
BuildStep publisher = null;
boolean configuredAggregation = false;
RunCondition aggregationCondition = null;
BuildStepRunner aggregationRunner = null;

if (formData != null) {
condition = req.bindJSON(RunCondition.class, formData.getJSONObject("condition"));
runner = req.bindJSON(BuildStepRunner.class, formData.getJSONObject("runner"));
if (formData.has("configuredAggregation")) {
configuredAggregation = formData.getBoolean("configuredAggregation");
aggregationCondition = req.bindJSON(RunCondition.class, formData.getJSONObject("aggregationCondition"));
aggregationRunner = req.bindJSON(BuildStepRunner.class, formData.getJSONObject("aggregationRunner"));
}

publisher = bindJSONWithDescriptor(req, formData, "publisher");
}
return new ConditionalPublisher(
condition,
publisher,
runner,
configuredAggregation,
aggregationCondition,
aggregationRunner
);
}

/**
* Construct an object from parameters input by a user.
*
* Not using {@link DataBoundConstructor},
* but using {@link Descriptor#newInstance(StaplerRequest, JSONObject)}.
*
* @param req
* @param formData
* @param fieldName
* @return
* @throws hudson.model.Descriptor.FormException
*/
private BuildStep bindJSONWithDescriptor(StaplerRequest req, JSONObject formData, String fieldName)
throws hudson.model.Descriptor.FormException {
formData = formData.getJSONObject(fieldName);
if (formData == null || formData.isNullObject()) {
return null;
}
if (!formData.has("stapler-class")) {
throw new FormException("No stapler-class is specified", fieldName);
}
String clazzName = formData.getString("stapler-class");
if (clazzName == null) {
throw new FormException("No stapler-class is specified", fieldName);
}
try {
@SuppressWarnings("unchecked")
Class<? extends Describable<?>> clazz = (Class<? extends Describable<?>>)Jenkins.getInstance().getPluginManager().uberClassLoader.loadClass(clazzName);
Descriptor<?> d = Jenkins.getInstance().getDescriptorOrDie(clazz);
return (BuildStep)d.newInstance(req, formData);
} catch(ClassNotFoundException e) {
throw new FormException(
String.format("Failed to instantiate %s", clazz),
e,
fieldName
);
}
}
}

@SuppressWarnings("rawtypes")
Expand Down
Expand Up @@ -33,7 +33,6 @@
import hudson.tasks.Publisher;
import org.kohsuke.stapler.DataBoundConstructor;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand All @@ -57,8 +56,7 @@ public List<? extends Descriptor<? extends BuildStep>> getAllowedPublishers(Abst
BuildStepDescriptor<? extends Publisher> buildStepDescriptor = (BuildStepDescriptor<? extends Publisher>) descriptor;
// would be nice to refuse if needsToRunAfterFinalized - but that's on the publisher which does not yet exist!
if (buildStepDescriptor.isApplicable(project.getClass())) {
if (hasDbc(buildStepDescriptor.clazz))
publishers.add(buildStepDescriptor);
publishers.add(buildStepDescriptor);
}
}
return publishers;
Expand All @@ -68,14 +66,6 @@ public DescriptorImpl getDescriptor() {
return Hudson.getInstance().getDescriptorByType(DescriptorImpl.class);
}

private boolean hasDbc(final Class<?> clazz) {
for (Constructor<?> constructor : clazz.getConstructors()) {
if (constructor.isAnnotationPresent(DataBoundConstructor.class))
return true;
}
return false;
}

@Extension
public static class DescriptorImpl extends Descriptor<PublisherDescriptorLister> {

Expand Down
Expand Up @@ -43,6 +43,7 @@
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

import org.jenkins_ci.plugins.run_condition.RunCondition;
Expand All @@ -57,12 +58,18 @@
import java.util.List;
import java.util.Set;

import jenkins.model.Jenkins;

public class FlexiblePublisher extends Recorder implements DependecyDeclarer, MatrixAggregatable{

public static final String PROMOTION_JOB_TYPE = "hudson.plugins.promoted_builds.PromotionProcess";

private List<ConditionalPublisher> publishers;

/**
* @param publishers
* @see FlexiblePublisherDescriptor#newInstance(StaplerRequest, JSONObject)
*/
@DataBoundConstructor
public FlexiblePublisher(final List<ConditionalPublisher> publishers) {
this.publishers = publishers;
Expand Down Expand Up @@ -170,6 +177,43 @@ public Object readResolve() {
return this;
}

/**
* Build a new instance from parameters a user input in a configuration page.
*
* Usually, it is done by {@link StaplerRequest#bindJSON(Class, JSONObject)},
* and {@link DataBoundConstructor} of classes of posted objects.
*
* But we have to use {@link Descriptor#newInstance(StaplerRequest, JSONObject)}
* for classes without {@link DataBoundConstructor} (such as {@link hudson.tasks.Mailer})
* and classes with {@link Descriptor#newInstance(StaplerRequest, JSONObject)}
* doing different from their constructors with {@link DataBoundConstructor}
* (such as {@link hudson.tasks.junit.JUnitResultArchiver}).
*
* @param req
* @param formData
* @return
* @throws hudson.model.Descriptor.FormException
* @see hudson.model.Descriptor#newInstance(org.kohsuke.stapler.StaplerRequest, net.sf.json.JSONObject)
* @see FlexiblePublisher#FlexiblePublisher(List)
*/
@Override
public FlexiblePublisher newInstance(StaplerRequest req, JSONObject formData)
throws hudson.model.Descriptor.FormException {
List<ConditionalPublisher> publishers = null;
if (formData != null) {
JSONArray a = JSONArray.fromObject(formData.get("publishers"));
if (a != null && !a.isEmpty()) {
@SuppressWarnings("unchecked")
Descriptor<ConditionalPublisher> d = Jenkins.getInstance().getDescriptorOrDie(ConditionalPublisher.class);
publishers = new ArrayList<ConditionalPublisher>(a.size());
for(int idx = 0; idx < a.size(); ++idx) {
publishers.add(d.newInstance(req, a.getJSONObject(idx)));
}
}
}

return new FlexiblePublisher(publishers);
}
}

@SuppressWarnings("rawtypes")
Expand Down
Expand Up @@ -24,22 +24,37 @@

package org.jenkins_ci.plugins.flexible_publish;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.List;

import hudson.Launcher;
import hudson.matrix.AxisList;
import hudson.matrix.MatrixProject;
import hudson.matrix.TextAxis;
import hudson.model.BuildListener;
import hudson.model.Descriptor;
import hudson.model.Result;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.FreeStyleProject;
import hudson.model.Saveable;
import hudson.tasks.ArtifactArchiver;
import hudson.tasks.BuildTrigger;
import hudson.tasks.Mailer;
import hudson.tasks.junit.TestDataPublisher;
import hudson.tasks.junit.TestResult;
import hudson.tasks.junit.TestResultAction.Data;
import hudson.tasks.junit.JUnitResultArchiver;
import hudson.util.DescribableList;

import org.jenkins_ci.plugins.run_condition.BuildStepRunner;
import org.jenkins_ci.plugins.run_condition.core.AlwaysRun;
import org.jenkins_ci.plugins.run_condition.core.NeverRun;
import org.jvnet.hudson.test.HudsonTestCase;
import org.jvnet.hudson.test.TestExtension;
import org.kohsuke.stapler.DataBoundConstructor;

import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
Expand Down Expand Up @@ -292,4 +307,92 @@ public void testMatrixDisableAggregationCondition() throws Exception {
assertEquals(Result.SUCCESS, trigger.getThreshold());
}

public void testNoDataBoundConstructor() throws Exception {
// assert that Mailer does not have a constructor with DataBoundConstructor.
{
for(Constructor<?> c: Mailer.class.getConstructors()) {
assertFalse(c.isAnnotationPresent(DataBoundConstructor.class));
}
}

FreeStyleProject p = createFreeStyleProject();
Mailer mailer = new Mailer();
mailer.recipients = "test@example.com";
mailer.dontNotifyEveryUnstableBuild = true;
mailer.sendToIndividuals = true;

ConditionalPublisher conditionalPublisher = new ConditionalPublisher(
new AlwaysRun(),
mailer,
new BuildStepRunner.Run(),
false,
null,
null
);
FlexiblePublisher flexiblePublisher = new FlexiblePublisher(Arrays.asList(conditionalPublisher));
p.getPublishersList().add(flexiblePublisher);
p.save();

reconfigure(p);

conditionalPublisher = p.getPublishersList().get(FlexiblePublisher.class).getPublishers().get(0);
mailer = (Mailer)conditionalPublisher.getPublisher();
assertEquals("test@example.com", mailer.recipients);
// mailer.dontNotifyEveryUnstableBuild does not restored, for it is treated in a special way.
// Detail: there can be multiple "mailer_notifyEveryUnstableBuild"s in a form.
//assertTrue(mailer.dontNotifyEveryUnstableBuild);
assertTrue(mailer.sendToIndividuals);
}

public void testNewInstanceDifferFromDataBoundConstructor() throws Exception {
FreeStyleProject p = createFreeStyleProject();
DescribableList<TestDataPublisher, Descriptor<TestDataPublisher>> testDataPublishers
= new DescribableList<TestDataPublisher, Descriptor<TestDataPublisher>>(Saveable.NOOP);
testDataPublishers.add(new DummyTestDataPublisher());
JUnitResultArchiver archiver = new JUnitResultArchiver("**/*.xml", true, testDataPublishers);

ConditionalPublisher conditionalPublisher = new ConditionalPublisher(
new AlwaysRun(),
archiver,
new BuildStepRunner.Run(),
false,
null,
null
);
FlexiblePublisher flexiblePublisher = new FlexiblePublisher(Arrays.asList(conditionalPublisher));
p.getPublishersList().add(flexiblePublisher);
p.save();

reconfigure(p);

conditionalPublisher = p.getPublishersList().get(FlexiblePublisher.class).getPublishers().get(0);
archiver = (JUnitResultArchiver)conditionalPublisher.getPublisher();
assertEquals("**/*.xml", archiver.getTestResults());
assertTrue(archiver.isKeepLongStdio());
assertNotNull(archiver.getTestDataPublishers());
assertEquals(1, archiver.getTestDataPublishers().size());
assertEquals(DummyTestDataPublisher.class, archiver.getTestDataPublishers().get(0).getClass());
}

public static class DummyTestDataPublisher extends TestDataPublisher {
@DataBoundConstructor
public DummyTestDataPublisher() {
}

@Override
public Data getTestData(AbstractBuild<?, ?> build, Launcher launcher,
BuildListener listener, TestResult testResult)
throws IOException, InterruptedException {
return null;
}

@TestExtension
public static class DescriptorImpl extends Descriptor<TestDataPublisher> {
@Override
public String getDisplayName() {
return "DummyTestDataPublisher";
}

}
}
}

0 comments on commit f60940f

Please sign in to comment.