blob: a60e2be8978f541e67b4e23bca9ba257af552a3f [file] [log] [blame]
package org.apache.turbine.util.uri;
/*
* 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.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.fulcrum.parser.ParameterParser;
import org.apache.fulcrum.parser.ParserService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.turbine.services.TurbineServices;
import org.apache.turbine.util.RunData;
import org.apache.turbine.util.ServerData;
/**
* This class allows you to keep all the information needed for a single
* link at one place. It keeps your query data, path info, the server
* scheme, name, port and the script path.
*
* If you must generate a Turbine Link, use this class.
*
* @author <a href="mailto:jon@clearink.com">Jon S. Stevens</a>
* @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a>
* @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
* @version $Id$
*/
public class TurbineURI
extends BaseURI
{
/** Logging */
private static final Logger log = LogManager.getLogger(TurbineURI.class);
/** Contains the PathInfo and QueryData vectors */
private List<URIParam> [] dataVectors = null;
/** Local reference to the parser service for URI parameter folding */
private ParserService parserService;
/** URI Parameter encoding as defined by the parser service */
private Charset parameterEncoding;
/*
* ========================================================================
*
* Constructors
*
* ========================================================================
*
*/
/**
* Empty C'tor. Uses Turbine.getDefaultServerData().
*/
public TurbineURI()
{
super();
init();
}
/**
* Constructor with a RunData object.
*
* @param runData A RunData object
*/
public TurbineURI(RunData runData)
{
super(runData);
init();
}
/**
* Constructor, set explicit redirection.
*
* @param runData A RunData object
* @param redirect True if redirection allowed.
*/
public TurbineURI(RunData runData, boolean redirect)
{
super(runData, redirect);
init();
}
/**
* Constructor, set Screen.
*
* @param runData A RunData object
* @param screen A Screen Name
*/
public TurbineURI(RunData runData, String screen)
{
this(runData);
setScreen(screen);
}
/**
* Constructor, set Screen, set explicit redirection.
*
* @param runData A RunData object
* @param screen A Screen Name
* @param redirect True if redirection allowed.
*/
public TurbineURI(RunData runData, String screen, boolean redirect)
{
this(runData, redirect);
setScreen(screen);
}
/**
* Constructor, set Screen and Action.
*
* @param runData A RunData object
* @param screen A Screen Name
* @param action An Action Name
*/
public TurbineURI(RunData runData, String screen, String action)
{
this(runData, screen);
setAction(action);
}
/**
* Constructor, set Screen and Action, set explicit redirection.
*
* @param runData A RunData object
* @param screen A Screen Name
* @param action An Action Name
* @param redirect True if redirection allowed.
*/
public TurbineURI(RunData runData, String screen, String action, boolean redirect)
{
this(runData, screen, redirect);
setAction(action);
}
/**
* Constructor with a ServerData object.
*
* @param serverData A ServerData object
*/
public TurbineURI(ServerData serverData)
{
super(serverData);
init();
}
/**
* Constructor, set explicit redirection.
*
* @param serverData A ServerData object
* @param redirect True if redirection allowed.
*/
public TurbineURI(ServerData serverData, boolean redirect)
{
super(serverData, redirect);
init();
}
/**
* Constructor, set Screen.
*
* @param serverData A ServerData object
* @param screen A Screen Name
*/
public TurbineURI(ServerData serverData, String screen)
{
this(serverData);
setScreen(screen);
}
/**
* Constructor, set Screen, set explicit redirection.
*
* @param serverData A ServerData object
* @param screen A Screen Name
* @param redirect True if redirection allowed.
*/
public TurbineURI(ServerData serverData, String screen, boolean redirect)
{
this(serverData, redirect);
setScreen(screen);
}
/**
* Constructor, set Screen and Action.
*
* @param serverData A ServerData object
* @param screen A Screen Name
* @param action An Action Name
*/
public TurbineURI(ServerData serverData, String screen, String action)
{
this(serverData, screen);
setAction(action);
}
/**
* Constructor, set Screen and Action, set explicit redirection.
*
* @param serverData A ServerData object
* @param screen A Screen Name
* @param action An Action Name
* @param redirect True if redirection allowed.
*/
public TurbineURI(ServerData serverData, String screen, String action,
boolean redirect)
{
this(serverData, screen, redirect);
setAction(action);
}
/**
* Constructor, user Turbine.getDefaultServerData(), set Screen and Action.
*
* @param screen A Screen Name
* @param action An Action Name
*/
public TurbineURI(String screen, String action)
{
this();
setScreen(screen);
setAction(action);
}
/*
* ========================================================================
*
* Init
*
* ========================================================================
*
*/
/**
* Init the TurbineURI.
*/
@SuppressWarnings("unchecked")
private void init()
{
dataVectors = new List[2];
dataVectors[PATH_INFO] = new ArrayList<>();
dataVectors[QUERY_DATA] = new ArrayList<>();
parserService = (ParserService)TurbineServices.getInstance().getService(ParserService.ROLE);
try
{
parameterEncoding = Charset.forName(parserService.getParameterEncoding());
}
catch (IllegalCharsetNameException | UnsupportedCharsetException e)
{
log.error("Unsupported encoding {}", parserService.getParameterEncoding(), e);
}
}
/**
* Sets the action= value for this URL.
*
* By default it adds the information to the path_info instead
* of the query data. An empty value (null or "") cleans out
* an existing value.
*
* @param action A String with the action value.
*/
public void setAction(String action)
{
if(StringUtils.isNotEmpty(action))
{
add(PATH_INFO, CGI_ACTION_PARAM, action);
}
else
{
clearAction();
}
}
/**
* Sets the fired eventSubmit= value for this URL.
*
* @param event The event to fire.
*
*/
public void setEvent(String event)
{
add(PATH_INFO, EVENT_PREFIX + event, event);
}
/**
* Sets the action= and eventSubmit= values for this URL.
*
* By default it adds the information to the path_info instead
* of the query data. An empty value (null or "") for the action cleans out
* an existing value. An empty value (null or "") for the event has no
* effect.
*
* @param action A String with the action value.
* @param event A string with the event name.
*/
public void setActionEvent(String action, String event)
{
setAction(action);
if(StringUtils.isNotEmpty(event))
{
setEvent(event);
}
}
/**
* Clears the action= value for this URL.
*/
public void clearAction()
{
removePathInfo(CGI_ACTION_PARAM);
}
/**
* Sets the screen= value for this URL.
*
* By default it adds the information to the path_info instead
* of the query data. An empty value (null or "") cleans out
* an existing value.
*
* @param screen A String with the screen value.
*/
public void setScreen(String screen)
{
if(StringUtils.isNotEmpty(screen))
{
add(PATH_INFO, CGI_SCREEN_PARAM, screen);
}
else
{
clearScreen();
}
}
/**
* Clears the screen= value for this URL.
*/
public void clearScreen()
{
removePathInfo(CGI_SCREEN_PARAM);
}
/*
* ========================================================================
*
* Adding and removing Data from the Path Info and Query Data
*
* ========================================================================
*/
/**
* Adds a name=value pair for every entry in a ParameterParser
* object to the path_info string.
*
* @param pp A ParameterParser.
*/
public void addPathInfo(ParameterParser pp)
{
add(PATH_INFO, pp);
}
/**
* Adds an existing List of URIParam objects to
* the path_info string.
*
* @param list A list with URIParam objects.
*/
public void addPathInfo(List<URIParam> list)
{
add(PATH_INFO, list);
}
/**
* Adds a name=value pair to the path_info string.
*
* @param name A String with the name to add.
* @param value An Object with the value to add.
*/
public void addPathInfo(String name, Object value)
{
add(PATH_INFO, name, null == value ? null : value.toString());
}
/**
* Adds a name=value pair to the path_info string.
*
* @param name A String with the name to add.
* @param value A String with the value to add.
*/
public void addPathInfo(String name, String value)
{
add(PATH_INFO, name, value);
}
/**
* Adds a name=value pair to the path_info string.
*
* @param name A String with the name to add.
* @param value A double with the value to add.
*/
public void addPathInfo(String name, double value)
{
add(PATH_INFO, name, Double.toString(value));
}
/**
* Adds a name=value pair to the path_info string.
*
* @param name A String with the name to add.
* @param value An int with the value to add.
*/
public void addPathInfo(String name, int value)
{
add(PATH_INFO, name, Integer.toString(value));
}
/**
* Adds a name=value pair to the path_info string.
*
* @param name A String with the name to add.
* @param value A long with the value to add.
*/
public void addPathInfo(String name, long value)
{
add(PATH_INFO, name, Long.toString(value));
}
/**
* Adds a name=value pair to the query string.
*
* @param name A String with the name to add.
* @param value An Object with the value to add.
*/
public void addQueryData(String name, Object value)
{
add(QUERY_DATA, name, null == value ? null : value.toString());
}
/**
* Adds a name=value pair to the query string.
*
* @param name A String with the name to add.
* @param value A String with the value to add.
*/
public void addQueryData(String name, String value)
{
add(QUERY_DATA, name, value);
}
/**
* Adds a name=value pair to the query string.
*
* @param name A String with the name to add.
* @param value A double with the value to add.
*/
public void addQueryData(String name, double value)
{
add(QUERY_DATA, name, Double.toString(value));
}
/**
* Adds a name=value pair to the query string.
*
* @param name A String with the name to add.
* @param value An int with the value to add.
*/
public void addQueryData(String name, int value)
{
add(QUERY_DATA, name, Integer.toString(value));
}
/**
* Adds a name=value pair to the query string.
*
* @param name A String with the name to add.
* @param value A long with the value to add.
*/
public void addQueryData(String name, long value)
{
add(QUERY_DATA, name, Long.toString(value));
}
/**
* Adds a name=value pair for every entry in a ParameterParser
* object to the query string.
*
* @param pp A ParameterParser.
*/
public void addQueryData(ParameterParser pp)
{
add(QUERY_DATA, pp);
}
/**
* Adds an existing List of URIParam objects to the query data.
*
* @param list A list with URIParam objects.
*/
public void addQueryData(List<URIParam> list)
{
add(QUERY_DATA, list);
}
/**
* Is Path Info data set in this URI?
*
* @return true if Path Info has values
*/
public boolean hasPathInfo()
{
return !dataVectors[PATH_INFO].isEmpty();
}
/**
* Removes all the path info elements.
*/
public void removePathInfo()
{
dataVectors[PATH_INFO].clear();
}
/**
* Removes a name=value pair from the path info.
*
* @param name A String with the name to be removed.
*/
public void removePathInfo(String name)
{
remove(PATH_INFO, name);
}
/**
* Is Query data set in this URI?
*
* @return true if Query data has values
*/
public boolean hasQueryData()
{
return !dataVectors[QUERY_DATA].isEmpty();
}
/**
* Removes all the query string elements.
*/
public void removeQueryData()
{
dataVectors[QUERY_DATA].clear();
}
/**
* Removes a name=value pair from the query string.
*
* @param name A String with the name to be removed.
*/
public void removeQueryData(String name)
{
remove (QUERY_DATA, name);
}
/**
* Template Link and friends want to be able to turn the encoding
* of the servlet container off. After calling this method,
* the no encoding will happen any longer. If you think, that you
* need this outside a template context, think again.
*/
public void clearResponse()
{
setResponse(null);
}
/**
* 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>
* TurbineURI tui = new TurbineURI (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>
*
* @return A String with the built URL.
*/
public String getAbsoluteLink()
{
StringBuilder output = new StringBuilder();
getSchemeAndPort(output);
buildRelativeLink(output);
//
// Encode Response does all the fixup for the Servlet Container
//
return encodeResponse(output.toString());
}
/**
* 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>
* TurbineURI tui = new TurbineURI (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>
*
* @return A String with the built URL.
*/
public String getRelativeLink()
{
StringBuilder output = new StringBuilder();
buildRelativeLink(output);
//
// Encode Response does all the fixup for the Servlet Container
//
return encodeResponse(output.toString());
}
/**
* Add everything needed for a relative link to the passed StringBuilder.
*
* @param output A Stringbuffer
*/
private void buildRelativeLink(StringBuilder output)
{
getContextAndScript(output);
if (hasPathInfo())
{
output.append('/');
getPathInfoAsString(output);
}
if (hasReference())
{
output.append('#');
output.append(getReference());
}
if (hasQueryData())
{
output.append('?');
getQueryDataAsString(output);
}
}
/**
* Gets the current Path Info List.
*
* @return A List which contains all query data keys. The keys
* are URIParam objects.
*/
public List<URIParam> getPathInfo()
{
return dataVectors[PATH_INFO];
}
/**
* Sets the Query Data List. Replaces the current query data list
* with the one supplied. The list must contain only URIParam
* objects!
*
* @param pathInfo A List with new param objects.
*/
public void setPathInfo(List<URIParam> pathInfo)
{
dataVectors[PATH_INFO] = pathInfo;
}
/**
* Gets the current Query Data List.
*
* @return A List which contains all query data keys. The keys
* are URIParam objects.
*/
public List<URIParam> getQueryData()
{
return dataVectors[QUERY_DATA];
}
/**
* Sets the Query Data List. Replaces the current query data list
* with the one supplied. The list must contain only URIParam
* objects!
*
* @param queryData A List with new param objects.
*/
public void setQueryData(List<URIParam> queryData)
{
dataVectors[QUERY_DATA] = queryData;
}
/**
* Simply calls getAbsoluteLink(). You should not use this in your
* code unless you have to. Use getAbsoluteLink.
*
* @return This URI as a String
*
*/
@Override
public String toString()
{
return getAbsoluteLink();
}
/*
* ========================================================================
*
* Protected / Private Methods
*
* ========================================================================
*
*/
/**
* Returns the Path Info data as a String.
*
* @param output The StringBuilder that should hold the path info.
*/
private void getPathInfoAsString(StringBuilder output)
{
doEncode(output, dataVectors[PATH_INFO], "/", "/");
}
/**
* Returns the Query data as a String.
*
* @param output The StringBuilder that should hold the query data.
*/
private void getQueryDataAsString(StringBuilder output)
{
doEncode(output, dataVectors[QUERY_DATA], "&", "=");
}
/**
* URL encode the given string, catching possible Exceptions
*
* @param string the string
* @return the encoded string
*/
private String urlEncode(String string)
{
try
{
// Java10: return URLEncoder.encode(string, parameterEncoding);
return URLEncoder.encode(string, parameterEncoding.name());
}
catch (UnsupportedEncodingException e)
{
log.warn("Unsupported encoding {}", parameterEncoding);
}
return StringUtils.EMPTY;
}
/**
* Does the actual encoding for pathInfoAsString and queryDataAsString.
*
* @param output The String builder that should contain the information.
* @param list A list of key to value pairs
* @param fieldDelim A char which is used to separate key/value pairs
* @param valueDelim A char which is used to separate key and value
*/
private void doEncode(StringBuilder output, Collection<URIParam> list, String fieldDelim, String valueDelim)
{
if(!list.isEmpty())
{
output.append(list.stream()
.map(uriParam ->
String.join(
valueDelim,
urlEncode(uriParam.getKey()),
urlEncode(Objects.toString(uriParam.getValue()))))
.collect(Collectors.joining(fieldDelim)));
}
}
/**
* If the type is PATH_INFO, then add name/value to the pathInfo
* hashtable.
* <p>
* If the type is QUERY_DATA, then add name/value to the queryData
* hashtable.
*
* @param type Type (PATH_INFO or QUERY_DATA) of insertion.
* @param name A String with the name to add.
* @param value A String with the value to add.
*/
protected void add(int type,
String name,
String value)
{
URIParam uriParam = new URIParam(parserService.convertAndTrim(name), value);
dataVectors[type].add(uriParam); // Code so clean you can eat from...
}
/**
* Method for a quick way to add all the parameters in a
* ParameterParser.
*
* <p>If the type is P (0), then add name/value to the pathInfo
* hashtable.
*
* <p>If the type is Q (1), then add name/value to the queryData
* hashtable.
*
* @param type Type of insertion (@see #add(char type, String name, String value))
* @param pp A ParameterParser.
*/
protected void add(int type,
ParameterParser pp)
{
for(String key : pp.keySet())
{
if (!key.equalsIgnoreCase(CGI_ACTION_PARAM) &&
!key.equalsIgnoreCase(CGI_SCREEN_PARAM))
{
String[] values = pp.getStrings(key);
if(values != null)
{
for (String value : values)
{
add(type, key, value);
}
}
else
{
add(type, key, "");
}
}
}
}
/**
* Method for a quick way to add all the parameters in a
* List with URIParam objects.
*
* <p>If the type is P (0), then add name/value to the pathInfo
* hashtable.
*
* <p>If the type is Q (1), then add name/value to the queryData
* hashtable.
*
* @param type Type of insertion (@see #add(char type, String name, String value))
* @param list A List of URIParam objects
*/
protected void add(int type, List<URIParam> list)
{
dataVectors[type].addAll(list);
}
/**
* If the type is P (0), then remove name/value from the
* pathInfo hashtable.
*
* <p>If the type is Q (1), then remove name/value from the
* queryData hashtable.
*
* @param type Type (P or Q) of removal.
* @param name A String with the name to be removed.
*/
protected void remove (int type, String name)
{
String key = parserService.convertAndTrim(name);
dataVectors[type].removeIf(uriParam -> key.equals(uriParam.getKey()));
}
}