This is an automated email from the ASF dual-hosted git repository.

robertlazarski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git

commit 37322ed08f8b6874b77d7c993a24c3ea45fb1eb4
Author: Robert Lazarski <[email protected]>
AuthorDate: Wed May 13 11:16:59 2026 -1000

    Add embedded Tomcat support to axis2-spring-boot-starter
    
    The starter previously required WAR deployment because
    WarBasedAxisConfigurator depends on ServletContext.getRealPath()
    which returns null in embedded Tomcat.
    
    New axis2.repo property (application.properties or -D flag):
    - When set, overrides getRealPath() for both repository path and
      axis2.xml location
    - When empty (default), falls back to getRealPath() — no change
      to existing WAR deployments
    
    Changes:
    - Axis2Properties: new axis2.repo property with Javadoc
    - Axis2RepositoryAutoConfiguration: checks axis2.repo before
      getRealPath() for axis2.xml staging
    - Axis2ServletAutoConfiguration: checks axis2.repo before
      getRealPath() for repository path; also sets axis2.xml.path
      init-parameter (the root cause — embedded Tomcat's
      getResourceAsStream("/WEB-INF/conf/axis2.xml") returns null,
      causing WarBasedAxisConfigurator to load the minimal classpath
      default without JSON message builders)
    - Tests: 2 new tests for repo property (11 total, all pass)
    - spring-boot-starter.xml: updated header and deployment model
      to reflect embedded support
    
    Backward compatible: existing WAR deployments with axis2.repo
    unset continue to use getRealPath() as before.
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
 .../apache/axis2/spring/boot/Axis2Properties.java  | 24 +++++++++++++
 .../boot/Axis2RepositoryAutoConfiguration.java     | 11 ++++--
 .../spring/boot/Axis2ServletAutoConfiguration.java | 41 ++++++++++++++++++++--
 .../spring/boot/Axis2AutoConfigurationTest.java    | 16 +++++++++
 src/site/xdoc/docs/spring-boot-starter.xml         | 29 ++++++++-------
 5 files changed, 103 insertions(+), 18 deletions(-)

diff --git 
a/modules/spring-boot-starter/src/main/java/org/apache/axis2/spring/boot/Axis2Properties.java
 
b/modules/spring-boot-starter/src/main/java/org/apache/axis2/spring/boot/Axis2Properties.java
index b7826c18ed..ff6765bb9c 100644
--- 
a/modules/spring-boot-starter/src/main/java/org/apache/axis2/spring/boot/Axis2Properties.java
+++ 
b/modules/spring-boot-starter/src/main/java/org/apache/axis2/spring/boot/Axis2Properties.java
@@ -82,6 +82,22 @@ public class Axis2Properties {
      */
     private String configurationFile = "";
 
+    /**
+     * Explicit path to the Axis2 repository (WEB-INF directory containing
+     * conf/axis2.xml, services/*.aar, and modules/*.mar).
+     *
+     * <p>When set, this overrides {@code 
ServletContext.getRealPath("/WEB-INF")}
+     * and enables embedded Tomcat support — embedded mode creates a temp
+     * docbase that lacks the Axis2 directory structure.
+     *
+     * <p>For embedded mode, point this to the exploded WAR from the build:
+     * <pre>axis2.repo=target/deploy/axis2-json-api/WEB-INF</pre>
+     *
+     * <p>For external container (WAR) deployment, leave this empty —
+     * the path is resolved automatically from the servlet context.
+     */
+    private String repo = "";
+
     /**
      * OpenAPI and MCP sub-configuration.
      */
@@ -119,6 +135,14 @@ public class Axis2Properties {
         this.configurationFile = configurationFile;
     }
 
+    public String getRepo() {
+        return repo;
+    }
+
+    public void setRepo(String repo) {
+        this.repo = repo;
+    }
+
     public OpenApi getOpenapi() {
         return openapi;
     }
diff --git 
a/modules/spring-boot-starter/src/main/java/org/apache/axis2/spring/boot/Axis2RepositoryAutoConfiguration.java
 
b/modules/spring-boot-starter/src/main/java/org/apache/axis2/spring/boot/Axis2RepositoryAutoConfiguration.java
index 1327df81a5..0dff77ca0b 100644
--- 
a/modules/spring-boot-starter/src/main/java/org/apache/axis2/spring/boot/Axis2RepositoryAutoConfiguration.java
+++ 
b/modules/spring-boot-starter/src/main/java/org/apache/axis2/spring/boot/Axis2RepositoryAutoConfiguration.java
@@ -80,10 +80,17 @@ public class Axis2RepositoryAutoConfiguration {
     }
 
     private void stageAxis2Config(ServletContext servletContext, 
Axis2Properties properties) {
-        String webInfPath = servletContext.getRealPath("/WEB-INF");
+        // Priority: axis2.repo property > 
ServletContext.getRealPath("/WEB-INF")
+        // Embedded Tomcat returns null for getRealPath() because the temp
+        // docbase has no WEB-INF directory. The axis2.repo property points
+        // to the exploded WAR from the build.
+        String webInfPath = (properties.getRepo() != null && 
!properties.getRepo().isEmpty())
+                ? properties.getRepo()
+                : servletContext.getRealPath("/WEB-INF");
         if (webInfPath == null) {
             log.warn("Cannot resolve WEB-INF path — axis2.xml staging skipped. 
"
-                    + "Ensure axis2.xml is pre-staged in WEB-INF/conf/");
+                    + "For embedded mode, set axis2.repo in 
application.properties "
+                    + "or via -Daxis2.repo=target/deploy/.../WEB-INF");
             return;
         }
 
diff --git 
a/modules/spring-boot-starter/src/main/java/org/apache/axis2/spring/boot/Axis2ServletAutoConfiguration.java
 
b/modules/spring-boot-starter/src/main/java/org/apache/axis2/spring/boot/Axis2ServletAutoConfiguration.java
index 926e762391..c1243dfb44 100644
--- 
a/modules/spring-boot-starter/src/main/java/org/apache/axis2/spring/boot/Axis2ServletAutoConfiguration.java
+++ 
b/modules/spring-boot-starter/src/main/java/org/apache/axis2/spring/boot/Axis2ServletAutoConfiguration.java
@@ -66,15 +66,50 @@ public class Axis2ServletAutoConfiguration {
         axisServlet.setLoadOnStartup(1);
 
         // Set repository path — the critical init-parameter for 
WarBasedAxisConfigurator.
+        // Priority: axis2.repo property > 
ServletContext.getRealPath("/WEB-INF")
         // getRealPath() is called eagerly here to avoid WildFly VFS lazy-init 
issues.
-        String webInfPath = servletContext.getRealPath("/WEB-INF");
+        // Embedded Tomcat returns null for getRealPath() — axis2.repo is 
required.
+        String webInfPath = (properties.getRepo() != null && 
!properties.getRepo().isEmpty())
+                ? properties.getRepo()
+                : servletContext.getRealPath("/WEB-INF");
         if (webInfPath != null) {
             axisServlet.setInitParameter(
                     WarBasedAxisConfigurator.PARAM_AXIS2_REPOSITORY_PATH, 
webInfPath);
             log.info("axis2.repository.path = " + webInfPath);
+
+            // Also set axis2.xml.path so WarBasedAxisConfigurator loads the
+            // correct axis2.xml (with JSON builders, enableJSONOnly, etc.)
+            // instead of falling back to the minimal classpath default.
+            // Critical for embedded Tomcat where 
servletContext.getResourceAsStream
+            // ("/WEB-INF/conf/axis2.xml") returns null.
+            java.io.File axis2xml = new java.io.File(webInfPath, 
"conf/axis2.xml");
+            if (!axis2xml.isFile() && properties.getRepo() != null && 
!properties.getRepo().isEmpty()) {
+                // axis2.repo was explicitly set but axis2.xml is missing — 
fail fast
+                // instead of letting WarBasedAxisConfigurator silently load 
the minimal
+                // classpath default (which lacks JSON message builders).
+                throw new IllegalStateException(
+                        "axis2.repo is set to " + webInfPath
+                        + " but conf/axis2.xml does not exist at " + axis2xml
+                        + ". Run 'mvn clean install' first to build the 
exploded WAR.");
+            }
+            if (axis2xml.isFile()) {
+                try {
+                    axisServlet.setInitParameter(
+                            WarBasedAxisConfigurator.PARAM_AXIS2_XML_PATH,
+                            axis2xml.getCanonicalPath());
+                    log.info("axis2.xml.path = " + 
axis2xml.getCanonicalPath());
+                } catch (java.io.IOException e) {
+                    log.warn("Could not get canonical path for axis2.xml: "
+                            + e.getMessage() + ". Falling back to absolute 
path.");
+                    axisServlet.setInitParameter(
+                            WarBasedAxisConfigurator.PARAM_AXIS2_XML_PATH,
+                            axis2xml.getAbsolutePath());
+                }
+            }
         } else {
-            log.warn("ServletContext.getRealPath(\"/WEB-INF\") returned null — 
"
-                    + "AxisServlet will attempt to resolve the repository path 
itself");
+            log.warn("Cannot resolve Axis2 repository path — "
+                    + "for embedded mode, set axis2.repo in 
application.properties "
+                    + "or via -Daxis2.repo=target/deploy/.../WEB-INF");
         }
 
         // Map to configured path
diff --git 
a/modules/spring-boot-starter/src/test/java/org/apache/axis2/spring/boot/Axis2AutoConfigurationTest.java
 
b/modules/spring-boot-starter/src/test/java/org/apache/axis2/spring/boot/Axis2AutoConfigurationTest.java
index e7e0cee3e0..d46b666a25 100644
--- 
a/modules/spring-boot-starter/src/test/java/org/apache/axis2/spring/boot/Axis2AutoConfigurationTest.java
+++ 
b/modules/spring-boot-starter/src/test/java/org/apache/axis2/spring/boot/Axis2AutoConfigurationTest.java
@@ -73,9 +73,25 @@ class Axis2AutoConfigurationTest {
         assertEquals("json", props.getMode(), "axis2.mode defaults to json");
         assertEquals("/services", props.getServicesPath(), 
"axis2.services-path defaults to /services");
         assertEquals("", props.getConfigurationFile(), 
"axis2.configuration-file defaults to empty");
+        assertEquals("", props.getRepo(), "axis2.repo defaults to empty");
         assertTrue(props.getOpenapi().isEnabled(), "axis2.openapi.enabled 
defaults to true");
     }
 
+    @Test
+    void repoPropertyOverridesDefault() {
+        Axis2Properties props = new Axis2Properties();
+        props.setRepo("target/deploy/myapp/WEB-INF");
+        assertEquals("target/deploy/myapp/WEB-INF", props.getRepo());
+    }
+
+    @Test
+    void emptyRepoPropertyDoesNotOverride() {
+        Axis2Properties props = new Axis2Properties();
+        // Empty string means "use ServletContext.getRealPath() as before"
+        assertTrue(props.getRepo().isEmpty(),
+                "Empty repo should not trigger the embedded mode path");
+    }
+
     @Test
     void soapModeSelectsSoapTemplate() {
         Axis2Properties props = new Axis2Properties();
diff --git a/src/site/xdoc/docs/spring-boot-starter.xml 
b/src/site/xdoc/docs/spring-boot-starter.xml
index d54bc2c60f..d6666d4c9e 100644
--- a/src/site/xdoc/docs/spring-boot-starter.xml
+++ b/src/site/xdoc/docs/spring-boot-starter.xml
@@ -34,14 +34,14 @@ from a multi-day configuration project to a single Maven 
dependency. It handles
 servlet registration, repository configuration, and OpenAPI/MCP endpoint 
activation
 with sensible defaults — for both SOAP and JSON-RPC services.</p>
 
-<p><strong>Important — WAR deployment only.</strong> This starter requires 
deployment
-as a WAR file to an external servlet container (Tomcat 11, WildFly 32/39, or 
similar).
-Spring Boot's embedded Tomcat mode (<code>java -jar</code>) is <strong>not
-supported</strong> in this release. See <a href="#deployment_model">Deployment 
Model</a>
-for details and rationale.</p>
+<p>This starter supports both <strong>WAR deployment</strong> to an external 
container
+(Tomcat 11, WildFly 32/39) and <strong>embedded Tomcat</strong> for local 
development.
+For embedded mode, set <code>axis2.repo</code> in 
<code>application.properties</code>
+to the exploded WAR directory from your build.
+See <a href="#deployment_model">Deployment Model</a> for details.</p>
 
 <ul>
-<li><a href="#deployment_model">0. Deployment Model (WAR Only)</a></li>
+<li><a href="#deployment_model">0. Deployment Model (WAR + Embedded)</a></li>
 <li><a href="#quickstart">1. Quick Start</a></li>
 <li><a href="#how_axisservlet_works">2. How AxisServlet Works (SOAP vs 
JSON)</a></li>
 <li><a href="#properties">3. Configuration Properties</a></li>
@@ -162,12 +162,13 @@ above 17.</p>
 <p>Other Jakarta EE 9+ containers (WebSphere Liberty, Payara) should work but
 are not tested.</p>
 
-<h3>Will embedded mode be supported in the future?</h3>
+<h3>Embedded mode limitations</h3>
 
-<p>Embedded Tomcat support is planned for a future release. It requires a
-<code>ClasspathBasedAxisConfigurator</code> that discovers services and modules
-from the classpath rather than from <code>WEB-INF/</code>. Contributions
-are welcome — see the
+<p>The current embedded Tomcat support requires a pre-built exploded WAR
+on the filesystem (via <code>axis2.repo</code>). A future improvement
+would be a <code>ClasspathBasedAxisConfigurator</code> that discovers
+services and modules directly from the classpath, eliminating the need
+for a filesystem layout entirely. Contributions are welcome — see the
 <a 
href="https://github.com/apache/axis-axis2-java-core/blob/master/AXIS2_MODERNIZATION_PLAN.md";>Axis2
 Modernization Plan</a>
 for the full roadmap.</p>
 
@@ -469,7 +470,8 @@ identical in structure to the configuration used by the 
legacy userguide
 <h3>Container matrix</h3>
 
 <p>See <a href="#deployment_model">Section 0</a> for the full container
-compatibility matrix and the rationale for WAR-only deployment.</p>
+compatibility matrix covering WAR deployment (Tomcat 11, WildFly 32/39)
+and embedded Tomcat for local development.</p>
 
 <!-- ============================================================ -->
 <a name="http2"/>
@@ -499,7 +501,8 @@ JSON message formatter pipeline:</p>
       with zero overhead when not used. See
       <a href="json-streaming-formatter.html#Field_Selection">Field 
Selection</a>.</li>
   <li><strong>C parity</strong> — the same streaming architecture exists
-      in Axis2/C via Apache httpd <code>mod_h2</code>. Both
+      in <a href="https://axis.apache.org/axis2/c/core/";>Axis2/C</a> via
+      Apache httpd <code>mod_h2</code>. Both
       implementations use the same 64 KB flush interval, producing
       identical HTTP/2 DATA frame patterns.</li>
 </ul>

Reply via email to