Developer: Java
Extending Oracle WebLogic Workshop 10 with Ant
by John Liptak
Learn how to extend the built-in features of Oracle WebLogic Workshop using a combination of generated and custom Ant build files.
Published July 2008
Oracle WebLogic Workshop has made major advances with version 10, particularly in the
design-build-run development cycle. The productivity gains from using the publish
capabilities of Workshop make the development process qualitatively better.
Advanced products such as Oracle WebLogic Portal are very difficult, if not impossible, to use without
the help of the IDE.
Unit testing (JUnit), continuous integration, and code coverage tools—made popular
by Agile software development—have grown into being viewed as
industry best practices for many development methodologies.
Being able to integrate customized command-line Ant capabilities is no longer
optional for many development teams. You should not have to give up these
incredibly valuable tools in order to use an IDE.
The answer is not to choose one or
the other but to use both techniques.
This article shows how to extend
the built-in features of WebLogic Workshop
using a combination of generated and custom Ant build files. Getting the best out
of both of them is not difficult, requiring only a little additional work,
planning, and the help of some of the newer features of Ant.
My minimal requirements for command-line use are the following:
The ability to use CruiseControl.
A unit test target that would test the entire application, not just a project.
In addition to this target being necessary for CruiseControl, I wanted to continue the development
practice of running all of the unit tests before checking into a shared branch
in source code
control.
The ability not to have to look at the generated
or the custom build files.
The creation of test and production builds from the command line on a
dedicated integration machine—not on a developer's personal workstation.
Our build machines do not have the Workshop IDE installed; they have only the runtime
support files.
The ability to continue to generate environment-specific files. As an in-house enterprise developer
with an outsourced operations environment, it is important to us to automate
the installation of new software versions.
Upgrading to WebLogic Workshop 10
When it came time to start our first WebLogic Workshop 10 application, there
were enough changes from our older 8.1 applications that I had to discover
new ways of integrating our build process. CruiseControl,
in particular the junit task, was going to be a challenge.
Setting the classpath for the junit task with WebLogic Workshop
8.1 was fairly straightforward: Add the ./.workshop/output/<subproject> and all of
our jar files
to the classpath, and everything worked pretty well.
WebLogic Workshop 10's classpath structure is far more complicated. Figure 1
illustrates how the various components combine to make up the classpath.

Figure 1. Classpath components
This new structure is far more functional. It allows a single project
to have a single copy of a jar file in the APP-INF/lib. It also allows
collections of shared libraries to be installed as WLS libraries.
There are obvious solutions to the problem, but they have major drawbacks:
Reverse engineer the complicated classpath generation done by Workshop.
This is the least attractive option, since every time you add a project,
facet, or library, you might find some part of the classpath generation
you didn't account for. You also lose a lot of the productivity
gains from your IDE if you have to reengineer every new
feature you start using.
Edit the generated Ant scripts to add your additional targets. The
problem with this solution is that you will have to re-edit the file
every time you need to export the build.xml file.
I wanted to find a better way. The good news is, there is a better way, and once you know
the principal technique,
it's straightforward. Step 1 is to use Workshop to generate a base set of Ant
build.xml files. I will review that procedure here
so you can generate your own and follow along.
Exporting Workshop Ant scripts
Exporting your project to a series of Ant
build files is documented
here.
In short, you need to export three things: the build file for the entire
application (in an ear project); the build files for dependent library and
web projects;
and a metadata file.
The build.xml file generated for the ear project will call each of the
sub-project's
build.xml files. This will correctly build all of the necessary
sub-projects and then assemble the ear file.
Now you have all of your generated build.xml files. Assuming that you
don't like the obvious solutions for extending the build files, what
should you do? I will describe using an Ant <import> pattern.
Adding an Application Project to the Workspace
The second step in supporting both the command-line Ant and the
Workshop IDE approach is to make sure they don't step on each other's build files.
Our team choose to create what we call the application project "above" the ear project in the workspace.
There are two main reasons for creating a separate application project. First,
Workshop will not generate a build.xml file for that project, so
we don't have to worry about Workshop generating any conflicting build instructions
for the project. Second, it's a great place to put configuration files,
WLST domain creation and configuration scripts, property files,
deployment plans, and the like.
Because we have different applications being developed against different versions
of Workshop, CruiseControl is configured to call a build.xml build
script. Rather than have a different configuration for Workshop applications
and those that pre-date the Eclipse-based version of Workshop, we can use our
application project build script, which provides another
level of redirection to resolve that conflict.
The Ant <import> Solution
Ant is a very popular build
tool that has been
evolving just like all of the other tools that we use. Version 1.6.5
introduced a new
<import>
feature that allows you to extend an existing build file. The usual
explanation for this task is to allow you to refactor your build files
and put the
shared, non-changing definitions, properties, and tasks in a common file
so that other build
files can reuse them.
At the same time, I was refactoring some old code to use dependency injection
with Spring, and it occurred to me that I could use the import feature to inject
the generated build.xml into our build process.
That's exactly what we did:
Create a second build file
<import> the build file generated by Workshop:
<import file="./build.xml" />
Use any of targets in the generated build file as targets:
<target name="unitTest" depends="build">
<echo>Did the imported build. Moving on...</echo>
...
Use any of the properties and task types created by Workshop.
The ear project
With the exception of the target name unitTest and
the name of the Ant file buildCustom.xml, the
following is an exact copy of the contents of the generated file's
build target for the ear project:
...
<for-each-project-in-build-order>
<if>
<not><equals arg1="${.project.name}"
arg2="${project.name}"/></not>
<then>
<antex
antfile="${.project.dir}/buildCustom.xml"
dir="${.project.dir}"
target="unitTest"
inheritUserProps="false"
inheritAll="false"
inheritRefs="false">
<property name="wl.home"
value="${wl.home}"/>
<property name="patch.home"
value="${patch.home}"/>
<property name="workspace"
value="${workspace}"/>
<property name="echo.metadata"
value="${echo.metadata}"/>
<property name="init.typedefs.executed"
value="true"/>
<propertyset>
<propertyref regex="${path.vars.regex}"/>
</propertyset>
</antex>
</then>
</if>
</for-each-project-in-build-order>
...
This task does exactly what it looks like it does: It loops through
all of the projects that the ear depends on and performs their
unit tests.
Sub-projects
The ear sub-projects follow a similar pattern. Now that we have a
unitTest target being called, we now need to define it:
<target name="unitTest" depends="build">
<echo level="info" >
Did the imported build.
Moving on...
</echo>
<for-each-java-src-path>
<if>
<available file="${.java.src.dir}"/>
<then>
<echo level="info" >
JUnit tests for ${.java.src.dir}
built to directory ${.java.src.output}
</echo>
<junit printsummary="yes"
haltonfailure="on"
reloading="false">
<classpath>
<path refid="java.classpath"/>
</classpath>
<formatter type="plain"
usefile="false"/>
<formatter type="xml"/>
<batchtest
todir="../wls10app/build/unitTest">
<fileset
dir="${.java.src.output}"
includes="**/*Test.class"/>
</batchtest>
</junit>
</then>
</if>
</for-each-java-src-path>
...
Back in our application project, we can call the Workshop generated
build target like this:
<target name="build" depends="prepare">
<ant dir="${earProject.dir}"
antfile="build.xml"
target="build" inheritRefs="false" />
</target>
And our newly created custom target like this:
<target name="unitTest" depends="prepare">
<mkdir dir="./build/unitTest" />
<ant dir="${earProject.dir}"
antfile="buildCustom.xml"
target="unitTest" inheritRefs="false" />
</target>
Continuous Integration with CruiseControl
CruiseControl is a popular open-source
continuous integration framework. CruiseControl will monitor your source-code
control
system and perform a build, run tests, and publish any results after each check-in.
This dramatically improves your development process because everyone sees the
status
of the source code. As such, it serves as both a positive and negative process
control. Everyone knows who is checking in code, writing unit tests, breaking
the build, and fixing the build. It becomes impossible for a developer to say that
they are 80-percent complete with a task if they have not checked in any code or unit
tests.
In my workplace, we have been using CruiseControl since version 1. We have also
adopted a build label pattern of
<release name>-<iteration number>-<build
number> so that the combination of all of the build notices gives a temporal
overview of what has changed in the code base. That same information is available
in the reporting server built into CruiseControl.
CruiseControl has advanced since that article was written, so here is a sample
configuration file that integrates multiple applications using the
project plug-in. It allows us to define the general process of
checking for changes in source code control, building, testing, and
publishing the results of a build process once. We can then control
multiple applications by simply accouning for what is different about each
application:
<cruisecontrol>
<property environment="env" toupper="true" />
<property name="reportdir"
value="${env.CCDIR}/webapps/cruisecontrol"/>
<property name="projectdir"
value="/projects/javaapps/${project.name}"/>
<property name="testdir"
value="${projectdir}/build/unitTest"/>
<property name="logdir"
value="/projects/javaapps/buildlogs/${project.name}" />
<property name="cvs_tag"
value="HEAD"/>
<property name="builder.name"
value="John Liptak" />
<property name="builder.email"
value="John.Liptak@companyname.com" />
<property name="builder2.email"
value="Other.Person@companyname.com" />
<property name="builder3.email"
value="Other.Person2@companyname.com" />
<plugin name="cvs"
cvsroot="your cvs root here"
module="${project.name}"
property="fileschanged"
propertyondelete="filesdelted"
tag="${cvs_tag}" />
<plugin name="ant"
antscript="/projects/javaapps/app2/buildcc.bat"
antWorkingDir="${projectdir}"
target="CCbuildAndTest"
uselogger="true"
usequiet="true">
<property name="cvs_tag" value="${cvs_tag}" />
</plugin>
<plugin name="htmlemail"
buildresultsurl="http://yourCCserver/${project.name}"
mailhost="mailgate.qintra.com"
returnaddress="${builder.email}"
returnname="${builder.name}"
subjectprefix="[BUILD ${project.name}]"
defaultsuffix="@companyname.com"
reportsuccess="always
spamwhilebroken="true"
xsldir="${reportdir}/xsl"
css="${reportdir}/css/cruisecontrol.css" />
<plugin name="currentbuildstatuslistener"
file="${logdir}/buildstatus.html"/>
<plugin name="labelincrementer" classname="net.sourceforge
.cruisecontrol.labelincrementers.CVSLabelIncrementer"/>
<plugin name="project.apptype1.type"
classname="net.sourceforge.cruisecontrol.ProjectConfig">
<bootstrappers>
<cvsbootstrapper
localWorkingCopy="/projects/javaapps/${project.name}"
file="build.xml" />
</bootstrappers>
<listeners>
<currentbuildstatuslistener/>
</listeners>
<log dir="${logdir}" encoding="ISO-8859-1">
<merge dir="${testdir}"/>
</log>
<modificationset quietperiod="60">
<cvs/>
</modificationset>
<schedule interval="3600">
<ant/>
</schedule>
<publishers>
<htmlemail logdir="${logdir}" >
<always address="${builder.email}"/>
<failure address="${builder.email}"/>
<always address="${builder2.email}"/>
<failure address="${builder2.email}"/>
</htmlemail>
</publishers>
</plugin>
<project.apptype1.type
name="app1" buildafterfailed="false">
</project.apptype1.type>
<project.apptype1.type
name="app2" buildafterfailed="false">
</project.apptype1.type>
<project.apptype1.type
name="app2" buildafterfailed="false">
</project.apptype1.type>
<project.apptype1.type
name="app3" buildafterfailed="false">
</project.apptype1.type>
<project.apptype1.type
name="app4" buildafterfailed="false">
</project.apptype1.type>
<project.apptype1.type
name="wls10app" buildafterfailed="false">
<property name="builder2.email"
value="Another.Person@companyname.com" />
<log dir="${logdir}" encoding="ISO-8859-1">
<merge dir="${projectdir}/appdir/build/unitTest" />
</log>
</project.apptype1.type>
<project.apptype1.type name="app5" buildafterfailed="false">
<property name="cvs_tag" value="branch_name" />
<property name="builder2.email"
value="John.Liptak@companyname.com" />
<log dir="${logdir}" encoding="ISO-8859-1">
<merge dir="${projectdir}/builds/unitTest" />
</log>
</project.apptype1.type>
</cruisecontrol>
A critical portion of the Ant task definition
is specifying the destination for the output
in a place where CruiseControl expects it to be. In this example,
../wls10app/build/unitTest is the common location for all of the
unit test output.
It's important to have the merge directory,
${projectdir}/builds/unitTest, in the CruiseControl
configuration match the value in the build file so that reporting includes all
of the unit tests. This allows the unit tests of all of the sub-projects
to be combined into a single report from CruiseControl.
Another important configuration issue is the Ant target that
CruiseControl will call—in my case CCbuildAndTest.
Choosing a good location for that target is also important.
Generating Environment Specific Files (using <macrodef>)
We use two steps to generate our environment-specific deployment
files. Because Ant does not allow the user to redefine properties
once they are set, we create properties for each supported environment
with a unique name prefix. For example, buildEnv.test1.JAVA_HOME
might have a value of /opt/bea/home10.0.1/jrockit_150_11. Rather
than repeat all of the property, fileset, and other variables assignments
over and over
again in our build file, we use the <macrodef> feature of Ant:
<target name="envTokenFilters"
depends="tokenFilter">
<macrodef name="env.macro">
<attribute name="envFile" />
<attribute name="envSuffix" />
<sequential>
<!-- Define properties per environment -->
<property
name="buildTypeFile.@{envSuffix}"
location="${config.env}/@{envFile}.properties" />
<dirname
property="buildTypeFile.@{envSuffix}.dirname"
file="${buildTypeFile.@{envSuffix}}" />
<basename
property="buildTypeFile.@{envSuffix}.basename"
file="${buildTypeFile.@{envSuffix}}" />
<available
file="${buildTypeFile.@{envSuffix}}"
property="buildTypeFile.@{envSuffix}.present"/>
<property
file="${buildTypeFile.@{envSuffix}}"
prefix="buildEnv.@{envSuffix}" />
<!-- Define token filterset per environment -->
<filterset id="tokenFilter.@{envSuffix}"
begintoken="@" endtoken="@">
<filtersfile
file="${commonBuildPropsFile}"/>
<filtersfile
file="${buildTypeFile.@{envSuffix}}" />
</filterset>
<!-- Define filelist per environment -->
<filelist id="tokenFilterFiles.@{envSuffix}"
dir="${buildTypeFile.@{envSuffix}.dirname}"
files="${buildTypeFile.@{envSuffix}.basename}" />
</sequential>
</macrodef>
<env.macro envFile="devUnix" envSuffix="dev"/>
<env.macro envFile="integration" envSuffix="int"/>
<env.macro envFile="integration2" envSuffix="int2"/>
<env.macro envFile="itv" envSuffix="test"/>
<env.macro envFile="itv2" envSuffix="test2"/>
<env.macro envFile="itv3" envSuffix="test3"/>
<env.macro envFile="e2e" envSuffix="e2etest"/>
<env.macro envFile="production" envSuffix="prod"/>
</target>
This is followed by a similar target (all of the dependency checking
is removed for
space reasons, but you would want that in a real build script)
that does the mapping for
each of the environment types to generate the output files:
<macrodef name="copyPropertyFiles.macro">
<attribute name="envSuffix" default=""/>
<sequential>
<!-- Perform token replacements -->
<copy overwrite="true"
todir="${dist.config}" >
<fileset
dir="${config.dir}/properties">
<patternset
refid="propertyFiles" />
</fileset>
<filterset
refid="tokenFilter@{envSuffix}" />
<mapper type="glob"
from="*"
to="*@{envSuffix}"/>
</copy>
<!-- A real build file has many copies of this
copy block. -->
</macrodef>
<copyPropertyFiles.macro/>
<copyPropertyFiles.macro envSuffix=".dev"/>
<copyPropertyFiles.macro envSuffix=".int"/>
<copyPropertyFiles.macro envSuffix=".int2"/>
<copyPropertyFiles.macro envSuffix=".test"/>
<copyPropertyFiles.macro envSuffix=".test2"/>
<copyPropertyFiles.macro envSuffix=".test3"/>
<copyPropertyFiles.macro envSuffix=".e2etest"/>
<copyPropertyFiles.macro envSuffix=".prod"/>
This way, all of the environment-specific files can be generated into a single build.
This makes both the testers and the operations people happy because there is a single
build for all of the environments.
If you add the same processing to WLST scripts to generate and configure your
domain, you can get very close to a touchless install process. Other than a manual
step to input the various WebLogic, database, and other credentials to keep your SOX
auditors at bay, there should be almost nothing to do by hand for an install.
Limitations
This solution does have a few limitations that you need to take into consideration:
- Duplicate class names in different projects will confuse the reporting from CruiseControl.
This is not a significant limitation because it would confuse the developers too, so we just don't allow
that in our projects.
- All of our JUnit classes end in
Test. We chose this so that the test
classes would show up next to their definition in the navigator view. Other people like
having an entire source tree with the same directory structure so that the test classes
look like they are in the same package but can be excluded from production builds.
- Concurrent processing of custom Ant tasks is not supported by Oracle at this time.
Some Ant
tasks assume that they have exclusive access to your temp directory and will fail if
more than one build is executing, even from a different workspace and/or project.
Conclusion
This article showed you how to extend the generated build files from WebLogic Workshop to your
advantage using Ant's <import> and <macrodef> features.
The <import> technique shows how to generate the necessary classpath
for using the <junit> task. Other classpath-based processing can
be integrated with this technique.
Creating an application project that is above the ear project in the
build process allows us to integrate CruiseControl. It also provides a place for
environment specific files—such as property files, WLST scripts, and
deployment plans—to be generated.
Careful setup of build processes gives you both the rich feature set
that comes from using command-line Ant and the advanced IDE features found in
WebLogic Workshop.
John Liptak is a senior software engineer with Qwest who has
been developing in Java since 1997.
|