The project I’m currently working on uses Spring on both the server side and the client side to wire everything together. We’ve been coding away for three or four months, doing all of our client/server integration testing with lightweight standalone RMI using Spring’s excellent remoting support. However, the time has come to shoehorn our stuff into the production application server: OC4J. This change involves adding a thin stateless session bean layer over our Spring-managed beans.
The largest unforeseen issue which we’ve encountered thus far has been that in order to perform JNDI lookups from the remote client, the OC4J client libraries need to be on the classpath. We’re using Maven 2 for build management, so normally this wouldn’t be an issue: just install the JAR into the local repository. Unfortunately, the OC4J client JAR declares a custom “Class-Path” entry in its manifest file, using relative paths. This constitutes a dependency declaration of sorts, but not one which Maven can work with.
The solution was to “translate” the manifest classpath dependency mechanism into Maven’s POM dependency mechanism. The following Groovy script targets a single JAR file (in this case the central OC4J client JAR), reads the classpath manifest entry, and adds each referenced JAR to the local Maven repository before finally adding the targeted JAR to the repository. Each JAR added to the repository is a copy of the original JAR, the only difference being that the “Class-Path” manifest attributes are removed from the copies. The script also generates a POM file for each JAR, assigning it a custom dependency configuration based on the removed “Class-Path” manifest attributes.
This was a quickie implementation, so there are a number of limitations: it uses the runtime environment, so it only works on Windows; the base path needs to be configured carefully, or the generated artifact IDs will contain some oddities; the dependency processing is not recursive, so multi-level dependency trees are not supported; etc. All of the limitations are easily fixable, I just don’t need the extra functionality
My apologies for the formatting, which doesn’t quite fit the site template…
// Installs the target JAR (see "jar" variable below) into the Maven 2
// repository, as well as any manifest classpath dependencies. All manifest
// classpath dependencies are used to build appropriate Maven POMs.
//
// http://maven.apache.org/plugins/maven-install-plugin/index.html
//
// @author Daniel Gredler
import java.util.jar.*;
import java.util.zip.*;
String groupId = "com.oracle.oc4j.client"
String version = "10.1.3.2.0"
String home = "C:javaoc4j-client-10.1.3.2.0\"
File base = new File(home)
File dir = new File(base, "j2ee/home")
File jar = new File(dir, "oc4jclient.jar")
String[] paths = getClassPath(jar)
paths.each() {
File f = new File(dir, it)
if(f.exists()) installJar(f, groupId, version, dir, home)
}
installJar(jar, groupId, version, dir, home)
println "Finished!"
def getClassPath(file) {
JarFile jar = new JarFile(file)
if(!jar.manifest) return null
String cp = jar.manifest.mainAttributes.getValue("Class-Path")
if(!cp) return null
return cp.split("s+")
}
def getArtifactId(file, home) {
String artifactId = file.canonicalPath
if(artifactId.startsWith(home)) artifactId = artifactId.substring(home.length())
if(artifactId.endsWith(".jar")) artifactId = artifactId.substring(0, artifactId.length() - 4)
artifactId = artifactId.replaceAll("\", "-")
println "Artifact name mapping: ${file.canonicalPath} -> ${artifactId}"
return artifactId
}
def createPom(groupId, artifactId, version, dependencies, dir, home) {
File pom = File.createTempFile("temp", ".pom")
FileWriter writer = new FileWriter(pom)
writer.write("<project>\r\n")
writer.write("t<modelVersion>4.0.0</modelVersion>rn")
writer.write("t<groupId>${groupId}</groupId>rn")
writer.write("t<artifactId>${artifactId}</artifactId>rn")
writer.write("t<version>${version}</version>rn")
if(dependencies) {
writer.write("t<dependencies>rn")
dependencies.each() {
File jar = new File(dir, it)
if(jar.exists()) {
String aid = getArtifactId(jar, home)
writer.write("tt<dependency>rn")
writer.write("ttt<groupId>${groupId}</groupId>rn")
writer.write("ttt<artifactId>${aid}</artifactId>rn")
writer.write("ttt<version>${version}</version>rn")
writer.write("tt</dependency>rn")
}
else {
println "WARNING: Not including dependency ${jar.canonicalPath}, because the file does not exist."
}
}
writer.write("t</dependencies>rn")
}
writer.write("</project>rn")
writer.close()
return pom
}
def makeCopyWithoutClassPathManifestEntry(file) {
JarFile jar = new JarFile(file)
Manifest manifest = (jar.manifest ? new Manifest(jar.manifest) : new Manifest())
manifest.mainAttributes.remove(new Attributes.Name("Class-Path"))
InputStream input = new BufferedInputStream(new FileInputStream(file))
File copy = File.createTempFile(getNamePrefix(file), getNameSuffix(file))
BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream(copy));
JarOutputStream output = new JarOutputStream(bo, manifest)
int b
byte[] buffer = new byte[4096]
jar.entries().each() {
if(!it.name.endsWith("MANIFEST.MF")) {
output.putNextEntry(new ZipEntry(it.name))
InputStream is = jar.getInputStream(it)
while((b = is.read(buffer)) != -1) output.write(buffer, 0, b)
is.close()
}
}
input.close()
output.close()
println "Copied ${file.canonicalPath} -> ${copy.canonicalPath}"
return copy
}
def getNamePrefix(file) {
if (file.name.contains(".")) return file.name.substring(0, file.name.lastIndexOf("."))
else return file.name
}
def getNameSuffix(file) {
if (file.name.contains(".")) return file.name.substring(file.name.lastIndexOf("."))
else return null
}
def installJar(file, groupId, version, dir, home) {
File copy = makeCopyWithoutClassPathManifestEntry(file)
String artifactId = getArtifactId(file, home)
String path = copy.canonicalPath
String[] dependencies = getClassPath(file)
int dependencyCount = (dependencies ? dependencies.length : 0)
File pom = createPom(groupId, artifactId, version, dependencies, dir, home)
String pomPath = pom.absolutePath
installJar(groupId, artifactId, version, path, pomPath, dependencyCount)
}
def installJar(groupId, artifactId, version, path, pomPath, dependencyCount) {
String cmd = "cmd /c mvn install:install-file -DgroupId=${groupId} -DartifactId=${artifactId} -Dversion=${version} -Dpackaging=jar -Dfile=\"${path}\" -DpomFile=\"${pomPath}\" -DcreateChecksum=true"
println "Installing ${artifactId} with ${dependencyCount} dependencies using command: ${cmd}"
println ""
Runtime.getRuntime().exec(cmd)
}