gradle ear application packaging for multiple environment - gradle

I have a web enterprise application that I am trying to automate building the EAR file for it with gradle (for the first time).
I have 2 projects
1 is the enterprise application APP_EAR and the other one is a dynamic web application APP that will be exported as a war file inside the main APP_EAR ear file.
I figured out how to use gradle to generate a war file for the dynamic web app, I am not sure how to use gradle to create the ear file that contains this war file.
The war file for my APP contains 3 tasks for each environment:
sys (system testing), qa (quality control) and prod for production
I created 3 tasks in the build.gradle for my APP like this:
task buildSys {
dependsOn copySys, war
}
task buildQa {
dependsOn copyQa, war
}
task buildProd {
dependsOn copyProd, war
}
the copySys, copyProd, etc... are just copy tasks that copies the appropriate configuration files for the environment I am targeting, before the war is created.
let's say I am creating a war file for sys , then I execute gradle buildSys
What I am trying to do is the following:
Assuming I want to create an EAR file for sys environment, I need to use gradle to perform those steps:
1) execute buildSys in the APP project to create a war file for sys environment
2) execute buildSysEAR that will package the created war file into an ear file.
I am not sure if I need to have 3 tasks in my build.gradle for APP_EAR to package the ear file for each environment or whether I can do it with one ear task and make it call the appropriate war task for that environment.
I just want to be able to execute one task that builds the war and the ear for this environment, I am not sure how to tie the war file to the ear file and/or trigger the correct build task for the war file from the APP_EAR build file.
Thank you in advance

Gradle supports creating an EAR using a plugin. You specify the WAR as a dependency and it will create the necessary files for you. (There is also a war plugin, it's simpler)
apply plugin: "ear"
dependencies
{
deploy project("some support project") // only necessary for ejb jars
earlib "log4j:log4j:1.2.17:jar"
}
ear
{
appDirName "EarContent" // or wherever, defaults to src/main/application
deploymentDescriptor
{
displayName = "app-ear"
description = "my application"
web = project("APP")
value = web.name + '-' + web.version + ".war"
webModule( value , "/web-context") // this is the important line, I need the version number
}
}

Assume project A (EAR Project) has a build.gradle and within ear, the below code can be solve this - in this example assuming 'B' is a dynamic web application need to deploy within project 'A' -
plugins {
id 'ear'
}
dependencies {
deploy project(path:':b', configuration: 'archives')
}
ear{
/* some basis configuration */
libDirName 'APP-INF/lib'
deploymentDescriptor {
/* Some basic attributes */
fileName = "application.xml"
version = "8"
def Set<Project> subProj = project.getSubprojects();
subProj.each{proj ->
if(proj.name.contains("B")){
webModule(proj.name + "-" + proj.version + ".war", "/"+ proj.name)
} //if close
}//each close
}//deploymentDescriptor close
}//ear close

Related

Gradle Ear plugin should not copy resources into .ear root

I have the following folder structure of my gradle module:
src
main
application
META-INF
application.xml
was.policy
was.webmodule
java
resources
image.bmp
logback.xml
... other files, properties
webapp
My goal is to build an ear archive, which will contain only Tclient.war and META-INF. However, gradle copies all resource files to the ear root.
Gradle documentation about Ear Plugin says that:
The default behavior of the Ear task is to copy the content of
src/main/application to the root of the archive. If your application
directory doesn’t contain a META-INF/application.xml deployment
descriptor then one will be generated for you.
So, It's not really clear why it puts resources into the root. Maybe, it work's like the org.gradle.api.tasks.bundling.Jar task and I should override this behaviour in some way?
Here is my partially build.gradle file:
jar {
description 'Creates tclient.jar'
archiveBaseName = 'tclient'
destinationDirectory = file('src/main/webapp/WEB-INF/lib')
from sourceSets.main.output
include '**/*'
include '**/*.properties'
include '**/*.cmd'
}
ear{
description = "Ear archive for WebSphere server"
archiveBaseName = 'Tclient'
appDirName('src/main/application')
// workaround to exclude classes from ear root
rootSpec.exclude('**/de/**')
rootSpec.exclude('**/org/**')
}
war {
archiveFileName = 'Tclient.war'
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
webInf {
from configurations.natives into 'lib/bin'
}
// do not put compiled classes inside WEB-INF/classes
rootSpec.exclude('**/de/**')
rootSpec.exclude('**/org/**')
rootSpec.exclude('urlrewrite*.dtd')
from(jar) {
into 'WEB-INF/lib'
}
}
dependencies {
// Place .war inside .ear root
deploy files(war)
....
}
The possible workaround is to exclude redundant resources from the rootspec:
ear{
description = "Ear archive for WebSphere server"
archiveBaseName = 'Tclient'
appDirName('src/main/application')
// workaround to exclude classes from ear root
rootSpec.exclude('**/de/**')
rootSpec.exclude('**/org/**')
// workaround
rootSpec.exclude('*.properties')
rootSpec.exclude('*.xml')
rootSpec.exclude('*.dtd')
rootSpec.exclude('*.bmp')
}
However, I'm finding this workaround a little bit ugly and it's a dirty hack.

How to use Gretty integrationTestTask with a war file?

Is it possible to use gretty integrationTestTask with a project that uses a war folder?
It seems from the documentation appBeforeIntegrationTest does not have access to the war. Is there another way to run test cases so that it uses the war folder?
Ideally, I want jettyStart -> test -> jettyStop to run. Although when I run it straight jettyStart hangs indefinitely, until jettyStop is run. Is there a way to run jettyStart in Gradle in the background or something?
Regardless what file structure your application has, the integrationTestTask is supposed to be configured with the name of an exsiting gradle task to execute when gradle integrationTest is run:
gretty {
// ...
integrationTestTask = 'integrationTest' // name of existing gradle task
// ...
}
What you want to archive is this:
gretty {
integrationTestTask = 'test'
}
Gretty's workflow when calling integrationTest is as follows:

Copy resources from a dependency jar or war file in Gradle

We have multiple war files in different projects, say A and B, sharing common resources like images. The common resources are placed in a war module in a separate project E. And this war file is added as dependency in all the war modules in projects A and B. Currently we are using maven resource plugins to copy these common resources to the root of A and B modules.
How can we do the same action using Gradle?
I am trying this with below configuration, but the files are not copied in to the generated war file. They are only copied to build/libs folder.
configurations {
commonWebResources
}
task extractApi(type: Copy) {
print 'File : ' + configurations.commonWebResources.singleFile
from zipTree(configurations.commonWebResources.singleFile)
into file("${project.buildDir}/libs/")
}
compileJava.dependsOn(extractApi)
When I have common resources like images that I want a build task to generate, I do the following:
sourceSets {
main {
resources.srcDirs += "src-gen/main/resources"
}
}
// (include your configurations block and extractApi task here)
processResources.dependsOn extractApi
task cleanGen << {
file('src-gen/main/resources').delete()
}
clean.dependsOn cleanGen

Gradle - Add folder to Eclipse classpath

I am migrating a legacy application from Ant to Gradle. The requirement is to build a zip file with a certain folder structure which is used by the deployment team. I am able to create the zip file in the correct format, so-far-so-good.
I am able to open the project in Eclipse, but cannot run it. In Eclipse (and IntelliJ) I need src/main/conf to be added to Eclipse's classpath, but not be included in the JAR (e.g. if I were to run gradle jar).
This is how the project is currently structured:
src
/main
/java
/com
/example
/App.java
/resources
/applicationConfiguration.xml
/conf
/dev.properties
/staging.properties
/prod.properties
How can I add the conf folder to Eclipse's classpath so that it is not included in the JAR that Gradle creates?
Given the limitations of Gradle's EclipseClasspath API, the most straightforward solution I can think of is to declare src/main/conf as another source directory:
sourceSets.main.java.srcDir "src/main/conf"
As long as the directory doesn't contain any Java files, this won't affect the outcome of the Gradle build. However, the directory will show up as a source directory in Eclipse, and its properties files will therefore be copied into the Eclipse output directory.
Another tip. If you need it to run in Eclipse WTP, then I set the sourceDirs property of eclipse.wtp.component:
eclipse {
project {
natures 'org.eclipse.wst.common.project.facet.core.nature',
'org.eclipse.wst.common.modulecore.ModuleCoreNature',
'org.eclipse.wst.jsdt.core.jsNature'
name 'blah-blah'
}
wtp {
facet {
facet type: 'fixed', name: 'wst.jsdt.web'
facet name: 'java', version: '1.7'
facet name: 'jst.web', version: '3.0'
facet name: 'wst.jsdt.web', version: '1.0'
}
component {
sourceDirs = new HashSet([
new File(project.getProjectDir().getAbsolutePath() + "/src/main/java"),
new File(project.getProjectDir().getAbsolutePath() + "/src/main/resources"),
new File(project.getProjectDir().getAbsolutePath() + "/src/main/conf")
])
}
}

Gradle dependency destination on non-jar config file

I can create a dependency to something other than a jar file like this:
dependencies {
compile files("../other-project/config.txt")
}
The above works fine, except that config.txt ends up in the WEB-INF/lib folder of my war file. Instead I need it to be in WEB-INF/classes in the war file, and in src/main/resources for jettyRun.
How can I control where the dependency ends up? Or am I going about this the wrong way?
I can also solve this with a copy task, but this really is a dependency in that I don't need the file updated unless it changes. An unconditional copy would work, but I'd rather do this the right way.
The war task (as configured by the war plugin) puts dependencies into WEB-INF/lib, the web project's own code/resources into WEB-INF/classes, and web app content (which by default goes into src/main/webapp) into WEB-INF. Other content can be added by explicitly configuring the war task. For example:
war {
into("WEB-INF/classes") {
from "../other-project/config.txt"
}
}
One way to make this work with embedded Jetty (though maybe not the most convenient during development) is to use jettyRunWar instead of jettyRun. Another solution that comes to mind, particularly if the content to be added resides in its own directory, is to declare that directory as an additional resource directory of the web project (sourceSets.main.resources.srcDir "../other-project/someResourceDir"). This is in fact an alternative to configuring the war task. If the web project already has a dependency on the other project, you could instead configure an additional resource directory for that project.
Let's say you have configured a multi-project build with the following directory and file structure:
/combined-war
/main-project
/src
/webapp
/WEB-INF
web.xml
build.gradle
/other-project
/resources
/WEB-INF
/classes
config.txt
build.gradle
build.gradle
In order to allow jettyRun to combine the contents of the webapp directory from main-project with the contents of the resources directory in other-project you need to add a workaround to your build.gradle of main-project (I've adapted the one posted by the user siasia on gist).
Adding the same directory content to the war file is quite simple and is documented in the Gradle User Guide and and the DSL reference.
apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'jetty'
import org.gradle.api.plugins.jetty.internal.JettyPluginWebAppContext
def newResourceCollection(File... resources) {
def script = '''
import org.mortbay.resource.ResourceCollection
new ResourceCollection(resources)
'''
def shell = new GroovyShell(JettyPluginWebAppContext.class.classLoader)
shell.setProperty("resources", resources as String[])
return shell.evaluate(script)
}
jettyRun.doFirst {
jettyRun.webAppConfig = new JettyPluginWebAppContext()
jettyRun.webAppConfig.baseResource = newResourceCollection(
// list the folders that should be combined
file(webAppDirName),
file("${project(':other-project').projectDir}/resources")
)
}
war {
from("${project(':other-project').projectDir}/resources")
}
Whenever you execute gradle jettyRun a new ResourceCollection is created that combines the given directories. Per default Jetty locks (at least on Windows) all the files it's serving. So, in case you want to edit those files while Jetty is running take a look at the following solutions.
Update
Since other-project in this case is not another Gradle project the two tasks in build.gradle should look like that:
jettyRun.doFirst {
jettyRun.webAppConfig = new JettyPluginWebAppContext()
jettyRun.webAppConfig.baseResource = newResourceCollection(
file(webAppDirName),
file("$projectDir/../other-project/resources")
)
}
war {
from("$projectDir/../other-project/resources")
}
I'm not aware of any solution that adds only one file (e.g. config.txt). You'll always have to add a complete directory.
As I mentioned above, it's simple enough to do an unconditional copy that solves the problem. Again, not the question I originally asked. But here's my solution that works for both war and jettyRun tasks:
processResources.doFirst {
copy {
from '../other-project/config.txt'
into 'src/main/resources'
}
}

Resources