| <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="de"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>TurbineURLMapperService.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Apache Turbine</a> > <a href="index.source.html" class="el_package">org.apache.turbine.services.urlmapper</a> > <span class="el_source">TurbineURLMapperService.java</span></div><h1>TurbineURLMapperService.java</h1><pre class="source lang-java linenums">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.util.Arrays; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| 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 jakarta.xml.bind.JAXBContext; |
| import jakarta.xml.bind.JAXBException; |
| import jakarta.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.databind.json.JsonMapper; |
| 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$ |
| */ |
| <span class="fc" id="L72">public class TurbineURLMapperService</span> |
| extends TurbineBaseService |
| implements URLMapperService |
| { |
| /** |
| * Logging. |
| */ |
| <span class="fc" id="L79"> private static final Logger log = LogManager.getLogger(TurbineURLMapperService.class);</span> |
| |
| /** |
| * 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, equivalent to the characters defined in java {@link Pattern} (private) groupname method. |
| */ |
| <span class="fc" id="L99"> private static final Pattern NAMED_GROUPS_PATTERN = Pattern.compile("\\(\\?<([a-zA-Z][a-zA-Z0-9]*)>.+?\\)");</span> |
| |
| /** |
| * Regex pattern for multiple slashes |
| */ |
| <span class="fc" id="L104"> private static final Pattern MULTI_SLASH_PATTERN = Pattern.compile("[/]+");</span> |
| |
| /** |
| * 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 |
| */ |
| <span class="fc" id="L119"> private static final Set<String> DEFAULT_PARAMETERS = new HashSet<>(Arrays.asList(</span> |
| CONTEXT_PATH_PARAMETER, |
| WEBAPP_ROOT_PARAMETER |
| )); |
| |
| /** |
| * 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) |
| { |
| <span class="pc bpc" id="L133" title="3 of 4 branches missed."> if (!uri.hasPathInfo() && !uri.hasQueryData())</span> |
| { |
| <span class="nc" id="L135"> return; // no mapping or mapping already done</span> |
| } |
| |
| <span class="fc" id="L138"> List<URIParam> pathInfo = uri.getPathInfo();</span> |
| <span class="fc" id="L139"> List<URIParam> queryData = uri.getQueryData();</span> |
| |
| // Create map from list, taking only the first appearance of a key |
| // PathInfo takes precedence |
| <span class="fc" id="L143"> Map<String, Object> uriParameterMap =</span> |
| <span class="fc" id="L144"> Stream.concat(pathInfo.stream(), queryData.stream())</span> |
| <span class="fc" id="L145"> .collect(Collectors.toMap(</span> |
| URIParam::getKey, |
| URIParam::getValue, |
| <span class="fc" id="L148"> (e1, e2) -> e1,</span> |
| LinkedHashMap::new)); |
| |
| <span class="pc bpc" id="L151" title="1 of 2 branches missed."> for (URLMapEntry urlMap : container.getMapEntries())</span> |
| { |
| <span class="fc" id="L153"> Set<String> keys = new HashSet<>(uriParameterMap.keySet());</span> |
| <span class="fc" id="L154"> keys.removeAll(urlMap.getIgnoreParameters().keySet());</span> |
| |
| <span class="fc" id="L156"> Set<String> entryKeys = new HashSet<>(urlMap.getGroupNamesMap().keySet());</span> |
| |
| <span class="fc" id="L158"> Set<String> implicitKeysFound = urlMap.getImplicitParameters().entrySet().stream()</span> |
| <span class="fc" id="L159"> .filter(entry -> Objects.equals(uriParameterMap.get(entry.getKey()), entry.getValue()))</span> |
| <span class="fc" id="L160"> .map(Map.Entry::getKey)</span> |
| <span class="fc" id="L161"> .collect(Collectors.toSet());</span> |
| |
| <span class="fc" id="L163"> entryKeys.addAll(implicitKeysFound);</span> |
| |
| <span class="fc bfc" id="L165" title="All 2 branches covered."> if (entryKeys.containsAll(keys))</span> |
| { |
| <span class="fc" id="L167"> Matcher matcher = NAMED_GROUPS_PATTERN.matcher(urlMap.getUrlPattern().pattern());</span> |
| <span class="fc" id="L168"> StringBuffer sb = new StringBuffer();</span> |
| |
| <span class="fc bfc" id="L170" title="All 2 branches covered."> while (matcher.find())</span> |
| { |
| <span class="fc" id="L172"> String key = matcher.group(1);</span> |
| |
| <span class="fc bfc" id="L174" title="All 2 branches covered."> if (CONTEXT_PATH_PARAMETER.equals(key))</span> |
| { |
| // remove |
| <span class="fc" id="L177"> matcher.appendReplacement(sb, "");</span> |
| <span class="fc bfc" id="L178" title="All 2 branches covered."> } else if (WEBAPP_ROOT_PARAMETER.equals(key))</span> |
| { |
| <span class="fc" id="L180"> matcher.appendReplacement(sb, uri.getScriptName());</span> |
| } else |
| { |
| <span class="fc" id="L183"> boolean ignore = urlMap.getIgnoreParameters().keySet().stream()</span> |
| <span class="fc" id="L184"> .anyMatch( x-> x.equals( key ) );</span> |
| <span class="fc" id="L185"> matcher.appendReplacement(sb,</span> |
| <span class="fc" id="L186"> Matcher.quoteReplacement(</span> |
| <span class="fc bfc" id="L187" title="All 2 branches covered."> (!ignore)? Objects.toString(uriParameterMap.get(key)):""));</span> |
| // Remove handled parameters (all of them!) |
| <span class="fc" id="L189"> pathInfo.removeIf(uriParam -> key.equals(uriParam.getKey()));</span> |
| <span class="fc" id="L190"> queryData.removeIf(uriParam -> key.equals(uriParam.getKey()));</span> |
| } |
| <span class="fc" id="L192"> }</span> |
| |
| <span class="fc" id="L194"> matcher.appendTail(sb);</span> |
| |
| <span class="fc" id="L196"> implicitKeysFound.forEach(key -> {</span> |
| <span class="fc" id="L197"> pathInfo.removeIf(uriParam -> key.equals(uriParam.getKey()));</span> |
| <span class="fc" id="L198"> queryData.removeIf(uriParam -> key.equals(uriParam.getKey()));</span> |
| <span class="fc" id="L199"> });</span> |
| |
| // Clean up |
| <span class="fc" id="L202"> uri.setScriptName(MULTI_SLASH_PATTERN.matcher(sb).replaceAll("/").replaceFirst( "/$", "" ));</span> |
| |
| <span class="fc" id="L204"> break;</span> |
| } |
| <span class="fc" id="L206"> }</span> |
| |
| <span class="fc" id="L208"> log.debug("mapped to uri: {} ", uri);</span> |
| <span class="fc" id="L209"> }</span> |
| |
| /** |
| * 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) |
| { |
| <span class="pc bpc" id="L220" title="1 of 2 branches missed."> for (URLMapEntry urlMap : container.getMapEntries())</span> |
| { |
| <span class="fc" id="L222"> url = url.replaceFirst( "/$", "" );</span> |
| <span class="fc" id="L223"> Matcher matcher = urlMap.getUrlPattern().matcher(url);</span> |
| <span class="fc bfc" id="L224" title="All 2 branches covered."> if (matcher.matches())</span> |
| { |
| // extract parameters from URL |
| <span class="fc" id="L227"> urlMap.getGroupNamesMap().entrySet().stream()</span> |
| // ignore default parameters |
| <span class="fc bfc" id="L229" title="All 2 branches covered."> .filter(group -> !DEFAULT_PARAMETERS.contains(group.getKey()))</span> |
| <span class="fc" id="L230"> .forEach(group -></span> |
| <span class="fc" id="L231"> pp.setString(group.getKey(), matcher.group(group.getValue().intValue())));</span> |
| |
| // add implicit parameters |
| <span class="fc" id="L234"> urlMap.getImplicitParameters()</span> |
| <span class="fc" id="L235"> .forEach(pp::add);</span> |
| |
| // add override parameters |
| <span class="fc" id="L238"> urlMap.getOverrideParameters()</span> |
| <span class="fc" id="L239"> .forEach(pp::setString);</span> |
| |
| // remove ignore parameters |
| <span class="fc" id="L242"> urlMap.getIgnoreParameters().keySet()</span> |
| <span class="fc" id="L243"> .forEach(pp::remove);</span> |
| |
| <span class="fc" id="L245"> log.debug("mapped {} params from url {} ", pp.getKeys().length, url);</span> |
| |
| <span class="fc" id="L247"> break;</span> |
| } |
| <span class="fc" id="L249"> }</span> |
| <span class="fc" id="L250"> }</span> |
| |
| // ---- Service initialization ------------------------------------------ |
| |
| /** |
| * Initializes the service. |
| */ |
| @Override |
| public void init() throws InitializationException |
| { |
| <span class="fc" id="L260"> Configuration cfg = getConfiguration();</span> |
| |
| <span class="fc" id="L262"> String configFile = cfg.getString(CONFIGURATION_FILE_KEY, DEFAULT_CONFIGURATION_FILE);</span> |
| |
| // context resource path has to begin with slash, cft. |
| // context.getResource |
| <span class="fc bfc" id="L266" title="All 2 branches covered."> if (!configFile.startsWith("/"))</span> |
| { |
| <span class="fc" id="L268"> configFile = "/" + configFile;</span> |
| } |
| |
| <span class="fc" id="L271"> ServletService servletService = (ServletService) TurbineServices.getInstance().getService(ServletService.SERVICE_NAME);</span> |
| |
| <span class="fc" id="L273"> try (InputStream reader = servletService.getResourceAsStream(configFile))</span> |
| { |
| <span class="fc bfc" id="L275" title="All 2 branches covered."> if (configFile.endsWith(".xml"))</span> |
| { |
| <span class="fc" id="L277"> JAXBContext jaxb = JAXBContext.newInstance(URLMappingContainer.class);</span> |
| <span class="fc" id="L278"> Unmarshaller unmarshaller = jaxb.createUnmarshaller();</span> |
| <span class="fc" id="L279"> container = (URLMappingContainer) unmarshaller.unmarshal(reader);</span> |
| <span class="pc bpc" id="L280" title="1 of 2 branches missed."> } else if (configFile.endsWith(".yml"))</span> |
| { |
| // org.apache.commons.configuration2.YAMLConfiguration does only expose property like configuration values, |
| // which is not what we need here -> java object deserialization. |
| <span class="nc" id="L284"> ObjectMapper mapper = new ObjectMapper(new YAMLFactory());</span> |
| <span class="nc" id="L285"> container = mapper.readValue(reader, URLMappingContainer.class);</span> |
| <span class="pc bpc" id="L286" title="1 of 2 branches missed."> } else if (configFile.endsWith(".json"))</span> |
| { |
| <span class="fc" id="L288"> ObjectMapper mapper = JsonMapper.builder().build();</span> |
| <span class="fc" id="L289"> container = mapper.readValue(reader, URLMappingContainer.class);</span> |
| } |
| } |
| <span class="nc" id="L292"> catch (IOException | JAXBException e)</span> |
| { |
| <span class="nc" id="L294"> throw new InitializationException("Could not load configuration file " + configFile, e);</span> |
| <span class="fc" id="L295"> }</span> |
| |
| // Get groupNamesMap for every Pattern and store it in the entry |
| <span class="fc bfc" id="L298" title="All 2 branches covered."> for (URLMapEntry urlMap : container.getMapEntries())</span> |
| { |
| <span class="fc" id="L300"> int position = 1;</span> |
| <span class="fc" id="L301"> Map<String, Integer> groupNamesMap = new HashMap<>();</span> |
| <span class="fc" id="L302"> Matcher matcher = NAMED_GROUPS_PATTERN.matcher(urlMap.getUrlPattern().pattern());</span> |
| |
| <span class="fc bfc" id="L304" title="All 2 branches covered."> while (matcher.find())</span> |
| { |
| <span class="fc" id="L306"> groupNamesMap.put(matcher.group(1), Integer.valueOf(position++));</span> |
| } |
| <span class="fc" id="L308"> urlMap.setGroupNamesMap(groupNamesMap);</span> |
| <span class="fc" id="L309"> }</span> |
| |
| <span class="fc" id="L311"> log.info("Loaded {} url-mappings from {}", Integer.valueOf(container.getMapEntries().size()), configFile);</span> |
| |
| <span class="fc" id="L313"> setInit(true);</span> |
| <span class="fc" id="L314"> }</span> |
| |
| /** |
| * Returns to uninitialized state. |
| */ |
| @Override |
| public void shutdown() |
| { |
| <span class="fc" id="L322"> container.getMapEntries().clear();</span> |
| <span class="fc" id="L323"> setInit(false);</span> |
| <span class="fc" id="L324"> }</span> |
| } |
| </pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html> |