Search code examples
groovyjenkins-pipeline

How to implement a singleton class in a Jenkins shared library?


I see posts like Jenkins Shared Library Global/Singleton Class but they don't really answer my particular case.

I'm trying to implement a singleton class in a Jenkins shared library. So far I have

File: ServiceBuilder.groovy
---------------------------
package com.company.test
abstract class ServiceBuilder implements Serializable {
  protected def jfile = null
  protected String msg = "Yo!"

  protected ServiceBuilder(final def jfile) {
    this.jfile = jfile
  }

  def printMsg() {
    jfile.println("${this.getClass()}: ${this.msg}")
  }

  def setMsg(final String msg) {
    throw new Exception("Implement in subclass")
  }

}


File: MonoBuilder.groovy
------------------------
package com.company.test
import com.company.test.ServiceBuilder

@Singleton(strict=false)
class MonoBuilder extends ServiceBuilder {
  MonoBuilder(final def jfile){
    super(jfile)
  }
  def setMsg(final String msg) {
    this.msg = "Mono: $msg"
  }
}

Here's the simple Jenkinsfile that uses the above classes

@Library("shared-lib") _
import com.company.test.ServiceBuilder
import com.company.test.MonoBuilder

node("linux-ubuntu") {
  stage("Mono"){
    def obj = new MonoBuilder(this)
    obj.printMsg()
  }
}

However, when I run it, I get

java.lang.NoSuchMethodError: com.company.test.ServiceBuilder: method 'void <init>()' not found
    at com.company.test.MonoBuilder.<init>(MonoBuilder.groovy)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
    at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:83)
    at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrapNoCoerce.callConstructor(ConstructorSite.java:105)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:59)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:238)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:242)
    at com.company.test.MonoBuilder.<clinit>(MonoBuilder.groovy)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
    at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:83)
    at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrapNoCoerce.callConstructor(ConstructorSite.java:105)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:59)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:238)
    at org.kohsuke.groovy.sandbox.impl.Checker$3.call(Checker.java:230)
    at org.kohsuke.groovy.sandbox.GroovyInterceptor.onNewInstance(GroovyInterceptor.java:42)
    at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onNewInstance(SandboxInterceptor.java:196)
    at org.kohsuke.groovy.sandbox.impl.Checker$3.call(Checker.java:227)
    at org.kohsuke.groovy.sandbox.impl.Checker.checkedConstructor(Checker.java:232)
    at com.cloudbees.groovy.cps.sandbox.SandboxInvoker.constructorCall(SandboxInvoker.java:21)
    at WorkflowScript.run(WorkflowScript:8)
    at ___cps.transform___(Native Method)
    at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.dispatchOrArg(FunctionCallBlock.java:100)
    at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.fixArg(FunctionCallBlock.java:85)
    at jdk.internal.reflect.GeneratedMethodAccessor282.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
    at com.cloudbees.groovy.cps.impl.LocalVariableBlock$LocalVariable.get(LocalVariableBlock.java:39)
    at com.cloudbees.groovy.cps.LValueBlock$GetAdapter.receive(LValueBlock.java:30)
    at com.cloudbees.groovy.cps.impl.LocalVariableBlock.evalLValue(LocalVariableBlock.java:28)
    at com.cloudbees.groovy.cps.LValueBlock$BlockImpl.eval(LValueBlock.java:55)
    at com.cloudbees.groovy.cps.LValueBlock.eval(LValueBlock.java:16)
    at com.cloudbees.groovy.cps.Next.step(Next.java:83)
    at com.cloudbees.groovy.cps.Continuable$1.call(Continuable.java:158)
    at com.cloudbees.groovy.cps.Continuable$1.call(Continuable.java:152)
    at org.codehaus.groovy.runtime.GroovyCategorySupport$ThreadCategoryInfo.use(GroovyCategorySupport.java:136)
    at org.codehaus.groovy.runtime.GroovyCategorySupport.use(GroovyCategorySupport.java:275)
    at com.cloudbees.groovy.cps.Continuable.run0(Continuable.java:152)
    at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.access$001(SandboxContinuable.java:18)
    at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.run0(SandboxContinuable.java:51)
    at org.jenkinsci.plugins.workflow.cps.CpsThread.runNextChunk(CpsThread.java:187)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:420)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.access$400(CpsThreadGroup.java:95)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:330)
    at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:294)
    at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$2.call(CpsVmExecutorService.java:67)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:139)
    at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
    at jenkins.security.ImpersonatingExecutorService$1.run(ImpersonatingExecutorService.java:68)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:829)
Finished: FAILURE

What does the error mean, and how do I fix it? TIA


Solution

  • To rid of the error, the base class needed a default constructor.

    File: ServiceBuilder.groovy
    ---------------------------
    package com.company.test
    abstract class ServiceBuilder implements Serializable {
      protected def jfile = null
      protected String msg = "Yo!"
    
      protected ServiceBuilder() {} // Default constructor
      protected ServiceBuilder(final def jfile) {
        this.jfile = jfile
      }
    
      def printMsg() {
        jfile.println("${this.getClass()}: ${this.msg}")
      }
    
      def setMsg(final String msg) {
        throw new Exception("Implement in subclass")
      }
    }