You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2012/04/06 19:44:44 UTC

svn commit: r1310501 - in /tapestry/tapestry5/trunk/tapestry-core/src: main/java/org/apache/tapestry5/internal/services/ test/java/org/apache/tapestry5/internal/services/

Author: hlship
Date: Fri Apr  6 17:44:44 2012
New Revision: 1310501

URL: http://svn.apache.org/viewvc?rev=1310501&view=rev
Log:
TAP5-1791: On some JDKs, the complex regular expression used by ComponentEventLinkEncoderImpl will cause a stack overflow

Modified:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentEventLinkEncoderImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentEventDispatcherTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentEventLinkEncoderImplTest.java

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentEventLinkEncoderImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentEventLinkEncoderImpl.java?rev=1310501&r1=1310500&r2=1310501&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentEventLinkEncoderImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentEventLinkEncoderImpl.java Fri Apr  6 17:44:44 2012
@@ -1,4 +1,4 @@
-// Copyright 2009, 2010, 2011 The Apache Software Foundation
+// Copyright 2009, 2010, 2011, 2012 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -16,14 +16,15 @@ package org.apache.tapestry5.internal.se
 
 import org.apache.tapestry5.*;
 import org.apache.tapestry5.internal.InternalConstants;
+import org.apache.tapestry5.internal.TapestryInternalUtils;
 import org.apache.tapestry5.ioc.annotations.Symbol;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.services.*;
 import org.apache.tapestry5.services.security.ClientWhitelist;
 
+import java.util.List;
 import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 public class ComponentEventLinkEncoderImpl implements ComponentEventLinkEncoder
 {
@@ -57,24 +58,6 @@ public class ComponentEventLinkEncoderIm
 
     private static final char SLASH = '/';
 
-    // A beast that recognizes all the elements of a path in a single go.
-    // We skip the leading slash, then take the next few terms (until a dot or a colon)
-    // as the page name. Then there's a sequence that sees a dot
-    // and recognizes the nested component id (which may be missing), which ends
-    // at the colon, or at the slash (or the end of the string). The colon identifies
-    // the event name (the event name is also optional). A valid path will always have
-    // a nested component id or an event name (or both) ... when both are missing, then the
-    // path is most likely a page render request. After the optional event name,
-    // the next piece is the action context, which is the remainder of the path.
-
-    private final Pattern COMPONENT_EVENT_REQUEST_PATH_PATTERN;
-
-    // Constants for the match groups in the above pattern.
-    private static final int LOGICAL_PAGE_NAME = 1;
-    private static final int NESTED_ID = 6;
-    private static final int EVENT_NAME = 9;
-    private static final int CONTEXT = 11;
-
     public ComponentEventLinkEncoderImpl(ComponentClassResolver componentClassResolver,
                                          ContextPathEncoder contextPathEncoder, LocalizationSetter localizationSetter, Request request,
                                          Response response, RequestSecurityManager requestSecurityManager, BaseURLSource baseURLSource,
@@ -97,20 +80,6 @@ public class ComponentEventLinkEncoderIm
         boolean hasAppFolder = applicationFolder.equals("");
 
         applicationFolderPrefix = hasAppFolder ? null : SLASH + applicationFolder;
-
-        String applicationFolderPattern = hasAppFolder ? "" : applicationFolder + SLASH;
-
-        COMPONENT_EVENT_REQUEST_PATH_PATTERN = Pattern.compile(
-
-                "^/" + // The leading slash is recognized but skipped
-                        applicationFolderPattern + // The folder containing the application (TAP5-743)
-                        "(((\\w(?:\\w|-)*)/)*(\\w+))" + // A series of folder names (which allow dashes) leading up to the page name, forming
-                        // the logical page name (may include the locale name)
-                        "(\\.(\\w+(\\.\\w+)*))?" + // The first dot separates the page name from the nested
-                        // component id
-                        "(\\:(\\w+))?" + // A colon, then the event type
-                        "(/(.*))?", // A slash, then the action context
-                Pattern.COMMENTS);
     }
 
     public Link createPageRenderLink(PageRenderRequestParameters parameters)
@@ -234,69 +203,180 @@ public class ComponentEventLinkEncoderIm
         return result;
     }
 
-    public ComponentEventRequestParameters decodeComponentEventRequest(Request request)
+    /**
+     * Splits path at slashes into a <em>mutable</em> list of strings. Empty terms, including the
+     * expected leading term (paths start with a '/') are dropped.
+     *
+     * @param path
+     * @return mutable list of path elements
+     */
+    private List<String> splitPath(String path)
     {
-        boolean explicitLocale = false;
+        String[] split = TapestryInternalUtils.splitPath(path);
 
-        Matcher matcher = COMPONENT_EVENT_REQUEST_PATH_PATTERN.matcher(request.getPath());
+        List<String> result = CollectionFactory.newList();
 
-        if (!matcher.matches())
-            return null;
+        for (String name : split)
+        {
+            if (name.length() > 0)
+            {
+                result.add(name);
+            }
+        }
+
+        return result;
+    }
+
+    private String joinPath(List<String> path)
+    {
+        if (path.isEmpty())
+        {
+            return "";
+        }
 
-        String nestedComponentId = matcher.group(NESTED_ID);
+        StringBuilder builder = new StringBuilder(100);
+        String sep = "";
 
-        String eventType = matcher.group(EVENT_NAME);
+        for (String term : path)
+        {
+            builder.append(sep).append(term);
+            sep = "/";
+        }
+
+        return builder.toString();
+    }
 
-        if (nestedComponentId == null && eventType == null)
+    private String peekFirst(List<String> path)
+    {
+        if (path.size() == 0)
+        {
             return null;
+        }
+
+        return path.get(0);
+    }
 
-        String activePageName = matcher.group(LOGICAL_PAGE_NAME);
+    public ComponentEventRequestParameters decodeComponentEventRequest(Request request)
+    {
+        String explicitLocale = null;
 
-        int slashx = activePageName.indexOf('/');
+        // Split the path around slashes into a mutable list of terms, which will be consumed term by term.
 
-        String possibleLocaleName = slashx > 0 ? activePageName.substring(0, slashx) : "";
+        List<String> path = splitPath(request.getPath());
 
-        if (localizationSetter.setLocaleFromLocaleName(possibleLocaleName))
+        if (this.applicationFolder.length() > 0)
         {
-            activePageName = activePageName.substring(slashx + 1);
-            explicitLocale = true;
+            // TODO: Should this be case insensitive
+
+            String inPath = path.remove(0);
+
+            if (!inPath.equals(this.applicationFolder))
+            {
+                return null;
+            }
         }
 
-        if (!componentClassResolver.isPageName(activePageName))
+        if (path.isEmpty())
+        {
             return null;
+        }
 
-        activePageName = componentClassResolver.canonicalizePageName(activePageName);
+        // Next up: the locale (which is optional)
 
-        if (isWhitelistOnlyAndNotValid(activePageName))
+        String potentialLocale = path.get(0);
+
+        if (localizationSetter.isSupportedLocaleName(potentialLocale))
         {
-            return null;
+            explicitLocale = potentialLocale;
+            path.remove(0);
         }
 
-        EventContext eventContext = contextPathEncoder.decodePath(matcher.group(CONTEXT));
+        StringBuilder pageName = new StringBuilder(100);
+        String sep = "";
+
+        while (!path.isEmpty())
+        {
+            String name = path.remove(0);
+            String eventType = EventConstants.ACTION;
+            String nestedComponentId = "";
+
+            boolean found = false;
+
+            // First, look for an explicit action name.
 
-        EventContext activationContext = contextPathEncoder.decodePath(request
-                .getParameter(InternalConstants.PAGE_CONTEXT_NAME));
+            int colonx = name.lastIndexOf(':');
 
-        // The event type is often omitted, and defaults to "action".
+            if (colonx > 0)
+            {
+                found = true;
+                eventType = name.substring(colonx + 1);
+                name = name.substring(0, colonx);
+            }
 
-        if (eventType == null)
-            eventType = EventConstants.ACTION;
+            int dotx = name.indexOf('.');
 
-        if (nestedComponentId == null)
-            nestedComponentId = "";
+            if (dotx > 0)
+            {
+                found = true;
+                nestedComponentId = name.substring(dotx + 1);
+                name = name.substring(0, dotx);
+            }
 
-        String containingPageName = request.getParameter(InternalConstants.CONTAINER_PAGE_NAME);
+            pageName.append(sep).append(name);
 
-        if (containingPageName == null)
-            containingPageName = activePageName;
-        else
-            containingPageName = componentClassResolver.canonicalizePageName(containingPageName);
+            if (found)
+            {
+                ComponentEventRequestParameters result = validateAndConstructComponentEventRequest(request, pageName.toString(), nestedComponentId, eventType, path);
 
-        if (!explicitLocale)
+                if (result == null)
+                {
+                    return result;
+                }
+
+                if (explicitLocale == null)
+                {
+                    setLocaleFromRequest(request);
+                } else
+                {
+                    localizationSetter.setLocaleFromLocaleName(explicitLocale);
+                }
+
+                return result;
+            }
+
+            // Continue on to the next name in the path
+            sep = "/";
+        }
+
+        // Path empty before finding something that looks like a component id or event name, so
+        // it is not a component event request.
+
+        return null;
+    }
+
+    private ComponentEventRequestParameters validateAndConstructComponentEventRequest(Request request, String pageName, String nestedComponentId, String eventType, List<String> remainingPath)
+    {
+        if (!componentClassResolver.isPageName(pageName))
         {
-            setLocaleFromRequest(request);
+            return null;
         }
 
+        String activePageName = componentClassResolver.canonicalizePageName(pageName);
+
+        if (isWhitelistOnlyAndNotValid(activePageName))
+        {
+            return null;
+        }
+
+        String value = request.getParameter(InternalConstants.CONTAINER_PAGE_NAME);
+
+        String containingPageName = value == null
+                ? activePageName
+                : componentClassResolver.canonicalizePageName(value);
+
+        EventContext eventContext = contextPathEncoder.decodePath(joinPath(remainingPath));
+        EventContext activationContext = contextPathEncoder.decodePath(request.getParameter(InternalConstants.PAGE_CONTEXT_NAME));
+
         return new ComponentEventRequestParameters(activePageName, containingPageName, nestedComponentId, eventType,
                 activationContext, eventContext);
     }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentEventDispatcherTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentEventDispatcherTest.java?rev=1310501&r1=1310500&r2=1310501&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentEventDispatcherTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentEventDispatcherTest.java Fri Apr  6 17:44:44 2012
@@ -1,4 +1,4 @@
-// Copyright 2007, 2008, 2009, 2011 The Apache Software Foundation
+// Copyright 2007, 2008, 2009, 2011, 2012 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -46,13 +46,16 @@ public class ComponentEventDispatcherTes
     {
         Request request = mockRequest();
         Response response = mockResponse();
+        LocalizationSetter ls = mockLocalizationSetter();
+
+        expect(ls.isSupportedLocaleName("foo")).andReturn(false);
 
         train_getPath(request, "/foo/bar/baz");
 
         replay();
 
         Dispatcher dispatcher = new ComponentEventDispatcher(null,
-                new ComponentEventLinkEncoderImpl(null, contextPathEncoder, null, request,
+                new ComponentEventLinkEncoderImpl(null, contextPathEncoder, ls, request,
                         response, null, null, null, true, "", null, null));
 
         assertFalse(dispatcher.dispatch(request, response));
@@ -146,9 +149,10 @@ public class ComponentEventDispatcherTes
                 new String[]
                         {"alpha", "beta"}), new EmptyEventContext());
 
+
         train_getPath(request, "/mypage:eventname");
 
-        train_setLocaleFromLocaleName(ls, "", false);
+        expect(ls.isSupportedLocaleName("mypage:eventname")).andReturn(false);
 
         train_isPageName(resolver, "mypage", true);
 
@@ -189,7 +193,7 @@ public class ComponentEventDispatcherTes
 
         train_getPath(request, "/activepage:eventname");
 
-        train_setLocaleFromLocaleName(ls, "", false);
+        expect(ls.isSupportedLocaleName("activepage:eventname")).andReturn(false);
 
         train_isPageName(resolver, "activepage", true);
 
@@ -224,9 +228,10 @@ public class ComponentEventDispatcherTes
         ComponentClassResolver resolver = mockComponentClassResolver();
         LocalizationSetter ls = mockLocalizationSetter();
 
+        expect(ls.isSupportedLocaleName("en")).andReturn(true);
+
         train_getPath(request, "/en/mypage.foo");
 
-        train_setLocaleFromLocaleName(ls, "en", true);
         train_isPageName(resolver, "mypage", false);
 
         replay();
@@ -256,7 +261,7 @@ public class ComponentEventDispatcherTes
 
         train_getPath(request, requestPath);
 
-        train_setLocaleFromLocaleName(localizationSetter, localeName, false);
+        expect(localizationSetter.isSupportedLocaleName(localeName)).andReturn(false);
 
         train_isPageName(resolver, containerPageName, true);
 
@@ -304,9 +309,11 @@ public class ComponentEventDispatcherTes
                 containerPageName, containerPageName, "", "anevent",
                 new EmptyEventContext(), new EmptyEventContext());
 
+
+
         train_getPath(request, "/foo/MyPage:anevent");
 
-        train_setLocaleFromLocaleName(localizationSetter, "foo", false);
+        expect(localizationSetter.isSupportedLocaleName("foo")).andReturn(false);
 
         train_isPageName(resolver, containerPageName, true);
 
@@ -351,7 +358,7 @@ public class ComponentEventDispatcherTes
 
         train_getPath(request, requestPath);
 
-        train_setLocaleFromLocaleName(localizationSetter, localeName, false);
+        expect(localizationSetter.isSupportedLocaleName("foo")).andReturn(false);
 
         train_isPageName(resolver, containerPageName, true);
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentEventLinkEncoderImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentEventLinkEncoderImplTest.java?rev=1310501&r1=1310500&r2=1310501&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentEventLinkEncoderImplTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentEventLinkEncoderImplTest.java Fri Apr  6 17:44:44 2012
@@ -1,4 +1,4 @@
-// Copyright 2009, 2010, 2011 The Apache Software Foundation
+// Copyright 2009, 2010, 2011, 2012 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -420,6 +420,8 @@ public class ComponentEventLinkEncoderIm
         LocalizationSetter ls = mockLocalizationSetter();
         MetaDataLocator metaDataLocator = neverWhitelistProtected();
 
+        expect(ls.isSupportedLocaleName("foo-bar")).andReturn(false);
+
         train_getParameter(request, InternalConstants.PAGE_CONTEXT_NAME, null);
         train_getParameter(request, InternalConstants.CONTAINER_PAGE_NAME, null);
         train_getLocale(request, Locale.ENGLISH);
@@ -429,8 +431,6 @@ public class ComponentEventLinkEncoderIm
         String path = "/foo-bar/baz.biff";
         train_getPath(request, path);
 
-        train_setLocaleFromLocaleName(ls, "foo-bar", false);
-
         train_isPageName(resolver, "foo-bar/baz", true);
 
         train_canonicalizePageName(resolver, "foo-bar/baz", "foo-bar/Baz");