//
//Copyright (c) 2003, Caltha - Gajda, Krzewski, Mach, Potempski Sp.J.
//All rights reserved.
//
//Redistribution and use in source and binary forms, with or without modification,
//are permitted provided that the following conditions are met:
//
//* Redistributions of source code must retain the above copyright notice,
//this list of conditions and the following disclaimer.
//* Redistributions in binary form must reproduce the above copyright notice,
//this list of conditions and the following disclaimer in the documentation
//and/or other materials provided with the distribution.
//* Neither the name of the Caltha - Gajda, Krzewski, Mach, Potempski Sp.J.
//nor the names of its contributors may be used to endorse or promote products
//derived from this software without specific prior written permission.
//
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
//AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
//IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
//INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
//OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
//WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
//ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
//POSSIBILITY OF SUCH DAMAGE.
//
package org.objectledge.web.mvc.tools;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import org.jcontainer.dna.Configurable;
import org.jcontainer.dna.ConfigurationException;
import org.objectledge.ComponentInitializationError;
import org.objectledge.parameters.Parameters;
import org.objectledge.parameters.RequestParameters;
import org.objectledge.parameters.SortedParameters;
import org.objectledge.utils.StringUtils;
import org.objectledge.web.HttpContext;
import org.objectledge.web.WebConfigurator;
import org.objectledge.web.mvc.MVCContext;
/**
* Context tool used to build web application links. It works in a pull manner. The template
* designer provides instances of LinkTool with all the necessary parameters,
* the LinkTool itself is responsible for generation of a proper URL string based on
* this parameters. An example of LinkTool usage in Velocity template:
*
*
* $link.view('somepackage.SomeView').action('somepackage.SomeAction').set('paramName','paramValue').fragment('this_line')
*
*
* * /context/servlet/view/somepackage.SomeView?action=somepackage.SomeAction¶mName=paramValue#this_line ** *
The links are encoded using UTF-8 encoding no matter what encoding is used in the generated * HTML page. This is in line with JavaScript URI encoding and decoding functions.
* * @author Pawel Potempski * @author Damian Gajda * @version $Id: LinkTool.java,v 1.2 2008-06-08 14:51:32 mgolebsk Exp $ */ public class LinkTool { /** utf encoding. */ public static final String PARAMETER_ENCODING = "UTF-8"; /** query string parameter values and content paths encoder. */ private static final org.objectledge.encodings.URLEncoder URL_ENCODER = new org.objectledge.encodings.URLEncoder(); /** configuration. */ protected LinkTool.Configuration config; /** the http context. */ protected HttpContext httpContext; /** the mvc context. */ protected MVCContext mvcContext; /** the request parameters. */ protected RequestParameters requestParameters; /** view, null for current request parameters view value, empty string for unset value */ private String view; /** the action, empty string for unset value */ private String action; /** is content link switch */ private boolean contentLink; /** include session information */ private boolean includeSession; /** show the protocol in link */ private boolean showProtocolName; /** protocol name */ private String protocolName; /** the port */ private int port; /** the content path */ private String path; /** the special path info suffix */ private String pathInfoSuffix; /** the fragment part of the url */ private String fragment; /** the parameters */ private Parameters parameters; /** the string buffer */ private StringBuilder sb; /** * Tool constructor. * * @param httpContext the http context. * @param mvcContext the mvc context. * @param requestParameters the request parameters. * @param config the link tool configuraiton. */ public LinkTool(HttpContext httpContext, MVCContext mvcContext, RequestParameters requestParameters, LinkTool.Configuration config) { this.config = config; this.httpContext = httpContext; this.mvcContext = mvcContext; this.requestParameters = requestParameters; contentLink = false; includeSession = true; showProtocolName = false; protocolName = ""; port = 0; // this means no override for view view = null; action = ""; pathInfoSuffix = null; fragment = null; if(config.stickyParameterNames.size() > 0) { parameters = new SortedParameters(requestParameters); parameters.removeExcept(config.stickyParameterNames); } else { parameters = new SortedParameters(); } } /** * Set the protocol to http with default port on 80. * * @return the link tool. */ public LinkTool http() { return http(80); } /** * Set the protocol to http with specified port. * * @param port the port. * @return the link tool. */ public LinkTool http(int port) { LinkTool target = getLinkTool(this); target.showProtocolName = true; target.protocolName = "http"; target.port = port; return target; } /** * Set the protocol to https with default port on 443. * * @return the link tool. */ public LinkTool https() { return https(443); } /** * Set the protocol to https with specified port. * * @param port the port. * @return the link tool. */ public LinkTool https(int port) { LinkTool target = getLinkTool(this); target.showProtocolName = true; target.protocolName = "https"; target.port = port; return target; } /** * Generate an absolute link including protocol name (schema), server name and port number. * * @return the link tool. */ public LinkTool absolute() { LinkTool target = getLinkTool(this); target.showProtocolName = true; if(target.port == 0) { target.port = httpContext.getRequest().getServerPort(); } if(target.protocolName == null || target.protocolName.length()==0) { if(httpContext.getRequest().isSecure()) { target.protocolName = "https"; } else { target.protocolName = "http"; } } return target; } /** * Avoid session information in link - useful for content served using external HTTP server. * * @return the link tool. */ public LinkTool sessionless() { LinkTool target = getLinkTool(this); target.includeSession = false; return target; } /** * Set link to point to the content stored in/ directory of servlet context.
* May be used to generate relative content paths (not recommended).
*
* @param path the path to content.
* @return the link tool.
*/
public LinkTool rootContent(String path)
{
LinkTool target = getLinkTool(this);
target.contentLink = true;
target.includeSession = this.includeSession && !config.externalContent;
target.path = path;
return target;
}
/**
* Set link to point to the content stored in configured content (/content)
* directory of servlet context.
*
* @param path the relative path to content.
* @return the link tool.
*/
public LinkTool content(String path)
{
if (path.length() == 0)
{
path = config.baseContentPath;
}
else if (path.charAt(0) != '/')
{
path = config.baseContentPath + '/' + path;
}
else
{
path = config.baseContentPath + path;
}
return rootContent(path);
}
// parameter set methods ----------------------------------------------------------------------
/**
* Sets a request parameter, replacing previously set value.
* Unless configured differently it will be rendered in the link as query string parameter.
*
* @param name the name of the parameter.
* @param value the value of the parameter.
* @return the link tool.
*/
public LinkTool set(String name, String value)
{
LinkTool target = getSetTargetLinkTool(name);
target.parameters.set(name, value);
return target;
}
/**
* Sets a request parameter, replacing previously set value.
* Unless configured differently it will be rendered in the link as query string parameter.
*
* @param name the name of the parameter.
* @param value the value of the parameter.
* @return the link tool.
*/
public LinkTool set(String name, int value)
{
LinkTool target = getSetTargetLinkTool(name);
target.parameters.set(name, value);
return target;
}
/**
* Sets a request parameter, replacing previously set value.
* Unless configured differently it will be rendered in the link as query string parameter.
*
* @param name the name of the parameter.
* @param value the value of the parameter.
* @return the link tool.
*/
public LinkTool set(String name, long value)
{
LinkTool target = getSetTargetLinkTool(name);
target.parameters.set(name, value);
return target;
}
/**
* Sets a request parameter, replacing previously set value.
* Unless configured differently it will be rendered in the link as query string parameter.
*
* @param name the name of the parameter.
* @param value the value of the parameter.
* @return the link tool.
*/
public LinkTool set(String name, float value)
{
LinkTool target = getSetTargetLinkTool(name);
target.parameters.set(name, value);
return target;
}
/**
* Sets a request parameter, replacing previously set value.
* Unless configured differently it will be rendered in the link as query string parameter.
*
* @param name the name of the parameter.
* @param value the value of the parameter.
* @return the link tool.
*/
public LinkTool set(String name, boolean value)
{
LinkTool target = getSetTargetLinkTool(name);
target.parameters.set(name, value);
return target;
}
/**
* Sets multiple values of a parameter using a {@link java.util.List}.
*
* @param name the name of the parameter.
* @param list a list of parameter values.
* @return the link tool.
*/
public LinkTool set(String name, List#fragment-value to the rendered link.
*
* @param fragment the fragment.
* @return the link tool.
*/
public LinkTool fragment(String fragment)
{
LinkTool target = getLinkTool(this);
target.fragment = fragment;
return target;
}
/**
* Returns the fragment for this link.
*
* @return the fragment in the current link
*/
public String fragment()
{
return fragment;
}
/**
* Sets the parameters to be equal to the paremeters of the
* current request.
*
* WARN: This method creates links different from the request URI if some of the parameters * were passed as path info parameters and not configured as such.
* * @return the link tool. */ public LinkTool self() { LinkTool target = getLinkTool(this); target.parameters.remove(config.stickyParameterNames); target.parameters.add(requestParameters, true); target.parameters.remove(config.viewToken); target.parameters.remove(config.actionToken); String url = httpContext.getRequest().getRequestURI(); if (url.indexOf('#') > 0) { target.fragment = url.substring(url.lastIndexOf('#') + 1); } return target; } /** * Prepare link tool pointed to referer page * @return the link tool */ public LinkTool getReferer() { Enumeration enumeration = httpContext.getRequest().getHeaders("referer"); if( enumeration != null && enumeration.hasMoreElements()) { return parseURL(enumeration.nextElement().toString()); } else { return null; } } /** * Parse given string to obtain link tool object * @param url - string of the toString() shape * @return the link tool * @see #toString() */ private LinkTool parseURL(String url) { LinkTool target = getLinkTool(this); int index = url.indexOf(config.viewToken+"/"); if( StringUtils.isEmpty(url) || index < 0) { return target; } target.parseURLParams(url.substring(index), "&=?/"); return target; } private static final int START = 0; private static final int NAME = 1; private static final int SEPARATOR_AFTER_NAME = 2; private static final int VALUE_PARAM = 3; private static final int VALUE_VIEW = 4; private static final int VALUE_ACTION = 5; private static final int SEPARATOR_AFTER_VALUE = 6; private MapWARN: This implementation only returns a reference to this LinkTool's
* parameters field. If the subclass needs to change the rendered parameters, it should copy
* the parameters object.
true if port number is different from default
* protocol port (default port number for http is 80, for https 443).
*
* @param protocolNameTmp appended protocol name
* @param portTmp appended (or not) port number
* @return true if port number must be appended.
*/
protected boolean mustAppendPort(String protocolNameTmp, int portTmp)
{
return ((protocolNameTmp.length() == 0 || protocolNameTmp.equals("http")) && portTmp != 80)
|| (protocolNameTmp.equals("https") && portTmp != 443)
|| (protocolNameTmp.length() > 0
&& !protocolNameTmp.equals("https") && !protocolNameTmp.equals("http"));
}
/**
* Allows subclasses to override port number rendered in {@link #toString()} method.
*
* @return the overriden port number
*/
protected int getPort()
{
return port;
}
private void appendContentLink(StringBuilder sb)
throws UnsupportedEncodingException
{
final String pathTmp = getPath();
if (pathTmp.length() > 0)
{
if (pathTmp.charAt(0) != '/')
{
sb.append('/');
}
sb.append(URL_ENCODER.encodeContentPath(pathTmp, PARAMETER_ENCODING));
}
}
/**
* Allows subclasses to override static content path rendered in {@link #toString()} method.
*
* @return the overriden content path
*/
protected String getPath()
{
return path;
}
private void appendPathInfo(StringBuilder sb, String[] keys, Parameters parametersTmp)
throws UnsupportedEncodingException
{
String outView = getView();
if (outView == null) // override with current view
{
outView = mvcContext.getView();
}
if (outView != null && outView.length() > 0)
{
sb.append('/').append(config.viewToken).append('/').append(outView);
}
for (int i = 0; i < keys.length; i++)
{
String key = keys[i];
if (config.pathinfoParameterNames.contains(key))
{
String[] values = parametersTmp.getStrings(key);
for (int j = 0; j < values.length; j++)
{
sb.append('/').append(URLEncoder.encode(key, PARAMETER_ENCODING));
sb.append('/').append(URLEncoder.encode(values[j], PARAMETER_ENCODING));
}
}
}
}
/**
* Allows other Tools to check link properties and
* creaton of subclasses which can override action name
* rendered in {@link #toString()} method.
*
* @return the overriden view name or null if it should be replaced with view
* from current request.
*/
public String getView()
{
return view;
}
private void appendPathInfoSuffix(StringBuilder sb)
throws UnsupportedEncodingException
{
final String pathInfoSuffixTmp = getPathInfoSuffix();
if(pathInfoSuffixTmp != null && pathInfoSuffixTmp.length() > 0)
{
if(pathInfoSuffixTmp.charAt(0) != '/')
{
sb.append('/');
}
sb.append(URLEncoder.encode(pathInfoSuffixTmp, PARAMETER_ENCODING));
}
}
/**
* Allows subclasses to override pathinfo suffix rendered in {@link #toString()} method.
*
* @return the overriden pathinfo suffix
*/
protected String getPathInfoSuffix()
{
return pathInfoSuffix;
}
private void appendQueryString(StringBuilder sb, String[] keys, Parameters parametersTmp)
throws UnsupportedEncodingException
{
String querySeparator = "?";
final String querySeparator2 = config.queryStringSeparator;
String actionTmp = getAction();
if (actionTmp != null && actionTmp.length() > 0)
{
sb.append(querySeparator);
sb.append(config.actionToken).append('=').append(actionTmp);
querySeparator = querySeparator2;
}
for (int i = 0; i < keys.length; i++)
{
String key = keys[i];
if (!config.pathinfoParameterNames.contains(key))
{
String[] values = parametersTmp.getStrings(key);
for (int j = 0; j < values.length; j++)
{
sb.append(querySeparator);
sb.append(URLEncoder.encode(key, PARAMETER_ENCODING));
sb.append('=');
sb.append(URL_ENCODER
.encodeQueryStringValue(values[j], PARAMETER_ENCODING));
querySeparator = querySeparator2;
}
}
}
}
/**
* Allows other Tools to check link properties and
* creaton of subclasses which can override action name
* rendered in {@link #toString()} method.
*
* @return the overriden action name
*/
public String getAction()
{
return action;
}
// end toString() - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/**
* Clone the given LinkTool.
*
* @param source to LinkTool to clone
* @return the clone.
*/
private LinkTool getLinkTool(LinkTool source)
{
LinkTool target = createInstance(source);
target.view = source.view;
target.action = source.action;
target.contentLink = source.contentLink;
target.includeSession = source.includeSession;
target.showProtocolName = source.showProtocolName;
target.protocolName = source.protocolName;
target.port = source.port;
target.path = source.path;
target.pathInfoSuffix = source.pathInfoSuffix;
target.fragment = source.fragment;
target.parameters = new SortedParameters(source.parameters);
return target;
}
/**
* Creates the LinkTool instance for copying. This method is intended to be overriden by
* extending classes in order to provide LinkTool instances of proper class.
*
* @param source copied object
* @return created instance of the linktool.
*/
protected LinkTool createInstance(LinkTool source)
{
return new LinkTool(source.httpContext, source.mvcContext, source.requestParameters,
source.config);
}
private void checkSetParamName(String name)
{
if (name.equals(config.viewToken))
{
throw new IllegalArgumentException("to set the value of the view parameter, " +
"call the view(String) method");
}
else if (name.equals(config.actionToken))
{
throw new IllegalArgumentException("to set the value of the action parameter, " +
"call the action(String) method");
}
}
private void checkAddParamName(String name)
{
if (name.equals(config.viewToken))
{
throw new IllegalArgumentException("to set the value of the view parameter, " +
"call the view(String) method");
}
else if (name.equals(config.actionToken))
{
throw new IllegalArgumentException("to set the value of the action parameter, " +
"call the action(String) method");
}
}
/**
* Represents the shared configuration of the LinkTools.
*
* Created on Jan 14, 2004
* @author rafal@caltha.pl */ public static class Configuration implements Configurable { /** the default query separator. */ public static final String DEFAULT_QUERY_SEPARATOR = "&"; /** the default base content path. */ public static final String DEFAULT_BASE_CONTENT_PATH = "/content"; /** the sticky parameters keys */ private Set stickyParameterNames = new HashSet(); /** the pathinfo parameters keys */ private Set pathinfoParameterNames = new HashSet(); /** the query separator */ private String queryStringSeparator; /** external content switch */ private boolean externalContent; /** rewrite (drop context and servlet path from URLs) switch. */ private boolean rewrite; /** the web configurator. */ private WebConfigurator webConfigurator; /** base content path */ private String baseContentPath; /** currently used view parameter name */ private String viewToken; /** currently used action parameter name */ private String actionToken; /** * Initializes the configuraiton object. * * @param config DNA configuration * @param webConfigurator the configuration of the web subsystem. * @throws ConfigurationException if the configuration is invalid. */ public Configuration(org.jcontainer.dna.Configuration config, WebConfigurator webConfigurator) throws ConfigurationException { this.webConfigurator = webConfigurator; configure(config); } /** * Initializes the internal state from DNA configuration object. * *This method may be used to reconfigure link tool at runtime.
* * @param config DNA configuration * @throws ConfigurationException if the configuration is invalid. */ public void configure(org.jcontainer.dna.Configuration config) throws ConfigurationException { try { org.jcontainer.dna.Configuration[] keys = config.getChild("sticky").getChildren("key"); for (int i = 0; i < keys.length; i++) { stickyParameterNames.add(keys[i].getValue()); } keys = config.getChild("pathinfo").getChildren("key"); for (int i = 0; i < keys.length; i++) { pathinfoParameterNames.add(keys[i].getValue()); } baseContentPath = config.getChild("base_content_path").getValue(DEFAULT_BASE_CONTENT_PATH); } ///CLOVER:OFF catch (ConfigurationException e) { throw new ComponentInitializationError("failed to configure the component", e); } ///CLOVER:ON queryStringSeparator = config.getChild("query_separator"). getValue(DEFAULT_QUERY_SEPARATOR); externalContent = config.getChild("external_content").getValueAsBoolean(false); rewrite = config.getChild("rewrite").getValueAsBoolean(false); // TODO: remove WebConfigurator viewToken = webConfigurator.getViewToken(); actionToken = webConfigurator.getActionToken(); } } }