I have problem with creating constructor, which Jenkins can call for some JSON data originating from a Jelly form,. For testing, I created a minimal Jenkins plugin with mvn hpi:create and following two custom files:
src/main/resources/foo/hyde/jenkins/plugins/OptionalBlockSampleBuilder/config.jelly
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:block>
<table>
<f:optionalBlock name="enableText" title="Enable optional text" checked="${instance.enableText}">
<f:entry title="Optional text" field="text">
<f:textbox />
</f:entry>
</f:optionalBlock>
</table>
</f:block>
src/main/java/foo/hyde/jenkins/plugins/OptionalBlockSampleBuilder.java
package foo.hyde.jenkins.plugins;
public class OptionalBlockSampleBuilder extends hudson.tasks.Builder {
public final String text;
public final boolean enableText;
@org.kohsuke.stapler.DataBoundConstructor
public OptionalBlockSampleBuilder(String text, Boolean enableText) {
this.text = text;
this.enableText = (enableText != null) && enableText;
}
@Override
public boolean perform(hudson.model.AbstractBuild build, hudson.Launcher launcher, hudson.model.BuildListener listener) {
listener.getLogger().println("OptionalBlockSampleBuilder " + enableText + "/" + text);
return true;
}
@hudson.Extension
public static final class DescriptorImpl extends hudson.tasks.BuildStepDescriptor<hudson.tasks.Builder> {
public boolean isApplicable(Class<? extends hudson.model.AbstractProject> aClass) {
return true;
}
public String getDisplayName() {
return "Optional Block Sample";
}
}
}
I'm building against pom.xml parent <groupId>org.jenkins-ci.plugins</groupId><artifactId>plugin</artifactId><version>1.454</version>, and everything builds, Netbeans 6.9.1 launches Debug Jenkins and I get to create a job with this build step. Everything works if I don't check that checkbox, and I get expected OptionalBlockSampleBuilder false/null to job's console output.
But if I do check the checkbox and add text, then saving/applying the job config gives this exception from the depths of Jenkins code, when it tries to call my constructor:
java.lang.RuntimeException:
Failed to instantiate class
foo.hyde.jenkins.plugins.OptionalBlockSampleBuilder
from {
"enableText":{"text":"xx"},
"kind":"foo.hyde.jenkins.plugins.OptionalBlockSampleBuilder",
"stapler-class":"foo.hyde.jenkins.plugins.OptionalBlockSampleBuilder"
}
There has to be a simple fix. I have tried many different changes, and also tried to see how other plugins use it, and finally created this minimal test plugin. How to fix it to make optionalBlock work?
The hint comes from the JSON data:
You can see here that
enableTextcontains a child property,text. That means that thef:optionalBlockis actually expecting an encapsulation of all the fields contained within the block -- when the block is checked, you will receive an instance of the encapsulation field class; when it is unchecked, that field will benull. To use theoptionalBlockproperly, you would need the@DataBoundConstructorto take in a single nullable class instance that encapsulates the entireoptionalBlock. For example:Notice that the
enableTextfield in this case is actually an instance ofEnableTextBlockclass, which contains a child property,text. That will satisfy the JSON object that is being sent in the form.Instead, if all you need is a single field that has a checkbox to enable entry of that field, you might want to consider instead using the
f:optionalPropertytag, which will take care of that single-field encapsulation for you. However, in many cases, theoptionalBlockis actually needed to configure multiple fields, in which case the encapsulation class--as exampled above--is usually the correct way to go.The encapsulation class does not have to be a static inner class; it could be a separate class within your package, but the important part is that the
DataBoundConstructorshould take in an argument that matches the JSON structure being passed from the form.