Building WebLogic EARs in Gradle

I recently wanted to convert an old Ant WebLogic build to use the Gradle build tool instead of Ant.  The main driving force behind that change was that the Ant build.xml file housed many different responsibilities, from building and testing the source code, to maintaining property files, to deploying and running the application, along with many tasks that were any combination of unused, not understood, or just plain wrong.

Oracle does not provide any fancy Grails plugins for building EAR files in Gradle, but that was not a concern since Gradle has a very nice Ant integration, allowing a user to define and run Ant tasks, or even just call out to an external Ant build file if necessary.  I could have chosen to move the builds to Maven; Oracle does have some support for Maven builds, however looking over the documentation for the Maven plugins, it looked very limited and not well supported.  Gradle seems to be a more standard technology today than Ant, so there you have it.

Sounds Simple(ish)

Sounds like a fairly simple, or at least fairly straightforward task.  I already had a functional Ant build to base my work off of, and if all else failed, as a last resort I could just pull out the build tasks I needed from the legacy build.xml file and call out to them from the new Gradle build.  That definitely was a last-resort fallback position that I wanted to avoid at all costs, but there were points during this process that made that position very appealing.  Probably the one reason I did not give in to that temptation was that the Ant build had such convoluted setup and initialization steps that it was not at all clear which pieces of those tasks would be required for a more targeted build machine.

In all honesty, I do not think the words “simple” and “Enterprise Application aRchive”, or even “WebLogic” can really be said together in the same sentence with a straight face.

Just Tell Me What you Did!

Right down to it.  It works.  It is not pretty.  Gradle accepts it, but I don’t think she likes it (would Gradle have a masculine or feminine declination?).

Gradle’s EAR Plugin

Gradle has a plugin for building EAR files.  Be careful with this, as Oracle’s WebLogic build tools really want to be in charge of generating the EAR file, and those build tools and this plugin will fight incessantly if you are not careful.

WebLogic Install

Of course you need WebLogic installed.  Not only for the build tools that come with the weblogic.jar, but also to just get your app to compile (we said Enterprise earlier, remember?).

Configurations

Configurations galore!  If all you’ve really done with Gradle before was just use it to build and publish fairly standard Java apps, you may not have much experience with Gradle configurations.  I know I didn’t, besides the default “compile” and “testCompile” dependency configurations.

Mileage May Vary

Depending on just how much WebLogic stuff you use (ejbgen, wlappc, appMerge, etc.), these steps may be totally wrong for you.  We were running WebLogic 10.3.6, with old EJB 2 style WebLogic annotations, and we wanted an offline merge of a WebLogic library EAR into the end result.

build.gradle:
buildscript {
  ext {
    // Ensure that we can find a WebLogic installation 
    if(!project.hasProperty('WL_SERVER_HOME') && !System.env.WL_SERVER_HOME) {
      println '''"WL_SERVER_HOME" is a required property.  This should be set to WebLogic server's base directory,
e.g. "/u01/app/Oracle/Middleware1036/wlserver'''
      throw new InvalidUserDataException('"WL_SERVER_HOME" property must be specified')
    } else if (!project.hasProperty('WL_SERVER_HOME')) {
      WL_SERVER_HOME = System.env.WL_SERVER_HOME
    }
  }
}

apply plugin: 'java'
apply plugin: 'ear'

sourceCompatibility = '1.7'
targetCompatibility = '1.7'

configurations {
  weblogic
  shared_ear // The library EAR that we will do an offline merge of
}

sourceSets {
  main {
    java {
      srcDirs = [
        //This is basically WebLogic's split directory development model
        'src/main/java',
        'src/main/SomeMDB',
        'src/main/awesomeApplication/WEB-INF/src'
      ]
      includes = ['**/*.ejb']
    }
    compileClasspath += configurations.earlib
  }
}

dependencies {
  def sharedLibraryEarVersion = '1.0.0'
  
  compileOnly files(
    //any compile dependencies your code has on WL
    "${WL_SERVER_HOME}/../modules/com.bea.core.ejbgen_1.1.0.3.jar",
    "${WL_SERVER_HOME}/../modules/javax.ejb_3.0.1.jar")
  
  weblogic files(
    //files needed for WL tool invocations later
    "${WL_SERVER_HOME}/server/lib/weblogic.jar",
    "${System.getProperty('java.home')}/../lib/tools.jar")
  
  //The shared library ear
  shared_ear group: 'com.example', name: 'shared-ear', version: sharedLibraryEarVersion
  
  //Dependencies I want bundled in with the EAR
  earlib group: 'log4j', name: 'log4j', version: '1.2.14'
  // etc.
  
  testCompile group: 'junit', name: 'junit', version: '4.10'
  testCompile files(
    //Some of those compileOnly files are needed here too
    "${WL_SERVER_HOME}/../modules/javax.ejb_3.0.1.jar")   
}

//Pay attention, here's where things get tricky

//Add WL to the 'Ant Classpath', so that invocation of the WL tools via Ant
// does not blow out the max command line length on Windows boxes
ClassLoader antClassLoader = org.apache.tools.ant.Project.class.classLoader
configurations.weblogic.each {File f ->
  antClassLoader.addURL(f.toURI().toURL())
}

task weblogicCompile {
  description 'Calls the wlcompile task to generate class files based on WL EJB sources'
  outputs.dir("${buildDir}/AwesomeApplication")
  inputs.dir('src/main')
  
  doLast {
    ant.taskdef(
      name: 'wlcompile',
      classname: 'weblogic.ant.taskdefs.build.WLCompileTask')
    ant.taskdef(
      name: 'ejbgen',
      classname: 'com.bea.wls.ejbgen.ant.EJBGenAntTask')
    
    ant.wlcompile(
      srcDir: 'src/main',
      destDir: "${buildDir}/AwesomeApplication",
      classpath: (configurations.earlib + configurations.weblogic).asPath) {
        ant.ejbgen(
          source: '1.5',
          forceGeneration: false,
          verbose: true,
          localBaseClass: 'com.example.service.EJBLocalService',
          remoteBaseClass: 'com.example.service.EJBRemoteService',
          fork: true)
        library(name: 'Shared-Lib', file: configurations.shared_ear.singleFile)
      }
    }
  }
}

task weblogicAppc {
  description 'Calls the wlpackage task to package jsp and other WEB-INF files'
  outputs.dir("${buildDir}/AwesomeApplication/awesomeness")
  inputs.dir('src/main/awesomeApplication')
  dependsOn weblogicCompile
  
  doLast {
    ant.taskdef(
      name: 'wlpackage',
      classname: 'weblogic.ant.taskdefs.build.WLPackageTask')
    ant.taskdef(
      name: 'wlappc',
      classname: 'weblogic.ant.taskdefs.j2ee.Appc')
    
    ant.wlpackage(
      srcDir: 'src/main/awesomeApplication',
      destDir: "${buildDir}/AwesomeApplication/awesomeness",
      toDir: "${buildDir}/AwesomeApplication/awesomeness")
    ant.wlappc(
      source: '${buildDir}/AwesomeApplication/awesomeness',
      keepgenerated: true,
      verbose: true,
      classpath: (configurations.earlib + configurations.weblogic).asPath) {
        library(name: 'Shared-Lib', file: configurations.shared_ear.singleFile)
      }
  }
}

ear {
  dependsOn weblogicCompile, weblogicAppc
  libDirName 'APP-INF/lib'
  
  exclude '**/.beabuild.txt'
  
  into('/') {
    from("${buildDir}/AwesomeApplication")
  }
}

task copyEar(type: Copy) {
  description 'Back up the compiled ear before merging in the shared library ear'
  dependsOn ear
  from ear.archivePath
  into ear.archivePath.getParent()
  rename '.ear', '-slim.ear'
}

task fat_ear(type: JavaExec) {
  description 'Calls the WL appmerge task to generate a fat ear'
  dependsOn ear, copyEar
  
  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
  ]
}

//make sure the "fat_ear" task gets called for the final result
assemble.dependsOn fat_ear

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.