You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by re...@apache.org on 2025/04/15 14:16:22 UTC

(wicket) branch master updated: [WICKET-7153] 1) make system mapper more reusable by making newResourceReferenceMapper protected and 2) roll a hash code version of ResourceReferenceMapper that encodes class names by using their hash code.

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 340a2d1870 [WICKET-7153] 1) make system mapper more reusable by making newResourceReferenceMapper protected and 2) roll a hash code version of ResourceReferenceMapper that encodes class names by using their hash code.
340a2d1870 is described below

commit 340a2d18700e6176a053c0fc1ea26626ca825312
Author: reiern70 <re...@gmail.com>
AuthorDate: Sat Apr 12 13:35:30 2025 -0500

    [WICKET-7153] 1) make system mapper more reusable by making newResourceReferenceMapper protected and 2) roll a hash code version of ResourceReferenceMapper that encodes class names by using their hash code.
---
 .../HashBasedResourceReferenceMapperTest.java      | 105 ++++++++++++
 .../main/java/org/apache/wicket/SystemMapper.java  |   6 +-
 .../mapper/HashBasedResourceReferenceMapper.java   | 182 +++++++++++++++++++++
 3 files changed, 290 insertions(+), 3 deletions(-)

diff --git a/wicket-core-tests/src/test/java/org/apache/wicket/core/request/mapper/HashBasedResourceReferenceMapperTest.java b/wicket-core-tests/src/test/java/org/apache/wicket/core/request/mapper/HashBasedResourceReferenceMapperTest.java
new file mode 100644
index 0000000000..0df8105816
--- /dev/null
+++ b/wicket-core-tests/src/test/java/org/apache/wicket/core/request/mapper/HashBasedResourceReferenceMapperTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.wicket.core.request.mapper;
+
+import java.util.function.Supplier;
+import org.apache.wicket.Application;
+import org.apache.wicket.DefaultMapperContext;
+import org.apache.wicket.Page;
+import org.apache.wicket.SystemMapper;
+import org.apache.wicket.markup.head.CssHeaderItem;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.JavaScriptHeaderItem;
+import org.apache.wicket.protocol.http.WebApplication;
+import org.apache.wicket.request.IRequestMapper;
+import org.apache.wicket.request.mapper.parameter.PageParametersEncoder;
+import org.apache.wicket.request.resource.CssResourceReference;
+import org.apache.wicket.request.resource.JavaScriptResourceReference;
+import org.apache.wicket.request.resource.caching.IResourceCachingStrategy;
+import org.apache.wicket.resource.DummyApplication;
+import org.apache.wicket.util.tester.DummyHomePage;
+import org.apache.wicket.util.tester.WicketTester;
+import org.junit.jupiter.api.Test;
+
+class HashBasedResourceReferenceMapperTest {
+
+    public static class OtherPage extends DummyHomePage
+    {
+        private static final JavaScriptResourceReference JAVA_SCRIPT_RESOURCE_REFERENCE = new JavaScriptResourceReference(OtherPage.class, "a.js");
+        private static final CssResourceReference CSS_RESOURCE_REFERENCE = new CssResourceReference(OtherPage.class, "b.css");
+
+    }
+
+    public static class HomePage extends DummyHomePage
+    {
+        private static final JavaScriptResourceReference JAVA_SCRIPT_RESOURCE_REFERENCE = new JavaScriptResourceReference(HomePage.class, "a.js");
+        private static final CssResourceReference CSS_RESOURCE_REFERENCE = new CssResourceReference(HomePage.class, "b.css");
+
+        @Override
+        public void renderHead(IHeaderResponse response) {
+            response.render(JavaScriptHeaderItem.forReference(JAVA_SCRIPT_RESOURCE_REFERENCE));
+            response.render(JavaScriptHeaderItem.forReference(OtherPage.JAVA_SCRIPT_RESOURCE_REFERENCE));
+            response.render(CssHeaderItem.forReference(CSS_RESOURCE_REFERENCE));
+            response.render(CssHeaderItem.forReference(OtherPage.CSS_RESOURCE_REFERENCE));
+        }
+    }
+
+    @Test
+    void withJavaHash() {
+        final WebApplication dummyApplication = new DummyApplication() {
+            @Override
+            public Class<? extends Page> getHomePage() {
+                return HomePage.class;
+            }
+
+            @Override
+            protected IMapperContext newMapperContext() {
+                return new DefaultMapperContext(this) {
+                    @Override
+                    public String getNamespace() {
+                        // we want to hide wicket from URL.
+                        return "appres";
+                    }
+                };
+            }
+
+
+            @Override
+            protected void init() {
+                super.init();
+                setRootRequestMapper(new SystemMapper(this) {
+                    @Override
+                    protected IRequestMapper newResourceReferenceMapper(PageParametersEncoder pageParametersEncoder, ParentFolderPlaceholderProvider parentFolderPlaceholderProvider, Supplier<IResourceCachingStrategy> resourceCachingStrategy, Application application) {
+                        return HashBasedResourceReferenceMapper.withJavaHash(pageParametersEncoder, parentFolderPlaceholderProvider, resourceCachingStrategy, false);
+                    }
+                });
+            }
+        };
+
+        final WicketTester tester = new WicketTester(dummyApplication);
+        long hash = HomePage.class.getName().hashCode();
+        long hash1 = OtherPage.class.getName().hashCode();
+        tester.startPage(HomePage.class);
+        tester.assertRenderedPage(HomePage.class);
+        // we check resource urls are hash encoded
+        tester.assertContains("./appres/resource/" + hash + "/a.js");
+        tester.assertContains("./appres/resource/" + hash1 + "/a.js");
+        tester.assertContains("./appres/resource/" + hash + "/b.css");
+        tester.assertContains("./appres/resource/" + hash1 + "/b.css");
+        tester.destroy();
+    }
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/SystemMapper.java b/wicket-core/src/main/java/org/apache/wicket/SystemMapper.java
index 6f0084ca93..2140372003 100644
--- a/wicket-core/src/main/java/org/apache/wicket/SystemMapper.java
+++ b/wicket-core/src/main/java/org/apache/wicket/SystemMapper.java
@@ -51,7 +51,7 @@ public class SystemMapper extends CompoundRequestMapper
 		add(newBookmarkableMapper());
 		add(newHomePageMapper(new HomePageProvider(application)));
 		add(newResourceReferenceMapper(new PageParametersEncoder(),
-			new ParentFolderPlaceholderProvider(application), getResourceCachingStrategy()));
+			new ParentFolderPlaceholderProvider(application), getResourceCachingStrategy(), application));
 		add(newUrlResourceReferenceMapper());
 		add(RestartResponseAtInterceptPageException.MAPPER);
 		add(newBufferedResponseMapper());
@@ -67,9 +67,9 @@ public class SystemMapper extends CompoundRequestMapper
 		return new UrlResourceReferenceMapper();
 	}
 
-	private IRequestMapper newResourceReferenceMapper(PageParametersEncoder pageParametersEncoder,
+	protected IRequestMapper newResourceReferenceMapper(PageParametersEncoder pageParametersEncoder,
 	                                                  ParentFolderPlaceholderProvider parentFolderPlaceholderProvider,
-	                                                  Supplier<IResourceCachingStrategy> resourceCachingStrategy)
+	                                                  Supplier<IResourceCachingStrategy> resourceCachingStrategy, Application application)
 	{
 		return new ResourceReferenceMapper(pageParametersEncoder, parentFolderPlaceholderProvider,resourceCachingStrategy);
 	}
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/HashBasedResourceReferenceMapper.java b/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/HashBasedResourceReferenceMapper.java
new file mode 100644
index 0000000000..58c4816ab4
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/request/mapper/HashBasedResourceReferenceMapper.java
@@ -0,0 +1,182 @@
+/*
+ * 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.wicket.core.request.mapper;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.request.mapper.ParentPathReferenceRewriter;
+import org.apache.wicket.request.mapper.parameter.IPageParametersEncoder;
+import org.apache.wicket.request.resource.caching.IResourceCachingStrategy;
+import org.apache.wicket.util.lang.Args;
+
+/**
+ * <p>
+ * Resource reference {@link org.apache.wicket.request.IRequestMapper} that encodes class names as hash codes.
+ * This allows hiding the class name from resource references. i.e., instead of <em>/wicket/resource/org.xxx.yyy.ZPanel/a.js</em> the
+ * URL will display  <em>/wicket/resource/ddd/a.js</em>, where ddd = hash(org.xxx.yyy.ZPanel). This allows globally hiding
+ * class structure of your application (not displaying it via URLs).
+ * </p>
+ *
+ * <p>
+ *     Caveat: we don't take into account hash collisions. I.e. two different class names having the same hash code.
+ * </p>
+ * <p>
+ *     Note: if you want to hide the "wicket" part of URL for "xxx" you can do:
+ *	   <code>
+ *    protected IMapperContext newMapperContext() {
+ * 		return new DefaultMapperContext(this) {
+ *            public String getNamespace() {
+ * 				return "xxx";
+ *            }
+ *        };
+ *    }
+ *    </code>
+ *    on your Application class.
+ * </p>
+ * @author Ernesto Reinaldo Barreiro (reienr70 at gmail.com)
+ */
+public class HashBasedResourceReferenceMapper extends ParentPathReferenceRewriter
+{
+	/**
+	 * Knows how to compute the hash of a class name.
+	 */
+	public interface IHasher {
+
+		long computeHash(String name);
+	}
+
+	public static class HashBasedBasicResourceReferenceMapper extends BasicResourceReferenceMapper
+	{
+
+		private final Map<Long, String> hashMap = new ConcurrentHashMap<>();
+
+		private final boolean checkHashCollision;
+
+		private final IHasher hasher;
+
+		/**
+		 * Construct.
+		 *
+		 * @param pageParametersEncoder {@link IPageParametersEncoder}
+		 * @param cachingStrategy Supplier for {@link IResourceCachingStrategy}
+		 * @param checkHashCollision whether it should check for hash collisions or not.
+		 * @param hasher method used to compute hash
+		 */
+		public HashBasedBasicResourceReferenceMapper(IPageParametersEncoder pageParametersEncoder,
+													 Supplier<? extends IResourceCachingStrategy> cachingStrategy,
+													 boolean checkHashCollision,
+													 IHasher hasher)
+		{
+			super(pageParametersEncoder, cachingStrategy);
+            this.checkHashCollision = checkHashCollision;
+			Args.notNull(hasher, "hasher");
+            this.hasher = hasher;
+        }
+
+		@Override
+		protected Class<?> resolveClass(String name)
+		{
+			try
+			{
+				long hash = Long.parseLong(name);
+				String className = hashMap.get(hash);
+				if (className == null)
+				{
+					return super.getPageClass(name);
+				}
+				return super.resolveClass(className);
+			}
+			catch (NumberFormatException e)
+			{
+				return super.getPageClass(name);
+			}
+		}
+
+		@Override
+		protected String getClassName(Class<?> scope)
+		{
+			String name = super.getClassName(scope);
+			long hash = hasher.computeHash(name);
+			if (checkHashCollision)	{
+				String existing = hashMap.get(hash);
+				if (existing != null && !existing.equals(name)) {
+					throw new WicketRuntimeException("Class " + name + " has collision with " + existing);
+				}
+			}
+			hashMap.putIfAbsent(hash, name);
+			return Long.toString(hash);
+		}
+	}
+
+	/**
+	 * Construct.
+	 *
+	 * @param pageParametersEncoder {@link IPageParametersEncoder}
+	 * @param parentPathPartEscapeSequence Supplier fpr String
+	 * @param cachingStrategy Supplier fo IResourceCachingStrategy
+	 * @param checkHashCollision whether it should check for hash collisions or not
+	 * @param hasher method used to compute hash
+	 */
+	public HashBasedResourceReferenceMapper(IPageParametersEncoder pageParametersEncoder,
+											Supplier<String> parentPathPartEscapeSequence,
+											Supplier<IResourceCachingStrategy> cachingStrategy,
+											boolean checkHashCollision,
+											IHasher hasher)
+	{
+		super(new HashBasedBasicResourceReferenceMapper(pageParametersEncoder, cachingStrategy, checkHashCollision, hasher), parentPathPartEscapeSequence);
+	}
+
+	/**
+	 * Create  a HashBasedResourceReferenceMapper which uses standard java hash implementation.
+	 *
+	 * @param pageParametersEncoder {@link IPageParametersEncoder}
+	 * @param parentPathPartEscapeSequence  Supplier for String
+	 * @param cachingStrategy Supplier for {@link IResourceCachingStrategy}
+	 * @param checkHashCollision whether it should check for hash collisions or not
+	 * @return HashBasedResourceReferenceMapper
+	 */
+	public static HashBasedResourceReferenceMapper withJavaHash(IPageParametersEncoder pageParametersEncoder,
+																Supplier<String> parentPathPartEscapeSequence,
+																Supplier<IResourceCachingStrategy> cachingStrategy,
+																boolean checkHashCollision) {
+		return withOtherHash(pageParametersEncoder, parentPathPartEscapeSequence, cachingStrategy, checkHashCollision, new IHasher() {
+			@Override
+			public long computeHash(String name) {
+				return name != null ? name.hashCode() : 0;
+			}
+		});
+	}
+
+	/**
+	 * Creates a HashBasedResourceReferenceMapper with a custom {@link IHasher}
+	 *
+	 * @param pageParametersEncoder {@link IPageParametersEncoder}
+	 * @param parentPathPartEscapeSequence {@link Supplier} for String
+	 * @param cachingStrategy Supplier for {@link IResourceCachingStrategy}
+	 * @param checkHashCollision whether it should check for hash collisions or not
+	 * @param hasher method used to compute hash
+	 * @return HashBasedResourceReferenceMapper
+	 */
+	public static HashBasedResourceReferenceMapper withOtherHash(IPageParametersEncoder pageParametersEncoder,
+																Supplier<String> parentPathPartEscapeSequence,
+																Supplier<IResourceCachingStrategy> cachingStrategy,
+																boolean checkHashCollision, IHasher hasher) {
+		return new HashBasedResourceReferenceMapper(pageParametersEncoder, parentPathPartEscapeSequence, cachingStrategy, checkHashCollision, hasher);
+	}
+}