Merged revision(s) 1885148-1885758 from turbine/core/branches/URLMapperService:
git-svn-id: https://svn.apache.org/repos/asf/turbine/core/trunk@1885760 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/conf/test/TurbineURLMapperServiceTest.properties b/conf/test/TurbineURLMapperServiceTest.properties
new file mode 100644
index 0000000..53a36d3
--- /dev/null
+++ b/conf/test/TurbineURLMapperServiceTest.properties
@@ -0,0 +1,133 @@
+# 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.
+
+# -------------------------------------------------------------------
+#
+# L O G 4 J 2 - L O G G I N G
+#
+# -------------------------------------------------------------------
+# log4j2 may loads automatically if found on classpath, cf. https://logging.apache.org/log4j/2.x
+log4j2.file = log4j2.xml
+
+# resource relative to context
+pipeline.default.descriptor = /conf/turbine-classic-pipeline.xml
+
+
+# If module.cache=true, then how large should we make the hashtables
+# by default.
+
+action.cache.size=20
+layout.cache.size=10
+navigation.cache.size=10
+page.cache.size=5
+screen.cache.size=50
+scheduledjob.cache.size=10
+
+# -------------------------------------------------------------------
+#
+# M O D U L E P A C K A G E S
+#
+# -------------------------------------------------------------------
+# This is the "classpath" for Turbine. In order to locate your own
+# modules, you should add them to this path. For example, if you have
+# com.company.actions, com.company.screens, com.company.navigations,
+# then this setting would be "com.company,org.apache.turbine.modules".
+# This path is searched in order. For example, Turbine comes with a
+# screen module named "Login". If you wanted to have your own screen
+# module named "Login", then you would specify the path to your
+# modules before the others.
+#
+# Note: org.apache.turbine.modules will always be added to the search
+# path. If it is not explicitly added here, it will be added to the
+# end.
+#
+# Default: org.apache.turbine.modules
+# -------------------------------------------------------------------
+
+module.packages=@MODULE_PACKAGES@
+
+# Choose between the two available implementations of an Avalon container - ECM or YAAFI
+
+# services.AvalonComponentService.classname=org.apache.turbine.services.avaloncomponent.TurbineAvalonComponentService
+services.AvalonComponentService.classname=org.apache.turbine.services.avaloncomponent.TurbineYaafiComponentService
+
+services.RunDataService.classname=org.apache.turbine.services.rundata.TurbineRunDataService
+services.AssemblerBrokerService.classname=org.apache.turbine.services.assemblerbroker.TurbineAssemblerBrokerService
+services.TemplateService.classname=org.apache.turbine.services.template.TurbineTemplateService
+
+# required by url mapper service
+services.ServletService.classname=org.apache.turbine.services.servlet.TurbineServletService
+
+services.URLMapperService.classname=org.apache.turbine.services.urlmapper.TurbineURLMapperService
+
+# -------------------------------------------------------------------
+#
+# R U N D A T A S E R V I C E
+#
+# -------------------------------------------------------------------
+
+services.RunDataService.default.run.data=org.apache.turbine.services.rundata.DefaultTurbineRunData
+services.RunDataService.default.parameter.parser=org.apache.fulcrum.parser.DefaultParameterParser
+services.RunDataService.default.cookie.parser=org.apache.fulcrum.parser.DefaultCookieParser
+
+# -------------------------------------------------------------------
+#
+# A S S E M B L E R B R O K E R S E R V I C E
+#
+# -------------------------------------------------------------------
+# A list of AssemblerFactory classes that will be registered
+# with TurbineAssemblerBrokerService
+# -------------------------------------------------------------------
+
+services.AssemblerBrokerService.screen=org.apache.turbine.services.assemblerbroker.util.java.JavaScreenFactory
+# services.AssemblerBrokerService.screen=org.apache.turbine.services.assemblerbroker.util.python.PythonScreenFactory
+services.AssemblerBrokerService.action=org.apache.turbine.services.assemblerbroker.util.java.JavaActionFactory
+services.AssemblerBrokerService.layout=org.apache.turbine.services.assemblerbroker.util.java.JavaLayoutFactory
+services.AssemblerBrokerService.page=org.apache.turbine.services.assemblerbroker.util.java.JavaPageFactory
+services.AssemblerBrokerService.navigation=org.apache.turbine.services.assemblerbroker.util.java.JavaNavigationFactory
+services.AssemblerBrokerService.scheduledjob=org.apache.turbine.services.assemblerbroker.util.java.JavaScheduledJobFactory
+
+# -------------------------------------------------------------------
+#
+# T E M P L A T E S E R V I C E
+#
+# -------------------------------------------------------------------
+
+# Roughly, the number of templates in each category.
+#
+# Defaults: layout=2, navigation=10, screen=50
+
+services.TemplateService.layout.cache.size=2
+services.TemplateService.navigation.cache.size=10
+services.TemplateService.screen.cache.size=50
+
+# -------------------------------------------------------------------
+#
+# A V A L O N C O M P O N E N T S E R V I C E
+#
+# -------------------------------------------------------------------
+
+services.AvalonComponentService.componentConfiguration = conf/test/fulcrumComponentConfiguration.xml
+services.AvalonComponentService.componentRoles = conf/test/fulcrumRoleConfiguration.xml
+services.AvalonComponentService.lookup = org.apache.fulcrum.cache.GlobalCacheService
+
+# -------------------------------------------------------------------
+#
+# U R L M A P P E R S E R V I C E
+#
+# -------------------------------------------------------------------
+services.URLMapperService.configFile = /conf/turbine-url-mapping.xml
diff --git a/conf/test/fulcrumComponentConfiguration.xml b/conf/test/fulcrumComponentConfiguration.xml
index 7a1c009..c45ee5b 100644
--- a/conf/test/fulcrumComponentConfiguration.xml
+++ b/conf/test/fulcrumComponentConfiguration.xml
@@ -53,8 +53,15 @@
<parser>
<parameterEncoding>utf-8</parameterEncoding>
<automaticUpload>true</automaticUpload>
+ <pool2>
+ <!-- cft. defaults in org.apache.commons.pool2.impl.BaseObjectPoolConfig and GenericKeyedObjectPoolConfig -->
+ <maxTotal>-1</maxTotal><!-- default no limit = -1, other plausible values 1024, 2048 -->
+ <blockWhenExhausted>true</blockWhenExhausted><!-- default true -->
+ <maxWaitMillis>350</maxWaitMillis><!-- default 0 -->
+ <testOnReturn>true</testOnReturn>
+ </pool2>
</parser>
-
+
<!-- These components belong to the Fulcrum-Security services -->
<securityService/>
<authenticator/>
diff --git a/conf/test/log4j2.xml b/conf/test/log4j2.xml
index dfe68ac..99529bd 100644
--- a/conf/test/log4j2.xml
+++ b/conf/test/log4j2.xml
@@ -30,15 +30,18 @@
<Loggers>
<Logger name="org.apache.turbine" level="debug" additivity="false">
<AppenderRef ref="logfile"/>
+ <AppenderRef ref="console"/>
</Logger>
<Logger name="avalon" level="info" additivity="false">
<AppenderRef ref="logfile"/>
+ <AppenderRef ref="console"/>
</Logger>
<Logger name="org.apache.logging.log4j" level="debug" additivity="false">
<AppenderRef ref="logfile"/>
- </Logger>
- <Root level="error">
- <AppenderRef ref="logfile"/>
- </Root>
+ </Logger>
+ <Root>
+ <AppenderRef ref="logfile" level="error"/>
+ <AppenderRef ref="console" level="debug"/>
+ </Root>
</Loggers>
</Configuration>
\ No newline at end of file
diff --git a/conf/turbine-url-mapping.xml b/conf/turbine-url-mapping.xml
new file mode 100644
index 0000000..d172e18
--- /dev/null
+++ b/conf/turbine-url-mapping.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<url-mapping name="default">
+ <maps>
+ <map>
+ <pattern>/(?<contextPath>\w+)/book/(?<bookId>\d+)</pattern>
+ <implicit-parameters>
+ <parameter key="template">Book.vm</parameter>
+ <parameter key="detail">0</parameter>
+ </implicit-parameters>
+ </map>
+ <map>
+ <pattern>/(?<contextPath>\w+)/book/(?<bookId>\d+)/(?<detail>\d)</pattern>
+ <implicit-parameters>
+ <parameter key="template">Book.vm</parameter>
+ </implicit-parameters>
+ <ignore-parameters>
+ <parameter key="view" />
+ </ignore-parameters>
+ </map>
+ <map>
+ <pattern>/(?<contextPath>\w+)/register</pattern>
+ <implicit-parameters>
+ <parameter key="media-type">html</parameter>
+ <parameter key="role">anon</parameter>
+ <parameter key="template">Registerone.vm</parameter>
+ <parameter key="js_pane">random-id-123-abc</parameter>
+ </implicit-parameters>
+ </map>
+ <map>
+ <pattern>/(?<contextPath>\w+)/contact</pattern>
+ <implicit-parameters>
+ <parameter key="media-type">html</parameter>
+ <parameter key="role">anon</parameter>
+ <parameter key="page">Contact</parameter>
+ <parameter key="js_pane">another-random-id-876-dfg</parameter>
+ </implicit-parameters>
+ <override-parameters>
+ <parameter key="role">anon</parameter>
+ </override-parameters>
+ </map>
+ <map>
+ <pattern>/(?<contextPath>\w+)/(?<id>\d+)/(?<role>\w+)/(?<language>\w+)</pattern>
+ <implicit-parameters>
+ <parameter key="media-type">html</parameter>
+ <parameter key="template">default.vm</parameter>
+ </implicit-parameters>
+ </map>
+ </maps>
+</url-mapping>
\ No newline at end of file
diff --git a/conf/turbine-url-mapping.yml b/conf/turbine-url-mapping.yml
new file mode 100644
index 0000000..6a47d64
--- /dev/null
+++ b/conf/turbine-url-mapping.yml
@@ -0,0 +1,31 @@
+
+name: default
+maps:
+ - pattern: /(?<contextPath>\w+)/book/(?<bookId>\d+)
+ implicit-parameters:
+ template: Book.vm
+ detail: 0
+ - pattern: /(?<contextPath>\w+)/book/(?<bookId>\d+)/(?<detail>\d)
+ implicit-parameters:
+ template: Book.vm
+ ignore-parameters:
+ view: null
+ - pattern: /(?<contextPath>\w+)/register
+ implicit-parameters:
+ media-type: html
+ role: anon
+ template: Registerone.vm
+ js_pane: random-id-123-abc
+ - pattern: /(?<contextPath>\w+)/contact
+ implicit-parameters:
+ media-type: html
+ page: Contact
+ js_pane: another-random-id-876-dfg
+ role: anon
+ override-parameters:
+ role: anon
+ - pattern: /(?<contextPath>\w+)/(?<id>\d+)/(?<role>\w+)/(?<language>\w+)
+ implicit-parameters:
+ media-type: html
+ template: default.vm
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 7178da7..6aa42d7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>org.apache.turbine</groupId>
<artifactId>turbine-parent</artifactId>
- <version>8-SNAPSHOT</version>
+ <version>7</version>
</parent>
<artifactId>turbine</artifactId>
<name>Apache Turbine</name>
@@ -547,14 +547,18 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
- <version>3.0.0-M5</version>
- <configuration>
- <!--default setting is forkCount=1/reuseForks=true -->
- <reuseForks>false</reuseForks>
- <forkCount>1</forkCount>
- <excludedGroups>performance,docker</excludedGroups>
- <!--useSystemClassLoader>false</useSystemClassLoader-->
- </configuration>
+ <version>3.0.0-M5</version>
+ <executions>
+ <execution>
+ <id>default-test</id>
+ <configuration>
+ <!--default setting is forkCount=1/reuseForks=true -->
+ <reuseForks>false</reuseForks>
+ <forkCount>1</forkCount>
+ <excludedGroups>performance,docker,yaml</excludedGroups>
+ </configuration>
+ </execution>
+ </executions>
</plugin>
<plugin>
<groupId>org.apache.torque</groupId>
@@ -888,6 +892,12 @@
<version>1.9.4</version>
</dependency>
<dependency>
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-yaml</artifactId>
+ <version>2.11.2</version>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
<groupId>nl.basjes.parse.useragent</groupId>
<artifactId>yauaa</artifactId>
<version>5.19</version>
@@ -1181,7 +1191,7 @@
<id>default-test</id>
<configuration>
<groups>docker</groups>
- <excludedGroups>performance</excludedGroups>
+ <excludedGroups>performance,yaml</excludedGroups>
</configuration>
</execution>
</executions>
@@ -1237,6 +1247,37 @@
</dependency>
</dependencies>
</profile>
+ <profile>
+ <id>yaml</id>
+ <activation>
+ <activeByDefault>false</activeByDefault>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>3.0.0-M5</version>
+ <executions>
+ <execution>
+ <id>default-test</id>
+ <configuration><!-- to override excludedGroups set something else (bug?)-->
+ <groups>yaml</groups>
+ <excludedGroups>docker,performance</excludedGroups>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-yaml</artifactId>
+ <version>2.11.2</version>
+ </dependency>
+ </dependencies>
+ </profile>
</profiles>
<properties>
@@ -1255,3 +1296,4 @@
</properties>
</project>
+
diff --git a/src/java/org/apache/turbine/services/pull/TurbinePullService.java b/src/java/org/apache/turbine/services/pull/TurbinePullService.java
index 00e1537..c2be3bf 100644
--- a/src/java/org/apache/turbine/services/pull/TurbinePullService.java
+++ b/src/java/org/apache/turbine/services/pull/TurbinePullService.java
@@ -466,7 +466,7 @@
{
try
{
- Object tool = toolData.toolClass.newInstance();
+ Object tool = toolData.toolClass.getDeclaredConstructor().newInstance();
// global tools are init'd with a null data parameter
initTool(tool, null);
diff --git a/src/java/org/apache/turbine/services/pull/tools/TemplateLink.java b/src/java/org/apache/turbine/services/pull/tools/TemplateLink.java
index e870ade..122226a 100644
--- a/src/java/org/apache/turbine/services/pull/tools/TemplateLink.java
+++ b/src/java/org/apache/turbine/services/pull/tools/TemplateLink.java
@@ -520,7 +520,7 @@
* tui.getRelativeLink();
* </pre>
*
- * The above call to absoluteLink() would return the String:
+ * The above call to relativeLink() would return the String:
* <p>
* /servlets/Turbine/screen/UserScreen/user/jon
* <p>
diff --git a/src/java/org/apache/turbine/services/urlmapper/MappedTemplateLink.java b/src/java/org/apache/turbine/services/urlmapper/MappedTemplateLink.java
new file mode 100644
index 0000000..ea2b1c1
--- /dev/null
+++ b/src/java/org/apache/turbine/services/urlmapper/MappedTemplateLink.java
@@ -0,0 +1,116 @@
+package org.apache.turbine.services.urlmapper;
+
+/*
+ * 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.
+ */
+
+import org.apache.turbine.annotation.TurbineService;
+import org.apache.turbine.services.pull.tools.TemplateLink;
+
+/**
+ * This is a pull to to be used in Templates to convert links in
+ * Templates into the correct references.
+ *
+ * The pull service might insert this tool into the Context.
+ * in templates. Here's an example of its Velocity use:
+ *
+ * <p><code>
+ * $link.setPage("index.vm").addPathInfo("hello","world")
+ * This would return: http://foo.com/Turbine/template/index.vm/hello/world
+ * </code>
+ *
+ * <p>
+ *
+ * This is an application pull tool for the template system. You should <b>not</b>
+ * use it in a normal application!
+ *
+ * @author <a href="mbryson@mont.mindspring.com">Dave Bryson</a>
+ * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
+ * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
+ * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
+ * @author <a href="mailto:peter@courcoux.biz">Peter Courcoux</a>
+ * @version $Id: TemplateLink.java 1854688 2019-03-03 10:36:42Z tv $
+ */
+
+public class MappedTemplateLink extends TemplateLink
+{
+ /**
+ * The URL Mapper service.
+ */
+ @TurbineService
+ private URLMapperService urlMapperService;
+
+
+ /**
+ * Builds the URL with all of the data URL-encoded as well as
+ * encoded using HttpServletResponse.encodeUrl(). The resulting
+ * URL is absolute; it starts with http/https...
+ *
+ * <pre>
+ * TemplateURI tui = new TemplateURI (data, "UserScreen");
+ * tui.addPathInfo("user","jon");
+ * tui.getAbsoluteLink();
+ * </pre>
+ *
+ * The above call to absoluteLink() would return the String:
+ * <p>
+ * http://www.server.com/servlets/Turbine/screen/UserScreen/user/jon
+ * <p>
+ * After rendering the URI, it clears the
+ * pathInfo and QueryString portions of the TemplateURI. So you can
+ * use the $link reference multiple times on a page and start over
+ * with a fresh object every time.
+ *
+ * @return A String with the built URL.
+ */
+ public String getAbsoluteLink()
+ {
+ urlMapperService.mapToURL(templateURI);
+ return super.getAbsoluteLink();
+ }
+
+
+ /**
+ * Builds the URL with all of the data URL-encoded as well as
+ * encoded using HttpServletResponse.encodeUrl(). The resulting
+ * URL is relative to the webserver root.
+ *
+ * <pre>
+ * TemplateURI tui = new TemplateURI (data, "UserScreen");
+ * tui.addPathInfo("user","jon");
+ * tui.getRelativeLink();
+ * </pre>
+ *
+ * The above call to relativeLink() would return the String:
+ * <p>
+ * /servlets/Turbine/screen/UserScreen/user/jon
+ * <p>
+ * After rendering the URI, it clears the
+ * pathInfo and QueryString portions of the TemplateURI. So you can
+ * use the $link reference multiple times on a page and start over
+ * with a fresh object every time.
+ *
+ * @return A String with the built URL.
+ */
+ public String getRelativeLink()
+ {
+ urlMapperService.mapToURL(templateURI);
+ return super.getRelativeLink();
+ }
+
+}
diff --git a/src/java/org/apache/turbine/services/urlmapper/TurbineURLMapperService.java b/src/java/org/apache/turbine/services/urlmapper/TurbineURLMapperService.java
new file mode 100644
index 0000000..dc79c2f
--- /dev/null
+++ b/src/java/org/apache/turbine/services/urlmapper/TurbineURLMapperService.java
@@ -0,0 +1,319 @@
+package org.apache.turbine.services.urlmapper;
+
+/*
+ * 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.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+
+import org.apache.commons.configuration2.Configuration;
+import org.apache.fulcrum.parser.ParameterParser;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.turbine.services.InitializationException;
+import org.apache.turbine.services.TurbineBaseService;
+import org.apache.turbine.services.TurbineServices;
+import org.apache.turbine.services.servlet.ServletService;
+import org.apache.turbine.services.urlmapper.model.URLMapEntry;
+import org.apache.turbine.services.urlmapper.model.URLMappingContainer;
+import org.apache.turbine.util.uri.TurbineURI;
+import org.apache.turbine.util.uri.URIParam;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+
+/**
+ * The URL mapper service provides methods to map a set of parameters to a
+ * simplified URL and vice-versa. This service was inspired by the
+ * Liferay Friendly URL Mapper.
+ * <p>
+ * A mapper valve and a link pull tool are provided for easy application.
+ *
+ * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
+ * @see URLMapperService
+ * @see MappedTemplateLink
+ * @see URLMapperValve
+ *
+ * @version $Id$
+ */
+public class TurbineURLMapperService
+ extends TurbineBaseService
+ implements URLMapperService
+{
+ /**
+ * Logging.
+ */
+ private static final Logger log = LogManager.getLogger(TurbineURLMapperService.class);
+
+ /**
+ * The default configuration file.
+ */
+ private static final String DEFAULT_CONFIGURATION_FILE = "/WEB-INF/conf/turbine-url-mapping.xml";
+
+ /**
+ * The configuration key for the configuration file.
+ */
+ private static final String CONFIGURATION_FILE_KEY = "configFile";
+
+ /**
+ * The container with the URL mappings.
+ */
+ private URLMappingContainer container;
+
+ /**
+ * Regex pattern for group names
+ */
+ private static final Pattern namedGroupsPattern = Pattern.compile("\\(\\?<([a-zA-Z][a-zA-Z0-9]*)>.+?\\)");
+
+ /**
+ * Symbolic group name for context path
+ */
+ private static final String CONTEXT_PATH_PARAMETER = "contextPath";
+
+ /**
+ * Symbolic group name for web application root
+ */
+ private static final String WEBAPP_ROOT_PARAMETER = "webAppRoot";
+
+ /**
+ * Symbolic group names that will not be added to parameters
+ */
+ private static final Set<String> DEFAULT_PARAMETERS = Stream.of(
+ CONTEXT_PATH_PARAMETER,
+ WEBAPP_ROOT_PARAMETER
+ ).collect(Collectors.toSet());
+
+ /**
+ * Map a set of parameters (contained in TurbineURI PathInfo and QueryData)
+ * to a TurbineURI
+ *
+ * @param uri the URI to be modified (with setScriptName())
+ */
+ @Override
+ public void mapToURL(TurbineURI uri)
+ {
+ // Create map from list, taking only the first appearance of a key
+ // PathInfo takes precedence
+ Map<String, Object> uriParameterMap = Stream.concat(
+ uri.getPathInfo().stream(),
+ uri.getQueryData().stream())
+ .collect(Collectors.toMap(
+ URIParam::getKey,
+ URIParam::getValue,
+ (e1, e2) -> e1,
+ LinkedHashMap::new));
+
+ Set<String> keys = new HashSet<>(uriParameterMap.keySet());
+
+ if (keys.isEmpty() && uri.getQueryData().isEmpty() || uri.getPathInfo().isEmpty())
+ {
+ return; // no mapping or mapping already done
+ }
+
+ for (URLMapEntry urlMap : container.getMapEntries())
+ {
+ Set<String> entryKeys = new HashSet<>();
+
+ Map<String, Integer> groupNamesMap = urlMap.getGroupNamesMap();
+ if (groupNamesMap != null)
+ {
+ entryKeys.addAll(groupNamesMap.keySet());
+ }
+
+ Set<String> implicitKeysFound = urlMap.getImplicitParameters().entrySet().stream()
+ .filter(entry -> Objects.equals(uriParameterMap.get(entry.getKey()), entry.getValue()))
+ .map(Map.Entry::getKey)
+ .collect(Collectors.toSet());
+
+ entryKeys.addAll(implicitKeysFound);
+ implicitKeysFound.forEach(key -> {
+ uri.removePathInfo(key);
+ uri.removeQueryData(key);
+ });
+
+ keys.removeAll(urlMap.getIgnoreParameters().keySet());
+
+ if (entryKeys.containsAll(keys))
+ {
+ Matcher matcher = namedGroupsPattern.matcher(urlMap.getUrlPattern().pattern());
+ StringBuffer sb = new StringBuffer();
+
+ while (matcher.find())
+ {
+ String key = matcher.group(1);
+
+ if (CONTEXT_PATH_PARAMETER.equals(key))
+ {
+ // remove
+ matcher.appendReplacement(sb, "");
+ } else if (WEBAPP_ROOT_PARAMETER.equals(key))
+ {
+ matcher.appendReplacement(sb, uri.getScriptName());
+ } else
+ {
+ matcher.appendReplacement(sb,
+ Matcher.quoteReplacement(
+ Objects.toString(uriParameterMap.get(key))));
+ // Remove handled parameters (all of them!)
+ uri.removePathInfo(key);
+ uri.removeQueryData(key);
+ }
+ }
+
+ matcher.appendTail(sb);
+ // Clean up
+ uri.setScriptName(sb.toString().replace("//", "/"));
+ break;
+ }
+ }
+ }
+
+ /**
+ * Map a simplified URL to a set of parameters
+ *
+ * @param url the URL
+ * @param pp a ParameterParser to use for parameter mangling
+ */
+ @Override
+ public void mapFromURL(String url, ParameterParser pp)
+ {
+ for (URLMapEntry urlMap : container.getMapEntries())
+ {
+ Matcher matcher = urlMap.getUrlPattern().matcher(url);
+ if (matcher.matches())
+ {
+ // extract parameters from URL
+ Map<String, Integer> groupNameMap = urlMap.getGroupNamesMap();
+
+ if (groupNameMap != null)
+ {
+ groupNameMap.entrySet().stream()
+ // ignore default parameters
+ .filter(group -> !DEFAULT_PARAMETERS.contains(group.getKey()))
+ .forEach(group ->
+ pp.setString(group.getKey(), matcher.group(group.getValue().intValue())));
+ }
+
+ // add implicit parameters
+ urlMap.getImplicitParameters().entrySet().forEach(e ->
+ pp.add(e.getKey(), e.getValue()));
+
+ // add override parameters
+ urlMap.getOverrideParameters().entrySet().forEach(e ->
+ pp.setString(e.getKey(), e.getValue()));
+
+ // remove ignore parameters
+ urlMap.getIgnoreParameters().keySet().forEach(k ->
+ pp.remove(k));
+
+ break;
+ }
+ }
+ }
+
+ // ---- Service initialization ------------------------------------------
+
+ /**
+ * Initializes the service.
+ */
+ @Override
+ public void init() throws InitializationException
+ {
+ Configuration cfg = getConfiguration();
+
+ String configFile = cfg.getString(CONFIGURATION_FILE_KEY, DEFAULT_CONFIGURATION_FILE);
+
+ // context resource path has to begin with slash, cft.
+ // context.getResource
+ if (!configFile.startsWith("/"))
+ {
+ configFile = "/" + configFile;
+ }
+
+ ServletService servletService = (ServletService) TurbineServices.getInstance().getService(ServletService.SERVICE_NAME);
+
+ try (InputStream reader = servletService.getResourceAsStream(configFile))
+ {
+ if (configFile.endsWith(".xml"))
+ {
+ JAXBContext jaxb = JAXBContext.newInstance(URLMappingContainer.class);
+ Unmarshaller unmarshaller = jaxb.createUnmarshaller();
+ container = (URLMappingContainer) unmarshaller.unmarshal(reader);
+ } else if (configFile.endsWith(".yml"))
+ {
+ // org.apache.commons.configuration2.YAMLConfiguration does only expose property like configuration values,
+ // which is not what we need here -> java object deserialization.
+ ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
+ container = mapper.readValue(reader, URLMappingContainer.class);
+ }
+ }
+ catch (IOException | JAXBException e)
+ {
+ throw new InitializationException("Could not load configuration file " + configFile, e);
+ }
+
+ // Get groupNamesMap for every Pattern and store it in the entry
+ try
+ {
+ Method namedGroupsMethod = Pattern.class.getDeclaredMethod("namedGroups");
+ namedGroupsMethod.setAccessible(true);
+
+ for (URLMapEntry urlMap : container.getMapEntries())
+ {
+ @SuppressWarnings("unchecked")
+ Map<String, Integer> groupNamesMap = (Map<String, Integer>) namedGroupsMethod.invoke(urlMap.getUrlPattern());
+ urlMap.setGroupNamesMap(groupNamesMap);
+ }
+ }
+ catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
+ | NoSuchMethodException | SecurityException e)
+ {
+ throw new InitializationException("Could not invoke method Pattern.getNamedGroups", e);
+ }
+
+ log.info("Loaded {} url-mappings from {}", Integer.valueOf(container.getMapEntries().size()), configFile);
+
+ setInit(true);
+ }
+
+ /**
+ * Returns to uninitialized state.
+ */
+ @Override
+ public void shutdown()
+ {
+ container = null;
+ setInit(false);
+ }
+}
diff --git a/src/java/org/apache/turbine/services/urlmapper/URLMapperService.java b/src/java/org/apache/turbine/services/urlmapper/URLMapperService.java
new file mode 100644
index 0000000..98e9b2d
--- /dev/null
+++ b/src/java/org/apache/turbine/services/urlmapper/URLMapperService.java
@@ -0,0 +1,58 @@
+package org.apache.turbine.services.urlmapper;
+
+
+/*
+ * 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.
+ */
+
+import org.apache.fulcrum.parser.ParameterParser;
+import org.apache.turbine.services.Service;
+import org.apache.turbine.util.uri.TurbineURI;
+
+/**
+ * The URL mapper service provides methods to map a set of parameters to a
+ * simplified URL and vice-versa. This service was inspired by the
+ * Liferay Friendly URL Mapper.
+ *
+ * A mapper valve and a link pull tool are provided for easy application.
+ *
+ * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
+ */
+public interface URLMapperService extends Service
+{
+ /**
+ * The service identifier.
+ */
+ String SERVICE_NAME = "URLMapperService";
+
+ /**
+ * Map a set of parameters (contained in TurbineURI PathInfo and QueryData)
+ * to a TurbineURI
+ *
+ * @param uri the URI to be modified (with setScriptName())
+ */
+ void mapToURL(TurbineURI uri);
+
+ /**
+ * Map a simplified URL to a set of parameters
+ *
+ * @param url the URL
+ * @param pp a ParameterParser to use for parameter mangling
+ */
+ void mapFromURL(String url, ParameterParser pp);
+}
diff --git a/src/java/org/apache/turbine/services/urlmapper/URLMapperValve.java b/src/java/org/apache/turbine/services/urlmapper/URLMapperValve.java
new file mode 100644
index 0000000..e81b3c1
--- /dev/null
+++ b/src/java/org/apache/turbine/services/urlmapper/URLMapperValve.java
@@ -0,0 +1,61 @@
+package org.apache.turbine.services.urlmapper;
+
+
+/*
+ * 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.
+ */
+
+
+import java.io.IOException;
+
+import org.apache.turbine.annotation.TurbineService;
+import org.apache.turbine.pipeline.PipelineData;
+import org.apache.turbine.pipeline.Valve;
+import org.apache.turbine.pipeline.ValveContext;
+import org.apache.turbine.util.RunData;
+import org.apache.turbine.util.TurbineException;
+
+/**
+ * This valve is responsible for parsing parameters out of
+ * simplified URLs.
+ *
+ * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
+ */
+public class URLMapperValve
+ implements Valve
+{
+ /** Injected service instance */
+ @TurbineService
+ private URLMapperService urlMapperService;
+
+ /**
+ * @see org.apache.turbine.pipeline.Valve#invoke(PipelineData, ValveContext)
+ */
+ @Override
+ public void invoke(PipelineData pipelineData, ValveContext context)
+ throws IOException, TurbineException
+ {
+ RunData data = pipelineData.getRunData();
+ String uri = data.getRequest().getRequestURI();
+
+ urlMapperService.mapFromURL(uri, data.getParameters());
+
+ // Pass control to the next Valve in the Pipeline
+ context.invokeNext(pipelineData);
+ }
+}
diff --git a/src/java/org/apache/turbine/services/urlmapper/model/URLMapEntry.java b/src/java/org/apache/turbine/services/urlmapper/model/URLMapEntry.java
new file mode 100644
index 0000000..ccb4d3f
--- /dev/null
+++ b/src/java/org/apache/turbine/services/urlmapper/model/URLMapEntry.java
@@ -0,0 +1,175 @@
+package org.apache.turbine.services.urlmapper.model;
+
+/*
+ * 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.
+ */
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * The url map model class
+ *
+ * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
+ */
+@XmlType(name="map")
+@XmlAccessorType(XmlAccessType.NONE)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class URLMapEntry
+{
+ private Pattern urlPattern;
+ private Map<String, String> implicit = new LinkedHashMap<>();
+ private Map<String, String> ignore = new LinkedHashMap<>();
+ private Map<String, String> override = new LinkedHashMap<>();
+
+ private Map<String, Integer> groupNamesMap;
+ private Set<String> relevantKeys = null;
+
+ /**
+ * @return the urlPattern
+ */
+ @XmlElement(name="pattern")
+ @XmlJavaTypeAdapter(XmlPatternAdapter.class)
+ @JsonProperty("pattern")
+ public Pattern getUrlPattern()
+ {
+ return urlPattern;
+ }
+
+ /**
+ * @param urlPattern the urlPattern to set
+ */
+ protected void setUrlPattern(Pattern urlPattern)
+ {
+ this.urlPattern = urlPattern;
+ }
+
+ /**
+ * @return the implicit parameters
+ */
+ @XmlElement(name="implicit-parameters")
+ @XmlJavaTypeAdapter(XmlParameterAdapter.class)
+ @JsonProperty("implicit-parameters")
+ public Map<String, String> getImplicitParameters()
+ {
+ return implicit;
+ }
+
+ /**
+ * @param implicit the implicit parameters to set
+ */
+ protected void setImplicitParameters(Map<String, String> implicit)
+ {
+ this.implicit = implicit;
+ }
+
+ /**
+ * @return the ignored parameters
+ */
+ @XmlElement(name="ignore-parameters")
+ @XmlJavaTypeAdapter(XmlParameterAdapter.class)
+ @JsonProperty("ignore-parameters")
+ public Map<String, String> getIgnoreParameters()
+ {
+ return ignore;
+ }
+
+ /**
+ * @param ignore the ignored parameters to set
+ */
+ protected void setIgnoreParameters(Map<String, String> ignore)
+ {
+ this.ignore = ignore;
+ }
+
+ /**
+ * @return the override parameters
+ */
+ @XmlElement(name="override-parameters")
+ @XmlJavaTypeAdapter(XmlParameterAdapter.class)
+ @JsonProperty("override-parameters")
+ public Map<String, String> getOverrideParameters()
+ {
+ return override;
+ }
+
+ /**
+ * @param override the override parameters to set
+ */
+ protected void setOverrideParameters(Map<String, String> override)
+ {
+ this.override = override;
+ }
+
+ /**
+ * Get the map of group names to group indices for the stored Pattern
+ *
+ * @return the groupNamesMap
+ */
+ public Map<String, Integer> getGroupNamesMap()
+ {
+ return groupNamesMap;
+ }
+
+ /**
+ * Set the map of group names to group indices for the stored Pattern
+ *
+ * @param groupNamesMap the groupNamesMap to set
+ */
+ public void setGroupNamesMap(Map<String, Integer> groupNamesMap)
+ {
+ this.groupNamesMap = groupNamesMap;
+ }
+
+ /**
+ * Get the set of relevant keys for comparison (cached for performance)
+ *
+ * @return the relevantKeys
+ */
+ public Set<String> getRelevantKeys()
+ {
+ return relevantKeys;
+ }
+
+ /**
+ * Set the set of relevant keys for comparison (cached for performance)
+ *
+ * @param relevantKeys the relevantKeys to set
+ */
+ public void setRelevantKeys(Set<String> relevantKeys)
+ {
+ this.relevantKeys = relevantKeys;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "URLMapEntry: [ pattern: " + urlPattern + ", implicit-parameters: " + implicit + ", override-parameters: " + override + ", ignore-parameters:" + ignore + " ]";
+ }
+}
diff --git a/src/java/org/apache/turbine/services/urlmapper/model/URLMappingContainer.java b/src/java/org/apache/turbine/services/urlmapper/model/URLMappingContainer.java
new file mode 100644
index 0000000..9da6915
--- /dev/null
+++ b/src/java/org/apache/turbine/services/urlmapper/model/URLMappingContainer.java
@@ -0,0 +1,98 @@
+package org.apache.turbine.services.urlmapper.model;
+
+/*
+ * 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.
+ */
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * URL Map Container Model Class
+ *
+ * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
+ */
+@XmlRootElement(name="url-mapping")
+@XmlAccessorType(XmlAccessType.NONE)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class URLMappingContainer
+{
+
+ /**
+ * Name of this map.
+ */
+ @XmlAttribute
+ private String name;
+
+ /**
+ * The list of map entries
+ */
+ @JsonProperty("maps")
+ private List<URLMapEntry> urlMapEntries = new CopyOnWriteArrayList<>();
+
+ /**
+ * Set the name of this map.
+ *
+ * @param name
+ * Name of this map.
+ */
+ protected void setName(String name)
+ {
+ this.name = name;
+ }
+
+ /**
+ * Get the name of this map.
+ *
+ * @return String Name of this map.
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * Get the list of map entries
+ */
+ @XmlElementWrapper(name="maps")
+ @XmlElement(name="map")
+ public List<URLMapEntry> getMapEntries()
+ {
+ return urlMapEntries;
+ }
+
+ /**
+ * Set new map entries during deserialization
+ *
+ * @param newURLMapEntries the newURLMapEntries to set
+ */
+ protected void setMapEntries(List<URLMapEntry> newURLMapEntries)
+ {
+ this.urlMapEntries = new CopyOnWriteArrayList<URLMapEntry>(newURLMapEntries);
+ }
+}
diff --git a/src/java/org/apache/turbine/services/urlmapper/model/XmlParameterAdapter.java b/src/java/org/apache/turbine/services/urlmapper/model/XmlParameterAdapter.java
new file mode 100644
index 0000000..609905d
--- /dev/null
+++ b/src/java/org/apache/turbine/services/urlmapper/model/XmlParameterAdapter.java
@@ -0,0 +1,62 @@
+package org.apache.turbine.services.urlmapper.model;
+
+/*
+ * 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.
+ */
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+
+import org.apache.turbine.services.urlmapper.model.XmlParameterList.XmlParameter;
+
+/**
+ * Creates Map objects from XmlParameterList objects and vice-versa.
+ *
+ * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
+ */
+public class XmlParameterAdapter extends XmlAdapter<XmlParameterList, Map<String, String>>
+{
+ /**
+ * @see javax.xml.bind.annotation.adapters.XmlAdapter#unmarshal(java.lang.Object)
+ */
+ @Override
+ public Map<String, String> unmarshal(XmlParameterList xmlList) throws Exception
+ {
+ // Make sure that order is kept
+ return xmlList.getXmlParameters().stream()
+ .collect(Collectors.toMap(xml -> xml.key, xml -> xml.value,
+ (e1, e2) -> e1, LinkedHashMap::new));
+ }
+
+ /**
+ * @see javax.xml.bind.annotation.adapters.XmlAdapter#marshal(java.lang.Object)
+ */
+ @Override
+ public XmlParameterList marshal(Map<String, String> map) throws Exception
+ {
+ XmlParameterList xmlList = new XmlParameterList();
+ xmlList.setXmlParameters(map.entrySet().stream()
+ .map(entry -> new XmlParameter(entry.getKey(), entry.getValue()))
+ .collect(Collectors.toList()));
+
+ return xmlList;
+ }
+}
diff --git a/src/java/org/apache/turbine/services/urlmapper/model/XmlParameterList.java b/src/java/org/apache/turbine/services/urlmapper/model/XmlParameterList.java
new file mode 100644
index 0000000..7f8a769
--- /dev/null
+++ b/src/java/org/apache/turbine/services/urlmapper/model/XmlParameterList.java
@@ -0,0 +1,108 @@
+package org.apache.turbine.services.urlmapper.model;
+
+/*
+ * 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.
+ */
+
+import java.util.List;
+
+/*
+ * 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.
+ */
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlValue;
+
+/**
+ * A JAXB Class for holding a list of entries with key (in an attribute) and a value.
+ *
+ * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
+ */
+@XmlAccessorType(XmlAccessType.NONE)
+public class XmlParameterList
+{
+ public static class XmlParameter
+ {
+ @XmlAttribute
+ public String key;
+
+ @XmlValue
+ public String value;
+
+ /**
+ * Default Constructor
+ */
+ public XmlParameter()
+ {
+ // empty
+ }
+
+ /**
+ * Constructor
+ *
+ * @param key the key
+ * @param value the value
+ */
+ public XmlParameter(String key, String value)
+ {
+ this.key = key;
+ this.value = value;
+ }
+ }
+
+ private List<XmlParameter> xmlParameters;
+
+ /**
+ * Get the list of XmlParameters
+ *
+ * @return the xmlParameters
+ */
+ @XmlElement(name="parameter")
+ public List<XmlParameter> getXmlParameters()
+ {
+ return xmlParameters;
+ }
+
+ /**
+ * Set a list of XmlParameters
+ *
+ * @param xmlParameters the xmlParameters to set
+ */
+ public void setXmlParameters(List<XmlParameter> xmlParameters)
+ {
+ this.xmlParameters = xmlParameters;
+ }
+}
diff --git a/src/java/org/apache/turbine/services/urlmapper/model/XmlPatternAdapter.java b/src/java/org/apache/turbine/services/urlmapper/model/XmlPatternAdapter.java
new file mode 100644
index 0000000..68a52c5
--- /dev/null
+++ b/src/java/org/apache/turbine/services/urlmapper/model/XmlPatternAdapter.java
@@ -0,0 +1,50 @@
+package org.apache.turbine.services.urlmapper.model;
+
+/*
+ * 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.
+ */
+
+import java.util.regex.Pattern;
+
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+
+/**
+ * Creates Regex Pattern objects.
+ *
+ * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
+ */
+public class XmlPatternAdapter extends XmlAdapter<String, Pattern>
+{
+ /**
+ * @see javax.xml.bind.annotation.adapters.XmlAdapter#unmarshal(java.lang.Object)
+ */
+ @Override
+ public Pattern unmarshal(String urlPattern) throws Exception
+ {
+ return Pattern.compile(urlPattern);
+ }
+
+ /**
+ * @see javax.xml.bind.annotation.adapters.XmlAdapter#marshal(java.lang.Object)
+ */
+ @Override
+ public String marshal(Pattern pattern) throws Exception
+ {
+ return pattern.pattern();
+ }
+}
diff --git a/src/java/org/apache/turbine/services/urlmapper/package.html b/src/java/org/apache/turbine/services/urlmapper/package.html
new file mode 100644
index 0000000..2f8516f
--- /dev/null
+++ b/src/java/org/apache/turbine/services/urlmapper/package.html
@@ -0,0 +1,29 @@
+<!--
+ 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.
+-->
+<html>
+<head>
+<!-- head part is ignored -->
+</head>
+
+<body>
+Provide back-and-forth-mapping facilities for simplified URLs
+<br>
+<font size="-2">$Id$</font>
+</body>
+</html>
diff --git a/src/test/org/apache/turbine/services/urlmapper/TurbineURLMapperServiceTest.java b/src/test/org/apache/turbine/services/urlmapper/TurbineURLMapperServiceTest.java
new file mode 100644
index 0000000..aaaa62e
--- /dev/null
+++ b/src/test/org/apache/turbine/services/urlmapper/TurbineURLMapperServiceTest.java
@@ -0,0 +1,353 @@
+package org.apache.turbine.services.urlmapper;
+
+/*
+ * 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.
+ */
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Spliterator;
+import java.util.SplittableRandom;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.IntConsumer;
+import java.util.stream.IntStream;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.fulcrum.parser.ParameterParser;
+import org.apache.turbine.Turbine;
+import org.apache.turbine.pipeline.PipelineData;
+import org.apache.turbine.services.TurbineServices;
+import org.apache.turbine.test.BaseTestCase;
+import org.apache.turbine.util.RunData;
+import org.apache.turbine.util.TurbineConfig;
+import org.apache.turbine.util.uri.TemplateURI;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+public class TurbineURLMapperServiceTest extends BaseTestCase
+{
+ private TurbineConfig tc = null;
+
+ private URLMapperService urlMapper = null;
+
+ @BeforeEach
+ public void setUp() throws Exception
+ {
+ tc =
+ new TurbineConfig(
+ ".",
+ "/conf/test/TurbineURLMapperServiceTest.properties");
+ tc.initialize();
+
+ urlMapper = (URLMapperService) TurbineServices.getInstance().getService(URLMapperService.SERVICE_NAME);
+ }
+
+ @AfterEach
+ public void tearDown() throws Exception
+ {
+ if (tc != null)
+ {
+ tc.dispose();
+ }
+ }
+
+ /**
+ * Tests
+ *
+ * <code>scheme://bob/wow/damn2/bookId/123</code>
+ * <code>scheme://bob/wow/book/123</code>
+ * <p>
+ * and
+ *
+ * <code>scheme://bob/wow/damn2/bookId/123/template/Book.vm?detail=1&detail=2&view=collapsed</code>
+ * <code>scheme://bob/wow/book/123/1?view=collapsed</code>
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testMapToURL() throws Exception
+ {
+ assertNotNull(urlMapper);
+ HttpServletRequest request = getMockRequest();
+ HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+
+ PipelineData pipelineData = getPipelineData(request, response, tc.getTurbine().getServletConfig());
+ assertNotNull(pipelineData);
+
+ TemplateURI uri = new TemplateURI(pipelineData.getRunData());
+ uri.clearResponse(); // avoid encoding on mocked HTTPServletResponse
+ uri.addPathInfo("bookId", 123);
+ uri.setTemplate("Book.vm");
+ uri.addQueryData("detail", 0);
+
+ urlMapper.mapToURL(uri);
+ assertEquals("/wow/book/123", uri.getRelativeLink());
+ assertTrue(uri.getPathInfo().isEmpty());
+ assertTrue(uri.getQueryData().isEmpty());
+
+ uri = new TemplateURI(pipelineData.getRunData());
+ uri.clearResponse(); // avoid encoding on mocked HTTPServletResponse
+ uri.addPathInfo("bookId", 123);
+ uri.setTemplate("Book.vm");
+ uri.addQueryData("detail", 1);
+ uri.addQueryData("detail", 2);
+ uri.addQueryData("view", "collapsed");
+
+ urlMapper.mapToURL(uri);
+ assertEquals("/wow/book/123/1?view=collapsed", uri.getRelativeLink());
+ assertTrue(uri.getPathInfo().isEmpty());
+ assertEquals(1, uri.getQueryData().size());
+ }
+
+ /**
+ * Tests
+ *
+ * <code>scheme:///app/book/123/4</code>
+ * <code>scheme:///wow/damn2/detail/4/bookId/123</code>
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testMapFromURL() throws Exception
+ {
+ assertNotNull(urlMapper);
+ HttpServletRequest request = getMockRequest();
+ HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+
+ PipelineData pipelineData = getPipelineData(request, response, tc.getTurbine().getServletConfig());
+ assertNotNull(pipelineData);
+ ParameterParser pp = pipelineData.get(Turbine.class, ParameterParser.class);
+ assertNotNull(pp);
+ assertTrue(pp.keySet().isEmpty());
+
+ urlMapper.mapFromURL("/app/book/123/4", pp);
+
+ assertEquals(3, pp.keySet().size());
+ assertEquals(123, pp.getInt("bookId"));
+ assertEquals("Book.vm", pp.getString("template"));
+ assertEquals(4, pp.getInt("detail"));
+
+ // double check
+ TemplateURI uri = new TemplateURI(pipelineData.getRunData());
+ uri.clearResponse();
+ uri.addPathInfo(pp);
+ assertEquals("/wow/damn2/detail/4/bookId/123", uri.getRelativeLink());
+ urlMapper.mapToURL(uri);
+ assertEquals("/wow/book/123/4", uri.getRelativeLink());
+ }
+
+
+ @Tag("performance")
+ @Test
+ public void testPerformance() throws Exception
+ {
+ assertNotNull(urlMapper);
+ int templateURIs = 5;
+ List<AtomicLong> counterSum = new ArrayList<>();
+ List<AtomicInteger> counters = new ArrayList<>();
+ for (int i = 0; i < templateURIs; i++)
+ {
+ counters.add(i, new AtomicInteger(0));
+ counterSum.add(i, new AtomicLong(0L));
+ }
+ int calls = 10_000; // above 1024, set max total of parser pool2 in fulcrum component configuration ..
+ boolean parallel = false;
+ IntStream range = IntStream.range(0, calls);
+ if (parallel)
+ {
+ range = range.parallel();
+ }
+
+ SplittableRandom sr = new SplittableRandom();
+
+// range
+// .peek(e -> System.out.println("current value: " + e))
+// .forEach( actionInt -> {
+// runCheck(templateURIs, counterSum, counters, parallel, sr);
+// });
+
+ Spliterator.OfInt spliterator1 = range.spliterator();
+ Spliterator.OfInt spliterator2 = spliterator1.trySplit();
+
+ System.out.println("s1 estimateSize: " + spliterator1.estimateSize());
+ spliterator1.forEachRemaining((IntConsumer) i ->
+ {
+ runCheck(templateURIs, counterSum, counters, parallel, sr);
+ });
+ System.out.println("s2 estimateSize: " + spliterator2.estimateSize());
+ spliterator2.forEachRemaining((IntConsumer) i ->
+ {
+ runCheck(templateURIs, counterSum, counters, parallel, sr);
+ });
+
+ for (int i = 0; i < counters.size() - 1; i++)
+ {
+ long time = counterSum.get(i).longValue() / 1_000_000;
+ int count = counters.get(i).get();
+ TemplateURI turi = getURI(i);
+ String relativeLink = turi.getRelativeLink();
+ callMapToUrl(turi);
+ System.out.printf("time = %dms (%d calls),average time = %5.3fmics, uri=%s, map=%s%n", time, count,
+ count > 0 ? ((double) time * 1000 / count) : 0,
+ relativeLink, turi.getRelativeLink());
+ }
+ System.out.printf("total time = %dms (%d total calls) parallel:%s%n",
+ counterSum.stream().mapToInt(i -> i.intValue()).sum() / 1_000_000,
+ counters.stream().mapToInt(i -> i.intValue()).sum(),
+ parallel
+ );
+ }
+
+ private void runCheck(int templateURIs, List<AtomicLong> counterSum, List<AtomicInteger> counters, boolean parallel,
+ SplittableRandom sr)
+ {
+ int randomNum = sr.nextInt(templateURIs);
+ TemplateURI turi = getURI(randomNum);
+ long time = System.nanoTime();
+ try
+ {
+ callMapToUrl(turi);
+ }
+ finally
+ {
+ time = System.nanoTime() - time;
+ counterSum.get(randomNum).addAndGet(time);
+ counters.get(randomNum).incrementAndGet();
+ }
+ }
+
+ /**
+ * to get a fresh URI
+ *
+ * @param tnr
+ * @return
+ */
+ private TemplateURI getURI(int tnr)
+ {
+ TemplateURI turi = null;
+ switch (tnr)
+ {
+ case 0:
+ turi = getURI1();
+ break;
+ case 1:
+ turi = getURI2();
+ break;
+ case 2:
+ turi = getURI3();
+ break;
+ case 3:
+ turi = getURI4();
+ break;
+ case 4:
+ turi = getURI5();
+ break;
+ default:
+ break;
+ }
+ return turi;
+ }
+
+ private TemplateURI getURI1()
+ {
+ TemplateURI uri = new TemplateURI(getRunData());
+ uri.clearResponse(); // avoid encoding on mocked HTTPServletResponse
+ uri.addPathInfo("bookId", 123);
+ uri.setTemplate("Book.vm");
+ uri.addQueryData("detail", 0);
+ return uri;
+ }
+
+ private TemplateURI getURI2()
+ {
+ TemplateURI uri2 = new TemplateURI(getRunData());
+ uri2.clearResponse();
+ uri2.addPathInfo("bookId", 123);
+ uri2.setTemplate("Book.vm");
+ uri2.addQueryData("detail", 1);
+ uri2.addQueryData("detail", 2);
+ uri2.addQueryData("view", "collapsed");
+ return uri2;
+ }
+
+ private TemplateURI getURI3()
+ {
+ TemplateURI uri3 = new TemplateURI(getRunData());
+ uri3.clearResponse();
+ uri3.addPathInfo("id", 1234);
+ uri3.addPathInfo("role", "guest");
+ uri3.addPathInfo("language", "de");
+ return uri3;
+ }
+
+ private TemplateURI getURI4()
+ {
+ TemplateURI uri4 = new TemplateURI(getRunData());
+ uri4.clearResponse();
+ uri4.addPathInfo("js_pane", "random-id-123-abc");
+ uri4.addPathInfo("role", "anon");
+ uri4.addPathInfo("media-type", "html");
+ uri4.setTemplate("Registerone.vm");
+ return uri4;
+ }
+
+ private TemplateURI getURI5()
+ {
+ TemplateURI uri5 = new TemplateURI(getRunData());
+ uri5.clearResponse();
+ uri5.addPathInfo("js_pane", "another-random-id-876-dfg");
+ uri5.addPathInfo("role", "anon");
+ uri5.addPathInfo("media-type", "html");
+ uri5.addPathInfo("page", "Contact");
+ return uri5;
+ }
+
+ private RunData getRunData()
+ {
+ HttpServletRequest request = getMockRequest();
+ HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+ try
+ {
+ PipelineData pipelineData = getPipelineData(request, response, tc.getTurbine().getServletConfig());
+ assertNotNull(pipelineData);
+ return pipelineData.getRunData();
+ }
+ catch (Exception e)
+ {
+ fail();
+ }
+ return null;
+ }
+
+ private void callMapToUrl(TemplateURI uri)
+ {
+ urlMapper.mapToURL(uri);
+ assertTrue(uri.getPathInfo().isEmpty(), "path is not empty:" + uri.getPathInfo());
+ }
+}
diff --git a/src/test/org/apache/turbine/services/urlmapper/TurbineYamlURLMapperServiceTest.java b/src/test/org/apache/turbine/services/urlmapper/TurbineYamlURLMapperServiceTest.java
new file mode 100644
index 0000000..b5bdc7a
--- /dev/null
+++ b/src/test/org/apache/turbine/services/urlmapper/TurbineYamlURLMapperServiceTest.java
@@ -0,0 +1,206 @@
+package org.apache.turbine.services.urlmapper;
+
+/*
+ * 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.
+ */
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.fulcrum.parser.ParameterParser;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.turbine.Turbine;
+import org.apache.turbine.pipeline.PipelineData;
+import org.apache.turbine.services.TurbineServices;
+import org.apache.turbine.test.BaseTestCase;
+import org.apache.turbine.util.RunData;
+import org.apache.turbine.util.TurbineConfig;
+import org.apache.turbine.util.uri.TemplateURI;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+@Tag("yaml")
+public class TurbineYamlURLMapperServiceTest extends BaseTestCase {
+
+ private static TurbineConfig tc = null;
+
+ private static URLMapperService urlMapper = null;
+
+ private RunData data;
+
+ Logger log = LogManager.getLogger();
+
+ @BeforeAll
+ public static void setUp() throws Exception {
+ tc = new TurbineConfig(".", "/conf/test/TurbineYamlURLMapperServiceTest.properties");
+ tc.initialize();
+
+ urlMapper = (URLMapperService) TurbineServices.getInstance().getService(URLMapperService.SERVICE_NAME);
+ }
+
+ @AfterAll
+ public static void tearDown() throws Exception {
+ if (tc != null) {
+ tc.dispose();
+ }
+ }
+
+ @BeforeEach
+ public void init() throws Exception {
+
+ ServletConfig config = tc.getTurbine().getServletConfig();
+ // mock(ServletConfig.class);
+ HttpServletRequest request = getMockRequest();
+ HttpServletResponse response = mock(HttpServletResponse.class);
+
+ data = getRunData(request, response, config);
+
+ Mockito.when(response.encodeURL(Mockito.anyString())).thenAnswer(invocation -> invocation.getArgument(0));
+ }
+
+ @Test
+ public void testMapToAnotherURL() throws Exception {
+
+ PipelineData pipelineData = data;
+
+ assertNotNull(urlMapper);
+
+ TemplateURI uri = new TemplateURI(pipelineData.getRunData());
+ uri.addPathInfo("id", 1234);
+ uri.addPathInfo("role", "guest");
+ uri.addPathInfo("language", "de");
+
+ String unMappedURL = uri.getAbsoluteLink(); // scheme://bob/wow/damn2/id/1234/role/guest
+
+ urlMapper.mapToURL(uri);
+ urlMapper.mapToURL(uri); // should be idempotent
+
+ String mappedLink = uri.getRelativeLink(); // wow/damn2/id/1234/role/guest
+ log.info(unMappedURL);
+ log.info(mappedLink);
+
+ String expectedMappedURL = "/wow/1234/guest/de";
+ String expectedRawURL = "scheme://bob/wow/damn2/id/1234/role/guest/language/de";
+ // raw url
+ assertEquals(expectedRawURL, unMappedURL);
+ assertEquals(expectedMappedURL, mappedLink);
+
+ ParameterParser pp = pipelineData.get(Turbine.class, ParameterParser.class);
+ assertNotNull(pp);
+ assertTrue(pp.keySet().isEmpty());
+ urlMapper.mapFromURL(mappedLink, pp);
+
+ assertEquals(5, pp.keySet().size());
+ assertEquals(1234, pp.getInt("id"));
+ assertEquals("guest", pp.getString("role"));
+ assertEquals("de", pp.getString("language"));
+ assertEquals("html", pp.getString("media-type"));
+
+ TemplateURI uri2 = new TemplateURI(pipelineData.getRunData());
+ uri2.clearResponse();
+ uri2.setTemplate("default.vm");
+ uri2.addPathInfo(pp);
+ // this is an artifical url
+ assertEquals("scheme://bob/wow/damn2/template/default.vm/media-type/html/role/guest/id/1234/language/de",
+ uri2.getAbsoluteLink());
+ urlMapper.mapToURL(uri2);
+ assertEquals(expectedMappedURL, uri2.getRelativeLink());
+ }
+
+ @Test
+ public void testOverrideShortURL() throws Exception {
+
+ PipelineData pipelineData = data;
+
+ assertNotNull(urlMapper);
+
+ ParameterParser pp = pipelineData.get(Turbine.class, ParameterParser.class);
+ assertNotNull(pp);
+ assertTrue(pp.keySet().isEmpty());
+
+ pp.add("role", "admin"); // will not be overridden
+ urlMapper.mapFromURL("/app/register", pp);
+
+ assertEquals(4, pp.keySet().size());
+ assertEquals("random-id-123-abc", pp.getString("js_pane"));
+ assertEquals("admin", pp.getString("role"));
+// assertEquals("de", pp.getString("language"));
+ assertEquals("html", pp.getString("media-type"));
+ assertEquals("Registerone.vm", pp.getString("template"));
+
+ TemplateURI uri2 = new TemplateURI(pipelineData.getRunData());
+ uri2.clearResponse();
+ uri2.setTemplate("Registerone.vm");
+ pp.remove("role");
+ pp.add("role", "anon");
+ uri2.addPathInfo(pp);
+
+ // this is an artifical url, as the exact sequence could not be reconstructed as
+ // ParameterParser uses expicitely a random access table
+ assertEquals(
+ "scheme://bob/wow/damn2/template/Registerone.vm/media-type/html/js_pane/random-id-123-abc/role/anon",
+ uri2.getAbsoluteLink());
+ urlMapper.mapToURL(uri2);
+ String expectedMappedURL = "/wow/register";
+ assertEquals(expectedMappedURL, uri2.getRelativeLink());
+
+ pp.clear();
+ pp.add("role", "admin");// will be overridden
+ urlMapper.mapFromURL("/app/contact", pp);
+ assertEquals(4, pp.keySet().size());
+ assertEquals("anon", pp.getString("role"));
+ assertEquals("another-random-id-876-dfg", pp.getString("js_pane"));
+
+ uri2 = new TemplateURI(pipelineData.getRunData());
+ uri2.clearResponse();
+ uri2.addPathInfo(pp);
+
+ // this is an artifical url
+ assertEquals("scheme://bob/wow/damn2/page/Contact/media-type/html/js_pane/another-random-id-876-dfg/role/anon",
+ uri2.getAbsoluteLink());
+ urlMapper.mapToURL(uri2);
+ expectedMappedURL = "/wow/contact";
+ assertEquals(expectedMappedURL, uri2.getRelativeLink());
+
+ }
+
+// /**
+// * Not implemented Test for MappedTemplateLink:
+// * - To work with <i>MappedTemplateLink</i>, we need access to the urlmapperservice in order to
+// * - simulate a request without pipeline (setting velocity context and initializing the service):
+// */
+// @Test
+// public void testMappedURILink() {
+// MappedTemplateLink ml = MappedTemplateLink.class.getDeclaredConstructor().newInstance();
+// assertNotNull(ml);
+// ml.setUrlMapperService(urlMapper);
+// ml.init(data);
+// }
+
+}
diff --git a/src/test/org/apache/turbine/services/urlmapper/model/URLMappingContainerTest.java b/src/test/org/apache/turbine/services/urlmapper/model/URLMappingContainerTest.java
new file mode 100644
index 0000000..45b6412
--- /dev/null
+++ b/src/test/org/apache/turbine/services/urlmapper/model/URLMappingContainerTest.java
@@ -0,0 +1,65 @@
+package org.apache.turbine.services.urlmapper.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Unmarshaller;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class URLMappingContainerTest
+{
+ URLMappingContainer container;
+
+ @Before
+ public void setUp() throws Exception
+ {
+ try (InputStream reader = new FileInputStream("conf/turbine-url-mapping.xml"))
+ {
+ JAXBContext jaxb = JAXBContext.newInstance(URLMappingContainer.class);
+ Unmarshaller unmarshaller = jaxb.createUnmarshaller();
+ container = (URLMappingContainer) unmarshaller.unmarshal(reader);
+ }
+ }
+
+ @Test
+ public void testGetName()
+ {
+ assertNotNull(container);
+ assertEquals("default", container.getName());
+ }
+
+ @Test
+ public void testGetMapEntries()
+ {
+ assertNotNull(container);
+
+ List<URLMapEntry> mapEntries = container.getMapEntries();
+ assertNotNull(mapEntries);
+ assertNotEquals(0, mapEntries.size());
+
+ URLMapEntry entry = mapEntries.get(0);
+ assertNotNull(entry);
+
+ Pattern pattern = entry.getUrlPattern();
+ assertNotNull(pattern);
+ assertTrue(pattern.matcher("/app/book/123").matches());
+
+ Map<String, String> implicit = entry.getImplicitParameters();
+ assertNotNull(implicit);
+ assertEquals(2, implicit.size());
+ assertEquals("Book.vm", implicit.get("template"));
+ assertEquals("0", implicit.get("detail"));
+ }
+
+}
diff --git a/src/test/org/apache/turbine/services/urlmapper/model/YamlURLMappingContainerTest.java b/src/test/org/apache/turbine/services/urlmapper/model/YamlURLMappingContainerTest.java
new file mode 100644
index 0000000..b35f310
--- /dev/null
+++ b/src/test/org/apache/turbine/services/urlmapper/model/YamlURLMappingContainerTest.java
@@ -0,0 +1,66 @@
+package org.apache.turbine.services.urlmapper.model;
+
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+
+@Tag("yaml")
+public class YamlURLMappingContainerTest
+{
+ private static URLMappingContainer container;
+
+ @BeforeAll
+ public static void setUp() throws Exception
+ {
+ try (InputStream reader = new FileInputStream("conf/turbine-url-mapping.yml"))
+ {
+ ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
+ // List<URLMapEntry> urlList =
+ // mapper.readValue(reader, mapper.getTypeFactory().constructCollectionType(List.class, URLMapEntry.class));//
+ container = mapper.readValue(reader, URLMappingContainer.class);
+ }
+ }
+
+ @Test
+ public void testGetName()
+ {
+ assertNotNull(container);
+ assertEquals("default", container.getName());
+ }
+
+ @Test
+ public void testGetMapEntries()
+ {
+ assertNotNull(container);
+
+ List<URLMapEntry> mapEntries = container.getMapEntries();
+ assertNotNull(mapEntries);
+ assertNotEquals(0, mapEntries.size());
+
+ URLMapEntry entry = mapEntries.get(0);
+ assertNotNull(entry);
+
+ Pattern pattern = entry.getUrlPattern();
+ assertNotNull(pattern);
+ assertTrue(pattern.matcher("/app/book/123").matches());
+
+ Map<String, String> implicit = entry.getImplicitParameters();
+ assertNotNull(implicit);
+ assertEquals(2, implicit.size());
+ assertEquals("Book.vm", implicit.get("template"));
+ assertEquals("0", implicit.get("detail"));
+ }
+
+}
diff --git a/src/test/org/apache/turbine/test/BaseTestCase.java b/src/test/org/apache/turbine/test/BaseTestCase.java
index 9d539ea..dc1ff2f 100644
--- a/src/test/org/apache/turbine/test/BaseTestCase.java
+++ b/src/test/org/apache/turbine/test/BaseTestCase.java
@@ -41,10 +41,10 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
-import javax.xml.parsers.FactoryConfigurationError;
import org.apache.log4j.PropertyConfigurator;
-import org.apache.log4j.xml.DOMConfigurator;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+import org.apache.logging.log4j.core.config.Configurator;
import org.apache.turbine.TurbineConstants;
import org.apache.turbine.pipeline.PipelineData;
import org.apache.turbine.services.TurbineServices;
@@ -65,43 +65,13 @@
*/
public abstract class BaseTestCase
{
- static File log4jFile = new File("conf/test/log4j.xml");
+ static File log4j2File = new File("conf/test/log4j2.xml");
@BeforeClass
public static void baseInit()
throws Exception
{
-
- if (log4jFile.getName().endsWith(".xml"))
- {
- // load XML type configuration
- // NOTE: Only system property expansion available
- try
- {
- DOMConfigurator.configure(log4jFile.getAbsolutePath());
- }
- catch (FactoryConfigurationError e)
- {
- System.err.println("Could not configure Log4J from configuration file "
- + log4jFile + ": ");
- e.printStackTrace();
- }
- }
- else {
- Properties p = new Properties();
- try
- {
- p.load(new FileInputStream(log4jFile));
- p.setProperty(TurbineConstants.APPLICATION_ROOT_KEY, new File(".").getAbsolutePath());
- PropertyConfigurator.configure(p);
-
- }
- catch (FileNotFoundException fnf)
- {
- System.err.println("Could not open Log4J configuration file "
- + log4jFile);
- }
- }
+ // auto load log4j2 file
}
protected RunData getRunData(HttpServletRequest request,HttpServletResponse response,ServletConfig config) throws Exception {