This is a follow-up of a previous post (click here) where I had described a custom SSO solution using Weblogic’s Identity Provider. In that post, one can see a snippet from Maven’s POM file used to build an Identity Asserter. That is because the Identity Asserter needs to be built using Weblogic’s MBean maker facility. For that, I needed to build it remotely.

This post will go further on the build process path and describe how we implemented the building and deployment of artifacts needed by the Custom SSO solution using Maven and WLST. It will comprise procedures for deployment of ADF application, Data Source inside Weblogic and Identity Asserter inside Weblogic.

I will also provide some tips along the way, showing how I overcame some issues that I had encountered.

Overview

We wanted to be able to trigger the build & deployment for all the artifacts of this solution in one shot (not going through each of them and deploy one by one). So we build everything as an application inside JDeveloper with three projects: ADF application, Client library and Identity Asserter.

From Maven’s perspective, we have a parent POM file which has three modules, one for each project. Of course, then each project will have its own POM. This allows us the flexibility to deploy also one-by-one by using the children’s POM files, but also in one shot by calling the parent POM file which will automatically call each child POM.

The POM file for the parent has the sole role of calling modules. Therefore, the relevant code is only to define them.

<modules>
    <module>Backend</module>
    <module>Client</module>
    <module>IdentityProvider</module>
</modules>

Backend project

You can have a look on the complete POM file at: backend_pom

We use ojmake to build our war. In order for this to work you need to have ojserver started. This can be found at <Oracle_HOME>/jdeveloper/jdev/bin/ and can be started with: ./ojserver –start

TIP:

We need to skip maven compiler plugin so we need to set the flag in the configuration

<configuration>
    <skipMain>true</skipMain>
</configuration>

In order to skip to default Maven WAR plugin (because we defined the packaging as WAR), we have to define an execution with the same ID and set none for the phase. The documentation states that there is a skip flag but I could not not make it work. Here’s the code:

<plugin>
    <!-- In order to skip the plugin's execution, we need to override the default phase and set the phase to none -->
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.4</version>
    <configuration>
        <webappDirectory>public_html/</webappDirectory>
        <packagingExcludes>WEB-INF/lib/*.jar,WEB-INF/classes/pom.xml,WEB-INF/classes/*.jpr,WEB-INF/classes/.data</packagingExcludes>
    </configuration>
    <executions>
        <execution>
            <id>default-war</id>
            <phase>none</phase>
        </execution>
    </executions>
</plugin>

 

Besides installing the WAR (which is a WebService) on the Weblogic, we also need to create a data source that the WebService needs to communicate with the Database. For this, we need to connect through WLST and execute a Python script which does just that. To call WLST from Maven, we need weblogic-maven-plugin.

You can find the full code in the attached POM file; Here I will show the execution which calls the Python file.

<execution>
    <id>install-data-source</id>
    <phase>package</phase>
    <goals>
        <goal>
            wlst-client
        </goal>
    </goals>
    <configuration>
        <fileName>${project.basedir}/wlst/installDataSource.py</fileName>
        <scriptArgs>
            <arg>${wls.wlst.user}</arg>
            <arg>${wls.wlst.password}</arg>
            <arg>${wls.wlst.adminurl}</arg>
            <arg>${wls.wlst.datasource.name}</arg>
            <arg>${wls.wlst.datasource.jndi}</arg>
            <arg>${wls.wlst.datasource.db.url}</arg>
            <arg>${wls.wlst.datasource.jdbc.driver}</arg>
            <arg>${wls.wlst.datasource.db.password}</arg>
            <arg>${wls.wlst.datasource.db.user}</arg>
            <arg>${wls.wlst.targets}</arg>
            <arg>${wls.wlst.datasource.target.type}</arg>
        </scriptArgs>
    </configuration>
</execution>                 

This way, we call the python script and send some parameters that we need to it.

The Python script, for its part, will connect to the Admin Server and will check if a DataSource with the same name already exists. If it does, it deletes it and if not, it will create it.

The full script can be found here: installDataSource

In the same weblogic-maven-plugin executions there is also an undeploy task, which has the job of undeploying the application before deploying this one.

<execution>
    <id>wls-undeploy</id>
    <phase>install</phase>
    <goals>
        <goal>undeploy</goal>
    </goals>
</execution>                                        

According to the POM, the installation of Data Source will be done in the package phase and the undeploy and deploy of the application will be done at install phase. Therefore, calling mvn install will do both.

Client project

The resulting JAR from this project acts as the client used to call the WebService in the Backend project. It will be used during compile time for the IdentityProvider project and then it will be placed in the lib directory of the server so that it will be in the classhpath (Identity Asserter needs it).

Complete POM file is here: client_pom

The purpose of this POM is to compile & generate the JAR file and then push it to the maven repository so that it can be later reused by the IdentityProvider project.

The generation of JAR can be done with the default maven-jar-plugin. Maven uses the output of this to push it to maven repository. You can specify using the <configuration> tag what files to include.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>2.4</version>
    <configuration>
        <skipMain>true</skipMain>
        <includes>
            <include>de/**</include>
            <include>META-INF/**</include>
        </includes>
    </configuration>
</plugin>

In addition to this, we need to signal using the <packaging> tag that we are generating a JAR file.

That’s it for the Client project. Now, when we call “Install” on this POM, the JAR will be generated and pushed to the local repository of mavenn (Or remote if you have configured one).

TIP: You can specify the remote repository by using <distributionManagement> tag. You can specify two types of repositories: one for snapshot artifacts and the other one for releases. Maven will publish the artifact to SNAPSHOT repository when the version of the artifact has the suffix -SNAPSHOT

    <distributionManagement>
        <repository>
            <id>central</id>
            <name>Repo-releases</name>
            <url>${artifactory.home}/artifactory/releases</url>
        </repository>
        <snapshotRepository>
            <id>snapshots</id>
            <name>Repo-snapshots</name>
            <url>${artifactory.home}/artifactory/zit_snapshots_local</url>
        </snapshotRepository>
    </distributionManagement>

 

TIP: I am not sure if this is related to JDeveloper only or it is something internal to Weblogic, but I had encountered an issue while refactoring the WebService. The changes I made were regarding the packages name, classes names, changes in the xmls and so on. After doing this and trying to deploy to Weblogic, I hit a strange error regarding the reading of annotations. The error looks like this:

weblogic.application.ModuleException: java.lang.NullPointerException
at weblogic.application.internal.ExtensibleModuleWrapper.prepare(ExtensibleModuleWrapper.java:114)
at weblogic.application.internal.flow.ModuleListenerInvoker.prepare(ModuleListenerInvoker.java:100)
at weblogic.application.internal.flow.ModuleStateDriver$1.next(ModuleStateDriver.java:192)
at weblogic.application.internal.flow.ModuleStateDriver$1.next(ModuleStateDriver.java:187)
at weblogic.application.utils.StateMachineDriver$ParallelChange.run(StateMachineDriver.java:83)
at weblogic.application.utils.StateMachineDriver.nextStateInParallel(StateMachineDriver.java:144)

……………………………………………………………………………………………………………………………………………..

Caused By: java.lang.NullPointerException
at weblogic.j2ee.dd.xml.BaseJ2eeAnnotationProcessor.getClassPersistenceContextRefs(BaseJ2eeAnnotationProcessor.java:1180)
at weblogic.j2ee.dd.xml.J2eeAnnotationProcessor.processJ2eeAnnotations(J2eeAnnotationProcessor.java:44)
at weblogic.servlet.internal.WebAnnotationProcessor.processJ2eeAnnotations(WebAnnotationProcessor.java:707)
at weblogic.servlet.internal.WebAnnotationProcessor.processServlet(WebAnnotationProcessor.java:475)
at weblogic.servlet.internal.AnnotationProcessingManager.processServlets(AnnotationProcessingManager.java:242)
at weblogic.servlet.internal.AnnotationProcessingManager.processAnnotationsInWebXml(AnnotationProcessingManager.java:228)
at weblogic.servlet.internal.AnnotationProcessingManager.processAnnotations(AnnotationProcessingManager.java:131)
at weblogic.servlet.internal.AnnotationProcessingManager.processAnnotations(AnnotationProcessingManager.java:105)
at weblogic.servlet.internal.WebAppModule.processAnnotations(WebAppModule.java:2001)
at weblogic.servlet.internal.WebAppModule.processAnnotations(WebAppModule.java:1963)
at weblogic.servlet.internal.WebAppModule.prepare(WebAppModule.java:800)

To make sure that it wasn’t a fault that I made while refactoring I quickly created another domain, a clean one, and installed the WebService there. It worked.

The solution to this is to rename the WebService, redeploy and then it works.

Note that if the name of the service is the only thing which changes, you do not have to regenerate the client for the WebService, instead you can keep using the old WSDL which is stored offline in the package of where the Client was generated.

 

IdentityProvider project

Complete POM file is here: identityprovider_pom

The main thing to learn from here is that we need to build our IdentityAsserter remotely, on the target server. That is because we need the MBean maker utility, which has dependencies on other JARs in the Weblogic installation.

Here’s the outline of what this build file will do:

  • Copy needed files remotely to the target server
  • Compile & generate the IdentityAsserter
  • Copy IdentityAsserter into the mbean folder
  • Copy Client library into the lib folder
  • Install the IdentityAsserter on the server
  • Restart servers

We do all these tasks using the maven-antrun-plugin. We create a single <execution> tag inside, which will do all the job.

We want to use SCP and SSH for working with the remote system. SCP and SSH tasks are not by default included in ANT, so we need to define them.

First, we need to define two dependencies to the jar that SCP and SSH need.

<dependency>
    <groupId>org.apache.ant</groupId>
    <artifactId>ant-jsch</artifactId>
    <version>1.8.4</version>
</dependency>
<dependency>
    <groupId>com.jcraft</groupId>
    <artifactId>jsch</artifactId>
    <version>0.1.54</version>
</dependency>

This will put the required jar into the classpath. Now, we need to define the tasks inside ANT code and point the classpathref to point to the maven classhpath:

<taskdef name="scp" classname="org.apache.tools.ant.taskdefs.optional.ssh.Scp" classpathref="maven.compile.classpath" />
<taskdef name="sshexec" classname="org.apache.tools.ant.taskdefs.optional.ssh.SSHExec" classpathref="maven.compile.classpath" />

Now, we use these tasks to copy and execute commands remotely, for example, this will copy the src directory remotely:

<scp todir="${scp.user}:${scp.password}@${scp.host}:${scp.remote.project.dir}/src" trust="true" failonerror="false">
    <fileset dir="${src.dir}" />
</scp>

After we have created the required structure remotely, we can execute the MBean maker utility like this:

<sshexec host="${scp.host}" username="${scp.user}" password="${scp.password}" 
command="java -classpath ${wlst.wls.classpath.jar.path}:${java.tools.jar.path}:${scp.remote.project.dir}/lib/ServiceClient-${client.jar.version}.jar:. 
-Dfiles=${scp.remote.project.dir}/temp -DMDFDIR=${scp.remote.project.dir}/temp -DMJF=${scp.remote.project.dir}/temp/${wls.asserter.jar.name} -DpreserveStubs=true 
-DcreateStubs=true weblogic.management.commo.WebLogicMBeanMaker" trust="true"/>

This will generate the JAR for the Identity Asserter. The next step is to copy the Identity Asserter into the mbeantypes folder, and the client library into the lib folder.

sername="${scp.user}" password="${scp.password}" command="yes | cp ${scp.remote.project.dir}/temp/${wls.asserter.jar.name} ${wls.lib.dir}/mbeantypes" trust="true"/>
<sshexec host="${scp.host}" username="${scp.user}" password="${scp.password}" command="yes | cp ${scp.remote.project.dir}/lib/ServiceClient-${client.jar.version}.jar ${wls.domain.home.path}/lib" trust="true"/>

The final step in the ANT execution is to invoke the Pyhton script which will install the Identity Asserter on the server.

We call it like this:

<sshexec host="${scp.host}" username="${scp.user}" password="${scp.password}" 
command="sh ${wls.oracle.home}/oracle_common/common/bin/wlst.sh ${scp.remote.project.dir}/wlst/${wls.wlst.script.path} ${wls.wlst.username} ${wls.wlst.password} 
${wls.wlst.adminurl} ${wls.wlst.domainname} ${wls.wlst.realmname} ${wls.wlst.assertername}  ${wls.wlst.asserterclass} ${wls.wlst.wsdl} ${nm.username} ${nm.password} ${nm.host} 
${nm.port} ${nm.domain.location} ${nm.connection.protocol} ${wlst.admin.server.name}" trust="true"/>

Complete Python script is here: installIdentityAsserter

The essential part here is the creation of the IdentityAsserter:

authProviderMbean = cmo.lookupAuthenticationProvider( sys.argv[6] )
if authProviderMbean is not None:
 print('Authentication provider already exists, have to delete it first')
 cmo.destroyAuthenticationProvider( authProviderMbean )
 print('Destroyed authentication provider:' + sys.argv[6])
 
cmo.createAuthenticationProvider(sys.argv[6], sys.argv[7])
cd('/SecurityConfiguration/' + sys.argv[4] + '/Realms/' + sys.argv[5] + '/AuthenticationProviders/' + sys.argv[6])
cmo.setUrlSSOServiceWSDL(sys.argv[8])

 

TIP: When using WLST scripts to install IdentityAsserter or do other configuration tasks, you are actually changing the central config.xml of the domain (<domain_home>/config/config.xml). This is validated by a schema and may result in starting the server if some changes are done which does not conform this schema. However, you can disable this validation by adding the startup JVM argument to the server:

-Dweblogic.configuration.schemaValidationEnabled=false

In our case, we have automated this task by adding this flag (if not present) to all managed servers and then restart them. The relevant method in our python script is this.

def prepareJvmArguments( jvmArguments ) :
 if jvmArguments is not None:
  print( 'Current JVM arguments: ' + jvmArguments )
  if "-Dweblogic.configuration.schemaValidationEnabled=false" not in jvmArguments:
   print('Adding -Dweblogic.configuration.schemaValidationEnabled=false to JVM args of the server')
   jvmArguments = jvmArguments +  " -Dweblogic.configuration.schemaValidationEnabled=false"
  if "-Dweblogic.configuration.schemaValidationEnabled=true" in jvmArguments:
   print('weblogic.configuration.schemaValidationEnabled is explicitly set to true. Setting it to false.')
   jvmArguments.replace( "-Dweblogic.configuration.schemaValidationEnabled=true", "-Dweblogic.configuration.schemaValidationEnabled=false" )  
 else:
  jvmArguments = "-Dweblogic.configuration.schemaValidationEnabled=false" 
 print('New JVM arguments line: ' + jvmArguments)
 return jvmArguments