Uploaded image for project: 'Jenkins'
  1. Jenkins
  2. JENKINS-28510

DescribableHelper error with @DataBoundSetter on Map<String,String>

    XMLWordPrintable

Details

    • Bug
    • Status: Reopened (View Workflow)
    • Minor
    • Resolution: Unresolved
    • structs-plugin
    • Jenkins 1.609
      workflow-plugin 1.6

    Description

      I'm working on implementing a new step in a plugin, and came across the following error:
      If one argument step is a Map, and it is a field annotated with @DataBoundSetter, Ok, no problems. But if it is a Setter annotated with @DataBoundSetter the following error occurs during instantiation:

      java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl cannot be cast to java.lang.Class
      	at org.jenkinsci.plugins.workflow.structs.DescribableHelper.coerce(DescribableHelper.java:214)
      	at org.jenkinsci.plugins.workflow.structs.DescribableHelper.buildArguments(DescribableHelper.java:186)
      	at org.jenkinsci.plugins.workflow.structs.DescribableHelper.injectSetters(DescribableHelper.java:308)
      	at org.jenkinsci.plugins.workflow.structs.DescribableHelper.instantiate(DescribableHelper.java:93)
      	at org.jenkinsci.plugins.workflow.steps.StepDescriptor.newInstance(StepDescriptor.java:95)
      	at org.jenkinsci.plugins.workflow.cps.DSL.invokeMethod(DSL.java:133)
      	at org.jenkinsci.plugins.workflow.cps.CpsScript.invokeMethod(CpsScript.java:100)
      	at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:45)
      	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)
      	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
      	at com.cloudbees.groovy.cps.sandbox.DefaultInvoker.methodCall(DefaultInvoker.java:15)
      	at WorkflowScript.run(WorkflowScript:5)
      ...
      

      Apparently the error is in injectSetters() method of the class DescribableHelper

      Attachments

        Issue Links

          Activity

            jglick Jesse Glick added a comment -

            Probably caused by use of wildcards in the type.

            jglick Jesse Glick added a comment - Probably caused by use of wildcards in the type.
            valones Flávio Augusto Valones added a comment - - edited

            Hi Jesse, wildcards are not used in this case, it is a Map<String, String>.
            In the implementation of the AbstractStepImpl, if the map param is annotated in the field it works correctly, but if it is annotated in setter the error occurs.
            There are other parameters of types String, int and boolean, but these work normally whether annotated in the field or setter.

            valones Flávio Augusto Valones added a comment - - edited Hi Jesse, wildcards are not used in this case, it is a Map<String, String> . In the implementation of the AbstractStepImpl , if the map param is annotated in the field it works correctly, but if it is annotated in setter the error occurs. There are other parameters of types String , int and boolean , but these work normally whether annotated in the field or setter.
            jglick Jesse Glick added a comment -

            A bug in DescribableHelper that it throws this low-level exception, but I doubt it could work in any case. Jenkins has no databinding support for Map<String,String>. You must choose a type for which an actual /lib/form control exists.

            jglick Jesse Glick added a comment - A bug in DescribableHelper that it throws this low-level exception, but I doubt it could work in any case. Jenkins has no databinding support for Map<String,String> . You must choose a type for which an actual /lib/form control exists.
            jglick Jesse Glick added a comment -

            (You could override StepDescriptor.newInstance and manually parse out content coming in from a Groovy script, but Snippet Generator will not work, so this is not a good idea.)

            jglick Jesse Glick added a comment - (You could override StepDescriptor.newInstance and manually parse out content coming in from a Groovy script, but Snippet Generator will not work, so this is not a good idea.)
            valones Flávio Augusto Valones added a comment - - edited

            Thanks for the reply, I use the Map<String, String> only as a workflow-step param, the conventional UI Builder uses only supported databinds compatible with /lib/form controls. Here's the code:

            public class SCTMStep extends AbstractStepImpl {
              
              private final int projectId;
              private final List<Integer> execDefIds;
              
              @DataBoundSetter public Map<String, String> params;
              @DataBoundSetter public String buildNumber;
              @DataBoundSetter public int delay;
              @DataBoundSetter public boolean continueOnError;
              @DataBoundSetter public boolean collectResults = true;
              @DataBoundSetter public boolean ignoreSetupCleanup;  
              
              @DataBoundConstructor
              public SCTMStep(final int projectId, final List<Integer> execDefIds) {
                this.projectId = projectId;
                this.execDefIds = execDefIds;    
              }  
            
              //getters for private fields
              
              @Extension(optional = true) 
              public static final class DescriptorImpl extends AbstractStepDescriptorImpl {
            
                public DescriptorImpl() { super(Execution.class); }
            
                @Override public String getFunctionName() { return "sctm"; }
            
                @Override public String getDisplayName() { return Messages.getString("SCTMExecutorDescriptor.plugin.title"); }
            
              }
              
              public static final class Execution extends AbstractSynchronousStepExecution<Boolean> {
                
                private static final long serialVersionUID = 1L;
                
                @Inject private transient SCTMStep step;    
                
                @StepContextParameter private transient TaskListener listener;    
                @StepContextParameter private transient FilePath workspace;
                @StepContextParameter private transient Run<?, ?> run;
            
                @Override
                protected Boolean run() throws Exception {
                  SCTMExe sctmExe = new SCTMExe(workspace, listener);
                  sctmExe.execute(step.projectId, step.execDefIds, step.params, step.buildNumber, 
                      step.delay, step.collectResults, step.ignoreSetupCleanup, step.continueOnError, false);
                  
                  if (step.collectResults) {
                    sctmExe.publish(run);
                  }
                  return true;
                }
                
              }
              
            }
            

            The databind of the Map<String, String> is done correctly and in the workflow Job this works like a charm:

            ...
            sctm projectId: 123, execDefIds: [456, 789], params: [NAME: 'Flávio Augusto Valones', ADDR: 'Some Street']
            ...
            

            The sctm step is listed in the Snippet Generator (I have not yet implemented the jelly form for it), and everything goes as expected.

            The exception only occurs if I do this in the SCTMStep:

            ...
            private Map<String, String> params;
            
            @DataBoundSetter public void setParams(Map<String, String> params) { ... }
            ...
            
            valones Flávio Augusto Valones added a comment - - edited Thanks for the reply, I use the Map<String, String> only as a workflow-step param, the conventional UI Builder uses only supported databinds compatible with /lib/form controls. Here's the code: public class SCTMStep extends AbstractStepImpl { private final int projectId; private final List< Integer > execDefIds; @DataBoundSetter public Map< String , String > params; @DataBoundSetter public String buildNumber; @DataBoundSetter public int delay; @DataBoundSetter public boolean continueOnError; @DataBoundSetter public boolean collectResults = true ; @DataBoundSetter public boolean ignoreSetupCleanup; @DataBoundConstructor public SCTMStep( final int projectId, final List< Integer > execDefIds) { this .projectId = projectId; this .execDefIds = execDefIds; } //getters for private fields @Extension(optional = true ) public static final class DescriptorImpl extends AbstractStepDescriptorImpl { public DescriptorImpl() { super (Execution.class); } @Override public String getFunctionName() { return "sctm" ; } @Override public String getDisplayName() { return Messages.getString( "SCTMExecutorDescriptor.plugin.title" ); } } public static final class Execution extends AbstractSynchronousStepExecution< Boolean > { private static final long serialVersionUID = 1L; @Inject private transient SCTMStep step; @StepContextParameter private transient TaskListener listener; @StepContextParameter private transient FilePath workspace; @StepContextParameter private transient Run<?, ?> run; @Override protected Boolean run() throws Exception { SCTMExe sctmExe = new SCTMExe(workspace, listener); sctmExe.execute(step.projectId, step.execDefIds, step.params, step.buildNumber, step.delay, step.collectResults, step.ignoreSetupCleanup, step.continueOnError, false ); if (step.collectResults) { sctmExe.publish(run); } return true ; } } } The databind of the Map<String, String> is done correctly and in the workflow Job this works like a charm: ... sctm projectId: 123, execDefIds: [456, 789], params: [NAME: 'Flávio Augusto Valones' , ADDR: 'Some Street' ] ... The sctm step is listed in the Snippet Generator (I have not yet implemented the jelly form for it), and everything goes as expected. The exception only occurs if I do this in the SCTMStep : ... private Map< String , String > params; @DataBoundSetter public void setParams(Map< String , String > params) { ... } ...
            jglick Jesse Glick added a comment -

            Well @DataBoundSetter is supposed to be on the setter, not the getter of course.

            Creating Step implementations with parameter types that cannot be data-bound is possible, but not easy and not recommended if you can possibly avoid it. StepDescriptor.newInstance (and potentially .defineArguments) overrides are needed to bypass DescribableHelper, and config.jelly would need to omit that parameter while documenting its syntax in help.html. BuildTriggerStep is the only example I can think of.

            jglick Jesse Glick added a comment - Well @DataBoundSetter is supposed to be on the setter , not the getter of course. Creating Step implementations with parameter types that cannot be data-bound is possible, but not easy and not recommended if you can possibly avoid it. StepDescriptor.newInstance (and potentially .defineArguments ) overrides are needed to bypass DescribableHelper , and config.jelly would need to omit that parameter while documenting its syntax in help.html . BuildTriggerStep is the only example I can think of.

            Oops, my fault, the error occurs in the setter, I erroneously transcribed manually on getter, fixed previous comment. Thanks for the tips, I'll think of a way to refactor the parameters to make them practical as the Map, but data-bound supported.

            valones Flávio Augusto Valones added a comment - Oops, my fault, the error occurs in the setter, I erroneously transcribed manually on getter, fixed previous comment. Thanks for the tips, I'll think of a way to refactor the parameters to make them practical as the Map, but data-bound supported.

            People

              jglick Jesse Glick
              valones Flávio Augusto Valones
              Votes:
              1 Vote for this issue
              Watchers:
              3 Start watching this issue

              Dates

                Created:
                Updated: