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

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


The following commit(s) were added to refs/heads/7.0.x by this push:
     new b5b6675  Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=63521
b5b6675 is described below

commit b5b66756b2e0eb8511ba0d323b81c10a4cc74224
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Fri Jun 21 20:36:37 2019 +0100

    Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=63521
    
    As required by the WebSocket specification, if a POJO that is deployed
    as a result of the SCI scan for annotated POJOs is subsequently deployed
    via the programmatic API ignore the programmatic deployment.
---
 java/org/apache/tomcat/websocket/server/WsSci.java |   2 +-
 .../tomcat/websocket/server/WsServerContainer.java | 129 +++++++++-------
 .../websocket/server/TestWsServerContainer.java    | 170 ++++++++++++++++++---
 webapps/docs/changelog.xml                         |  10 ++
 4 files changed, 240 insertions(+), 71 deletions(-)

diff --git a/java/org/apache/tomcat/websocket/server/WsSci.java 
b/java/org/apache/tomcat/websocket/server/WsSci.java
index b2237db..db9bbe1 100644
--- a/java/org/apache/tomcat/websocket/server/WsSci.java
+++ b/java/org/apache/tomcat/websocket/server/WsSci.java
@@ -139,7 +139,7 @@ public class WsSci implements ServletContainerInitializer {
             }
             // Deploy POJOs
             for (Class<?> clazz : filteredPojoEndpoints) {
-                sc.addEndpoint(clazz);
+                sc.addEndpoint(clazz, true);
             }
         } catch (DeploymentException e) {
             throw new ServletException(e);
diff --git a/java/org/apache/tomcat/websocket/server/WsServerContainer.java 
b/java/org/apache/tomcat/websocket/server/WsServerContainer.java
index 467803d..cc2855d 100644
--- a/java/org/apache/tomcat/websocket/server/WsServerContainer.java
+++ b/java/org/apache/tomcat/websocket/server/WsServerContainer.java
@@ -19,14 +19,12 @@ package org.apache.tomcat.websocket.server;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.EnumSet;
 import java.util.Map;
 import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentSkipListMap;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.SynchronousQueue;
 import java.util.concurrent.ThreadFactory;
@@ -83,15 +81,14 @@ public class WsServerContainer extends WsWebSocketContainer
     private final WsWriteTimeout wsWriteTimeout = new WsWriteTimeout();
 
     private final ServletContext servletContext;
-    private final Map<String,ServerEndpointConfig> configExactMatchMap =
-            new ConcurrentHashMap<String, ServerEndpointConfig>();
-    private final ConcurrentMap<Integer,SortedSet<TemplatePathMatch>>
-            configTemplateMatchMap = new ConcurrentHashMap<Integer, 
SortedSet<TemplatePathMatch>>();
+    private final Map<String,ExactPathMatch> configExactMatchMap = new 
ConcurrentHashMap<>();
+    private final 
ConcurrentHashMap<Integer,ConcurrentSkipListMap<String,TemplatePathMatch>> 
configTemplateMatchMap =
+            new ConcurrentHashMap<>();
     private volatile boolean enforceNoAddAfterHandshake =
             org.apache.tomcat.websocket.Constants.STRICT_SPEC_COMPLIANCE;
     private volatile boolean addAllowed = true;
     private final ConcurrentMap<String,Set<WsSession>> authenticatedSessions =
-            new ConcurrentHashMap<String, Set<WsSession>>();
+            new ConcurrentHashMap<>();
     private final ExecutorService executorService;
     private final ThreadGroup threadGroup;
     private volatile boolean endpointsRegistered = false;
@@ -171,6 +168,11 @@ public class WsServerContainer extends WsWebSocketContainer
      */
     @Override
     public void addEndpoint(ServerEndpointConfig sec) throws 
DeploymentException {
+        addEndpoint(sec, false);
+    }
+
+
+    void addEndpoint(ServerEndpointConfig sec, boolean fromAnnotatedPojo) 
throws DeploymentException {
 
         if (enforceNoAddAfterHandshake && !addAllowed) {
             throw new DeploymentException(
@@ -203,32 +205,50 @@ public class WsServerContainer extends 
WsWebSocketContainer
             UriTemplate uriTemplate = new UriTemplate(path);
             if (uriTemplate.hasParameters()) {
                 Integer key = Integer.valueOf(uriTemplate.getSegmentCount());
-                SortedSet<TemplatePathMatch> templateMatches =
+                ConcurrentSkipListMap<String,TemplatePathMatch> 
templateMatches =
                         configTemplateMatchMap.get(key);
                 if (templateMatches == null) {
                     // Ensure that if concurrent threads execute this block 
they
-                    // both end up using the same TreeSet instance
-                    templateMatches = new TreeSet<TemplatePathMatch>(
-                            TemplatePathMatchComparator.getInstance());
+                    // all end up using the same ConcurrentSkipListMap instance
+                    templateMatches = new ConcurrentSkipListMap<>();
                     configTemplateMatchMap.putIfAbsent(key, templateMatches);
                     templateMatches = configTemplateMatchMap.get(key);
                 }
-                if (!templateMatches.add(new TemplatePathMatch(sec, 
uriTemplate))) {
-                    // Duplicate uriTemplate;
-                    throw new DeploymentException(
-                            sm.getString("serverContainer.duplicatePaths", 
path,
-                                         sec.getEndpointClass(),
-                                         sec.getEndpointClass()));
+                TemplatePathMatch newMatch = new TemplatePathMatch(sec, 
uriTemplate, fromAnnotatedPojo);
+                TemplatePathMatch oldMatch = 
templateMatches.putIfAbsent(uriTemplate.getNormalizedPath(), newMatch);
+                if (oldMatch != null) {
+                    // Note: This depends on Endpoint instances being added
+                    //       before POJOs in WsSci#onStartup()
+                    if (oldMatch.isFromAnnotatedPojo() && 
!newMatch.isFromAnnotatedPojo() &&
+                            oldMatch.getConfig().getEndpointClass() == 
newMatch.getConfig().getEndpointClass()) {
+                        // The WebSocket spec says to ignore the new match in 
this case
+                        templateMatches.put(path, oldMatch);
+                    } else {
+                        // Duplicate uriTemplate;
+                        throw new DeploymentException(
+                                sm.getString("serverContainer.duplicatePaths", 
path,
+                                             sec.getEndpointClass(),
+                                             sec.getEndpointClass()));
+                    }
                 }
             } else {
                 // Exact match
-                ServerEndpointConfig old = configExactMatchMap.put(path, sec);
-                if (old != null) {
-                    // Duplicate path mappings
-                    throw new DeploymentException(
-                            sm.getString("serverContainer.duplicatePaths", 
path,
-                                         old.getEndpointClass(),
-                                         sec.getEndpointClass()));
+                ExactPathMatch newMatch = new ExactPathMatch(sec, 
fromAnnotatedPojo);
+                ExactPathMatch oldMatch = configExactMatchMap.put(path, 
newMatch);
+                if (oldMatch != null) {
+                    // Note: This depends on Endpoint instances being added
+                    //       before POJOs in WsSci#onStartup()
+                    if (oldMatch.isFromAnnotatedPojo() && 
!newMatch.isFromAnnotatedPojo() &&
+                            oldMatch.getConfig().getEndpointClass() == 
newMatch.getConfig().getEndpointClass()) {
+                        // The WebSocket spec says to ignore the new match in 
this case
+                        configExactMatchMap.put(path, oldMatch);
+                    } else {
+                        // Duplicate path mappings
+                        throw new DeploymentException(
+                                sm.getString("serverContainer.duplicatePaths", 
path,
+                                             
oldMatch.getConfig().getEndpointClass(),
+                                             sec.getEndpointClass()));
+                    }
                 }
             }
 
@@ -249,6 +269,11 @@ public class WsServerContainer extends WsWebSocketContainer
      */
     @Override
     public void addEndpoint(Class<?> pojo) throws DeploymentException {
+        addEndpoint(pojo, false);
+    }
+
+
+    void addEndpoint(Class<?> pojo, boolean fromAnnotatedPojo) throws 
DeploymentException {
 
         if (deploymentFailed) {
             throw new 
DeploymentException(sm.getString("serverContainer.failedDeployment",
@@ -299,7 +324,7 @@ public class WsServerContainer extends WsWebSocketContainer
             throw de;
         }
 
-        addEndpoint(sec);
+        addEndpoint(sec, fromAnnotatedPojo);
     }
 
 
@@ -397,9 +422,9 @@ public class WsServerContainer extends WsWebSocketContainer
         }
 
         // Check an exact match. Simple case as there are no templates.
-        ServerEndpointConfig sec = configExactMatchMap.get(path);
-        if (sec != null) {
-            return new WsMappingResult(sec, Collections.<String, 
String>emptyMap());
+        ExactPathMatch match = configExactMatchMap.get(path);
+        if (match != null) {
+            return new WsMappingResult(match.getConfig(), Collections.<String, 
String>emptyMap());
         }
 
         // No exact match. Need to look for template matches.
@@ -413,8 +438,7 @@ public class WsServerContainer extends WsWebSocketContainer
 
         // Number of segments has to match
         Integer key = Integer.valueOf(pathUriTemplate.getSegmentCount());
-        SortedSet<TemplatePathMatch> templateMatches =
-                configTemplateMatchMap.get(key);
+        ConcurrentSkipListMap<String,TemplatePathMatch> templateMatches = 
configTemplateMatchMap.get(key);
 
         if (templateMatches == null) {
             // No templates with an equal number of segments so there will be
@@ -424,8 +448,9 @@ public class WsServerContainer extends WsWebSocketContainer
 
         // List is in alphabetical order of normalised templates.
         // Correct match is the first one that matches.
+        ServerEndpointConfig sec = null;
         Map<String,String> pathParams = null;
-        for (TemplatePathMatch templateMatch : templateMatches) {
+        for (TemplatePathMatch templateMatch : templateMatches.values()) {
             pathParams = templateMatch.getUriTemplate().match(pathUriTemplate);
             if (pathParams != null) {
                 sec = templateMatch.getConfig();
@@ -572,11 +597,13 @@ public class WsServerContainer extends 
WsWebSocketContainer
     private static class TemplatePathMatch {
         private final ServerEndpointConfig config;
         private final UriTemplate uriTemplate;
+        private final boolean fromAnnotatedPojo;
 
-        public TemplatePathMatch(ServerEndpointConfig config,
-                UriTemplate uriTemplate) {
+        public TemplatePathMatch(ServerEndpointConfig config, UriTemplate 
uriTemplate,
+                boolean fromAnnotatedPojo) {
             this.config = config;
             this.uriTemplate = uriTemplate;
+            this.fromAnnotatedPojo = fromAnnotatedPojo;
         }
 
 
@@ -588,31 +615,31 @@ public class WsServerContainer extends 
WsWebSocketContainer
         public UriTemplate getUriTemplate() {
             return uriTemplate;
         }
-    }
 
 
-    /**
-     * This Comparator implementation is thread-safe so only create a single
-     * instance.
-     */
-    private static class TemplatePathMatchComparator
-            implements Comparator<TemplatePathMatch> {
+        public boolean isFromAnnotatedPojo() {
+            return fromAnnotatedPojo;
+        }
+    }
 
-        private static final TemplatePathMatchComparator INSTANCE =
-                new TemplatePathMatchComparator();
 
-        public static TemplatePathMatchComparator getInstance() {
-            return INSTANCE;
+    private static class ExactPathMatch {
+        private final ServerEndpointConfig config;
+        private final boolean fromAnnotatedPojo;
+
+        public ExactPathMatch(ServerEndpointConfig config, boolean 
fromAnnotatedPojo) {
+            this.config = config;
+            this.fromAnnotatedPojo = fromAnnotatedPojo;
         }
 
-        private TemplatePathMatchComparator() {
-            // Hide default constructor
+
+        public ServerEndpointConfig getConfig() {
+            return config;
         }
 
-        @Override
-        public int compare(TemplatePathMatch tpm1, TemplatePathMatch tpm2) {
-            return tpm1.getUriTemplate().getNormalizedPath().compareTo(
-                    tpm2.getUriTemplate().getNormalizedPath());
+
+        public boolean isFromAnnotatedPojo() {
+            return fromAnnotatedPojo;
         }
     }
 
diff --git a/test/org/apache/tomcat/websocket/server/TestWsServerContainer.java 
b/test/org/apache/tomcat/websocket/server/TestWsServerContainer.java
index 93c375d..636fcc6 100644
--- a/test/org/apache/tomcat/websocket/server/TestWsServerContainer.java
+++ b/test/org/apache/tomcat/websocket/server/TestWsServerContainer.java
@@ -27,6 +27,7 @@ import javax.websocket.DeploymentException;
 import javax.websocket.Session;
 import javax.websocket.WebSocketContainer;
 import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpoint;
 import javax.websocket.server.ServerEndpointConfig;
 
 import org.junit.Assert;
@@ -127,8 +128,7 @@ public class TestWsServerContainer extends 
WebSocketBaseTest {
 
     @Test
     public void testSpecExample3() throws Exception {
-        WsServerContainer sc =
-                new WsServerContainer(new TesterServletContext());
+        WsServerContainer sc = new WsServerContainer(new 
TesterServletContext());
 
         ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
                 Object.class, "/a/{var}/c").build();
@@ -149,8 +149,7 @@ public class TestWsServerContainer extends 
WebSocketBaseTest {
 
     @Test
     public void testSpecExample4() throws Exception {
-        WsServerContainer sc =
-                new WsServerContainer(new TesterServletContext());
+        WsServerContainer sc = new WsServerContainer(new 
TesterServletContext());
 
         ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
                 Object.class, "/{var1}/d").build();
@@ -164,10 +163,9 @@ public class TestWsServerContainer extends 
WebSocketBaseTest {
     }
 
 
-    @Test(expected = javax.websocket.DeploymentException.class)
-    public void testDuplicatePaths_01() throws Exception {
-        WsServerContainer sc =
-                new WsServerContainer(new TesterServletContext());
+    @Test(expected = DeploymentException.class)
+    public void testDuplicatePaths01() throws Exception {
+        WsServerContainer sc = new WsServerContainer(new 
TesterServletContext());
 
         ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
                 Object.class, "/a/b/c").build();
@@ -179,10 +177,9 @@ public class TestWsServerContainer extends 
WebSocketBaseTest {
     }
 
 
-    @Test(expected = javax.websocket.DeploymentException.class)
-    public void testDuplicatePaths_02() throws Exception {
-        WsServerContainer sc =
-                new WsServerContainer(new TesterServletContext());
+    @Test(expected = DeploymentException.class)
+    public void testDuplicatePaths02() throws Exception {
+        WsServerContainer sc = new WsServerContainer(new 
TesterServletContext());
 
         ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
                 Object.class, "/a/b/{var}").build();
@@ -194,10 +191,9 @@ public class TestWsServerContainer extends 
WebSocketBaseTest {
     }
 
 
-    @Test(expected = javax.websocket.DeploymentException.class)
-    public void testDuplicatePaths_03() throws Exception {
-        WsServerContainer sc =
-                new WsServerContainer(new TesterServletContext());
+    @Test(expected = DeploymentException.class)
+    public void testDuplicatePaths03() throws Exception {
+        WsServerContainer sc = new WsServerContainer(new 
TesterServletContext());
 
         ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
                 Object.class, "/a/b/{var1}").build();
@@ -210,9 +206,8 @@ public class TestWsServerContainer extends 
WebSocketBaseTest {
 
 
     @Test
-    public void testDuplicatePaths_04() throws Exception {
-        WsServerContainer sc =
-                new WsServerContainer(new TesterServletContext());
+    public void testDuplicatePaths04() throws Exception {
+        WsServerContainer sc = new WsServerContainer(new 
TesterServletContext());
 
         ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
                 Object.class, "/a/{var1}/{var2}").build();
@@ -225,4 +220,141 @@ public class TestWsServerContainer extends 
WebSocketBaseTest {
         Assert.assertEquals(configA, sc.findMapping("/a/x/y").getConfig());
         Assert.assertEquals(configB, sc.findMapping("/a/b/y").getConfig());
     }
+
+
+    /*
+     * Simulates a class that gets picked up for extending Endpoint and for
+     * being annotated.
+     */
+    @Test(expected = DeploymentException.class)
+    public void testDuplicatePaths11() throws Exception {
+        WsServerContainer sc = new WsServerContainer(new 
TesterServletContext());
+
+        ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
+                Pojo.class, "/foo").build();
+
+        sc.addEndpoint(configA, false);
+        sc.addEndpoint(Pojo.class, true);
+    }
+
+
+    /*
+     * POJO auto deployment followed by programmatic duplicate. Keep POJO.
+     */
+    @Test
+    public void testDuplicatePaths12() throws Exception {
+        WsServerContainer sc = new WsServerContainer(new 
TesterServletContext());
+
+        ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
+                Pojo.class, "/foo").build();
+
+        sc.addEndpoint(Pojo.class, true);
+        sc.addEndpoint(configA);
+
+        Assert.assertNotEquals(configA, sc.findMapping("/foo").getConfig());
+    }
+
+
+    /*
+     * POJO programmatic followed by programmatic duplicate.
+     */
+    @Test(expected = DeploymentException.class)
+    public void testDuplicatePaths13() throws Exception {
+        WsServerContainer sc = new WsServerContainer(new 
TesterServletContext());
+
+        ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
+                Pojo.class, "/foo").build();
+
+        sc.addEndpoint(Pojo.class);
+        sc.addEndpoint(configA);
+    }
+
+
+    /*
+     * POJO auto deployment followed by programmatic on same path.
+     */
+    @Test(expected = DeploymentException.class)
+    public void testDuplicatePaths14() throws Exception {
+        WsServerContainer sc = new WsServerContainer(new 
TesterServletContext());
+
+        ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
+                Object.class, "/foo").build();
+
+        sc.addEndpoint(Pojo.class, true);
+        sc.addEndpoint(configA);
+    }
+
+
+    /*
+     * Simulates a class that gets picked up for extending Endpoint and for
+     * being annotated.
+     */
+    @Test(expected = DeploymentException.class)
+    public void testDuplicatePaths21() throws Exception {
+        WsServerContainer sc = new WsServerContainer(new 
TesterServletContext());
+
+        ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
+                PojoTemplate.class, "/foo/{a}").build();
+
+        sc.addEndpoint(configA, false);
+        sc.addEndpoint(PojoTemplate.class, true);
+    }
+
+
+    /*
+     * POJO auto deployment followed by programmatic duplicate. Keep POJO.
+     */
+    @Test
+    public void testDuplicatePaths22() throws Exception {
+        WsServerContainer sc = new WsServerContainer(new 
TesterServletContext());
+
+        ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
+                PojoTemplate.class, "/foo/{a}").build();
+
+        sc.addEndpoint(PojoTemplate.class, true);
+        sc.addEndpoint(configA);
+
+        Assert.assertNotEquals(configA, 
sc.findMapping("/foo/{a}").getConfig());
+    }
+
+
+    /*
+     * POJO programmatic followed by programmatic duplicate.
+     */
+    @Test(expected = DeploymentException.class)
+    public void testDuplicatePaths23() throws Exception {
+        WsServerContainer sc = new WsServerContainer(new 
TesterServletContext());
+
+        ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
+                PojoTemplate.class, "/foo/{a}").build();
+
+        sc.addEndpoint(PojoTemplate.class);
+        sc.addEndpoint(configA);
+    }
+
+
+    /*
+     * POJO auto deployment followed by programmatic on same path.
+     */
+    @Test(expected = DeploymentException.class)
+    public void testDuplicatePaths24() throws Exception {
+        WsServerContainer sc = new WsServerContainer(new 
TesterServletContext());
+
+        ServerEndpointConfig configA = ServerEndpointConfig.Builder.create(
+                Object.class, "/foo/{a}").build();
+
+        sc.addEndpoint(PojoTemplate.class, true);
+        sc.addEndpoint(configA);
+    }
+
+
+    @ServerEndpoint("/foo")
+    public static class Pojo {
+    }
+
+
+    @ServerEndpoint("/foo/{a}")
+    public static class PojoTemplate {
+    }
+
 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 9a12f57..b7723c1 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -154,6 +154,16 @@
       </fix>
     </changelog>
   </subsection>
+  <subsection name="WebSocket">
+    <changelog>
+      <fix>
+        <bug>63521</bug>: As required by the WebSocket specification, if a POJO
+        that is deployed as a result of the SCI scan for annotated POJOs is
+        subsequently deployed via the programmatic API ignore the programmatic
+        deployment. (markt)
+      </fix>
+    </changelog>
+  </subsection>
   <subsection name="Tribes">
     <changelog>
       <fix>


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

Reply via email to