WebLogic Library EARs in Gradle

As a follow-on to my previous post, here’s a word specifically on building and using Library EARs in gradle.

What is a Library EAR?

WebLogic allows you to create a Library EAR, which basically acts like a third-party library dependency in other contexts.  A single location for some shared code.  I won’t get into what this means in a J2EE app with classloaders, JNDI contexts, and all that, but for our purposes you can think of it as an EAR whose purpose is to provide code and functionality which is to be pulled into other EARs.  We use WebLogic’s offline AppMerge tool to combine a library EAR with an application EAR, in order to build self-contained EARs, but WebLogic’s normal deployment model for a Library EAR is to deploy the Library EAR to the domain first, and then have the application EARs reference the Library EAR.

How it Works

To get this scheme to work, without constant library issues, we make the Library EAR a “thin” EAR, which to us means that its APP-INF/lib directory is empty.  Typically, the EAR would contain all of its dependencies, like a “fat JAR” would.  Instead, we make sure the pom generated contains all of the dependencies, so that clients of the Library EAR can let Gradle do its dependency analysis and resolution.  To generate this POM file, we use the maven-publish plugin for Gradle, with a configuration like this:

publishing {
  publications {
    ear(MavenPublication) {
      pom {
        description = 'The shared library EAR'

        withXml {
          def dependenciesNode = asNode().appendNode('dependencies')
          def appendDependency = { dep ->
            def dependencyNode = dependenciesNode.appendNode('dependency')
            dependencyNode.appendNode('groupId', dep.group)
            dependencyNode.appendNode('artifactId', dep.name)
            dependencyNode.appendNode('version', dep.version)
          }

          configurations.compile.allDependencies.each appendDependency
        }
      }
      artifactId project.artifact
      artifact earArtifact
    }
  }
}

When consuming that library EAR then, we need to do some tricks to get both Gradle dependency resolution to work, and WebLogic’s AppMerge tool to work.

We add a new configuration specifically for the Library EAR, which will pull it in without transitive dependencies.  We can add the Library EAR to the `earlib` configuration, and allow that one to pull transitive dependencies.

configurations {
  shared_ear
}

dependencies {
  def sharedVersion = '1.0.0'

  shared_ear(group: 'com.example', name: 'shared', version: sharedVersion) {
    transitive = false
  }
  
  earlib group: 'com.example', name: 'shared', version: sharedVersion
}

AppMerge uses the specific configuration:

task fat_ear(type: JavaExec) {
  description 'Calls the appmerge task to generate a self-contained "fat" ear'
  dependsOn ear, copyEar //copyEar moved the result of the ear task from 'project.ear' to 'project-slim.ear'

  def inputFile = ear.archivePath.toString().replace('.ear', '-slim.ear')
  inputs.file(inputFile)
  outputs.file(ear.archivePath)

  jvmArgs "-XX:MaxPermSize=256m"
  main = 'weblogic.appmerge'
  classpath = configurations.weblogic
  args = [
    '-output',
    ear.archivePath,
    '-library',
    configurations.shared_ear.singleFile,
    inputFile
  ]
}

This seems like a hack, and it probably is.  There should be a way to just use the ‘normal’ earlib dependency, but I could not determine the correct Gradle magic to use to just pull out this single EAR file from that configuration to use in this task.

There is one last hack we need to perform, which may not strictly be necessary, but it helps to keep things clean.  Because we added the Library EAR to the `earlib` configuration, the .ear itself is included in the final EAR’s APP-INF/lib directory.  This is not necessary after the AppMerge, so we can basically post-process the EAR file to remove that.

task update_fat_ear {
  dependsOn fat_ear
  doLast {
    ant.unzip( src: ear.archivePath, dest: "${buildDir}/tmp_unzip" )
    
    delete fileTree("${buildDir}/tmp_unzip/APP-INF/lib") {
      include 'shared-*.ear'
    }

    ant.jar(destfile: ear.archivePath, basedir: "${buildDir}/tmp_unzip")
  }
}

Again, there is probably some Gradle magic that can be worked in the configurations section to avoid this, but I could not find any way to tell Gradle “pull down all of this module’s dependencies, but not the module itself”.

You may also want the project that builds the library EAR to publish a JAR like it normally would too.  The reason for that is because the library EAR should have all the Java classes packaged under the APP-INF/classes directory, since that is where WebLogic expects to find them.  However, tools like Gradle and your IDE do not expect them there, so just using the EAR on your classpath will not allow clean builds in your IDE, without resorting to using the wlcompile task all the time.  Exposing the JAR artifact as well, with a different classifier, will allow you to add that to your dependencies as well, and get the build tools to still behave with minimal fighting.

Originally Posted on my Blogger site June of 2018

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.