Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import io.openliberty.tools.common.plugins.config.LooseConfigData
import org.apache.commons.io.FilenameUtils
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.logging.Logger
import org.gradle.plugins.ear.Ear
import org.w3c.dom.Element
Expand Down Expand Up @@ -90,12 +91,16 @@ public class LooseEarApplication extends LooseApplication {
}

public Element addJarModule(Project proj) throws Exception {
logger.debug("Adding JAR module for project: ${proj.name}")
Element moduleArchive = config.addArchive("/" + proj.jar.getArchiveFileName().get());
proj.sourceSets.main.getOutput().getClassesDirs().each{config.addDir(moduleArchive, it, "/");}
if (resourcesDirContentsExist(proj)) {
config.addDir(moduleArchive, proj.sourceSets.main.getOutput().getResourcesDir(), "/");
}
addModules(moduleArchive, proj)

addDependencyClassDirectories(moduleArchive, proj)

return moduleArchive;
}

Expand All @@ -119,5 +124,83 @@ public class LooseEarApplication extends LooseApplication {
}
}
}

/**
* Recursively collect all project dependencies (including transitive)
* @param project The project to collect dependencies from
* @param collected The set to add dependencies to
*/
private void collectProjectDependenciesRecursively(Project project, Set<Project> collected) {
// Add the project itself
collected.add(project)

// Recursively collect dependencies from compileClasspath
if (project.configurations.findByName('compileClasspath') != null) {
project.configurations.compileClasspath.allDependencies.each { dep ->
if (dep instanceof ProjectDependency) {
Project depProj = project.rootProject.findProject(dep.path)
if (depProj != null && !collected.contains(depProj)) {
collectProjectDependenciesRecursively(depProj, collected)
}
}
}
}

// Recursively collect dependencies from runtimeClasspath
if (project.configurations.findByName('runtimeClasspath') != null) {
project.configurations.runtimeClasspath.allDependencies.each { dep ->
if (dep instanceof ProjectDependency) {
Project depProj = project.rootProject.findProject(dep.path)
if (depProj != null && !collected.contains(depProj)) {
collectProjectDependenciesRecursively(depProj, collected)
Comment thread
cherylking marked this conversation as resolved.
}
}
}
}
}

private void addDependencyClassDirectories(Element moduleArchive, Project proj) {
try {
Set<Project> projectDependencies = new HashSet<Project>();

// Collect all project dependencies (including transitive) recursively
collectProjectDependenciesRecursively(proj, projectDependencies)
// Remove the project itself - we only want dependencies
projectDependencies.remove(proj)

logger.debug("Found ${projectDependencies.size()} project dependencies (including transitive) for ${proj.name}")

// Process each project dependency
projectDependencies.each { dependencyProject ->
if (!dependencyProject.hasProperty('sourceSets')) { // Not a Java project
logger.debug("Skipping ${dependencyProject.name} - no sourceSets found (not a Java project)")
return
}

logger.debug("Adding dependency ${dependencyProject.name} class directories to ${proj.name}")

// Add all class directories
dependencyProject.sourceSets.main.output.classesDirs.files.each { File classesDirectory ->
if (classesDirectory.exists()) {
logger.debug("Adding class dir: ${classesDirectory}")
config.addDir(moduleArchive, classesDirectory, "/")
} else {
logger.debug("Skipping non-existent class dir: ${classesDirectory}")
}
}

// Add resource directory
def resourcesDirectory = dependencyProject.sourceSets.main.output.resourcesDir
if (resourcesDirectory?.exists()) {
logger.debug("Adding resource dir: ${resourcesDirectory}")
config.addDir(moduleArchive, resourcesDirectory, "/")
} else {
logger.debug("No resources dir or doesn't exist: ${resourcesDirectory}")
}
}
} catch (Exception e) {
logger.warn("Could not add dependency class directories for ${proj.name}: ${e.message}", e)
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package io.openliberty.tools.gradle

import org.junit.AfterClass
import org.junit.BeforeClass
import org.junit.Test

import java.io.File
import java.io.FileInputStream
import java.io.IOException

import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.xpath.XPath
import javax.xml.xpath.XPathConstants
import javax.xml.xpath.XPathFactory

import org.apache.commons.io.FileUtils
import org.junit.Assert
import org.w3c.dom.Document
import org.w3c.dom.NodeList
import org.w3c.dom.Node

public class TestMultiModuleLooseEarEjbDependency extends AbstractIntegrationTest {
static File resourceDir = new File("build/resources/test/multi-module-loose-ear-ejb-dependency-test")
static File buildDir = new File(integTestDir, "/multi-module-loose-ear-ejb-dependency-test")

@BeforeClass
public static void setup() {
createDir(buildDir)
// Inline createTestProject logic
if (!resourceDir.exists()){
throw new AssertionError("The source file '${resourceDir.canonicalPath}' doesn't exist.", null)
}
try {
// Copy all resources
FileUtils.copyDirectory(resourceDir, buildDir)
Comment thread
cherylking marked this conversation as resolved.
// Copy gradle.properties
copyFile(new File("build/gradle.properties"), new File(buildDir, "gradle.properties"))
} catch (IOException e) {
throw new AssertionError("Unable to copy directory '${buildDir.canonicalPath}'.", e)
}
}

@AfterClass
public static void tearDown() throws Exception {
runTasks(buildDir, 'libertyStop')
}

@Test
public void test_loose_config_file_exists() {
try {
runTasks(buildDir, 'deploy')
} catch (Exception e) {
throw new AssertionError("Fail on task deploy.", e)
}

File looseXml = new File('build/testBuilds/multi-module-loose-ear-ejb-dependency-test/ear/build/wlp/usr/servers/testServer/apps/ejb-dependency-ear-1.0.ear.xml')
assert looseXml.exists() : 'Loose application config file was not created'
}

@Test
public void test_ejb_module_includes_dependency_classes() {
File looseXml = new File('build/testBuilds/multi-module-loose-ear-ejb-dependency-test/ear/build/wlp/usr/servers/testServer/apps/ejb-dependency-ear-1.0.ear.xml')
FileInputStream input = new FileInputStream(looseXml)

DocumentBuilderFactory inputBuilderFactory = DocumentBuilderFactory.newInstance()
inputBuilderFactory.setIgnoringComments(true)
inputBuilderFactory.setCoalescing(true)
inputBuilderFactory.setIgnoringElementContentWhitespace(true)
inputBuilderFactory.setValidating(false)
inputBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false)
inputBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
DocumentBuilder inputBuilder = inputBuilderFactory.newDocumentBuilder()
Document inputDoc = inputBuilder.parse(input)

XPath xPath = XPathFactory.newInstance().newXPath()

String expression = "/archive/archive[@targetInArchive='/ejb-dependency-ejb-jar-1.0.jar']"
NodeList ejbModuleNodes = (NodeList) xPath.compile(expression).evaluate(inputDoc, XPathConstants.NODESET)
Assert.assertEquals("Should have exactly one EJB module archive", 1, ejbModuleNodes.getLength())

Node ejbModuleNode = ejbModuleNodes.item(0)

expression = "dir"
NodeList dirNodes = (NodeList) xPath.compile(expression).evaluate(ejbModuleNode, XPathConstants.NODESET)

Assert.assertEquals("EJB module should have exactly 2 <dir> elements (own classes + dependency classes)",
2, dirNodes.getLength())

// Verify that one of the directories is from lib-jar module
boolean foundLibJarClasses = false
for (int i = 0; i < dirNodes.getLength(); i++) {
Node dirNode = dirNodes.item(i)
String sourceOnDisk = dirNode.getAttributes().getNamedItem("sourceOnDisk").getNodeValue()

if (sourceOnDisk.contains("lib-jar") && sourceOnDisk.contains("classes")) {
foundLibJarClasses = true

// Verify targetInArchive is "/"
String targetInArchive = dirNode.getAttributes().getNamedItem("targetInArchive").getNodeValue()
Assert.assertEquals("Dependency classes should be at root of EJB JAR", "/", targetInArchive)
break
}
}

Assert.assertTrue("EJB module should include lib-jar classes directory", foundLibJarClasses)
}

@Test
public void test_ejb_module_own_classes_included() {
File looseXml = new File('build/testBuilds/multi-module-loose-ear-ejb-dependency-test/ear/build/wlp/usr/servers/testServer/apps/ejb-dependency-ear-1.0.ear.xml')
FileInputStream input = new FileInputStream(looseXml)

DocumentBuilderFactory inputBuilderFactory = DocumentBuilderFactory.newInstance()
inputBuilderFactory.setIgnoringComments(true)
inputBuilderFactory.setCoalescing(true)
inputBuilderFactory.setIgnoringElementContentWhitespace(true)
inputBuilderFactory.setValidating(false)
inputBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false)
inputBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
DocumentBuilder inputBuilder = inputBuilderFactory.newDocumentBuilder()
Document inputDoc = inputBuilder.parse(input)

XPath xPath = XPathFactory.newInstance().newXPath()

String expression = "/archive/archive[@targetInArchive='/ejb-dependency-ejb-jar-1.0.jar']/dir[contains(@sourceOnDisk, 'ejb-jar') and contains(@sourceOnDisk, 'classes')]"
NodeList ejbModuleOwnClasses = (NodeList) xPath.compile(expression).evaluate(inputDoc, XPathConstants.NODESET)

Assert.assertTrue("EJB module should include its own classes directory", ejbModuleOwnClasses.getLength() > 0)

// Verify it targets root of JAR
Node dirNode = ejbModuleOwnClasses.item(0)
String targetInArchive = dirNode.getAttributes().getNamedItem("targetInArchive").getNodeValue()
Assert.assertEquals("EJB module's own classes should be at root of JAR", "/", targetInArchive)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package io.openliberty.tools.gradle

import org.junit.AfterClass
import org.junit.BeforeClass
import org.junit.Test
import org.junit.FixMethodOrder
import org.junit.runners.MethodSorters

import java.io.File
import java.io.FileInputStream
import java.io.IOException

import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.xpath.XPath
import javax.xml.xpath.XPathConstants
import javax.xml.xpath.XPathFactory

import org.apache.commons.io.FileUtils
import org.junit.Assert
import org.w3c.dom.Document
import org.w3c.dom.NodeList
import org.w3c.dom.Node

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestMultiModuleLooseEarMixedDependency extends AbstractIntegrationTest {
static File resourceDir = new File("build/resources/test/multi-module-loose-ear-mixed-test")
static File buildDir = new File(integTestDir, "/multi-module-loose-ear-mixed-test")

@BeforeClass
public static void setup() {
createDir(buildDir)
// Inline createTestProject logic
if (!resourceDir.exists()){
throw new AssertionError("The source file '${resourceDir.canonicalPath}' doesn't exist.", null)
}
try {
// Copy all resources
FileUtils.copyDirectory(resourceDir, buildDir)
// Copy gradle.properties
copyFile(new File("build/gradle.properties"), new File(buildDir, "gradle.properties"))
} catch (IOException e) {
throw new AssertionError("Unable to copy directory '${buildDir.canonicalPath}'.", e)
}
}

@AfterClass
public static void tearDown() throws Exception {
runTasks(buildDir, 'libertyStop')
}

@Test
public void test_loose_config_file_exists() {
try {
runTasks(buildDir, 'deploy')
} catch (Exception e) {
throw new AssertionError("Fail on task deploy.", e)
}

File looseXml = new File('build/testBuilds/multi-module-loose-ear-mixed-test/ear/build/wlp/usr/servers/testServer/apps/ejb-mixed-dependency-ear-1.0.ear.xml')
assert looseXml.exists() : 'Loose application config file was not created'
}

@Test
public void test_project_dependencies_included() {
File looseXml = new File('build/testBuilds/multi-module-loose-ear-mixed-test/ear/build/wlp/usr/servers/testServer/apps/ejb-mixed-dependency-ear-1.0.ear.xml')
FileInputStream input = new FileInputStream(looseXml)

DocumentBuilderFactory inputBuilderFactory = DocumentBuilderFactory.newInstance()
inputBuilderFactory.setIgnoringComments(true)
inputBuilderFactory.setCoalescing(true)
inputBuilderFactory.setIgnoringElementContentWhitespace(true)
inputBuilderFactory.setValidating(false)
inputBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false)
inputBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
DocumentBuilder inputBuilder = inputBuilderFactory.newDocumentBuilder()
Document inputDoc = inputBuilder.parse(input)

XPath xPath = XPathFactory.newInstance().newXPath()

String expression = "/archive/archive[@targetInArchive='/ejb-mixed-dependency-ejb-jar-1.0.jar']"
NodeList ejbModuleNodes = (NodeList) xPath.compile(expression).evaluate(inputDoc, XPathConstants.NODESET)
Assert.assertEquals("Should have exactly one EJB module archive", 1, ejbModuleNodes.getLength())

Node ejbModuleNode = ejbModuleNodes.item(0)

// Check for <dir> elements (project dependencies)
expression = "dir"
NodeList dirNodes = (NodeList) xPath.compile(expression).evaluate(ejbModuleNode, XPathConstants.NODESET)

// Should have exactly 2 <dir> elements: ejb-jar's own classes + lib-jar classes
Assert.assertEquals("EJB module should have exactly 2 <dir> elements (own classes + lib-jar project dependency)",
2, dirNodes.getLength())

// Verify that one of the directories is from lib-jar module
boolean foundLibJarClasses = false
boolean foundEjbClasses = false
for (int i = 0; i < dirNodes.getLength(); i++) {
Node dirNode = dirNodes.item(i)
String sourceOnDisk = dirNode.getAttributes().getNamedItem("sourceOnDisk").getNodeValue()

if (sourceOnDisk.contains("lib-jar") && sourceOnDisk.contains("classes")) {
foundLibJarClasses = true
}
if (sourceOnDisk.contains("ejb-jar") && sourceOnDisk.contains("classes")) {
foundEjbClasses = true
}
}

Assert.assertTrue("EJB module should include its own classes directory", foundEjbClasses)
Assert.assertTrue("EJB module should include lib-jar classes directory (project dependency)", foundLibJarClasses)
}
}
Loading
Loading