You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by re...@apache.org on 2020/06/23 10:03:10 UTC

[tomcat] branch master updated: Various improvements to code generation

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

remm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/master by this push:
     new fd431c2  Various improvements to code generation
fd431c2 is described below

commit fd431c2f18cd1c04bc2902400fe3344fa58a656f
Author: remm <re...@apache.org>
AuthorDate: Tue Jun 23 12:02:48 2020 +0200

    Various improvements to code generation
    
    Add a configuration for the location of the generated sources.
    Catalina start and stop will now use the same code. As a result, stop
    will now use the configuration source and will also generate stop code.
    If the use generated flag is enabled, ContextConfig will not look at the
    real files at all, this is just simpler (and there could be 100s of
    webapps too).
    Pass arguments from Tomcat to Catalina, since in addition to educational
    uses, the feature would be useful with AOT compilation.
---
 java/org/apache/catalina/startup/Catalina.java     | 177 +++++++++++++--------
 .../catalina/startup/ConnectorCreateRule.java      |   1 +
 .../org/apache/catalina/startup/ContextConfig.java |  39 +++--
 .../catalina/startup/LocalStrings.properties       |   3 +-
 java/org/apache/catalina/startup/Tomcat.java       |  39 ++++-
 .../tomcat/util/digester/ObjectCreateRule.java     |   1 +
 .../tomcat/util/digester/SetPropertiesRule.java    |   3 -
 webapps/docs/changelog.xml                         |   4 +
 8 files changed, 182 insertions(+), 85 deletions(-)

diff --git a/java/org/apache/catalina/startup/Catalina.java b/java/org/apache/catalina/startup/Catalina.java
index 272d428..5280d32 100644
--- a/java/org/apache/catalina/startup/Catalina.java
+++ b/java/org/apache/catalina/startup/Catalina.java
@@ -18,7 +18,6 @@ package org.apache.catalina.startup;
 
 
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
@@ -148,6 +147,18 @@ public class Catalina {
 
 
     /**
+     * Location of generated sources.
+     */
+    protected File generatedCodeLocation = null;
+
+
+    /**
+     * Value of the argument.
+     */
+    protected String generatedCodeLocationParameter = null;
+
+
+    /**
      * Use generated code as a replacement for configuration files.
      */
     protected boolean useGeneratedCode = false;
@@ -203,6 +214,16 @@ public class Catalina {
     }
 
 
+    public File getGeneratedCodeLocation() {
+        return this.generatedCodeLocation;
+    }
+
+
+    public void setGeneratedCodeLocation(File generatedCodeLocation) {
+        this.generatedCodeLocation = generatedCodeLocation;
+    }
+
+
     /**
      * @return <code>true</code> if an exception should be thrown if an error
      * occurs during server init
@@ -284,6 +305,7 @@ public class Catalina {
     protected boolean arguments(String args[]) {
 
         boolean isConfig = false;
+        boolean isGenerateCode = false;
 
         if (args.length < 1) {
             usage();
@@ -297,20 +319,29 @@ public class Catalina {
             } else if (arg.equals("-config")) {
                 isConfig = true;
             } else if (arg.equals("-generateCode")) {
-                generateCode = true;
+                setGenerateCode(true);
+                isGenerateCode = true;
             } else if (arg.equals("-useGeneratedCode")) {
-                useGeneratedCode = true;
+                setUseGeneratedCode(true);
+                isGenerateCode = false;
             } else if (arg.equals("-nonaming")) {
                 setUseNaming(false);
+                isGenerateCode = false;
             } else if (arg.equals("-help")) {
                 usage();
                 return false;
             } else if (arg.equals("start")) {
+                isGenerateCode = false;
                 // NOOP
             } else if (arg.equals("configtest")) {
+                isGenerateCode = false;
                 // NOOP
             } else if (arg.equals("stop")) {
+                isGenerateCode = false;
                 // NOOP
+            } else if (isGenerateCode) {
+                generatedCodeLocationParameter = arg;
+                isGenerateCode = false;
             } else {
                 usage();
                 return false;
@@ -527,6 +558,72 @@ public class Catalina {
     }
 
 
+    protected void parseServerXml(boolean start) {
+        // Set configuration source
+        ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile()));
+        File file = configFile();
+
+        // Init source location
+        File serverXmlLocation = null;
+        if (generateCode) {
+            if (generatedCodeLocationParameter != null) {
+                generatedCodeLocation = new File(generatedCodeLocationParameter);
+                if (!generatedCodeLocation.isAbsolute()) {
+                    generatedCodeLocation = new File(Bootstrap.getCatalinaHomeFile(), generatedCodeLocationParameter);
+                }
+            } else {
+                generatedCodeLocation = new File(Bootstrap.getCatalinaHomeFile(), "work");
+            }
+            serverXmlLocation = new File(generatedCodeLocation, "catalina");
+            if (!serverXmlLocation.isDirectory() && !serverXmlLocation.mkdirs()) {
+                log.warn(sm.getString("catalina.generatedCodeLocationError", generatedCodeLocation.getAbsolutePath()));
+                // Disable code generation
+                generateCode = false;
+            }
+        }
+
+        ServerXml serverXml = null;
+        if (useGeneratedCode) {
+            String xmlClassName = start ? "catalina.ServerXml" : "catalina.ServerXmlStop";
+            try {
+                serverXml = (ServerXml) Catalina.class.getClassLoader().loadClass(xmlClassName).newInstance();
+            } catch (Exception e) {
+                // Ignore, no generated code found
+            }
+        }
+
+        if (serverXml != null) {
+            serverXml.load(this);
+        } else {
+            try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {
+                // Create and execute our Digester
+                Digester digester = start ? createStartDigester() : createStopDigester();
+                InputStream inputStream = resource.getInputStream();
+                InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
+                inputSource.setByteStream(inputStream);
+                digester.push(this);
+                if (generateCode) {
+                    digester.startGeneratingCode();
+                    generateClassHeader(digester, start);
+                }
+                digester.parse(inputSource);
+                if (generateCode) {
+                    generateClassFooter(digester);
+                    try (FileWriter writer = new FileWriter(new File(serverXmlLocation,
+                            start ? "ServerXml.java" : "ServerXmlStop.java"))) {
+                        writer.write(digester.getGeneratedCode().toString());
+                    }
+                    digester.endGeneratingCode();
+                }
+            } catch (Exception e) {
+                log.warn(sm.getString("catalina.configFail", file.getAbsolutePath()), e);
+                if (file.exists() && !file.canRead()) {
+                    log.warn(sm.getString("catalina.incorrectPermissions"));
+                }
+            }
+        }
+    }
+
     public void stopServer() {
         stopServer(null);
     }
@@ -539,17 +636,9 @@ public class Catalina {
 
         Server s = getServer();
         if (s == null) {
-            // Create and execute our Digester
-            Digester digester = createStopDigester();
-            File file = configFile();
-            try (FileInputStream fis = new FileInputStream(file)) {
-                InputSource is =
-                    new InputSource(file.toURI().toURL().toString());
-                is.setByteStream(fis);
-                digester.push(this);
-                digester.parse(is);
-            } catch (Exception e) {
-                log.error(sm.getString("catalina.stopError"), e);
+            parseServerXml(false);
+            if (getServer() == null) {
+                log.error(sm.getString("catalina.stopError"));
                 System.exit(1);
             }
         } else {
@@ -605,53 +694,11 @@ public class Catalina {
         // Before digester - it may be needed
         initNaming();
 
-        // Set configuration source
-        ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile()));
-        File file = configFile();
-
-        ServerXml serverXml = null;
-        if (useGeneratedCode) {
-            String xmlClassName = "catalina.ServerXml";
-            try {
-                serverXml = (ServerXml) Catalina.class.getClassLoader().loadClass(xmlClassName).newInstance();
-            } catch (Exception e) {
-                // Ignore, no generated code found
-            }
-        }
-
-        if (serverXml != null) {
-            serverXml.load(this);
-        } else {
-            try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {
-                // Create and execute our Digester
-                Digester digester = createStartDigester();
-                InputStream inputStream = resource.getInputStream();
-                InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
-                inputSource.setByteStream(inputStream);
-                digester.push(this);
-                if (generateCode) {
-                    digester.startGeneratingCode();
-                    generateClassHeader(digester);
-                }
-                digester.parse(inputSource);
-                if (generateCode) {
-                    generateClassFooter(digester);
-                    File generatedSourceFolder = new File(new File(Bootstrap.getCatalinaHomeFile(), "work"), "catalina");
-                    if (generatedSourceFolder.isDirectory() || generatedSourceFolder.mkdirs()) {
-                        File generatedSourceFile = new File(generatedSourceFolder, "ServerXml.java");
-                        try (FileWriter writer = new FileWriter(generatedSourceFile)) {
-                            writer.write(digester.getGeneratedCode().toString());
-                        }
-                    }
-                    digester.endGeneratingCode();
-                }
-            } catch (Exception e) {
-                log.warn(sm.getString("catalina.configFail", file.getAbsolutePath()), e);
-                if (file.exists() && !file.canRead()) {
-                    log.warn(sm.getString("catalina.incorrectPermissions"));
-                }
-                return;
-            }
+        // Parse main server.xml
+        parseServerXml(true);
+        Server s = getServer();
+        if (s == null) {
+            return;
         }
 
         getServer().setCatalina(this);
@@ -860,10 +907,14 @@ public class Catalina {
     }
 
 
-    protected void generateClassHeader(Digester digester) {
+    protected void generateClassHeader(Digester digester, boolean start) {
         StringBuilder code = digester.getGeneratedCode();
         code.append("package catalina;").append(System.lineSeparator());
-        code.append("public class ServerXml implements ");
+        code.append("public class ServerXml");
+        if (!start) {
+            code.append("Stop");
+        }
+        code.append(" implements ");
         code.append(ServerXml.class.getName().replace('$', '.')).append(" {").append(System.lineSeparator());
         code.append("public void load(").append(Catalina.class.getName());
         code.append(" ").append(digester.toVariableName(this)).append(") {").append(System.lineSeparator());
diff --git a/java/org/apache/catalina/startup/ConnectorCreateRule.java b/java/org/apache/catalina/startup/ConnectorCreateRule.java
index a1b53d9..875fea7 100644
--- a/java/org/apache/catalina/startup/ConnectorCreateRule.java
+++ b/java/org/apache/catalina/startup/ConnectorCreateRule.java
@@ -76,6 +76,7 @@ public class ConnectorCreateRule extends Rule {
 
         StringBuilder code = digester.getGeneratedCode();
         if (code != null) {
+            code.append(System.lineSeparator());
             code.append(Connector.class.getName()).append(" ").append(digester.toVariableName(con));
             code.append(" = new ").append(Connector.class.getName());
             code.append("(new ").append(con.getProtocolHandlerClassName()).append("());");
diff --git a/java/org/apache/catalina/startup/ContextConfig.java b/java/org/apache/catalina/startup/ContextConfig.java
index ffae86e..14b815f 100644
--- a/java/org/apache/catalina/startup/ContextConfig.java
+++ b/java/org/apache/catalina/startup/ContextConfig.java
@@ -463,6 +463,17 @@ public class ContextConfig implements LifecycleListener {
     }
 
 
+    protected File getGeneratedCodeLocation() {
+        Catalina catalina = Container.getService(context).getServer().getCatalina();
+        if (catalina != null) {
+            return catalina.getGeneratedCodeLocation();
+        } else {
+            // Cannot happen
+            return null;
+        }
+    }
+
+
     protected static String getContextXmlPackageName(Container container) {
         StringBuffer result = new StringBuffer();
         Container host = null;
@@ -492,14 +503,12 @@ public class ContextConfig implements LifecycleListener {
     }
 
 
-    protected static File getContextXmlJavaSource(String contextXmlPackageName, String contextXmlSimpleClassName) {
-        File generatedSourceFolder = new File(Bootstrap.getCatalinaHomeFile(), "work");
-        if (generatedSourceFolder.isDirectory() || generatedSourceFolder.mkdirs()) {
-            String path = contextXmlPackageName.replace('.', File.separatorChar);
-            File packageFolder = new File(generatedSourceFolder, path);
-            if (packageFolder.isDirectory() || packageFolder.mkdirs()) {
-                return new File(packageFolder, contextXmlSimpleClassName + ".java");
-            }
+    protected File getContextXmlJavaSource(String contextXmlPackageName, String contextXmlSimpleClassName) {
+        File generatedSourceFolder = getGeneratedCodeLocation();
+        String path = contextXmlPackageName.replace('.', File.separatorChar);
+        File packageFolder = new File(generatedSourceFolder, path);
+        if (packageFolder.isDirectory() || packageFolder.mkdirs()) {
+            return new File(packageFolder, contextXmlSimpleClassName + ".java");
         }
         return null;
     }
@@ -579,7 +588,7 @@ public class ContextConfig implements LifecycleListener {
             if (contextXml != null) {
                 contextXml.load(context);
                 contextXml = null;
-            } else {
+            } else if (!useGeneratedCode) {
                 try (ConfigurationSource.Resource contextXmlResource =
                         ConfigFileLoader.getSource().getResource(defaultContextXml)) {
                     if (generateCode) {
@@ -618,7 +627,7 @@ public class ContextConfig implements LifecycleListener {
             if (contextXml != null) {
                 contextXml.load(context);
                 contextXml = null;
-            } else {
+            } else if (!useGeneratedCode) {
                 String hostContextFile = Container.getConfigPath(context, Constants.HostContextXml);
                 try (ConfigurationSource.Resource contextXmlResource =
                         ConfigFileLoader.getSource().getResource(hostContextFile)) {
@@ -660,7 +669,7 @@ public class ContextConfig implements LifecycleListener {
             if (contextXml != null) {
                 contextXml.load(context);
                 contextXml = null;
-            } else {
+            } else if (!useGeneratedCode) {
                 if (generateCode) {
                     contextXmlJavaSource = getContextXmlJavaSource(contextXmlPackageName, contextXmlSimpleClassName);
                     digester.startGeneratingCode();
@@ -922,9 +931,11 @@ public class ContextConfig implements LifecycleListener {
     protected synchronized void init() {
         // Called from StandardContext.init()
 
-        // FIXME: Try to avoid creation of the parser if not needed due to code generation
-        Digester contextDigester = createContextDigester();
-        contextDigester.getParser();
+        Digester contextDigester = null;
+        if (!getUseGeneratedCode()) {
+            contextDigester = createContextDigester();
+            contextDigester.getParser();
+        }
 
         if (log.isDebugEnabled()) {
             log.debug(sm.getString("contextConfig.init"));
diff --git a/java/org/apache/catalina/startup/LocalStrings.properties b/java/org/apache/catalina/startup/LocalStrings.properties
index 1c897e7..2d242a0 100644
--- a/java/org/apache/catalina/startup/LocalStrings.properties
+++ b/java/org/apache/catalina/startup/LocalStrings.properties
@@ -14,6 +14,7 @@
 # limitations under the License.
 
 catalina.configFail=Unable to load server configuration from [{0}]
+catalina.generatedCodeLocationError=Error using configured location for generated Tomcat embedded code [{0}]
 catalina.incorrectPermissions=Permissions incorrect, read permission is not allowed on the file
 catalina.init=Server initialization in [{0}] milliseconds
 catalina.initError=Error initializing Catalina
@@ -26,7 +27,7 @@ catalina.startup=Server startup in [{0}] milliseconds
 catalina.stopError=Error stopping Catalina
 catalina.stopServer=No shutdown port configured. Shut down server through OS signal. Server not shut down.
 catalina.stopServer.connectException=Could not contact [{0}:{1}] (base port [{2}] and offset [{3}]). Tomcat may not be running.
-catalina.usage=usage: java org.apache.catalina.startup.Catalina [ -config {pathname} ] [ -nonaming ] { -help | start | stop }
+catalina.usage=usage: java org.apache.catalina.startup.Catalina [ -config {pathname} ] [ -nonaming ] [ -generateCode [ {pathname} ] ] [ -useGeneratedCode ] { -help | start | stop }
 
 catalinaConfigurationSource.cannotObtainURL=Cannot obtain URL for the relative path [{0}]. Check that catalina.base is set.
 
diff --git a/java/org/apache/catalina/startup/Tomcat.java b/java/org/apache/catalina/startup/Tomcat.java
index be2d526..1ec95c5 100644
--- a/java/org/apache/catalina/startup/Tomcat.java
+++ b/java/org/apache/catalina/startup/Tomcat.java
@@ -436,12 +436,30 @@ public class Tomcat {
      * @param source The configuration source
      */
     public void init(ConfigurationSource source) {
+        init(source, null);
+    }
+
+    /**
+     * Initialize the server given the specified configuration source.
+     * The server will be loaded according to the Tomcat configuration
+     * files contained in the source (server.xml, web.xml, context.xml,
+     * SSL certificates, etc).
+     * If no configuration source is specified, it will use the default
+     * locations for these files.
+     * @param source The configuration source
+     * @param catalinaArguments The arguments that should be passed to Catalina
+     */
+    public void init(ConfigurationSource source, String[] catalinaArguments) {
         ConfigFileLoader.setSource(source);
         addDefaultWebXmlToWebapp = false;
         Catalina catalina = new Catalina();
         // Load the Catalina instance with the regular configuration files
         // from specified source
-        catalina.load();
+        if (catalinaArguments == null) {
+            catalina.load();
+        } else {
+            catalina.load(catalinaArguments);
+        }
         // Retrieve and set the server
         server = catalina.getServer();
     }
@@ -1289,9 +1307,19 @@ public class Tomcat {
      */
     public static void main(String[] args) throws Exception {
         // Process some command line parameters
-        for (String arg : args) {
-            if (arg.equals("--no-jmx")) {
+        String[] catalinaArguments = null;
+        for (int i = 0; i < args.length; i++) {
+            if (args[i].equals("--no-jmx")) {
                 Registry.disableRegistry();
+            } else if (args[i].equals("--catalina")) {
+                // This was already processed before
+                // Skip the rest of the arguments as they are for Catalina
+                ArrayList<String> result = new ArrayList<>();
+                for (int j = i + 1; j < args.length; j++) {
+                    result.add(args[j]);
+                }
+                catalinaArguments = result.toArray(new String[0]);
+                break;
             }
         }
         SecurityClassLoad.securityClassLoad(Thread.currentThread().getContextClassLoader());
@@ -1299,7 +1327,7 @@ public class Tomcat {
         // Create a Catalina instance and let it parse the configuration files
         // It will also set a shutdown hook to stop the Server when needed
         // Use the default configuration source
-        tomcat.init(null);
+        tomcat.init(null, catalinaArguments);
         boolean await = false;
         String path = "";
         // Process command line parameters
@@ -1319,6 +1347,9 @@ public class Tomcat {
                 await = true;
             } else if (args[i].equals("--no-jmx")) {
                 // This was already processed before
+            } else if (args[i].equals("--catalina")) {
+                // This was already processed before
+                // Skip the rest of the arguments as they are for Catalina
             } else {
                 throw new IllegalArgumentException(sm.getString("tomcat.invalidCommandLine", args[i]));
             }
diff --git a/java/org/apache/tomcat/util/digester/ObjectCreateRule.java b/java/org/apache/tomcat/util/digester/ObjectCreateRule.java
index 18e1e0b..ffcf4ba 100644
--- a/java/org/apache/tomcat/util/digester/ObjectCreateRule.java
+++ b/java/org/apache/tomcat/util/digester/ObjectCreateRule.java
@@ -107,6 +107,7 @@ public class ObjectCreateRule extends Rule {
 
         StringBuilder code = digester.getGeneratedCode();
         if (code != null) {
+            code.append(System.lineSeparator());
             code.append(realClassName).append(" ").append(digester.toVariableName(instance)).append(" = new ");
             code.append(realClassName).append("();").append(System.lineSeparator());
         }
diff --git a/java/org/apache/tomcat/util/digester/SetPropertiesRule.java b/java/org/apache/tomcat/util/digester/SetPropertiesRule.java
index 23a1e1a..284bc2f 100644
--- a/java/org/apache/tomcat/util/digester/SetPropertiesRule.java
+++ b/java/org/apache/tomcat/util/digester/SetPropertiesRule.java
@@ -123,9 +123,6 @@ public class SetPropertiesRule extends Rule {
             }
         }
 
-        if (code != null) {
-            code.append(System.lineSeparator());
-        }
     }
 
 
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index c2ab833..05dcd7d 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -74,6 +74,10 @@
         response is returned for an HTTP/2 request if the mapped servlet does
         implement the requested method rather than the more general
         <code>400</code> response. (markt)
+      <add>
+        Add generated classes using Tomcat embedded as an optional replacement
+        for the Catalina configuration files. (remm)
+      </add>
       </fix>
     </changelog>
   </subsection>


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: [tomcat] branch master updated: Various improvements to code generation

Posted by Rémy Maucherat <re...@apache.org>.
On Tue, Jun 23, 2020 at 12:03 PM <re...@apache.org> wrote:

> This is an automated email from the ASF dual-hosted git repository.
>
> remm pushed a commit to branch master
> in repository https://gitbox.apache.org/repos/asf/tomcat.git
>
>
> The following commit(s) were added to refs/heads/master by this push:
>      new fd431c2  Various improvements to code generation
> fd431c2 is described below
>
> commit fd431c2f18cd1c04bc2902400fe3344fa58a656f
> Author: remm <re...@apache.org>
> AuthorDate: Tue Jun 23 12:02:48 2020 +0200
>
>     Various improvements to code generation
>
>     Add a configuration for the location of the generated sources.
>     Catalina start and stop will now use the same code. As a result, stop
>     will now use the configuration source and will also generate stop code.
>     If the use generated flag is enabled, ContextConfig will not look at
> the
>     real files at all, this is just simpler (and there could be 100s of
>     webapps too).
>     Pass arguments from Tomcat to Catalina, since in addition to
> educational
>     uses, the feature would be useful with AOT compilation.
>

I plan to port this to Tomcat 9 and then document it as a possible step of
AOT compilation ( http://tomcat.apache.org/tomcat-9.0-doc/graal.html ).
I will then move the "documentation", which is in res/tomcat-maven at the
moment, to a new item in the modules focused on demonstrating packaging and
AOT compilation. Still thinking about the module name, considering
"stuffed" for that, as it is a Tomcat that won't move, so a stuffed animal.
Feel free to help me come up with a better name :)

Rémy