You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by mi...@apache.org on 2023/06/13 09:14:05 UTC

[tomcat] branch 8.5.x updated: Add ContextNamingInfoListener

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

michaelo pushed a commit to branch 8.5.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/8.5.x by this push:
     new ed1277b29b Add ContextNamingInfoListener
ed1277b29b is described below

commit ed1277b29b6b017059bb101030e4cddb6ccd0d8c
Author: Michael Osipov <mi...@apache.org>
AuthorDate: Thu Jun 8 12:09:15 2023 +0200

    Add ContextNamingInfoListener
    
    A listener which creates context naming information environment entries.
---
 .../catalina/core/ContextNamingInfoListener.java   | 121 +++++++++++++++++++++
 .../apache/catalina/core/LocalStrings.properties   |   3 +
 .../core/TestContextNamingInfoListener.java        | 119 ++++++++++++++++++++
 webapps/docs/changelog.xml                         |   5 +
 webapps/docs/config/listeners.xml                  |  27 +++++
 5 files changed, 275 insertions(+)

diff --git a/java/org/apache/catalina/core/ContextNamingInfoListener.java b/java/org/apache/catalina/core/ContextNamingInfoListener.java
new file mode 100644
index 0000000000..b77915c7d7
--- /dev/null
+++ b/java/org/apache/catalina/core/ContextNamingInfoListener.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.catalina.core;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleListener;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.descriptor.web.ContextEnvironment;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ *  Implementation of {@code LifecycleListener} that will create context naming information
+ *  environment entries.
+ * <p>
+ * This listener must only be nested within {@link Context} elements.
+ * <p>
+ * The following entries will be added to the initial context ({@code java:comp/env} implied):
+ * <ul>
+ * <li>Path: {@code context/path} from {@link Context#getPath()}</li>
+ * <li>Encoded Path: {@code context/encodedPath} from {@link Context#getEncodedPath()}</li>
+ * <li>Webapp Version: {@code context/webappVersion} from {@link Context#getWebappVersion()}</li>
+ * <li>Name: {@code context/name} from {@link Context#getName()}</li>
+ * <li>Base Name: {@code context/baseName} from {@link Context#getBaseName()}</li>
+ * <li>Display Name: {@code context/displayName} from {@link Context#getDisplayName()}</li>
+ * </ul>
+ * <p>
+ * See the <a href="https://tomcat.apache.org/tomcat-8.5-doc/config/context.html#Naming">Tomcat
+ * documentation</a> for more details on the values.
+ */
+public class ContextNamingInfoListener implements LifecycleListener {
+
+    private static final String PATH_ENTRY_NAME = "context/path";
+    private static final String ENCODED_PATH_ENTRY_NAME = "context/encodedPath";
+    private static final String WEBAPP_VERSION_ENTRY_NAME = "context/webappVersion";
+    private static final String NAME_ENTRY_NAME = "context/name";
+    private static final String BASE_NAME_ENTRY_NAME = "context/baseName";
+    private static final String DISPLAY_NAME_ENTRY_NAME = "context/displayName";
+
+    private static final Log log = LogFactory.getLog(ContextNamingInfoListener.class);
+    /**
+     * The string manager for this package.
+     */
+    private static final StringManager sm = StringManager.getManager(ContextNamingInfoListener.class);
+
+    private boolean emptyOnRoot = true;
+
+    /**
+     * Sets whether for the root context {@code context/path} and {@code context/encodedPath} will
+     * contain {@code "/"} and {@code context/name} will contain {@code "ROOT"} with a version, if any.
+     *
+     * @param emptyOnRoot whether paths and name for root context shall be empty
+     */
+    public void setEmptyOnRoot(boolean emptyOnRoot) {
+        this.emptyOnRoot = emptyOnRoot;
+    }
+
+    /**
+     * Gets whether paths and name for the root context will be empty.
+     *
+     * @return indicator whether paths and name for the root context will be empty
+     */
+    public boolean isEmptyOnRoot() {
+        return emptyOnRoot;
+    }
+
+    @Override
+    public void lifecycleEvent(LifecycleEvent event) {
+        if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
+            if (!(event.getLifecycle() instanceof Context)) {
+                log.warn(sm.getString("listener.notContext", event.getLifecycle().getClass().getSimpleName()));
+                return;
+            }
+            Context context = (Context) event.getLifecycle();
+            String path = context.getPath();
+            String encodedPath = context.getEncodedPath();
+            String name = context.getName();
+
+            if (!emptyOnRoot && path.isEmpty()) {
+                path = encodedPath = "/";
+                name = "ROOT" + name;
+            }
+
+            addEnvEntry(context, PATH_ENTRY_NAME, path);
+            addEnvEntry(context, ENCODED_PATH_ENTRY_NAME, encodedPath);
+            addEnvEntry(context, WEBAPP_VERSION_ENTRY_NAME, context.getWebappVersion());
+            addEnvEntry(context, NAME_ENTRY_NAME, name);
+            addEnvEntry(context, BASE_NAME_ENTRY_NAME, context.getBaseName());
+            addEnvEntry(context, DISPLAY_NAME_ENTRY_NAME, context.getDisplayName());
+        }
+    }
+
+    private void addEnvEntry(Context context, String name, String value) {
+        ContextEnvironment ce = new ContextEnvironment();
+        ce.setName(name);
+        ce.setOverride(true);
+        ce.setType("java.lang.String");
+        ce.setValue(value);
+        if (log.isDebugEnabled()) {
+            log.info(sm.getString("contextNamingInfoListener.envEntry",name, value));
+        }
+        context.getNamingResources().addEnvironment(ce);
+    }
+
+}
diff --git a/java/org/apache/catalina/core/LocalStrings.properties b/java/org/apache/catalina/core/LocalStrings.properties
index 47959993dd..e881233f6e 100644
--- a/java/org/apache/catalina/core/LocalStrings.properties
+++ b/java/org/apache/catalina/core/LocalStrings.properties
@@ -128,6 +128,8 @@ containerBase.realm.stop=Error stopping old realm
 containerBase.threadedStartFailed=A child container failed during start
 containerBase.threadedStopFailed=A child container failed during stop
 
+contextNamingInfoListener.envEntry=Adding context env entry [{0}] with value [{1}]
+
 defaultInstanceManager.invalidAnnotation=Invalid [{0}] annotation
 defaultInstanceManager.invalidInjection=Invalid method resource injection annotation
 defaultInstanceManager.postConstructNotFound=Post construct method [{0}] for class [{1}] is declared in deployment descriptor but cannot be found
@@ -154,6 +156,7 @@ jreLeakListener.jarUrlConnCacheFail=Failed to disable Jar URL connection caching
 jreLeakListener.ldapPoolManagerFail=Failed to trigger creation of the com.sun.jndi.ldap.LdapPoolManager class during Tomcat start to prevent possible memory leaks. This is expected on non-Sun JVMs.
 jreLeakListener.xmlParseFail=Error whilst attempting to prevent memory leaks during XML parsing
 
+listener.notContext=This listener must only be nested within Context elements, but is in [{0}].
 listener.notServer=This listener must only be nested within Server elements, but is in [{0}].
 
 naming.addEnvEntry=Adding environment entry [{0}]
diff --git a/test/org/apache/catalina/core/TestContextNamingInfoListener.java b/test/org/apache/catalina/core/TestContextNamingInfoListener.java
new file mode 100644
index 0000000000..875d0a574f
--- /dev/null
+++ b/test/org/apache/catalina/core/TestContextNamingInfoListener.java
@@ -0,0 +1,119 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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 org.apache.catalina.core;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.deploy.NamingResourcesImpl;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.catalina.util.ContextName;
+import org.apache.tomcat.util.descriptor.web.ContextEnvironment;
+
+@RunWith(Parameterized.class)
+public class TestContextNamingInfoListener extends TomcatBaseTest {
+
+    @Parameterized.Parameters(name = "{index}: contextPath[{0}], webappVersion[{1}], displayName[{2}], emptyOnRoot[{3}]")
+    public static Collection<Object[]> parameters() {
+        List<Object[]> parameterSets = new ArrayList<>();
+
+        parameterSets.add(new Object[] { "", "", null,  Boolean.FALSE, "/", "/", "ROOT" });
+        parameterSets.add(new Object[] { "", "42", null,  Boolean.FALSE, "/", "/", "ROOT##42" });
+        parameterSets.add(new Object[] { "", "", null,  Boolean.TRUE, "", "", "" });
+        parameterSets.add(new Object[] { "", "42", null,  Boolean.TRUE, "", "", "##42" });
+        for (Boolean b: Arrays.asList(Boolean.FALSE, Boolean.TRUE)) {
+            parameterSets.add(new Object[] { "/foo", "", null,  b, "/foo", "/foo", "/foo" });
+            parameterSets.add(new Object[] { "/foo", "", "My Foo Webapp",  b, "/foo", "/foo", "/foo" });
+            parameterSets.add(new Object[] { "/foo", "42", "My Foo Webapp",  b, "/foo", "/foo", "/foo##42" });
+            parameterSets.add(new Object[] { "/foo/bar", "", null,  b, "/foo/bar", "/foo/bar", "/foo/bar" });
+            parameterSets.add(new Object[] { "/foo/bar", "", "My Foobar Webapp",  b, "/foo/bar", "/foo/bar", "/foo/bar" });
+            parameterSets.add(new Object[] { "/foo/bar", "42", "My Foobar Webapp",  b, "/foo/bar", "/foo/bar", "/foo/bar##42" });
+            parameterSets.add(new Object[] { "/\u0444\u0443/\u0431\u0430\u0440", "", "\u041C\u043E\u0439 \u0424\u0443\u0431\u0430\u0440 \u0412\u0435\u0431\u0430\u043F\u043F",  b, "/\u0444\u0443/\u0431\u0430\u0440", "/%D1%84%D1%83/%D0%B1%D0%B0%D1%80", "/\u0444\u0443/\u0431\u0430\u0440" });
+            parameterSets.add(new Object[] { "/\u0444\u0443/\u0431\u0430\u0440", "42", "\u041C\u043E\u0439 \u0424\u0443\u0431\u0430\u0440 \u0412\u0435\u0431\u0430\u043F\u043F",  b, "/\u0444\u0443/\u0431\u0430\u0440", "/%D1%84%D1%83/%D0%B1%D0%B0%D1%80", "/\u0444\u0443/\u0431\u0430\u0440##42" });
+        }
+
+        return parameterSets;
+    }
+
+    @Parameter(0)
+    public String contextPath;
+    @Parameter(1)
+    public String webappVersion;
+    @Parameter(2)
+    public String displayName;
+    @Parameter(3)
+    public Boolean emptyOnRoot;
+    @Parameter(4)
+    public String expectedContextPath;
+    @Parameter(5)
+    public String expectedEncodedContextPath;
+    @Parameter(6)
+    public String expectedName;
+
+    @Test
+    public void testListener() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+
+        // No file system docBase required
+        ContextName cn = new ContextName(contextPath, webappVersion);
+        Context ctx = tomcat.addContext(cn.getPath(), null);
+        ctx.setName(cn.getName());
+        ctx.setWebappVersion(cn.getVersion());
+        ctx.setDisplayName(displayName);
+
+        // Enable JNDI - it is disabled by default
+        tomcat.enableNaming();
+
+        ContextNamingInfoListener listener = new ContextNamingInfoListener();
+        listener.setEmptyOnRoot(emptyOnRoot);
+
+        ctx.addLifecycleListener(listener);
+
+        tomcat.start();
+
+        Assert.assertEquals(LifecycleState.STARTED, ctx.getState());
+
+        NamingResourcesImpl namingResources = ctx.getNamingResources();
+        ContextEnvironment pathEnv = namingResources.findEnvironment("context/path");
+        ContextEnvironment encodedPathEnv = namingResources.findEnvironment("context/encodedPath");
+        ContextEnvironment webappVersionEnv = namingResources.findEnvironment("context/webappVersion");
+        ContextEnvironment nameEnv = namingResources.findEnvironment("context/name");
+        ContextEnvironment baseNameEnv = namingResources.findEnvironment("context/baseName");
+        ContextEnvironment displayNameEnv = namingResources.findEnvironment("context/displayName");
+
+        Assert.assertEquals(expectedContextPath, pathEnv.getValue());
+        Assert.assertEquals(expectedEncodedContextPath, encodedPathEnv.getValue());
+        Assert.assertEquals(ctx.getWebappVersion(), webappVersionEnv.getValue());
+        Assert.assertEquals(expectedName, nameEnv.getValue());
+        Assert.assertEquals(ctx.getBaseName(), baseNameEnv.getValue());
+        Assert.assertEquals(ctx.getDisplayName(), displayNameEnv.getValue());
+
+        tomcat.stop();
+    }
+
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 07534eef53..01f022789f 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -107,6 +107,11 @@
 <section name="Tomcat 8.5.91 (schultz)" rtext="in development">
   <subsection name="Catalina">
     <changelog>
+      <add>
+        Add <code>org.apache.catalina.core.ContextNamingInfoListener</code>,
+        a listener which creates context naming information environment entries.
+        (michaelo)
+      </add>
       <fix>
         Fix an edge case where intra-web application symlinks would be followed
         if the web applications were deliberately crafted to allow it even when
diff --git a/webapps/docs/config/listeners.xml b/webapps/docs/config/listeners.xml
index 328130553b..2ad33fdb6e 100644
--- a/webapps/docs/config/listeners.xml
+++ b/webapps/docs/config/listeners.xml
@@ -153,6 +153,33 @@
 
   </subsection>
 
+  <subsection name="Context Naming Info Listener - org.apache.catalina.core.ContextNamingInfoListener">
+
+    <p>The <strong>Context Naming Info Listener</strong> adds the following
+    environment entries (<code>java:comp/env</code> implied) from the
+    <a href="context.html">Context</a>: <code>context/path</code>,
+    <code>context/encodedPath</code>, <code>context/webappVersion</code>,
+    <code>context/name</code>, <code>context/baseName</code>,
+    <code>context/displayName</code>.</p>
+
+    <p>This listener must only be nested within
+    <a href="context.html">Context</a> elements.</p>
+
+    <p>The following additional attributes are supported by the
+    <strong>Context Naming Info Listener</strong>:</p>
+
+    <attributes>
+      <attribute name="emptyOnRoot" required="false">
+        <p>Whether for the root context <code>context/path</code> and
+        <code>context/encodedPath</code> will contain <code>"/"</code> and
+        <code>context/name</code> will contain <code>"ROOT"</code> with a version,
+        if any.</p>
+        <p>The default value is <code>true</code>.</p>
+      </attribute>
+    </attributes>
+
+  </subsection>
+
   <subsection name="Global Resources Lifecycle Listener - org.apache.catalina.mbeans.GlobalResourcesLifecycleListener">
 
     <p>The <strong>Global Resources Lifecycle Listener</strong> initializes the


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