diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 73ef567c7..efdbd1dc2 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -7,7 +7,7 @@ on: push: branches: '**' pull_request: - branches: [ main ] + branches: [ main, prepare-config] env: # server-config-props-it fix @@ -100,6 +100,7 @@ jobs: with: repository: OpenLiberty/ci.common path: ci.common + ref: prepare-config - name: Checkout ci.ant uses: actions/checkout@v3 with: @@ -206,7 +207,7 @@ jobs: - name: Clone ci.ant and ci.common repos to github.workspace run: | echo ${{github.workspace}} - git clone https://github.com/OpenLiberty/ci.common.git ${{github.workspace}}/ci.common + git clone https://github.com/OpenLiberty/ci.common.git --branch prepare-config --single-branch ${{github.workspace}}/ci.common git clone https://github.com/OpenLiberty/ci.ant.git ${{github.workspace}}/ci.ant - name: Set up Maven uses: stCarolas/setup-maven@v4.5 diff --git a/README.md b/README.md index 4d04cb183..a145f8306 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,7 @@ The Liberty Maven Plugin provides the following goals. | [install-server](docs/install-server.md#install-server) | Installs the Liberty runtime. This goal is implicitly invoked by all the other plugin goals and usually does not need to be executed explicitly. | | [java-dump](docs/java-dump.md#java-dump) | Dump diagnostic information from the server JVM. | | [package](docs/package.md#package) | Package a Liberty server. | +| [prepare-config](docs/prepare-config.md#prepare-config) | Prepare Liberty configuration and generate liberty-plugin-config.xml without creating the server or installing Liberty. Useful for IDE and language server support. | | [prepare-feature](docs/prepare-feature.md#prepare-feature) | Prepare a user feature for installation to the Liberty runtime. | | [run](docs/run.md#run) | Start a Liberty server in the foreground. The run goal implicitly creates the server, installs features referenced by the server.xml file, and deploys the application before starting the Liberty server. | | [start](docs/start.md#start) | Start a Liberty server in the background. The server instance will be automatically created if it does not exist. | diff --git a/docs/prepare-config.md b/docs/prepare-config.md new file mode 100644 index 000000000..bca8c613b --- /dev/null +++ b/docs/prepare-config.md @@ -0,0 +1,99 @@ +## prepare-config + +--- + +Prepare Liberty configuration and generate `liberty-plugin-config.xml` with a mock Liberty server structure. This lightweight goal creates a temporary Liberty server structure, copies configuration files, and generates metadata needed by IDE tools and language servers. + +**What this goal does:** +1. Creates a mock Liberty server structure in `target/.libertyls-var-cache/wlp/usr/servers/{serverName}/` (configurable) +2. Copies all configuration files (server.xml, bootstrap.properties, server.env, jvm.options, etc.) to the mock server +3. Generates `liberty-plugin-config.xml` pointing to the mock server structure + +**Note:** This goal does NOT install Liberty runtime. It only creates a minimal directory structure and copies configuration files. + +--- + +### Usage + +Run directly from the command line: + +```bash +mvn liberty:prepare-config +``` + +Or configure it to run automatically: + +```xml + + io.openliberty.tools + liberty-maven-plugin + + + prepare-config + initialize + + prepare-config + + + + +``` + +--- + +### Configuration + +The goal uses the [common parameters](common-parameters.md) and [common server parameters](common-server-parameters.md). + +The temporary directory for the mock server structure defaults to `.libertyls-var-cache` (a hidden directory). To override this, use the system property: + +```bash +mvn liberty:prepare-config -DprepareConfigTempDir=my-temp-dir +``` + +This will create the mock server structure in `target/my-temp-dir/wlp/usr/servers/{serverName}/` instead of the default location. + +--- + +### Generated Files + +The goal generates: + +1. **Mock Liberty Server Structure** in `target/.libertyls-var-cache/`: + ``` + target/.libertyls-var-cache/ + └── wlp/ + └── usr/ + └── servers/ + └── {serverName}/ + ├── server.xml + ├── bootstrap.properties + ├── server.env + └── jvm.options + ``` + +2. **Configuration Metadata File** `target/liberty-plugin-config.xml` containing project metadata, dependencies, and configuration file paths for IDE tools and language servers. + +--- + +### Use Cases + +- **IDE Language Server Support**: Provides metadata for code completion, validation, and diagnostics in Liberty configuration files +- **Quick Configuration Validation**: Validate configuration without full project build +- **CI/CD Integration**: Generate configuration metadata early in the pipeline + +**For full variable resolution features**, install Liberty first: + +```bash +mvn liberty:create +mvn liberty:prepare-config +``` + +--- + +### See Also + +- [Common Parameters](common-parameters.md) +- [Common Server Parameters](common-server-parameters.md) +- [create goal](create.md) - Install Liberty and create server +- [dev goal](dev.md) - Development mode with hot reload \ No newline at end of file diff --git a/liberty-maven-plugin/pom.xml b/liberty-maven-plugin/pom.xml index d212efabf..7f339fed7 100644 --- a/liberty-maven-plugin/pom.xml +++ b/liberty-maven-plugin/pom.xml @@ -89,7 +89,7 @@ io.openliberty.tools ci.common - 1.8.41 + 1.8.42-SNAPSHOT org.apache.commons diff --git a/liberty-maven-plugin/src/it/prepare-config-it/invoker.properties b/liberty-maven-plugin/src/it/prepare-config-it/invoker.properties new file mode 100644 index 000000000..7458bc25b --- /dev/null +++ b/liberty-maven-plugin/src/it/prepare-config-it/invoker.properties @@ -0,0 +1,3 @@ +# Integration test configuration for prepare-config goal +invoker.goals=initialize +invoker.maven.version=3.6.0+ \ No newline at end of file diff --git a/liberty-maven-plugin/src/it/prepare-config-it/pom.xml b/liberty-maven-plugin/src/it/prepare-config-it/pom.xml new file mode 100644 index 000000000..10011114b --- /dev/null +++ b/liberty-maven-plugin/src/it/prepare-config-it/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + net.wasdev.wlp.maven.test + prepare-config-it + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + + + + + jakarta.platform + jakarta.jakartaee-web-api + 9.1.0 + provided + + + org.eclipse.microprofile + microprofile + 5.0 + pom + provided + + + + + ${project.artifactId} + + + @project.groupId@ + @project.artifactId@ + @project.version@ + + + prepare-config + initialize + + prepare-config + + + testServer + true + + + + + + ${runtimeGroupId} + ${runtimeArtifactId} + ${runtimeVersion} + zip + + + + + + \ No newline at end of file diff --git a/liberty-maven-plugin/src/it/prepare-config-it/src/main/liberty/config/bootstrap.properties b/liberty-maven-plugin/src/it/prepare-config-it/src/main/liberty/config/bootstrap.properties new file mode 100644 index 000000000..eba4e17f3 --- /dev/null +++ b/liberty-maven-plugin/src/it/prepare-config-it/src/main/liberty/config/bootstrap.properties @@ -0,0 +1,4 @@ +# Bootstrap properties for prepare-config integration test +default.http.port=9080 +default.https.port=9443 +app.context.root=/ \ No newline at end of file diff --git a/liberty-maven-plugin/src/it/prepare-config-it/src/main/liberty/config/server.xml b/liberty-maven-plugin/src/it/prepare-config-it/src/main/liberty/config/server.xml new file mode 100644 index 000000000..a3ed01dfd --- /dev/null +++ b/liberty-maven-plugin/src/it/prepare-config-it/src/main/liberty/config/server.xml @@ -0,0 +1,17 @@ + + + + + jakartaee-9.1 + microProfile-5.0 + + + + + + + + + \ No newline at end of file diff --git a/liberty-maven-plugin/src/it/prepare-config-it/src/main/webapp/index.html b/liberty-maven-plugin/src/it/prepare-config-it/src/main/webapp/index.html new file mode 100644 index 000000000..aec4700e9 --- /dev/null +++ b/liberty-maven-plugin/src/it/prepare-config-it/src/main/webapp/index.html @@ -0,0 +1,11 @@ + + + + + Prepare Config Test + + +

Prepare Config Integration Test

+

This is a test application for the prepare-config goal.

+ + \ No newline at end of file diff --git a/liberty-maven-plugin/src/it/prepare-config-it/src/test/java/net/wasdev/wlp/maven/test/app/PrepareConfigIT.java b/liberty-maven-plugin/src/it/prepare-config-it/src/test/java/net/wasdev/wlp/maven/test/app/PrepareConfigIT.java new file mode 100644 index 000000000..18382c0a3 --- /dev/null +++ b/liberty-maven-plugin/src/it/prepare-config-it/src/test/java/net/wasdev/wlp/maven/test/app/PrepareConfigIT.java @@ -0,0 +1,366 @@ +/** + * (C) Copyright IBM Corporation 2026. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.wasdev.wlp.maven.test.app; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import io.openliberty.tools.common.plugins.util.PrepareConfigUtil; +import org.junit.Assert; +import org.junit.Test; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +/** + * Integration test for prepare-config goal + */ +public class PrepareConfigIT { + + private static final String PLUGIN_CONFIG_XML = "target/liberty-plugin-config.xml"; + private static final String TEMP_DIR_NAME = PrepareConfigUtil.DEFAULT_TEMP_DIR_NAME; + public static final String TARGET_TMP_WLP_USR_SERVERS_TEST_SERVER = "target/" + TEMP_DIR_NAME + "/wlp/usr/servers/testServer"; + public static final String BOOTSTRAP_PROPERTIES = "bootstrap.properties"; + public static final String SERVER_XML = "server.xml"; + public static final String LIBERTY_PLUGIN_CONFIG_CONFIG_FILE = "/liberty-plugin-config/configFile"; + + @Test + public void testPluginConfigXmlExists() throws Exception { + File configFile = new File(PLUGIN_CONFIG_XML); + Assert.assertTrue("liberty-plugin-config.xml should exist", configFile.exists()); + } + + @Test + public void testPluginConfigXmlContainsServerName() throws Exception { + String serverName = getXPathValue("/liberty-plugin-config/serverName"); + Assert.assertEquals("Server name should be testServer", "testServer", serverName); + } + + @Test + public void testPluginConfigXmlContainsProjectType() throws Exception { + String projectType = getXPathValue("/liberty-plugin-config/projectType"); + Assert.assertEquals("Project type should be war", "war", projectType); + } + + @Test + public void testPluginConfigXmlContainsConfigFile() throws Exception { + String configFile = getXPathValue(LIBERTY_PLUGIN_CONFIG_CONFIG_FILE); + Assert.assertNotNull("Config file should be present", configFile); + Assert.assertTrue("Config file should reference server.xml", configFile.contains(SERVER_XML)); + } + + @Test + public void testPluginConfigXmlContainsBootstrapPropertiesFile() throws Exception { + String bootstrapFile = getXPathValue("/liberty-plugin-config/bootstrapPropertiesFile"); + Assert.assertNotNull("Bootstrap properties file should be present", bootstrapFile); + Assert.assertTrue("Bootstrap file should reference bootstrap.properties", + bootstrapFile.contains(BOOTSTRAP_PROPERTIES)); + } + + @Test + public void testPluginConfigXmlContainsServerDirectory() throws Exception { + String serverDirectory = getXPathValue("/liberty-plugin-config/serverDirectory"); + Assert.assertNotNull("Server directory should be present", serverDirectory); + } + + @Test + public void testPluginConfigXmlContainsInstallDirectory() throws Exception { + String installDirectory = getXPathValue("/liberty-plugin-config/installDirectory"); + Assert.assertNotNull("Install directory should be present", installDirectory); + } + + @Test + public void testPluginConfigXmlContainsLooseApplication() throws Exception { + String looseApp = getXPathValue("/liberty-plugin-config/looseApplication"); + Assert.assertNotNull("Loose application setting should be present", looseApp); + } + + @Test + public void testPluginConfigXmlContainsAppsDirectory() throws Exception { + String appsDir = getXPathValue("/liberty-plugin-config/appsDirectory"); + Assert.assertNotNull("Apps directory should be present", appsDir); + // Should be "apps" since we have application configured in server.xml + Assert.assertEquals("Apps directory should be 'apps'", "apps", appsDir); + } + + @Test + public void testPluginConfigXmlContainsDependencies() throws Exception { + NodeList dependencies = getXPathNodeList("/liberty-plugin-config/projectCompileDependency"); + Assert.assertNotNull("Dependencies should be present", dependencies); + Assert.assertTrue("Should have at least one dependency", dependencies.getLength() > 0); + + // Check for Jakarta EE dependency + boolean foundJakartaEE = false; + for (int i = 0; i < dependencies.getLength(); i++) { + String dep = dependencies.item(i).getTextContent(); + if (dep.contains("jakarta.jakartaee-web-api")) { + foundJakartaEE = true; + break; + } + } + Assert.assertTrue("Should contain Jakarta EE dependency", foundJakartaEE); + } + + @Test + public void testPluginConfigXmlContainsApplicationFilename() throws Exception { + String appFilename = getXPathValue("/liberty-plugin-config/applicationFilename"); + Assert.assertNotNull("Application filename should be present", appFilename); + Assert.assertTrue("Application filename should be prepare-config-it.war.xml (loose app)", + appFilename.equals("prepare-config-it.war.xml")); + } + + @Test + public void testNoTempDirectoryLeftBehind() throws Exception { + // Check that no temporary Liberty installation directories are left behind + File tempDir = new File(System.getProperty("java.io.tmpdir")); + File[] tempLibertyDirs = tempDir.listFiles((dir, name) -> + name.startsWith("liberty-temp-") && name.endsWith("-install")); + + if (tempLibertyDirs != null) { + Assert.assertEquals("No temporary Liberty directories should remain", + 0, tempLibertyDirs.length); + } + } + + @Test + public void testMockLibertyServerStructureCreated() throws Exception { + // Verify that temp directory structure was created + File tmpDir = new File("target/" + TEMP_DIR_NAME); + Assert.assertTrue(TEMP_DIR_NAME + " directory should exist", tmpDir.exists()); + + File wlpDir = new File(tmpDir, "wlp"); + Assert.assertTrue("wlp directory should exist", wlpDir.exists()); + + File usrDir = new File(wlpDir, "usr"); + Assert.assertTrue("usr directory should exist", usrDir.exists()); + + File serversDir = new File(usrDir, "servers"); + Assert.assertTrue("servers directory should exist", serversDir.exists()); + + File serverDir = new File(serversDir, "testServer"); + Assert.assertTrue("testServer directory should exist", serverDir.exists()); + } + + @Test + public void testConfigFilesCopiedToMockServer() throws Exception { + // Verify that config files were copied to mock server structure + File mockServerDir = new File(TARGET_TMP_WLP_USR_SERVERS_TEST_SERVER); + + File serverXml = new File(mockServerDir, SERVER_XML); + Assert.assertTrue("server.xml should be copied to mock server", serverXml.exists()); + + File bootstrapProps = new File(mockServerDir, BOOTSTRAP_PROPERTIES); + Assert.assertTrue("bootstrap.properties should be copied to mock server", bootstrapProps.exists()); + } + + @Test + public void testPluginConfigXmlPointsToMockServer() throws Exception { + // Verify that liberty-plugin-config.xml has correct directory structure + // All directories should point to mock structure in tmp + + // installDirectory should point to mock Liberty in temp dir + String installDir = getXPathValue("/liberty-plugin-config/installDirectory"); + Assert.assertNotNull("Install directory should be present", installDir); + Assert.assertTrue("Install directory should point to " + TEMP_DIR_NAME + "/wlp", + installDir.contains(TEMP_DIR_NAME) && installDir.endsWith("wlp")); + + // userDirectory should point to mock user directory in temp dir + String userDir = getXPathValue("/liberty-plugin-config/userDirectory"); + Assert.assertNotNull("User directory should be present", userDir); + Assert.assertTrue("User directory should point to " + TEMP_DIR_NAME + "/wlp/usr", + userDir.contains(TEMP_DIR_NAME) && userDir.contains("wlp") && userDir.endsWith("usr")); + + // serverDirectory should point to mock server in temp dir + String serverDir = getXPathValue("/liberty-plugin-config/serverDirectory"); + Assert.assertNotNull("Server directory should be present", serverDir); + Assert.assertTrue("Server directory should point to mock server in " + TEMP_DIR_NAME, + serverDir.contains(TEMP_DIR_NAME) && serverDir.contains("testServer")); + + // serverOutputDirectory should point to mock server in temp dir + String serverOutputDir = getXPathValue("/liberty-plugin-config/serverOutputDirectory"); + Assert.assertNotNull("Server output directory should be present", serverOutputDir); + Assert.assertTrue("Server output directory should point to mock server in " + TEMP_DIR_NAME, + serverOutputDir.contains(TEMP_DIR_NAME) && serverOutputDir.contains("testServer")); + + // configFile should point to mock server + String configFile = getXPathValue(LIBERTY_PLUGIN_CONFIG_CONFIG_FILE); + Assert.assertNotNull("Config file should be present", configFile); + Assert.assertTrue("Config file should point to mock server in " + TEMP_DIR_NAME, + configFile.contains(TEMP_DIR_NAME) && configFile.contains("testServer")); + } + + /** + * Helper method to get XPath value from the plugin config XML + */ + private String getXPathValue(String expression) throws ParserConfigurationException, SAXException, IOException, XPathExpressionException { + Document doc = getDocument(configFile); + + XPathFactory xPathfactory = XPathFactory.newInstance(); + XPath xpath = xPathfactory.newXPath(); + + return xpath.evaluate(expression, doc); + } + + /** + * Helper method to get XPath NodeList from the plugin config XML + */ + private NodeList getXPathNodeList(String expression) throws ParserConfigurationException, SAXException, IOException, XPathExpressionException { + File configFile = new File(PLUGIN_CONFIG_XML); + Document doc = getDocument(configFile); + + XPathFactory xPathfactory = XPathFactory.newInstance(); + XPath xpath = xPathfactory.newXPath(); + + return (NodeList) xpath.evaluate(expression, doc, XPathConstants.NODESET); + } + + private static Document getDocument(File configFile) { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + + // Protect against XXE attacks + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + factory.setXIncludeAware(false); + factory.setExpandEntityReferences(false); + + factory.setIgnoringComments(true); + factory.setCoalescing(true); + factory.setIgnoringElementContentWhitespace(true); + factory.setValidating(false); + + DocumentBuilder builder = factory.newDocumentBuilder(); + Document doc; + try (FileInputStream fis = new FileInputStream(configFile)) { + doc = builder.parse(fis); + } + return doc; + } + + @Test + public void testPluginConfigXmlContainsUserDirectory() throws Exception { + String userDirectory = getXPathValue("/liberty-plugin-config/userDirectory"); + Assert.assertNotNull("User directory should be present", userDirectory); + Assert.assertTrue("User directory should point to mock user directory", + userDirectory.contains(TEMP_DIR_NAME) && userDirectory.contains("usr")); + } + + @Test + public void testPluginConfigXmlContainsServerOutputDirectory() throws Exception { + String serverOutputDirectory = getXPathValue("/liberty-plugin-config/serverOutputDirectory"); + Assert.assertNotNull("Server output directory should be present", serverOutputDirectory); + Assert.assertTrue("Server output directory should point to mock server", + serverOutputDirectory.contains(TEMP_DIR_NAME) && serverOutputDirectory.contains("testServer")); + } + + @Test + public void testBootstrapPropertiesContentCopied() throws Exception { + // Verify that bootstrap.properties was copied with correct content + File mockServerDir = new File(TARGET_TMP_WLP_USR_SERVERS_TEST_SERVER); + File bootstrapProps = new File(mockServerDir, BOOTSTRAP_PROPERTIES); + + Assert.assertTrue("bootstrap.properties should exist", bootstrapProps.exists()); + + // Read and verify content + String content = new String(Files.readAllBytes(bootstrapProps.toPath()), StandardCharsets.UTF_8); + + Assert.assertTrue("Bootstrap properties should contain default.http.port", + content.contains("default.http.port")); + Assert.assertTrue("Bootstrap properties should contain default.https.port", + content.contains("default.https.port")); + } + + @Test + public void testServerXmlContentCopied() throws Exception { + // Verify that server.xml was copied with correct content + File mockServerDir = new File(TARGET_TMP_WLP_USR_SERVERS_TEST_SERVER); + File serverXml = new File(mockServerDir, SERVER_XML); + + Assert.assertTrue("server.xml should exist", serverXml.exists()); + + // Read and verify content + String content = new String(Files.readAllBytes(serverXml.toPath()), StandardCharsets.UTF_8); + + Assert.assertTrue("server.xml should contain featureManager", + content.contains("featureManager")); + Assert.assertTrue("server.xml should contain jakartaee-9.1 feature", + content.contains("jakartaee-9.1")); + Assert.assertTrue("server.xml should contain microProfile-5.0 feature", + content.contains("microProfile-5.0")); + } + + @Test + public void testPluginConfigXmlContainsProjectDirectory() throws Exception { + String projectDirectory = getXPathValue("/liberty-plugin-config/projectDirectory"); + Assert.assertNotNull("Project directory should be present", projectDirectory); + Assert.assertFalse("Project directory should not be empty", projectDirectory.trim().isEmpty()); + } + + @Test + public void testPluginConfigXmlContainsBuildDirectory() throws Exception { + String buildDirectory = getXPathValue("/liberty-plugin-config/buildDirectory"); + Assert.assertNotNull("Build directory should be present", buildDirectory); + Assert.assertTrue("Build directory should contain 'target'", buildDirectory.contains("target")); + } + + @Test + public void testMockServerStructureIsComplete() throws Exception { + // Comprehensive check of the entire mock server structure + File tmpDir = new File("target/" + TEMP_DIR_NAME); + File wlpDir = new File(tmpDir, "wlp"); + File usrDir = new File(wlpDir, "usr"); + File serversDir = new File(usrDir, "servers"); + File serverDir = new File(serversDir, "testServer"); + + Assert.assertTrue(TEMP_DIR_NAME + " directory should be a directory", tmpDir.isDirectory()); + Assert.assertTrue("wlp directory should be a directory", wlpDir.isDirectory()); + Assert.assertTrue("usr directory should be a directory", usrDir.isDirectory()); + Assert.assertTrue("servers directory should be a directory", serversDir.isDirectory()); + Assert.assertTrue("testServer directory should be a directory", serverDir.isDirectory()); + + // Verify the structure matches Liberty's expected layout + Assert.assertEquals("wlp should be child of " + TEMP_DIR_NAME, tmpDir, wlpDir.getParentFile()); + Assert.assertEquals("usr should be child of wlp", wlpDir, usrDir.getParentFile()); + Assert.assertEquals("servers should be child of usr", usrDir, serversDir.getParentFile()); + Assert.assertEquals("testServer should be child of servers", serversDir, serverDir.getParentFile()); + } + + @Test + public void testIncludeServerInfoIsTrue() throws Exception { + // Verify that when includeServerInfo=true (default), server files are included + String configFile = getXPathValue(LIBERTY_PLUGIN_CONFIG_CONFIG_FILE); + String bootstrapFile = getXPathValue("/liberty-plugin-config/bootstrapPropertiesFile"); + + Assert.assertNotNull("Config file should be present when includeServerInfo=true", configFile); + Assert.assertFalse("Config file should not be empty", configFile.trim().isEmpty()); + + Assert.assertNotNull("Bootstrap file should be present when includeServerInfo=true", bootstrapFile); + Assert.assertFalse("Bootstrap file should not be empty", bootstrapFile.trim().isEmpty()); + } +} \ No newline at end of file diff --git a/liberty-maven-plugin/src/it/prepare-config-no-server-info-it/invoker.properties b/liberty-maven-plugin/src/it/prepare-config-no-server-info-it/invoker.properties new file mode 100644 index 000000000..dcb4f135f --- /dev/null +++ b/liberty-maven-plugin/src/it/prepare-config-no-server-info-it/invoker.properties @@ -0,0 +1,3 @@ +# Integration test configuration for prepare-config goal with includeServerInfo=false +invoker.goals=initialize +invoker.maven.version=3.6.0+ \ No newline at end of file diff --git a/liberty-maven-plugin/src/it/prepare-config-no-server-info-it/pom.xml b/liberty-maven-plugin/src/it/prepare-config-no-server-info-it/pom.xml new file mode 100644 index 000000000..b6aceafa0 --- /dev/null +++ b/liberty-maven-plugin/src/it/prepare-config-no-server-info-it/pom.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + + net.wasdev.wlp.maven.test + prepare-config-no-server-info-it + 1.0-SNAPSHOT + war + + + UTF-8 + 1.8 + 1.8 + + + + + jakarta.platform + jakarta.jakartaee-web-api + 9.1.0 + provided + + + + + ${project.artifactId} + + + @project.groupId@ + @project.artifactId@ + @project.version@ + + + prepare-config + initialize + + prepare-config + + + testServer + false + + + + + + ${runtimeGroupId} + ${runtimeArtifactId} + ${runtimeVersion} + zip + + + + + + \ No newline at end of file diff --git a/liberty-maven-plugin/src/it/prepare-config-no-server-info-it/src/main/liberty/config/server.xml b/liberty-maven-plugin/src/it/prepare-config-no-server-info-it/src/main/liberty/config/server.xml new file mode 100644 index 000000000..a1eddc8af --- /dev/null +++ b/liberty-maven-plugin/src/it/prepare-config-no-server-info-it/src/main/liberty/config/server.xml @@ -0,0 +1,14 @@ + + + + + jakartaee-9.1 + + + + + + + diff --git a/liberty-maven-plugin/src/it/prepare-config-no-server-info-it/src/test/java/net/wasdev/wlp/maven/test/app/PrepareConfigNoServerInfoIT.java b/liberty-maven-plugin/src/it/prepare-config-no-server-info-it/src/test/java/net/wasdev/wlp/maven/test/app/PrepareConfigNoServerInfoIT.java new file mode 100644 index 000000000..66d400919 --- /dev/null +++ b/liberty-maven-plugin/src/it/prepare-config-no-server-info-it/src/test/java/net/wasdev/wlp/maven/test/app/PrepareConfigNoServerInfoIT.java @@ -0,0 +1,276 @@ +/** + * (C) Copyright IBM Corporation 2026. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.wasdev.wlp.maven.test.app; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import io.openliberty.tools.common.plugins.util.PrepareConfigUtil; +import org.junit.Assert; +import org.junit.Test; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +/** + * Integration test for prepare-config goal with includeServerInfo=false + * This tests the lightweight mode that only includes basic project information + * without server-specific configuration file paths. + */ +public class PrepareConfigNoServerInfoIT { + + private static final String PLUGIN_CONFIG_XML = "target/liberty-plugin-config.xml"; + private static final String TEMP_DIR_NAME = PrepareConfigUtil.DEFAULT_TEMP_DIR_NAME; + + @Test + public void testPluginConfigXmlExists() throws Exception { + File configFile = new File(PLUGIN_CONFIG_XML); + Assert.assertTrue("liberty-plugin-config.xml should exist", configFile.exists()); + } + + @Test + public void testPluginConfigXmlContainsServerName() throws Exception { + String serverName = getXPathValue("/liberty-plugin-config/serverName"); + Assert.assertEquals("Server name should be testServer", "testServer", serverName); + } + + @Test + public void testPluginConfigXmlContainsProjectType() throws Exception { + String projectType = getXPathValue("/liberty-plugin-config/projectType"); + Assert.assertEquals("Project type should be war", "war", projectType); + } + + @Test + public void testPluginConfigXmlDoesNotContainConfigFile() throws Exception { + String configFile = getXPathValue("/liberty-plugin-config/configFile"); + Assert.assertTrue("Config file should be empty or null when includeServerInfo=false", + configFile == null || configFile.trim().isEmpty()); + } + + @Test + public void testPluginConfigXmlDoesNotContainBootstrapPropertiesFile() throws Exception { + String bootstrapFile = getXPathValue("/liberty-plugin-config/bootstrapPropertiesFile"); + Assert.assertTrue("Bootstrap properties file should be empty or null when includeServerInfo=false", + bootstrapFile == null || bootstrapFile.trim().isEmpty()); + } + + @Test + public void testPluginConfigXmlDoesNotContainServerEnvFile() throws Exception { + String serverEnvFile = getXPathValue("/liberty-plugin-config/serverEnvFile"); + Assert.assertTrue("Server env file should be empty or null when includeServerInfo=false", + serverEnvFile == null || serverEnvFile.trim().isEmpty()); + } + + @Test + public void testPluginConfigXmlDoesNotContainJvmOptionsFile() throws Exception { + String jvmOptionsFile = getXPathValue("/liberty-plugin-config/jvmOptionsFile"); + Assert.assertTrue("JVM options file should be empty or null when includeServerInfo=false", + jvmOptionsFile == null || jvmOptionsFile.trim().isEmpty()); + } + + @Test + public void testPluginConfigXmlContainsServerDirectory() throws Exception { + String serverDirectory = getXPathValue("/liberty-plugin-config/serverDirectory"); + Assert.assertNotNull("Server directory should still be present", serverDirectory); + } + + @Test + public void testPluginConfigXmlContainsInstallDirectory() throws Exception { + String installDirectory = getXPathValue("/liberty-plugin-config/installDirectory"); + Assert.assertNotNull("Install directory should still be present", installDirectory); + } + + @Test + public void testPluginConfigXmlContainsLooseApplication() throws Exception { + String looseApp = getXPathValue("/liberty-plugin-config/looseApplication"); + Assert.assertNotNull("Loose application setting should be present", looseApp); + } + + @Test + public void testPluginConfigXmlContainsDependencies() throws Exception { + NodeList dependencies = getXPathNodeList("/liberty-plugin-config/projectCompileDependency"); + Assert.assertNotNull("Dependencies should be present", dependencies); + Assert.assertTrue("Should have at least one dependency", dependencies.getLength() > 0); + + // Check for Jakarta EE dependency + boolean foundJakartaEE = false; + for (int i = 0; i < dependencies.getLength(); i++) { + String dep = dependencies.item(i).getTextContent(); + if (dep.contains("jakarta.jakartaee-web-api")) { + foundJakartaEE = true; + break; + } + } + Assert.assertTrue("Should contain Jakarta EE dependency", foundJakartaEE); + } + + @Test + public void testPluginConfigXmlContainsApplicationFilename() throws Exception { + String appFilename = getXPathValue("/liberty-plugin-config/applicationFilename"); + Assert.assertNotNull("Application filename should be present", appFilename); + Assert.assertTrue("Application filename should be prepare-config-no-server-info-it.war.xml (loose app)", + appFilename.equals("prepare-config-no-server-info-it.war.xml")); + } + + @Test + public void testMockLibertyServerStructureCreated() throws Exception { + // Verify that temp directory structure was created even with includeServerInfo=false + File tmpDir = new File("target/" + TEMP_DIR_NAME); + Assert.assertTrue(TEMP_DIR_NAME + " directory should exist", tmpDir.exists()); + + File wlpDir = new File(tmpDir, "wlp"); + Assert.assertTrue("wlp directory should exist", wlpDir.exists()); + + File usrDir = new File(wlpDir, "usr"); + Assert.assertTrue("usr directory should exist", usrDir.exists()); + + File serversDir = new File(usrDir, "servers"); + Assert.assertTrue("servers directory should exist", serversDir.exists()); + + File serverDir = new File(serversDir, "testServer"); + Assert.assertTrue("testServer directory should exist", serverDir.exists()); + } + + @Test + public void testConfigFilesNotCopiedToMockServer() throws Exception { + // When includeServerInfo=false, config files should still be copied + // because copyConfigFiles is called before the includeServerInfo check + File mockServerDir = new File("target/" + TEMP_DIR_NAME + "/wlp/usr/servers/testServer"); + + File serverXml = new File(mockServerDir, "server.xml"); + Assert.assertTrue("server.xml should be copied to mock server", serverXml.exists()); + } + + @Test + public void testPluginConfigXmlPointsToMockServer() throws Exception { + // Verify that liberty-plugin-config.xml has correct directory structure + // All directories should point to mock structure in temp dir + + // installDirectory should point to mock Liberty in temp dir + String installDir = getXPathValue("/liberty-plugin-config/installDirectory"); + Assert.assertNotNull("Install directory should be present", installDir); + Assert.assertTrue("Install directory should point to " + TEMP_DIR_NAME + "/wlp", + installDir.contains(TEMP_DIR_NAME) && installDir.endsWith("wlp")); + + // userDirectory should point to mock user directory in temp dir + String userDir = getXPathValue("/liberty-plugin-config/userDirectory"); + Assert.assertNotNull("User directory should be present", userDir); + Assert.assertTrue("User directory should point to " + TEMP_DIR_NAME + "/wlp/usr", + userDir.contains(TEMP_DIR_NAME) && userDir.contains("wlp") && userDir.endsWith("usr")); + + // serverDirectory should point to mock server in temp dir + String serverDir = getXPathValue("/liberty-plugin-config/serverDirectory"); + Assert.assertNotNull("Server directory should be present", serverDir); + Assert.assertTrue("Server directory should point to mock server in " + TEMP_DIR_NAME, + serverDir.contains(TEMP_DIR_NAME) && serverDir.contains("testServer")); + + // serverOutputDirectory should point to mock server in temp dir + String serverOutputDir = getXPathValue("/liberty-plugin-config/serverOutputDirectory"); + Assert.assertNotNull("Server output directory should be present", serverOutputDir); + Assert.assertTrue("Server output directory should point to mock server in " + TEMP_DIR_NAME, + serverOutputDir.contains(TEMP_DIR_NAME) && serverOutputDir.contains("testServer")); + } + + @Test + public void testFasterExecutionWithoutServerInfo() throws Exception { + // This test verifies that the goal completes successfully + // The actual performance benefit is that server-specific file paths + // are not included in the XML, making it lighter weight + File configFile = new File(PLUGIN_CONFIG_XML); + Assert.assertTrue("Config file should exist", configFile.exists()); + + // Verify the file is smaller/simpler by checking it doesn't contain + // server-specific file paths + String configFile1 = getXPathValue("/liberty-plugin-config/configFile"); + String bootstrapFile = getXPathValue("/liberty-plugin-config/bootstrapPropertiesFile"); + String serverEnvFile = getXPathValue("/liberty-plugin-config/serverEnvFile"); + String jvmOptionsFile = getXPathValue("/liberty-plugin-config/jvmOptionsFile"); + + boolean hasNoServerFiles = (configFile1 == null || configFile1.trim().isEmpty()) && + (bootstrapFile == null || bootstrapFile.trim().isEmpty()) && + (serverEnvFile == null || serverEnvFile.trim().isEmpty()) && + (jvmOptionsFile == null || jvmOptionsFile.trim().isEmpty()); + + Assert.assertTrue("Config should not contain server-specific file paths", hasNoServerFiles); + } + + /** + * Helper method to get XPath value from the plugin config XML + */ + private String getXPathValue(String expression) throws ParserConfigurationException, SAXException, IOException, XPathExpressionException { + File configFile = new File(PLUGIN_CONFIG_XML); + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + + // Protect against XXE attacks + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + factory.setXIncludeAware(false); + factory.setExpandEntityReferences(false); + + DocumentBuilder builder = factory.newDocumentBuilder(); + Document doc; + try (FileInputStream fis = new FileInputStream(configFile)) { + doc = builder.parse(fis); + } + + XPathFactory xPathfactory = XPathFactory.newInstance(); + XPath xpath = xPathfactory.newXPath(); + + return xpath.evaluate(expression, doc); + } + + /** + * Helper method to get XPath NodeList from the plugin config XML + */ + private NodeList getXPathNodeList(String expression) throws ParserConfigurationException, SAXException, IOException, XPathExpressionException { + File configFile = new File(PLUGIN_CONFIG_XML); + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + + // Protect against XXE attacks + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + factory.setXIncludeAware(false); + factory.setExpandEntityReferences(false); + + DocumentBuilder builder = factory.newDocumentBuilder(); + Document doc; + try (FileInputStream fis = new FileInputStream(configFile)) { + doc = builder.parse(fis); + } + + XPathFactory xPathfactory = XPathFactory.newInstance(); + XPath xpath = xPathfactory.newXPath(); + + return (NodeList) xpath.evaluate(expression, doc, XPathConstants.NODESET); + } +} diff --git a/liberty-maven-plugin/src/main/java/io/openliberty/tools/maven/server/PrepareConfigMojo.java b/liberty-maven-plugin/src/main/java/io/openliberty/tools/maven/server/PrepareConfigMojo.java new file mode 100644 index 000000000..9feaceb33 --- /dev/null +++ b/liberty-maven-plugin/src/main/java/io/openliberty/tools/maven/server/PrepareConfigMojo.java @@ -0,0 +1,220 @@ +/** + * (C) Copyright IBM Corporation 2026. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.openliberty.tools.maven.server; + +import java.io.File; +import java.io.IOException; +import java.text.MessageFormat; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; + +import io.openliberty.tools.common.plugins.util.PrepareConfigUtil; + +/** + * Prepare Liberty configuration and generate liberty-plugin-config.xml without + * creating the server. This lightweight goal evaluates project configuration + * and generates metadata needed by IDE tools and language servers. + * + *

+ * This goal is designed to be fast and non-invasive, making it suitable for + * automatic execution when Liberty configuration files are opened in an IDE. + *

+ * + *

+ * The generated liberty-plugin-config.xml file contains: + *

+ * + * + *

+ * Note: This goal does NOT install Liberty or create a server. If you need + * Liberty installed for full variable resolution in language servers, use the + * liberty:create or liberty:dev goals first. + *

+ * + *

+ * Usage Examples: + *

+ * + *
+ * {@code
+ * 
+ * mvn liberty:prepare-config
+ *
+ * 
+ * mvn liberty:prepare-config -DprepareConfigTempDir=my-temp
+ *
+ * 
+ * mvn liberty:create
+ * mvn liberty:prepare-config
+ * }
+ * 
+ */ +@Mojo(name = "prepare-config", requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, threadSafe = true) +public class PrepareConfigMojo extends PluginConfigSupport { + + @Override + public void execute() throws MojoExecutionException { + // Set flag to skip server config setup in init() to avoid Liberty runtime download + skipServerConfigSetup = true; + init(); + + if (skip) { + getLog().info("\nSkipping prepare-config goal.\n"); + return; + } + + // Set up minimal required fields that were skipped by skipServerConfigSetup + initializeMinimalServerConfig(); + + doPrepareConfig(); + } + + /** + * Initialize minimal server configuration needed for prepare-config goal. + * This sets up mock paths without downloading Liberty runtime. + */ + private void initializeMinimalServerConfig() { + // Set server name if not already set + if (serverName == null) { + serverName = "defaultServer"; + } + + // Check for system property override, otherwise use default + String tempDirName = System.getProperty("prepareConfigTempDir"); + if (tempDirName == null || tempDirName.trim().isEmpty()) { + tempDirName = PrepareConfigUtil.DEFAULT_TEMP_DIR_NAME; + } else { + tempDirName = tempDirName.trim(); + } + + // Set up mock Liberty directory structure + File buildDirectory = new File(project.getBuild().getDirectory()); + installDirectory = PrepareConfigUtil.getMockInstallDirectory(buildDirectory, tempDirName); + userDirectory = PrepareConfigUtil.getMockUserDirectory(buildDirectory, tempDirName); + File serversDirectory = PrepareConfigUtil.getMockServersDirectory(buildDirectory, tempDirName); + serverDirectory = new File(serversDirectory, serverName); + outputDirectory = serversDirectory; + + getLog().debug("prepare-config: Using mock Liberty paths (no runtime download)"); + getLog().debug(" tempDirectory: " + tempDirName); + getLog().debug(" installDirectory: " + installDirectory); + getLog().debug(" serverDirectory: " + serverDirectory); + } + + private void doPrepareConfig() throws MojoExecutionException { + getLog().info("Preparing Liberty configuration..."); + + try { + // Check for system property override, otherwise use default + String tempDirName = System.getProperty("prepareConfigTempDir"); + if (tempDirName == null || tempDirName.trim().isEmpty()) { + tempDirName = PrepareConfigUtil.DEFAULT_TEMP_DIR_NAME; + } else { + tempDirName = tempDirName.trim(); + } + + // Create mock Liberty server structure using common utility + File buildDirectory = new File(project.getBuild().getDirectory()); + File mockServerDir = PrepareConfigUtil.createMockLibertyServerStructure(buildDirectory, serverName, tempDirName); + + // Temporarily set serverDirectory to mock location for config file copying + File originalServerDir = serverDirectory; + try { + serverDirectory = mockServerDir; + + // Use parent's copyConfigFiles which handles: + // - configDirectory, serverXmlFile, jvmOptionsFile, bootstrapPropertiesFile, serverEnvFile overrides + // - inline configurations, mergeServerEnv logic, Maven property resolution + super.copyConfigFiles(); + + } finally { + // Restore original serverDirectory + serverDirectory = originalServerDir; + } + + // Generate liberty-plugin-config.xml with paths pointing to mock server + // Always include server info (server.xml, bootstrap.properties, etc.) + File configFile = exportParametersToXml(true); + getLog().info(MessageFormat.format("Liberty configuration file generated: {0}", + configFile.getAbsolutePath())); + getLog().info(MessageFormat.format("Mock Liberty server structure created: {0}", + mockServerDir.getAbsolutePath())); + + } catch (IOException | ParserConfigurationException | TransformerException e) { + throw new MojoExecutionException("Error preparing Liberty configuration.", e); + } + } + /** + * Override to generate liberty-plugin-config.xml pointing to mock server structure. + * All directories (installDirectory, userDirectory, serverDirectory, serverOutputDirectory) + * are set to mock locations in target/{tempDirName}/wlp/usr/servers/{serverName}. + * Server information is always included in the generated configuration. + */ + @Override + protected File exportParametersToXml(boolean includeServerInfo) + throws IOException, ParserConfigurationException, TransformerException { + // Check for system property override, otherwise use default + String tempDirName = System.getProperty("prepareConfigTempDir"); + if (tempDirName == null || tempDirName.trim().isEmpty()) { + tempDirName = PrepareConfigUtil.DEFAULT_TEMP_DIR_NAME; + } else { + tempDirName = tempDirName.trim(); + } + + // Build mock Liberty directory structure paths using common utility + File buildDirectory = new File(project.getBuild().getDirectory()); + File mockInstallDir = PrepareConfigUtil.getMockInstallDirectory(buildDirectory, tempDirName); + File mockUserDir = PrepareConfigUtil.getMockUserDirectory(buildDirectory, tempDirName); + File mockServersDir = PrepareConfigUtil.getMockServersDirectory(buildDirectory, tempDirName); + File mockServerDir = PrepareConfigUtil.getMockServerDirectory(buildDirectory, serverName, tempDirName); + + // Save original values to restore after XML generation + File originalInstallDir = installDirectory; + File originalUserDir = userDirectory; + File originalServerDir = serverDirectory; + File originalOutputDir = outputDirectory; + + try { + // Override all directories to point to mock structure + installDirectory = mockInstallDir; + userDirectory = mockUserDir; + serverDirectory = mockServerDir; + // Parent creates serverOutputDirectory as new File(outputDirectory, serverName) + // Set outputDirectory = mockServersDir to get mockServersDir/serverName = mockServerDir + outputDirectory = mockServersDir; + + // Call parent implementation with mock directories, always including server info + return super.exportParametersToXml(true); + + } finally { + // Restore original values + installDirectory = originalInstallDir; + userDirectory = originalUserDir; + serverDirectory = originalServerDir; + outputDirectory = originalOutputDir; + } + } +} \ No newline at end of file