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

Some interface idioms do not work in Groovy CPS

      Since class inheritance is not available to groovy workflow scripts, I thought I'd try some alternatives, so I gave "interfaces" a go instead. I want to specify an interface that provides the templating for a common behavior I want to achieve.

      e.g. MyFoo.groovy could implement an interface specified in Foo.groovy. Other scripts e.g. Bar.groovy could then invoke a common interface specified in YourFoo.groovy, MyFoo.groovy, RonaldsFoo.groovy and so on.

      In trying out a very simple example of an interface to achieve what I want, the groovy-cps compiler errors.

      Here is the valid groovy code that I tried (and works) under a groovy console:

      interface Foo {
      String name()
      }
      
      
      b = [name: {return "Ronald"}]
      a = b as Foo
      
      println a.name()
      
      return
      

      This prints "Ronald" as you'd expect.

      The code that I tried in an example workflow, I was hoping could do the same, but does not:

      node {
          b = [name: {return "Ronald"}]
          a = b as Foo
      
          echo a.name()
      }
      
      interface Foo {
      String name()
      }
      

      The error that is thrown when I hit "Apply" after entering the code in the Groovy CPS DSL box is:

      javax.servlet.ServletException: java.lang.NullPointerException: Cannot invoke method visit() on null object
      	at org.kohsuke.stapler.Stapler.tryInvoke(Stapler.java:796)
      	at org.kohsuke.stapler.Stapler.invoke(Stapler.java:876)
      	at org.kohsuke.stapler.MetaClass$6.doDispatch(MetaClass.java:249)
      	at org.kohsuke.stapler.NameBasedDispatcher.dispatch(NameBasedDispatcher.java:53)
      	at org.kohsuke.stapler.Stapler.tryInvoke(Stapler.java:746)
      	at org.kohsuke.stapler.Stapler.invoke(Stapler.java:876)
      	at org.kohsuke.stapler.MetaClass$6.doDispatch(MetaClass.java:249)
      	at org.kohsuke.stapler.NameBasedDispatcher.dispatch(NameBasedDispatcher.java:53)
      	at org.kohsuke.stapler.Stapler.tryInvoke(Stapler.java:746)
      	at org.kohsuke.stapler.Stapler.invoke(Stapler.java:876)
      	at org.kohsuke.stapler.Stapler.invoke(Stapler.java:649)
      	at org.kohsuke.stapler.Stapler.service(Stapler.java:238)
      	at javax.servlet.http.HttpServlet.service(HttpServlet.java:848)
      	at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:686)
      	at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1494)
      	at hudson.util.PluginServletFilter$1.doFilter(PluginServletFilter.java:123)
      	at hudson.plugins.greenballs.GreenBallFilter.doFilter(GreenBallFilter.java:58)
      	at hudson.util.PluginServletFilter$1.doFilter(PluginServletFilter.java:120)
      	at hudson.util.PluginServletFilter.doFilter(PluginServletFilter.java:114)
      	at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1482)
      	at hudson.security.csrf.CrumbFilter.doFilter(CrumbFilter.java:48)
      	at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1482)
      	at hudson.security.ChainedServletFilter$1.doFilter(ChainedServletFilter.java:84)
      	at hudson.security.ChainedServletFilter.doFilter(ChainedServletFilter.java:76)
      	at hudson.security.HudsonFilter.doFilter(HudsonFilter.java:168)
      	at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1482)
      	at org.kohsuke.stapler.compression.CompressionFilter.doFilter(CompressionFilter.java:49)
      	at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1482)
      	at hudson.util.CharacterEncodingFilter.doFilter(CharacterEncodingFilter.java:81)
      	at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1482)
      	at org.kohsuke.stapler.DiagnosticThreadNameFilter.doFilter(DiagnosticThreadNameFilter.java:30)
      	at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1474)
      	at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:499)
      	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:137)
      	at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:533)
      	at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:231)
      	at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1086)
      	at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:428)
      	at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:193)
      	at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1020)
      	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:135)
      	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116)
      	at org.eclipse.jetty.server.Server.handle(Server.java:370)
      	at org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:489)
      	at org.eclipse.jetty.server.AbstractHttpConnection.content(AbstractHttpConnection.java:960)
      	at org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.content(AbstractHttpConnection.java:1021)
      	at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:865)
      	at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:240)
      	at org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:82)
      	at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:668)
      	at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:52)
      	at winstone.BoundedExecutorService$1.run(BoundedExecutorService.java:77)
      	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
      	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
      	at java.lang.Thread.run(Thread.java:619)
      Caused by: java.lang.NullPointerException: Cannot invoke method visit() on null object
      	at org.codehaus.groovy.runtime.NullObject.invokeMethod(NullObject.java:77)
      	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.NullCallSite.call(NullCallSite.java:32)
      	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)
      	at org.codehaus.groovy.ast.stmt.BlockStatement$visit$0.call(Unknown Source)
      	at com.cloudbees.groovy.cps.CpsTransformer.visitMethod(CpsTransformer.groovy:159)
      	at sun.reflect.GeneratedMethodAccessor104.invoke(Unknown Source)
      	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
      	at java.lang.reflect.Method.invoke(Method.java:597)
      	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
      	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
      	at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:361)
      	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:903)
      	at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:66)
      	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:141)
      	at com.cloudbees.groovy.cps.CpsTransformer$_call_closure1.doCall(CpsTransformer.groovy:88)
      	at sun.reflect.GeneratedMethodAccessor1139.invoke(Unknown Source)
      	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
      	at java.lang.reflect.Method.invoke(Method.java:597)
      	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
      	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
      	at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272)
      	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:903)
      	at groovy.lang.Closure.call(Closure.java:415)
      	at groovy.lang.Closure.call(Closure.java:428)
      	at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:1379)
      	at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:1351)
      	at org.codehaus.groovy.runtime.dgm$170.invoke(Unknown Source)
      	at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:271)
      	at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:53)
      	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
      	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callSafe(AbstractCallSite.java:82)
      	at com.cloudbees.groovy.cps.CpsTransformer.call(CpsTransformer.groovy:88)
      	at org.codehaus.groovy.control.CompilationUnit.applyToPrimaryClassNodes(CompilationUnit.java:970)
      	at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:548)
      	at org.codehaus.groovy.control.CompilationUnit.processPhaseOperations(CompilationUnit.java:526)
      	at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:503)
      	at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:302)
      	at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:281)
      	at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:267)
      	at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:214)
      	at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:224)
      	at org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition$DescriptorImpl.doCheckScript(CpsFlowDefinition.java:123)
      	at sun.reflect.GeneratedMethodAccessor451.invoke(Unknown Source)
      	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
      	at java.lang.reflect.Method.invoke(Method.java:597)
      	at org.kohsuke.stapler.Function$InstanceFunction.invoke(Function.java:298)
      	at org.kohsuke.stapler.Function.bindAndInvoke(Function.java:161)
      	at org.kohsuke.stapler.Function.bindAndInvokeAndServeResponse(Function.java:96)
      	at org.kohsuke.stapler.MetaClass$1.doDispatch(MetaClass.java:121)
      	at org.kohsuke.stapler.NameBasedDispatcher.dispatch(NameBasedDispatcher.java:53)
      	at org.kohsuke.stapler.Stapler.tryInvoke(Stapler.java:746)
      	... 54 more
      

      So although this support for groovy interfaces does not appear to be supported - is it worthy of consideration?

          [JENKINS-27927] Some interface idioms do not work in Groovy CPS

          Thomas Dalton added a comment -

          I'm glad the intention is aligned with the desire to have these groovy language features available and understand that support for interface will require work in the groovy CPS.

          In the short term though, is there a recommended workaround that could provide some interface like equivalent? Or is it just not possible to do anything like this at this stage?
          Perhaps pass HashMaps of Closures between the top level "classes" as per
          https://github.com/jenkinsci/workflow-plugin/blob/master/cps-global-lib/README.md#writing-shared-code

          e.g.

          org/foo/Zot.groovy
          package org.foo
          // Closures define the interface for Zot
          Closure show
          Closure name
          
          def specify_interface (Hashmap hm) {
              this.show = hm["show"]
              this.name = hm["name"]
          }
          
          
          flow.groovy
          node {
              def z = new org.foo.Zot()
              z.specify_interface(["show": { echo "Hello" }, "name": { return "Bernard" }])
          
              // you can then pass z around beyond the scope of flow.groovy
              z.show()
              echo z.name()
          }
          

          Or is there a more elegant way within the constraints of what is and what is not possible today?

          Thomas Dalton added a comment - I'm glad the intention is aligned with the desire to have these groovy language features available and understand that support for interface will require work in the groovy CPS. In the short term though, is there a recommended workaround that could provide some interface like equivalent? Or is it just not possible to do anything like this at this stage? Perhaps pass HashMaps of Closures between the top level "classes" as per https://github.com/jenkinsci/workflow-plugin/blob/master/cps-global-lib/README.md#writing-shared-code e.g. org/foo/Zot.groovy package org.foo // Closures define the interface for Zot Closure show Closure name def specify_interface (Hashmap hm) { this .show = hm[ "show" ] this .name = hm[ "name" ] } flow.groovy node { def z = new org.foo.Zot() z.specify_interface([ "show" : { echo "Hello" }, "name" : { return "Bernard" }]) // you can then pass z around beyond the scope of flow.groovy z.show() echo z.name() } Or is there a more elegant way within the constraints of what is and what is not possible today?

          Jesse Glick added a comment -

          I would just try using Java-compatible syntax first. More likely to already be supported.

          Jesse Glick added a comment - I would just try using Java-compatible syntax first. More likely to already be supported.

          Thomas Dalton added a comment -

          Ahh.. in which case I apologies for the miscommunication on my part, I actually tried the Java syntax first - I then reduced the problem to simpler forms to post the issue.

          Here is what I tried originally:

          interface Foo {
              String name()
          }
          class Bar implements Foo {
              def String name() {
                  return "Ronald"
              }
          }
          
          node {
              b = new Bar()
              a = b as Foo
          
              echo a.name()
          }
          

          This doesn't work either and the same exception Cannot invoke method visit() on null object is thrown.

          Yet under a groovy console:

          interface Foo {
              String name()
          }
          
          class Bar implements Foo {
              def String name() {
                  return "Ronald"
              }
          }
          
          b = new Bar()
          a = b as Foo
          
          println a.name()
          

          It works fine. Hence I think it is likely a problem with all uses of interface and why I'm on the lookout for a decent workaround

          Thomas Dalton added a comment - Ahh.. in which case I apologies for the miscommunication on my part, I actually tried the Java syntax first - I then reduced the problem to simpler forms to post the issue. Here is what I tried originally: interface Foo { String name() } class Bar implements Foo { def String name() { return "Ronald" } } node { b = new Bar() a = b as Foo echo a.name() } This doesn't work either and the same exception Cannot invoke method visit() on null object is thrown. Yet under a groovy console: interface Foo { String name() } class Bar implements Foo { def String name() { return "Ronald" } } b = new Bar() a = b as Foo println a.name() It works fine. Hence I think it is likely a problem with all uses of interface and why I'm on the lookout for a decent workaround

          Jesse Glick added a comment -

          Well my first suggestion is to try actual Java syntax:

          Foo a = b;
          

          in case it is the as keyword which causes the problem.

          Jesse Glick added a comment - Well my first suggestion is to try actual Java syntax: Foo a = b; in case it is the as keyword which causes the problem.

          Thomas Dalton added a comment -

          If I replace

          a = b as Foo
          

          with

          Foo a = b
          

          It doesn't work either I'm afraid , same exception.

          Thomas Dalton added a comment - If I replace a = b as Foo with Foo a = b It doesn't work either I'm afraid , same exception.

          Like Thomas I too wanted to leverage interfaces in the workflow plugin for a more adaptable workflow to share amongst teams. I get the exact same error as is listed above.

          java.lang.NullPointerException: Cannot invoke method visit() on null object

          I'm really hoping for a fix soon. As this would be a power way to create a sharable workflow design with robust features. I'm envisioning a OOD aspect to a Jenkins Pipeline Job.

          Is fixing the above error and allowing groovy interfaces within the Jenkins Pipeline something that is on the current schedule? Is so, what is the timeline for this being added??

          Christopher Mertz added a comment - Like Thomas I too wanted to leverage interfaces in the workflow plugin for a more adaptable workflow to share amongst teams. I get the exact same error as is listed above. java.lang.NullPointerException: Cannot invoke method visit() on null object I'm really hoping for a fix soon. As this would be a power way to create a sharable workflow design with robust features. I'm envisioning a OOD aspect to a Jenkins Pipeline Job. Is fixing the above error and allowing groovy interfaces within the Jenkins Pipeline something that is on the current schedule? Is so, what is the timeline for this being added??

          Frédéric Chuong added a comment - Upstream issue: https://github.com/cloudbees/groovy-cps/issues/26

          Denys Digtiar added a comment -

          Seems to be resolved in workflow-cps:2.12 which depends on the groovy-cps:1.9 which includes the fix mentioned in the previous comment.

          Denys Digtiar added a comment - Seems to be resolved in workflow-cps:2.12 which depends on the groovy-cps:1.9 which includes the fix mentioned in the previous comment.

          Jesse Glick added a comment -

          duemir did you try the originally reported idiom, using the as keyword? I suspect that is the issue, not merely using interface.

          Jesse Glick added a comment - duemir did you try the originally reported idiom, using the as keyword? I suspect that is the issue, not merely using interface .

          Denys Digtiar added a comment -

          jglick My bad. Didn't think about it.
          Tried as keyword, it is still causing an error.

          Denys Digtiar added a comment - jglick My bad. Didn't think about it. Tried as keyword, it is still causing an error.

            kohsuke Kohsuke Kawaguchi
            tomjdalton Thomas Dalton
            Votes:
            4 Vote for this issue
            Watchers:
            10 Start watching this issue

              Created:
              Updated: