You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by da...@apache.org on 2011/05/02 15:15:50 UTC

svn commit: r1098550 - in /wicket/trunk: wicket-core/src/main/java/org/apache/wicket/request/mapper/ wicket-core/src/test/java/org/apache/wicket/request/mapper/ wicket-request/src/main/java/org/apache/wicket/request/mapper/

Author: dashorst
Date: Mon May  2 13:15:49 2011
New Revision: 1098550

URL: http://svn.apache.org/viewvc?rev=1098550&view=rev
Log:
MountedMapper now has optional parameter support. Thanks to Emond Papegaaij
Issue: WICKET-3660

Modified:
    wicket/trunk/wicket-core/src/main/java/org/apache/wicket/request/mapper/MountedMapper.java
    wicket/trunk/wicket-core/src/test/java/org/apache/wicket/request/mapper/MountedMapperTest.java
    wicket/trunk/wicket-request/src/main/java/org/apache/wicket/request/mapper/AbstractMapper.java

Modified: wicket/trunk/wicket-core/src/main/java/org/apache/wicket/request/mapper/MountedMapper.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-core/src/main/java/org/apache/wicket/request/mapper/MountedMapper.java?rev=1098550&r1=1098549&r2=1098550&view=diff
==============================================================================
--- wicket/trunk/wicket-core/src/main/java/org/apache/wicket/request/mapper/MountedMapper.java (original)
+++ wicket/trunk/wicket-core/src/main/java/org/apache/wicket/request/mapper/MountedMapper.java Mon May  2 13:15:49 2011
@@ -16,6 +16,9 @@
  */
 package org.apache.wicket.request.mapper;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import org.apache.wicket.request.Request;
 import org.apache.wicket.request.Url;
 import org.apache.wicket.request.component.IRequestablePage;
@@ -31,6 +34,10 @@ import org.apache.wicket.util.lang.Args;
  * <code>/mount/${foo}/path</code>. In that case the appropriate segment from the URL will be
  * accessible as named parameter "foo" in the {@link PageParameters}. Similarly when the URL is
  * constructed, the second segment will contain the value of the "foo" named page parameter.
+ * Optional parameters are denoted by using a # instead of $: <code>/mount/#{foo}/path/${bar}</code>
+ * has an optional {@code foo} parameter, a fixed {@code /path/} part and a required {@code bar}
+ * parameter. When in doubt, parameters are matched from left to right, where required parameters
+ * are matched before optional parameters, and optional parameters eager (from left to right).
  * <p>
  * Decodes and encodes the following URLs:
  * 
@@ -54,6 +61,72 @@ public class MountedMapper extends Abstr
 {
 	private final IPageParametersEncoder pageParametersEncoder;
 
+	private static class MountPathSegment
+	{
+		private int segmentIndex;
+		private String fixedPart;
+		private int minParameters;
+		private int optionalParameters;
+
+		public MountPathSegment(int segmentIndex)
+		{
+			this.segmentIndex = segmentIndex;
+		}
+
+		public void setFixedPart(String fixedPart)
+		{
+			this.fixedPart = fixedPart;
+		}
+
+		public void addRequiredParameter()
+		{
+			minParameters++;
+		}
+
+		public void addOptionalParameter()
+		{
+			optionalParameters++;
+		}
+
+		public int getSegmentIndex()
+		{
+			return segmentIndex;
+		}
+
+		public String getFixedPart()
+		{
+			return fixedPart;
+		}
+
+		public int getMinParameters()
+		{
+			return minParameters;
+		}
+
+		public int getOptionalParameters()
+		{
+			return optionalParameters;
+		}
+
+		public int getMaxParameters()
+		{
+			return getOptionalParameters() + getMinParameters();
+		}
+
+		public int getFixedPartSize()
+		{
+			return getFixedPart() == null ? 0 : 1;
+		}
+
+		@Override
+		public String toString()
+		{
+			return "(" + getSegmentIndex() + ") " + getMinParameters() + "-" + getMaxParameters() +
+				" " + (getFixedPart() == null ? "(end)" : getFixedPart());
+		}
+	}
+
+	private final List<MountPathSegment> pathSegments;
 	private final String[] mountSegments;
 
 	/** bookmarkable page class. */
@@ -82,7 +155,6 @@ public class MountedMapper extends Abstr
 		this(mountPath, pageClassProvider, new PageParametersEncoder());
 	}
 
-
 	/**
 	 * Construct.
 	 * 
@@ -114,6 +186,39 @@ public class MountedMapper extends Abstr
 		this.pageParametersEncoder = pageParametersEncoder;
 		this.pageClassProvider = pageClassProvider;
 		mountSegments = getMountSegments(mountPath);
+		pathSegments = getPathSegments(mountSegments);
+	}
+
+	private List<MountPathSegment> getPathSegments(String[] segments)
+	{
+		List<MountPathSegment> ret = new ArrayList<MountPathSegment>();
+		int segmentIndex = 0;
+		MountPathSegment curPathSegment = new MountPathSegment(segmentIndex);
+		ret.add(curPathSegment);
+		for (String curSegment : segments)
+		{
+			if (isFixedSegment(curSegment))
+			{
+				curPathSegment.setFixedPart(curSegment);
+				curPathSegment = new MountPathSegment(segmentIndex + 1);
+				ret.add(curPathSegment);
+			}
+			else if (getPlaceholder(curSegment) != null)
+			{
+				curPathSegment.addRequiredParameter();
+			}
+			else
+			{
+				curPathSegment.addOptionalParameter();
+			}
+			segmentIndex++;
+		}
+		return ret;
+	}
+
+	private boolean isFixedSegment(String segment)
+	{
+		return getOptionalPlaceholder(segment) == null && getPlaceholder(segment) == null;
 	}
 
 	/**
@@ -136,36 +241,113 @@ public class MountedMapper extends Abstr
 		{
 			// try to extract page and component information from URL
 			PageComponentInfo info = getPageComponentInfo(url);
-
 			Class<? extends IRequestablePage> pageClass = getPageClass();
+			PageParameters pageParameters = extractPageParameters(request, url);
 
-			// extract the PageParameters from URL if there are any
-			PageParameters pageParameters = extractPageParameters(request, mountSegments.length,
-				pageParametersEncoder);
+			return new UrlInfo(info, pageClass, pageParameters);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/*
+	 * extract the PageParameters from URL if there are any
+	 */
+	private PageParameters extractPageParameters(Request request, Url url)
+	{
+		int[] matchedParameters = getMatchedSegmentSizes(url);
+		int total = 0;
+		for (int curMatchSize : matchedParameters)
+			total += curMatchSize;
+		PageParameters pageParameters = extractPageParameters(request, total, pageParametersEncoder);
 
-			// check if there are placeholders in mount segments
-			for (int i = 0; i < mountSegments.length; ++i)
+		int skippedParameters = 0;
+		for (int pathSegmentIndex = 0; pathSegmentIndex < pathSegments.size(); pathSegmentIndex++)
+		{
+			MountPathSegment curPathSegment = pathSegments.get(pathSegmentIndex);
+			int matchSize = matchedParameters[pathSegmentIndex] - curPathSegment.getFixedPartSize();
+			int optionalParameterMatch = matchSize - curPathSegment.getMinParameters();
+			for (int matchSegment = 0; matchSegment < matchSize; matchSegment++)
 			{
-				String placeholder = getPlaceholder(mountSegments[i]);
+				if (pageParameters == null)
+				{
+					pageParameters = new PageParameters();
+				}
+
+				int curSegmentIndex = matchSegment + curPathSegment.getSegmentIndex();
+				String curSegment = mountSegments[curSegmentIndex];
+				String placeholder = getPlaceholder(curSegment);
+				String optionalPlaceholder = getOptionalPlaceholder(curSegment);
+				// extract the parameter from URL
 				if (placeholder != null)
 				{
-					// extract the parameter from URL
-					if (pageParameters == null)
-					{
-						pageParameters = new PageParameters();
-					}
-					pageParameters.add(placeholder, url.getSegments().get(i));
+					pageParameters.add(placeholder,
+						url.getSegments().get(curSegmentIndex - skippedParameters));
+				}
+				else if (optionalPlaceholder != null && optionalParameterMatch > 0)
+				{
+					pageParameters.add(optionalPlaceholder,
+						url.getSegments().get(curSegmentIndex - skippedParameters));
+					optionalParameterMatch--;
 				}
 			}
+			skippedParameters += curPathSegment.getMaxParameters() - matchSize;
+		}
+		return pageParameters;
+	}
 
-			return new UrlInfo(info, pageClass, pageParameters);
+	@Override
+	protected boolean urlStartsWith(Url url, String... segments)
+	{
+		if (url == null)
+		{
+			return false;
 		}
 		else
 		{
-			return null;
+			return getMatchedSegmentSizes(url) != null;
 		}
 	}
 
+	private int[] getMatchedSegmentSizes(Url url)
+	{
+		int[] ret = new int[pathSegments.size()];
+		int segmentIndex = 0;
+		int pathSegmentIndex = 0;
+		for (MountPathSegment curPathSegment : pathSegments.subList(0, pathSegments.size() - 1))
+		{
+			boolean foundFixedPart = false;
+			segmentIndex += curPathSegment.getMinParameters();
+			int max = Math.min(curPathSegment.getOptionalParameters() + 1,
+				url.getSegments().size() - segmentIndex);
+
+			for (int count = max - 1; count >= 0; count--)
+			{
+				if (url.getSegments()
+					.get(segmentIndex + count)
+					.equals(curPathSegment.getFixedPart()))
+				{
+					foundFixedPart = true;
+					segmentIndex += count + 1;
+					ret[pathSegmentIndex] = count + curPathSegment.getMinParameters() + 1;
+					break;
+				}
+			}
+			if (!foundFixedPart)
+				return null;
+			pathSegmentIndex++;
+		}
+		MountPathSegment lastSegment = pathSegments.get(pathSegments.size() - 1);
+		segmentIndex += lastSegment.getMinParameters();
+		if (segmentIndex > url.getSegments().size())
+			return null;
+		ret[pathSegmentIndex] = Math.min(lastSegment.getMaxParameters(), url.getSegments().size() -
+			segmentIndex + lastSegment.getMinParameters());
+		return ret;
+	}
+
 	protected PageParameters newPageParameters()
 	{
 		return new PageParameters();
@@ -186,14 +368,29 @@ public class MountedMapper extends Abstr
 
 		PageParameters copy = new PageParameters(info.getPageParameters());
 
+		int dropped = 0;
 		for (int i = 0; i < mountSegments.length; ++i)
 		{
 			String placeholder = getPlaceholder(mountSegments[i]);
+			String optionalPlaceholder = getOptionalPlaceholder(mountSegments[i]);
 			if (placeholder != null)
 			{
-				url.getSegments().set(i, copy.get(placeholder).toString(""));
+				url.getSegments().set(i - dropped, copy.get(placeholder).toString(""));
 				copy.remove(placeholder);
 			}
+			else if (optionalPlaceholder != null)
+			{
+				if (copy.getNamedKeys().contains(optionalPlaceholder))
+				{
+					url.getSegments().set(i - dropped, copy.get(optionalPlaceholder).toString(""));
+					copy.remove(optionalPlaceholder);
+				}
+				else
+				{
+					url.getSegments().remove(i - dropped);
+					dropped++;
+				}
+			}
 		}
 
 		return encodePageParameters(url, copy, pageParametersEncoder);

Modified: wicket/trunk/wicket-core/src/test/java/org/apache/wicket/request/mapper/MountedMapperTest.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-core/src/test/java/org/apache/wicket/request/mapper/MountedMapperTest.java?rev=1098550&r1=1098549&r2=1098550&view=diff
==============================================================================
--- wicket/trunk/wicket-core/src/test/java/org/apache/wicket/request/mapper/MountedMapperTest.java (original)
+++ wicket/trunk/wicket-core/src/test/java/org/apache/wicket/request/mapper/MountedMapperTest.java Mon May  2 13:15:49 2011
@@ -65,6 +65,16 @@ public class MountedMapperTest extends A
 		}
 	};
 
+	private final MountedMapper optionPlaceholderEncoder = new MountedMapper(
+		"/some/#{param1}/path/${param2}/#{param3}", MockPage.class)
+	{
+		@Override
+		protected IMapperContext getContext()
+		{
+			return context;
+		}
+	};
+
 	/**
 	 * 
 	 */
@@ -493,6 +503,23 @@ public class MountedMapperTest extends A
 		assertEquals("p2", page.getPageParameters().get("param2").toString());
 	}
 
+	/**	 */
+	public void testPlaceholderDecodeWithIndexedParameters()
+	{
+		Url url = Url.parse("some/p1/path/p2/p3/p4");
+		IRequestHandler handler = placeholderEncoder.mapRequest(getRequest(url));
+
+		assertTrue(handler instanceof RenderPageRequestHandler);
+		IRequestablePage page = ((RenderPageRequestHandler)handler).getPage();
+
+		assertEquals(2, page.getPageParameters().getIndexedCount());
+		assertTrue(page.getPageParameters().getNamedKeys().size() == 2);
+		assertEquals("p1", page.getPageParameters().get("param1").toString());
+		assertEquals("p2", page.getPageParameters().get("param2").toString());
+		assertEquals("p3", page.getPageParameters().get(0).toString());
+		assertEquals("p4", page.getPageParameters().get(1).toString());
+	}
+
 	/**
 	 * 
 	 */
@@ -541,4 +568,128 @@ public class MountedMapperTest extends A
 		Url url = placeholderEncoder.mapHandler(handler);
 		assertEquals("some/p1/path/p2/i1/i2?1&a=b&b=c", url.toString());
 	}
+
+	/** */
+	public void testOptionPlaceholderDecode1()
+	{
+		Url url = Url.parse("some/p1/path/p2/p3");
+		IRequestHandler handler = optionPlaceholderEncoder.mapRequest(getRequest(url));
+
+		assertTrue(handler instanceof RenderPageRequestHandler);
+		IRequestablePage page = ((RenderPageRequestHandler)handler).getPage();
+
+		assertEquals(0, page.getPageParameters().getIndexedCount());
+		assertTrue(page.getPageParameters().getNamedKeys().size() == 3);
+		assertEquals("p1", page.getPageParameters().get("param1").toString());
+		assertEquals("p2", page.getPageParameters().get("param2").toString());
+		assertEquals("p3", page.getPageParameters().get("param3").toString());
+	}
+
+	/** */
+	public void testOptionPlaceholderDecodeEagerMatchParameters()
+	{
+		Url url = Url.parse("some/path/path/path");
+		IRequestHandler handler = optionPlaceholderEncoder.mapRequest(getRequest(url));
+
+		assertTrue(handler instanceof RenderPageRequestHandler);
+		IRequestablePage page = ((RenderPageRequestHandler)handler).getPage();
+
+		assertEquals(0, page.getPageParameters().getIndexedCount());
+		assertTrue(page.getPageParameters().getNamedKeys().size() == 2);
+		assertEquals("path", page.getPageParameters().get("param1").toString());
+		assertEquals("path", page.getPageParameters().get("param2").toString());
+		assertFalse("param3 should not be set",
+			page.getPageParameters().getNamedKeys().contains("param3"));
+	}
+
+	/** */
+	public void testOptionPlaceholderDecode2()
+	{
+		Url url = Url.parse("some/p1/path/p2");
+		IRequestHandler handler = optionPlaceholderEncoder.mapRequest(getRequest(url));
+
+		assertTrue(handler instanceof RenderPageRequestHandler);
+		IRequestablePage page = ((RenderPageRequestHandler)handler).getPage();
+
+		assertEquals(0, page.getPageParameters().getIndexedCount());
+		assertTrue(page.getPageParameters().getNamedKeys().size() == 2);
+		assertEquals("p1", page.getPageParameters().get("param1").toString());
+		assertEquals("p2", page.getPageParameters().get("param2").toString());
+		assertFalse("param3 should not be set",
+			page.getPageParameters().getNamedKeys().contains("param3"));
+	}
+
+	/** */
+	public void testOptionPlaceholderDecode3()
+	{
+		Url url = Url.parse("some/path/p2");
+		IRequestHandler handler = optionPlaceholderEncoder.mapRequest(getRequest(url));
+
+		assertTrue(handler instanceof RenderPageRequestHandler);
+		IRequestablePage page = ((RenderPageRequestHandler)handler).getPage();
+
+		assertEquals(0, page.getPageParameters().getIndexedCount());
+		assertTrue(page.getPageParameters().getNamedKeys().size() == 1);
+		assertFalse("param1 should not be set",
+			page.getPageParameters().getNamedKeys().contains("param1"));
+		assertEquals("p2", page.getPageParameters().get("param2").toString());
+		assertFalse("param3 should not be set",
+			page.getPageParameters().getNamedKeys().contains("param3"));
+	}
+
+	/** */
+	public void testOptionPlaceholderDecodeWithIndexParams()
+	{
+		Url url = Url.parse("some/path/p2/p3/p4");
+		IRequestHandler handler = optionPlaceholderEncoder.mapRequest(getRequest(url));
+
+		assertTrue(handler instanceof RenderPageRequestHandler);
+		IRequestablePage page = ((RenderPageRequestHandler)handler).getPage();
+
+		assertEquals(1, page.getPageParameters().getIndexedCount());
+		assertTrue(page.getPageParameters().getNamedKeys().size() == 2);
+		assertFalse("param1 should not be set",
+			page.getPageParameters().getNamedKeys().contains("param1"));
+		assertEquals("p2", page.getPageParameters().get("param2").toString());
+		assertEquals("p3", page.getPageParameters().get("param3").toString());
+		assertEquals("p4", page.getPageParameters().get(0).toString());
+	}
+
+	/** */
+	public void testOptionPlaceholderEncode1()
+	{
+		PageParameters parameters = new PageParameters();
+		parameters.set(0, "i1");
+		parameters.set(1, "i2");
+		parameters.set("a", "b");
+		parameters.set("b", "c");
+		parameters.set("param1", "p1");
+		parameters.set("param2", "p2");
+
+
+		PageProvider provider = new PageProvider(MockPage.class, parameters);
+		provider.setPageSource(context);
+		IRequestHandler handler = new BookmarkablePageRequestHandler(provider);
+		Url url = optionPlaceholderEncoder.mapHandler(handler);
+		assertEquals("some/p1/path/p2/i1/i2?a=b&b=c", url.toString());
+	}
+
+	/** */
+	public void testOptionPlaceholderEncode2()
+	{
+		PageParameters parameters = new PageParameters();
+		parameters.set(0, "i1");
+		parameters.set(1, "i2");
+		parameters.set("a", "b");
+		parameters.set("b", "c");
+		parameters.set("param2", "p2");
+		parameters.set("param3", "p3");
+
+
+		PageProvider provider = new PageProvider(MockPage.class, parameters);
+		provider.setPageSource(context);
+		IRequestHandler handler = new BookmarkablePageRequestHandler(provider);
+		Url url = optionPlaceholderEncoder.mapHandler(handler);
+		assertEquals("some/path/p2/p3/i1/i2?a=b&b=c", url.toString());
+	}
 }

Modified: wicket/trunk/wicket-request/src/main/java/org/apache/wicket/request/mapper/AbstractMapper.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-request/src/main/java/org/apache/wicket/request/mapper/AbstractMapper.java?rev=1098550&r1=1098549&r2=1098550&view=diff
==============================================================================
--- wicket/trunk/wicket-request/src/main/java/org/apache/wicket/request/mapper/AbstractMapper.java (original)
+++ wicket/trunk/wicket-request/src/main/java/org/apache/wicket/request/mapper/AbstractMapper.java Mon May  2 13:15:49 2011
@@ -39,7 +39,33 @@ public abstract class AbstractMapper imp
 	 */
 	protected static String getPlaceholder(final String s)
 	{
-		if ((s == null) || (s.length() < 4) || !s.startsWith("${") || !s.endsWith("}"))
+		return getPlaceholder(s, '$');
+	}
+
+	/**
+	 * If the string is in an optional parameter placeholder format #{key} this method returns the
+	 * key.
+	 * 
+	 * @param s
+	 * @return placeholder key or <code>null</code> if string is not in right format
+	 */
+	protected static String getOptionalPlaceholder(final String s)
+	{
+		return getPlaceholder(s, '#');
+	}
+
+	/**
+	 * If the string is in a placeholder format x{key}, where 'x' can be specified, this method
+	 * returns the key.
+	 * 
+	 * @param s
+	 * @param startChar
+	 *            the character used to indicate the start of the placeholder
+	 * @return placeholder key or <code>null</code> if string is not in right format
+	 */
+	protected static String getPlaceholder(final String s, char startChar)
+	{
+		if ((s == null) || (s.length() < 4) || !s.startsWith(startChar + "{") || !s.endsWith("}"))
 		{
 			return null;
 		}