| package org.apache.velocity.tools.generic; |
| |
| /* |
| * 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.Serializable; |
| import java.io.UnsupportedEncodingException; |
| import java.lang.reflect.Array; |
| import java.net.URI; |
| import java.net.URLDecoder; |
| import java.net.URLEncoder; |
| import java.util.ArrayList; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.velocity.tools.Scope; |
| import org.apache.velocity.tools.ToolContext; |
| import org.apache.velocity.tools.config.DefaultKey; |
| import org.apache.velocity.tools.config.SkipSetters; |
| import org.apache.velocity.tools.config.ValidScope; |
| |
| /** |
| * <p>The LinkTool provides many methods to work with URIs and can help you:</p> |
| * <ul> |
| * <li>construct full URIs (opaque, absolute or relative)</li> |
| * <li>encode and decode URLs (part or whole)</li> |
| * <li>retrieve path info for the current request</li> |
| * <li>and more..</li> |
| * </ul> |
| * |
| * <p>This GenericTools (i.e. non-servlet based) version of LinkTool |
| * is largely based upon the same API and behavior as the older |
| * VelocityView version, with a few differences, particularly in |
| * internal representation and query handling. You can expect that |
| * in the future work will be done to more closely align the APIs. |
| * It is likely that the VelocityView version will become a subclass |
| * of this version that adds on servlet-awareness and related features. |
| * For now, though, they are entirely separate but similar tools. |
| * </p> |
| * |
| * <p>The LinkTool is somewhat special in that nearly all public methods return |
| * a new instance of LinkTool. This facilitates greatly the repeated use |
| * of the LinkTool in Velocity and leads to an elegant syntax.</p> |
| * |
| * <p>Template example(s):</p> |
| * <pre> |
| * #set( $base = $link.relative('MyPage.vm').anchor('view') ) |
| * <a href="$base.param('select','this')">this</a> |
| * <a href="$base.param('select','that')">that</a> |
| * |
| * Toolbox configuration: |
| * <tools> |
| * <toolbox scope="request"> |
| * <tool class="org.apache.velocity.tools.generic.LinkTool" |
| * uri="http://velocity.apache.org/tools/devel/"/> |
| * </toolbox> |
| * </tools> |
| * </pre> |
| * |
| * @author Nathan Bubna |
| * @since VelocityTools 2.0 |
| * @version $Id: LinkTool.java 601976 2007-12-07 03:50:51Z nbubna $ |
| */ |
| |
| @DefaultKey("link") |
| @SkipSetters |
| @ValidScope(Scope.REQUEST) |
| public class LinkTool extends SafeConfig implements Cloneable, Serializable |
| { |
| private static final long serialVersionUID = -9178414982270037545L; |
| |
| /** Standard HTML delimiter for query data ('&') */ |
| public static final String HTML_QUERY_DELIMITER = "&"; |
| |
| /** XHTML delimiter for query data ('&amp;') */ |
| public static final String XHTML_QUERY_DELIMITER = "&"; |
| |
| public static final String APPEND_PARAMS_KEY = "appendParameters"; |
| public static final String FORCE_RELATIVE_KEY = "forceRelative"; |
| public static final String DEFAULT_CHARSET = "UTF-8"; |
| public static final String DEFAULT_SCHEME = "http"; |
| public static final String SECURE_SCHEME = "https"; |
| |
| public static final String URI_KEY = "uri"; |
| public static final String SCHEME_KEY = "scheme"; |
| public static final String USER_KEY = "user"; |
| public static final String HOST_KEY = "host"; |
| public static final String PORT_KEY = "port"; |
| public static final String PATH_KEY = ToolContext.PATH_KEY; |
| public static final String QUERY_KEY = "params"; |
| public static final String FRAGMENT_KEY = "anchor"; |
| public static final String CHARSET_KEY = "charset"; |
| public static final String XHTML_MODE_KEY = "xhtml"; |
| |
| protected String scheme; |
| protected String user; |
| protected String host; |
| protected int port; |
| protected String path; |
| protected Map query; |
| protected String fragment; |
| protected String charset; |
| protected String queryDelim; |
| protected boolean appendParams; |
| protected boolean forceRelative; |
| protected boolean opaque; |
| protected final LinkTool self; |
| |
| |
| /** |
| * Default constructor. Tool typically is configured before use. |
| */ |
| public LinkTool() |
| { |
| scheme = null; |
| user = null; |
| host = null; |
| port = -1; |
| path = null; |
| query = null; |
| fragment = null; |
| charset = DEFAULT_CHARSET; |
| queryDelim = XHTML_QUERY_DELIMITER; |
| opaque = false; |
| appendParams = true; |
| forceRelative = false; |
| self = this; |
| } |
| |
| // --------------------------------------- Setup Methods ------------- |
| |
| /** |
| * Configuration |
| */ |
| protected void configure(ValueParser props) |
| { |
| String link = props.getString(URI_KEY); |
| if (link != null) |
| { |
| setFromURI(link); |
| } |
| |
| String schm = props.getString(SCHEME_KEY); |
| if (schm != null) |
| { |
| setScheme(schm); |
| } |
| String info = props.getString(USER_KEY); |
| if (info != null) |
| { |
| setUserInfo(info); |
| } |
| String hst = props.getString(HOST_KEY); |
| if (hst != null) |
| { |
| setHost(hst); |
| } |
| Integer prt = props.getInteger(PORT_KEY); |
| if (prt != null) |
| { |
| setPort(prt.intValue()); |
| } |
| String pth = props.getString(PATH_KEY); |
| if (pth != null) |
| { |
| setPath(pth); |
| } |
| String params = props.getString(QUERY_KEY); |
| if (params != null) |
| { |
| setQuery(params); |
| } |
| String anchor = props.getString(FRAGMENT_KEY); |
| if (anchor != null) |
| { |
| setFragment(anchor); |
| } |
| |
| String chrst = props.getString(CHARSET_KEY); |
| if (chrst != null) |
| { |
| setCharacterEncoding(chrst); |
| } |
| |
| Boolean xhtml = props.getBoolean(XHTML_MODE_KEY); |
| if (xhtml != null) |
| { |
| setXHTML(xhtml); |
| } |
| Boolean addParams = props.getBoolean(APPEND_PARAMS_KEY); |
| if (addParams != null) |
| { |
| setAppendParams(addParams); |
| } |
| Boolean forceRelative = props.getBoolean(FORCE_RELATIVE_KEY); |
| if (forceRelative != null) |
| { |
| setForceRelative(forceRelative); |
| } |
| } |
| |
| /** |
| * Equivalent to clone, but with no checked exceptions. |
| * If for some unfathomable reason clone() doesn't work, |
| * this will throw a RuntimeException. |
| * @return new LinkTool |
| */ |
| protected LinkTool duplicate() |
| { |
| return duplicate(false); |
| } |
| |
| /** |
| * Equivalent to clone, but with no checked exceptions. |
| * If for some unfathomable reason clone() doesn't work, |
| * this will throw a RuntimeException. If doing a deep |
| * clone, then the parameter Map will also be cloned. |
| * @param deep whether to make a deep copy |
| * @return new LinkTool |
| */ |
| protected LinkTool duplicate(boolean deep) |
| { |
| try |
| { |
| LinkTool that = (LinkTool)this.clone(); |
| if (deep && query != null) |
| { |
| that.query = new LinkedHashMap(query); |
| } |
| return that; |
| } |
| catch (CloneNotSupportedException e) |
| { |
| String msg = "Could not properly clone " + getClass(); |
| getLog().error(msg, e); |
| throw new RuntimeException(msg, e); |
| } |
| } |
| |
| public void setCharacterEncoding(String chrst) |
| { |
| this.charset = chrst; |
| } |
| |
| /** |
| * <p>Controls the delimiter used for separating query data pairs. |
| * By default, the standard '&' character is used.</p> |
| * <p>This is not exposed to templates as this decision is best not |
| * made at that level.</p> |
| * <p>Subclasses may easily override the init() method to set this |
| * appropriately and then call super.init()</p> |
| * |
| * @param xhtml if true, the XHTML query data delimiter ('&amp;') |
| * will be used. if false, then '&' will be used. |
| * @see <a href="http://www.w3.org/TR/xhtml1/#C_12">Using Ampersands in Attribute Values (and Elsewhere)</a> |
| */ |
| public void setXHTML(boolean xhtml) |
| { |
| queryDelim = (xhtml) ? XHTML_QUERY_DELIMITER : HTML_QUERY_DELIMITER; |
| } |
| |
| /** |
| * Sets whether or not the {@link #setParam} method |
| * will override existing query values for the same key or simply append |
| * the new value to a list of existing values. |
| * @param addParams whether to add or replace params |
| */ |
| public void setAppendParams(boolean addParams) |
| { |
| this.appendParams = addParams; |
| } |
| |
| /** |
| * Sets whether or not the {@link #createURI} method should ignore the |
| * scheme, user, port and host values for non-opaque URIs, thus making |
| * {@link #toString} print the link as a relative one, not an absolute |
| * one. NOTE: using {@link #absolute()}, {@link #absolute(Object)}, |
| * {@link #relative()}, or {@link #relative(Object)} will alter this |
| * setting accordingly on the new instances they return. |
| * @param forceRelative whether to force a relative URI |
| */ |
| public void setForceRelative(boolean forceRelative) |
| { |
| this.forceRelative = forceRelative; |
| } |
| |
| /** |
| * This will treat empty strings like null values |
| * and will trim any trailing ':' character. |
| * @param obj scheme |
| */ |
| public void setScheme(Object obj) |
| { |
| if (obj == null) |
| { |
| this.scheme = null; |
| } |
| else |
| { |
| this.scheme = String.valueOf(obj); |
| if (scheme.length() == 0) |
| { |
| this.scheme = null; |
| } |
| if (scheme.endsWith(":")) |
| { |
| this.scheme = scheme.substring(0, scheme.length() - 1); |
| } |
| } |
| } |
| |
| /** |
| * Sets user info |
| * @param obj user info |
| */ |
| public void setUserInfo(Object obj) |
| { |
| this.user = obj == null ? null : String.valueOf(obj); |
| } |
| |
| /** |
| Sets host |
| @param obj host |
| */ |
| public void setHost(Object obj) |
| { |
| this.host = obj == null ? null : String.valueOf(obj); |
| } |
| |
| /** |
| * If the specified object is null, this will set the port value |
| * to -1 to indicate that. If it is non-null and cannot be converted |
| * to an integer, then it will be set to -2 to indicate an error. |
| * @param obj port |
| */ |
| public void setPort(Object obj) |
| { |
| if (obj == null) |
| { |
| this.port = -1; |
| } |
| else if (obj instanceof Number) |
| { |
| this.port = ((Number)obj).intValue(); |
| } |
| else |
| { |
| try |
| { |
| this.port = Integer.parseInt(String.valueOf(obj)); |
| } |
| catch (NumberFormatException nfe) |
| { |
| getLog().error("Could not convert '{}' to int", obj, nfe); |
| this.port = -2; // use this to mean error |
| } |
| } |
| } |
| |
| /** |
| * If this instance is not opaque and the specified value does |
| * not start with a '/' character, then that will be prepended |
| * automatically. |
| * @param obj path |
| */ |
| public void setPath(Object obj) |
| { |
| if (obj == null) |
| { |
| this.path = null; |
| } |
| else |
| { |
| this.path = String.valueOf(obj); |
| if (!this.opaque && !path.startsWith("/")) |
| { |
| this.path = '/' + this.path; |
| } |
| } |
| } |
| |
| /** |
| * Uses {@link #combinePath} to add the specified value |
| * to the current {@link #getPath} value. If the specified |
| * value is null or this instance is opaque, then this is |
| * a no-op. |
| * @param obj path |
| */ |
| public void appendPath(Object obj) |
| { |
| if (obj != null && !this.opaque) |
| { |
| setPath(combinePath(getPath(), String.valueOf(obj))); |
| } |
| } |
| |
| /** |
| * If end is null, this will return start and vice versa. |
| * If neither is null, this will append the end to the start, |
| * making sure that there is only one '/' character between |
| * the two values. |
| * @param start start path |
| * @param end end path |
| * @return combined path |
| */ |
| protected String combinePath(String start, String end) |
| { |
| if (end == null) |
| { |
| return start; |
| } |
| if (start == null) |
| { |
| return end; |
| } |
| |
| // make sure we don't get // or nothing between start and end |
| boolean startEnds = start.endsWith("/"); |
| boolean endStarts = end.startsWith("/"); |
| if (startEnds ^ endStarts) //one |
| { |
| return start + end; |
| } |
| else if (startEnds & endStarts) //both |
| { |
| return start + end.substring(1, end.length()); |
| } |
| else //neither |
| { |
| return start + '/' + end; |
| } |
| } |
| |
| /** |
| * If the specified value is null, it will set the query to null. |
| * If a Map, it will copy all those values into a new LinkedHashMap and |
| * replace any current query value with that. If it is a String, |
| * it will use {@link #parseQuery(String)} to parse it into a map |
| * of keys to values. |
| * @param obj query |
| */ |
| public void setQuery(Object obj) |
| { |
| if (obj == null) |
| { |
| this.query = null; |
| } |
| else if (obj instanceof Map) |
| { |
| this.query = new LinkedHashMap(); |
| for (Map.Entry keyValues : (Set<Map.Entry>)((Map)obj).entrySet()) |
| { |
| String key = String.valueOf(keyValues.getKey()); |
| Object values = keyValues.getValue(); |
| if (values.getClass().isArray()) |
| { |
| int size = Array.getLength(values); |
| switch (size) |
| { |
| case 0: |
| break; |
| case 1: |
| this.query.put(key, Array.get(values, 0)); |
| break; |
| default: |
| List lst = new ArrayList(); |
| for (int i = 0; i < Array.getLength(values); ++i) |
| { |
| lst.add(Array.get(values, i)); |
| } |
| this.query.put(key, lst); |
| } |
| } |
| else |
| { |
| this.query.put(key, values); |
| } |
| } |
| } |
| else |
| { |
| String qs = normalizeQuery(String.valueOf(obj)); |
| this.query = parseQuery(qs); |
| } |
| } |
| |
| /** |
| * @param qs query string |
| * @return normalized string |
| */ |
| protected String normalizeQuery(String qs) |
| { |
| // if we have multiple pairs... |
| if (qs.indexOf('&') >= 0) |
| { |
| // ensure the delimeters match the xhtml setting |
| // this impl is not at all efficient, but it's easy |
| qs = qs.replaceAll("&(amp;)?", queryDelim); |
| } |
| return qs; |
| } |
| |
| /** |
| * Converts the map of keys to values into a query string. |
| * @param parameters parameters |
| * @return query string |
| */ |
| public String toQuery(Map parameters) |
| { |
| if (parameters == null) |
| { |
| return null; |
| } |
| StringBuilder query = new StringBuilder(); |
| for (Object e : parameters.entrySet()) |
| { |
| Map.Entry entry = (Map.Entry)e; |
| //add new pair to this LinkTool's query data |
| if (query.length() > 0) |
| { |
| query.append(queryDelim); |
| } |
| query.append(toQuery(entry.getKey(), entry.getValue())); |
| } |
| return query.toString(); |
| } |
| |
| /** |
| * Uses {@link #combineQuery} to append the specified value |
| * to the current {@link #getQuery} value. |
| * @param obj query to append |
| */ |
| public void appendQuery(Object obj) |
| { |
| if (obj != null) |
| { |
| setQuery(combineQuery(getQuery(), String.valueOf(obj))); |
| } |
| } |
| |
| /** |
| * If there is no existing value for this key in the query, it |
| * will simply add it and its value to the query. If the key |
| * already is present in the query and append |
| * is true, this will add the specified value to those |
| * already under that key. If {@link #appendParams} is |
| * false, this will override the existing values with the |
| * specified new value. |
| * @param key parameter key |
| * @param value parameter value |
| * @param append whether to append parameter to existing ones with same key |
| */ |
| public void setParam(Object key, Object value, boolean append) |
| { |
| // use all keys as strings, even null -> "null" |
| key = String.valueOf(key); |
| if (this.query == null) |
| { |
| this.query = new LinkedHashMap(); |
| putParam(key, value); |
| } |
| else if (append) |
| { |
| appendParam((String)key, value); |
| } |
| else |
| { |
| putParam(key, value); |
| } |
| } |
| |
| private void appendParam(String key, Object value) |
| { |
| if (query.containsKey(key)) |
| { |
| Object cur = query.get(key); |
| if (cur instanceof List) |
| { |
| addToList((List)cur, value); |
| } |
| else |
| { |
| List vals = new ArrayList(); |
| vals.add(cur); |
| addToList(vals, value); |
| putParam(key, vals); |
| } |
| } |
| else |
| { |
| putParam(key, value); |
| } |
| } |
| |
| private void putParam(Object key, Object value) |
| { |
| if (value instanceof Object[]) |
| { |
| List vals = new ArrayList(); |
| for (Object v : ((Object[])value)) |
| { |
| vals.add(v); |
| } |
| value = vals; |
| } |
| query.put(key, value); |
| } |
| |
| private void addToList(List vals, Object value) |
| { |
| if (value instanceof List) |
| { |
| for (Object v : ((List)value)) |
| { |
| vals.add(v); |
| } |
| } |
| else if (value instanceof Object[]) |
| { |
| for (Object v : ((Object[])value)) |
| { |
| vals.add(v); |
| } |
| } |
| else |
| { |
| vals.add(value); |
| } |
| } |
| |
| /** |
| * If append is false, this simply delegates to {@link #setQuery}. |
| * Otherwise, if the specified object is null, it does nothing. If the object |
| * is not a Map, it will turn it into a String and use {@link #parseQuery} to |
| * parse it. Once it is a Map, it will iterate through the entries appending |
| * each key/value to the current query data. |
| * @param obj parameters |
| * @param append whether to append parameters |
| */ |
| public void setParams(Object obj, boolean append) |
| { |
| if (!append) |
| { |
| setQuery(obj); |
| } |
| else if (obj != null) |
| { |
| if (!(obj instanceof Map)) |
| { |
| obj = parseQuery(String.valueOf(obj)); |
| } |
| if (obj != null) |
| { |
| if (query == null) |
| { |
| this.query = new LinkedHashMap(); |
| } |
| for (Object e : ((Map)obj).entrySet()) |
| { |
| Map.Entry entry = (Map.Entry)e; |
| String key = String.valueOf(entry.getKey()); |
| appendParam(key, entry.getValue()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Removes the query pair(s) with the specified key from the |
| * query data and returns the remove value(s), if any. |
| * @param key parameter key |
| * @return parameter value or null |
| */ |
| public Object removeParam(Object key) |
| { |
| if (query != null) |
| { |
| key = String.valueOf(key); |
| return query.remove(key); |
| } |
| return null; |
| } |
| |
| /** |
| * In this class, this method ignores true values. If passed a false value, |
| * it will call {@link #setQuery} with a null value to clear all query data. |
| * @param keep <code>false</code> will clear query |
| */ |
| protected void handleParamsBoolean(boolean keep) |
| { |
| if (!keep) |
| { |
| setQuery(null); |
| } |
| } |
| |
| /** |
| * If the second param is null or empty, this will simply return the first |
| * and vice versa. Otherwise, it will trim any '?' |
| * at the start of the second param and any '&' or '&amp;' at the |
| * end of the first one, then combine the two, making sure that they |
| * are separated by only one delimiter. |
| * @param current current query |
| * @param add added query |
| * @return combined query |
| */ |
| protected String combineQuery(String current, String add) |
| { |
| if (add == null || add.length() == 0) |
| { |
| return current; |
| } |
| if (add.startsWith("?")) |
| { |
| add = add.substring(1, add.length()); |
| } |
| if (current == null || current.length() == 0) |
| { |
| return add; |
| } |
| if (current.endsWith(queryDelim)) |
| { |
| current = current.substring(0, current.length() - queryDelim.length()); |
| } |
| else if (current.endsWith("&")) |
| { |
| current = current.substring(0, current.length() - 1); |
| } |
| if (add.startsWith(queryDelim)) |
| { |
| return current + add; |
| } |
| else if (add.startsWith("&")) |
| { |
| // drop the html delim in favor of the xhtml one |
| add = add.substring(1, add.length()); |
| } |
| return current + queryDelim + add; |
| } |
| |
| /** |
| * Turns the specified key and value into a properly encoded |
| * query pair string. If the value is an array or List, then |
| * this will create a delimited string of query pairs, reusing |
| * the same key for each of the values separately. |
| * @param key parameter key |
| * @param value parameter value(s) |
| * @return encoded query string fragment |
| */ |
| protected String toQuery(Object key, Object value) |
| { |
| StringBuilder out = new StringBuilder(); |
| if (value == null) |
| { |
| out.append(encode(key)); |
| out.append('='); |
| /* Interpret null as "no value" */ |
| } |
| else if (value instanceof List) |
| { |
| appendAsArray(out, key, ((List)value).toArray()); |
| } |
| else if (value instanceof Object[]) |
| { |
| appendAsArray(out, key, (Object[])value); |
| } |
| else |
| { |
| out.append(encode(key)); |
| out.append('='); |
| out.append(encode(value)); |
| } |
| return out.toString(); |
| } |
| |
| /* Utility method to avoid logic duplication in toQuery() */ |
| protected void appendAsArray(StringBuilder out, Object key, Object[] arr) |
| { |
| String encKey = encode(key); |
| for (int i=0; i < arr.length; i++) |
| { |
| out.append(encKey); |
| out.append('='); |
| if (arr[i] != null) |
| { |
| out.append(encode(arr[i])); |
| } |
| if (i+1 < arr.length) |
| { |
| out.append(queryDelim); |
| } |
| } |
| } |
| |
| /** |
| * Uses {@link #normalizeQuery} to make all delimiters in the |
| * specified query string match the current query delimiter |
| * and then uses {@link #parseQuery(String,String)} to parse it |
| * according to that same delimiter. |
| * @param query query string |
| * @return parameters map |
| */ |
| protected Map<String,Object> parseQuery(String query) |
| { |
| return parseQuery(normalizeQuery(query), this.queryDelim); |
| } |
| |
| /** |
| * This will use the specified query delimiter to parse the specified |
| * query string into a map of keys to values. |
| * If there are multiple query pairs in the string that have the same |
| * key, then the values will be combined into a single List value |
| * associated with that key. |
| * @param query query string |
| * @param queryDelim query delimiter |
| * @return parameters map |
| */ |
| protected Map<String,Object> parseQuery(String query, String queryDelim) |
| { |
| if (query.startsWith("?")) |
| { |
| query = query.substring(1, query.length()); |
| } |
| String[] pairs = query.split(queryDelim); |
| if (pairs.length == 0) |
| { |
| return null; |
| } |
| Map<String,Object> params = new LinkedHashMap<String,Object>(pairs.length); |
| for (String pair : pairs) |
| { |
| String[] kv = pair.split("="); |
| String key = kv[0]; |
| Object value = kv.length > 1 ? kv[1] : null; |
| if (params.containsKey(kv[0])) |
| { |
| Object oldval = params.get(key); |
| if (oldval instanceof List) |
| { |
| ((List)oldval).add((String)value); |
| value = oldval; |
| } |
| else |
| { |
| List<String> list = new ArrayList<String>(); |
| list.add((String)oldval); |
| list.add((String)value); |
| value = list; |
| } |
| } |
| params.put(key, value); |
| } |
| return params; |
| } |
| |
| /** |
| * Sets the anchor for this instance and treats empty strings like null. |
| * @param obj fragment |
| */ |
| public void setFragment(Object obj) |
| { |
| if (obj == null) |
| { |
| this.fragment = null; |
| } |
| else |
| { |
| this.fragment = String.valueOf(obj); |
| if (this.fragment.length() == 0) |
| { |
| this.fragment = null; |
| } |
| } |
| } |
| |
| /** |
| * If the specified value is null, this will set the scheme, userInfo, |
| * host, port, path, query, and fragment all to their null-equivalent |
| * values. Otherwise, this will |
| * convert the specified object into a {@link URI}, then those same |
| * values from the URI object to this instance, when not null or empty. |
| * In other words, when given a URI this will only set values present |
| * in the URI. |
| * @param obj URI |
| * @return success |
| */ |
| protected boolean setFromURI(Object obj) |
| { |
| if (obj == null) |
| { |
| // clear everything out... |
| setScheme(null); |
| setUserInfo(null); |
| setHost(null); |
| setPort(null); |
| setPath(null); |
| setQuery(null); |
| setFragment(null); |
| return true; |
| } |
| |
| URI uri = toURI(obj); |
| if (uri == null) |
| { |
| return false; |
| } |
| if (uri.getScheme() != null) |
| { |
| setScheme(uri.getScheme()); |
| } |
| if (uri.isOpaque()) |
| { |
| this.opaque = true; |
| if (uri.getSchemeSpecificPart() != null) |
| { |
| // path is used as scheme-specific part |
| setPath(uri.getSchemeSpecificPart()); |
| } |
| } |
| else |
| { |
| if (uri.getUserInfo() != null) |
| { |
| setUserInfo(uri.getUserInfo()); |
| } |
| if (uri.getHost() != null) |
| { |
| setHost(uri.getHost()); |
| } |
| if (uri.getPort() > -1) |
| { |
| setPort(uri.getPort()); |
| } |
| String pth = uri.getPath(); |
| if (pth != null) |
| { |
| if (pth.equals("/") || pth.length() == 0) |
| { |
| pth = null; |
| } |
| setPath(pth); |
| } |
| } |
| if (uri.getQuery() != null) |
| { |
| setQuery(uri.getQuery()); |
| } |
| if (uri.getFragment() != null) |
| { |
| setFragment(uri.getFragment()); |
| } |
| return true; |
| } |
| |
| /** |
| * Turns the specified object into a string and thereby a URI. |
| * @param obj source object |
| * @return URI or null |
| */ |
| protected URI toURI(Object obj) |
| { |
| if (obj instanceof URI) |
| { |
| return (URI)obj; |
| } |
| else |
| { |
| try |
| { |
| return new URI(String.valueOf(obj)); |
| } |
| catch (Exception e) |
| { |
| getLog().error("Could not convert '{}' to URI", obj, e); |
| return null; |
| } |
| } |
| } |
| |
| /** |
| * Tries to create a URI from the current port, opacity, scheme, |
| * userInfo, host, path, query and fragment set for this instance, |
| * using the {@link URI} constructor that is appropriate to the opacity. |
| * @return URI object or null |
| */ |
| protected URI createURI() |
| { |
| try |
| { |
| // fail if there was an error in setting the port |
| if (port > -2) |
| { |
| if (opaque) |
| { |
| if (query != null && query.size() > 0) |
| { |
| getLog().warn("Ignoring given parameters in opaque URI: {}", getQuery()); |
| } |
| // path is used as scheme-specific part |
| return new URI(scheme, path, fragment); |
| } |
| else if (forceRelative) |
| { |
| if (path == null && query == null && fragment == null) |
| { |
| return null; |
| } |
| return new URI(null, null, null, -1, path, toQuery(query), fragment); |
| } |
| else |
| { |
| // only create the URI if we have some values besides a port |
| if (scheme == null && user == null && host == null |
| && path == null && query == null && fragment == null) |
| { |
| return null; |
| } |
| return new URI(scheme, user, host, port, path, toQuery(query), fragment); |
| } |
| } |
| } |
| catch (Exception e) |
| { |
| getLog().error("Could not create URI", e); |
| } |
| return null; |
| } |
| |
| // --------------------------------------------- Template Methods ----------- |
| |
| /** |
| * Returns the configured charset used by the {@link #encode} and |
| * {@link #decode} methods. |
| * @return character encoding |
| */ |
| public String getCharacterEncoding() |
| { |
| return this.charset; |
| } |
| |
| /** |
| * Returns true if the query delimiter used by this instance is |
| * using <code>&amp;</code> as the delimiter for query data pairs |
| * or just using <code>&</code>. |
| * @return whether the query string delimiter uses the XHTML delimiter |
| */ |
| public boolean isXHTML() |
| { |
| return queryDelim.equals(XHTML_QUERY_DELIMITER); |
| } |
| |
| /** |
| * Returns true if {@link #param(Object,Object)} appends values; |
| * false if the method overwrites existing value(s) for the specified key. |
| * @return whether parameters are appended to the query string |
| */ |
| public boolean getAppendParams() |
| { |
| return this.appendParams; |
| } |
| |
| |
| /** |
| * Returns a new instance with the specified value set as its scheme. |
| * @param scheme scheme |
| * @return new LinkTool |
| */ |
| public LinkTool scheme(Object scheme) |
| { |
| LinkTool copy = duplicate(); |
| copy.setScheme(scheme); |
| return copy; |
| } |
| |
| /** |
| * Returns a new instance with the scheme set to "https". |
| * @return new LinkTool |
| */ |
| public LinkTool secure() |
| { |
| return scheme(SECURE_SCHEME); |
| } |
| |
| /** |
| * Returns a new instance with the scheme set to "http". |
| * @return new LinkTool |
| */ |
| public LinkTool insecure() |
| { |
| return scheme(DEFAULT_SCHEME); |
| } |
| |
| /** |
| * Return the scheme value for this instance. |
| * @return scheme |
| */ |
| public String getScheme() |
| { |
| return scheme; |
| } |
| |
| /** |
| * Returns true if this instance's scheme is "https". |
| * @return whether the protocol is secure |
| */ |
| public boolean isSecure() |
| { |
| return SECURE_SCHEME.equalsIgnoreCase(getScheme()); |
| } |
| |
| /** |
| * Returns true if this instance represents an opaque URI. |
| * @return opacity flag |
| * @see URI |
| */ |
| public boolean isOpaque() |
| { |
| return this.opaque; |
| } |
| |
| /** |
| * Returns a new instance with the specified value |
| * set as its user info. |
| * @param info user info |
| * @return new LinkTool |
| */ |
| public LinkTool user(Object info) |
| { |
| LinkTool copy = duplicate(); |
| copy.setUserInfo(info); |
| return copy; |
| } |
| |
| /** |
| * Returns the {@link URI#getUserInfo()} value for this instance. |
| * @return user info |
| */ |
| public String getUser() |
| { |
| return this.user; |
| } |
| |
| /** |
| * Returns a new instance with the specified value set as its |
| * host. If no scheme has yet been set, the new instance will |
| * also have its scheme set to the {@link #DEFAULT_SCHEME} (http). |
| * @param host host |
| * @return new LinkTool |
| */ |
| public LinkTool host(Object host) |
| { |
| LinkTool copy = duplicate(); |
| copy.setHost(host); |
| // if we have host but no scheme |
| if (copy.getHost() != null && !copy.isAbsolute()) |
| { |
| // use default scheme |
| copy.setScheme(DEFAULT_SCHEME); |
| } |
| return copy; |
| } |
| |
| /** |
| * Return the host value for this instance. |
| * @return host |
| */ |
| public String getHost() |
| { |
| return this.host; |
| } |
| |
| /** |
| * Returns a new instance with the specified value set |
| * as its port number. If the value cannot be parsed into |
| * an integer, the returned instance will always return |
| * null for {@link #toString} and other |
| * {@link #createURI}-dependent methods to alert the user |
| * to the error. |
| * @param port port |
| * @return new LinkTool |
| */ |
| public LinkTool port(Object port) |
| { |
| LinkTool copy = duplicate(); |
| copy.setPort(port); |
| return copy; |
| } |
| |
| /** |
| * Returns the port value, if any. |
| * @return port or null |
| */ |
| public Integer getPort() |
| { |
| if (this.port < 0) |
| { |
| return null; |
| } |
| return this.port; |
| } |
| |
| /** |
| * Returns a new instance with the specified value |
| * set as its path. |
| * @param pth path |
| * @return new link tool |
| */ |
| public LinkTool path(Object pth) |
| { |
| LinkTool copy = duplicate(); |
| copy.setPath(pth); |
| return copy; |
| } |
| |
| /** |
| * Returns the current path value for this instance. |
| * @return path |
| */ |
| public String getPath() |
| { |
| return this.path; |
| } |
| |
| /** |
| * Appends the given value to the end of the current |
| * path value. |
| * @param pth path to append |
| * @return new LinkTool |
| */ |
| public LinkTool append(Object pth) |
| { |
| LinkTool copy = duplicate(); |
| copy.appendPath(pth); |
| return copy; |
| } |
| |
| /** |
| * Returns the directory stack |
| * in the set {@link #getPath()} value, by just trimming |
| * off all that follows the last "/". |
| * @return directory stack |
| */ |
| public String getDirectory() |
| { |
| if (this.path == null || this.opaque) |
| { |
| return null; |
| } |
| int lastSlash = this.path.lastIndexOf('/'); |
| if (lastSlash < 0) |
| { |
| return ""; |
| } |
| return this.path.substring(0, lastSlash + 1); |
| } |
| |
| /** |
| * Returns the last section of the path, |
| * which is all that follows the final "/". |
| * @return file |
| */ |
| public String getFile() |
| { |
| if (this.path == null || this.opaque) |
| { |
| return null; |
| } |
| int lastSlash = this.path.lastIndexOf('/'); |
| if (lastSlash < 0) |
| { |
| return this.path; |
| } |
| return this.path.substring(lastSlash + 1, this.path.length()); |
| } |
| |
| /** |
| * Returns the "root" for this URI, if it has one. |
| * This does not stick close to URI dogma and will |
| * try to insert the default scheme if there is none, |
| * and will return null if there is no host or if there |
| * was an error when the port value was last set. It will |
| * return null for any opaque URLs as well, as those have |
| * no host or port. |
| * @return root |
| */ |
| public String getRoot() |
| { |
| LinkTool root = root(); |
| if (root == null) |
| { |
| return null; |
| } |
| return root.toString(); |
| } |
| |
| /** |
| * Returns a new LinkTool instance that represents |
| * the "root" of the current one, if it has one. |
| * This essentially calls {@link #absolute()} and |
| * sets the path, query, and fragment to null on |
| * the returned instance. |
| * @see #getRoot() |
| * @return new LinkTool |
| */ |
| public LinkTool root() |
| { |
| if (host == null || opaque || port == -2) |
| { |
| return null; |
| } |
| LinkTool copy = absolute(); |
| copy.setPath(null); |
| copy.setQuery(null); |
| copy.setFragment(null); |
| return copy; |
| } |
| |
| /** |
| * Returns a new LinkTool instance with |
| * the path set to the result of {@link #getDirectory()} |
| * and the query and fragment set to null. |
| * @return new LinkTool |
| */ |
| public LinkTool directory() |
| { |
| LinkTool copy = root(); |
| if (copy == null) |
| { |
| copy = duplicate(); |
| // clear query and fragment, since root() didn't |
| copy.setQuery(null); |
| copy.setFragment(null); |
| } |
| copy.setPath(getDirectory()); |
| return copy; |
| } |
| |
| /** |
| * Returns true if this instance is being forced to |
| * return relative URIs or has a null scheme value. |
| * @return whether the link is relative |
| */ |
| public boolean isRelative() |
| { |
| return (this.forceRelative || this.scheme == null); |
| } |
| |
| /** |
| * Returns a copy of this LinkTool instance that has |
| * {@link #setForceRelative} set to true. |
| * @return new LinkTool |
| */ |
| public LinkTool relative() |
| { |
| LinkTool copy = duplicate(); |
| copy.setForceRelative(true); |
| return copy; |
| } |
| |
| /** |
| * <p>Returns a copy of the link with the specified directory-relative |
| * URI reference set as the end of the path and {@link #setForceRelative} |
| * set to true. If the specified relative path is null, that is treated |
| * the same as an empty path.</p> |
| * |
| * <p>Example: |
| * <code><a href='$link.relative("/login/index.vm")'>Login Page</a></code><br> |
| * produces something like<br> |
| * <code><a href="/myapp/login/index.vm">Login Page</a></code></p> |
| * |
| * @param obj A directory-relative URI reference (e.g. file path in current directory) |
| * @return a new instance of LinkTool with the specified changes |
| * @see #relative() |
| */ |
| public LinkTool relative(Object obj) |
| { |
| LinkTool copy = relative(); |
| // prepend relative paths with the current directory |
| String pth; |
| if (obj == null) |
| { |
| pth = getContextPath(); |
| } |
| else |
| { |
| pth = combinePath(getContextPath(), String.valueOf(obj)); |
| } |
| if (copy.setFromURI(pth)) |
| { |
| return copy; |
| } |
| return null; |
| } |
| |
| /** |
| * At this level, this only returns the result of {@link #getDirectory}. |
| * It is here as an extension hook for subclasses to change the |
| * "context" for relative links. |
| * @return context path |
| * @see #relative(Object) |
| * @see #getDirectory |
| */ |
| public String getContextPath() |
| { |
| return getDirectory(); |
| } |
| |
| /** |
| * Returns true if this instance has a scheme value |
| * and is not being forced to create relative URIs. |
| * @return whether this link is absolute |
| */ |
| public boolean isAbsolute() |
| { |
| return (this.scheme != null && !this.forceRelative); |
| } |
| |
| /** |
| * Returns a copy of this LinkTool instance that has |
| * {@link #setForceRelative} set to false and sets the |
| * scheme to the "http" if no scheme has been set yet. |
| * @return new LinkTool |
| */ |
| public LinkTool absolute() |
| { |
| LinkTool copy = duplicate(); |
| copy.setForceRelative(false); |
| if (copy.getScheme() == null) |
| { |
| copy.setScheme(DEFAULT_SCHEME); |
| } |
| return copy; |
| } |
| |
| /** |
| * <p>Returns a copy of the link with the specified URI reference |
| * either used as or converted to an absolute (non-relative) |
| * URI reference. Unless the specified URI contains a query |
| * or anchor, those values will not be overwritten when using |
| * this method.</p> |
| * |
| * <p>Example:<br> |
| * <code><a href='$link.absolute("login/index.vm")'>Login Page</a></code><br> |
| * produces something like<br> |
| * <code><a href="http://myserver.net/myapp/login/index.vm">Login Page</a></code>;<br> |
| * <code><a href='$link.absolute("/login/index.vm")'>Login Page</a></code><br> |
| * produces something like<br> |
| * <code><a href="http://myserver.net/login/index.vm">Login Page</a></code>;<br> |
| * and<br> |
| * <code><a href='$link.absolute("http://theirserver.com/index.jsp")'>Their, Inc.</a></code><br> |
| * produces something like<br> |
| * <code><a href="http://theirserver.net/index.jsp">Their, Inc.</a></code> |
| * </p> |
| * |
| * @param obj A root-relative or context-relative path or an absolute URI. |
| * @return a new instance of LinkTool with the specified path or URI |
| * @see #absolute() |
| */ |
| public LinkTool absolute(Object obj) |
| { |
| // assume it's just a path value to go with current scheme/host/port |
| LinkTool copy = absolute(); |
| if (obj == null) |
| { |
| // just use the current directory path, if any |
| copy.setPath(getDirectory()); |
| } |
| else |
| { |
| String pth = String.valueOf(obj); |
| // paths that don't start with '/' |
| // are considered relative to the current directory |
| if (!pth.startsWith(DEFAULT_SCHEME) && !pth.startsWith("/")) |
| { |
| pth = combinePath(getDirectory(), pth); |
| } |
| if (!copy.setFromURI(pth)) |
| { |
| return null; |
| } |
| } |
| return copy; |
| } |
| |
| /** |
| * <p>Returns a copy of the link with the given URI reference set. |
| * Few changes are applied to the given URI reference. The URI |
| * reference can be absolute, server-relative, relative and may |
| * contain query parameters. This method will overwrite all previous |
| * settings for scheme, host port, path, query and anchor.</p> |
| * |
| * @param uri URI reference to set |
| * @return a new instance of LinkTool |
| */ |
| public LinkTool uri(Object uri) |
| { |
| LinkTool copy = duplicate(); |
| if (copy.setFromURI(uri)) |
| { |
| return copy; |
| } |
| return null; |
| } |
| |
| /** |
| * If the tool is not in "safe mode"--which it is by default-- |
| * this will return the {@link URI} representation of this instance, |
| * if any. |
| * @return URI |
| * @see SafeConfig#isSafeMode() |
| */ |
| public URI getUri() |
| { |
| if (!isSafeMode()) |
| { |
| return createURI(); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the full URI of this template without any query data. |
| * e.g. <code>http://myserver.net/myapp/stuff/View.vm</code> |
| * Note! The returned String will not represent any URI reference |
| * or query data set for this LinkTool. A typical application of |
| * this method is with the HTML base tag. For example: |
| * <code><base href="$link.baseRef"></code> |
| * @return base link |
| */ |
| public String getBaseRef() |
| { |
| LinkTool copy = duplicate(); |
| copy.setQuery(null); |
| copy.setFragment(null); |
| return copy.toString(); |
| } |
| |
| /** |
| * Sets the specified value as the current query data, |
| * after normalizing the pair delimiters. This overrides |
| * any existing query. |
| * @param query query string |
| * @return new LinkTool |
| */ |
| public LinkTool query(Object query) |
| { |
| LinkTool copy = duplicate(); |
| copy.setQuery(query); |
| return copy; |
| } |
| |
| /** |
| * Returns the current query as a string, if any. |
| * @return query string |
| */ |
| public String getQuery() |
| { |
| return toQuery(this.query); |
| } |
| |
| /** |
| * <p>Adds a key=value pair to the query data. Whether |
| * this new query pair is appended to the current query |
| * or overwrites any previous pair(s) with the same key |
| * is controlled by the {@link #getAppendParams} value. |
| * The default behavior is to append.</p> |
| * |
| * @param key key of new query parameter |
| * @param value value of new query parameter |
| * @return a new instance of LinkTool |
| */ |
| public LinkTool param(Object key, Object value) |
| { |
| LinkTool copy = duplicate(true); |
| copy.setParam(key, value, this.appendParams); |
| return copy; |
| } |
| |
| /** |
| * Appends a new key=value pair to the existing query |
| * data. |
| * |
| * @param key key of new query parameter |
| * @param value value of new query parameter |
| * @return a new instance of LinkTool |
| */ |
| public LinkTool append(Object key, Object value) |
| { |
| LinkTool copy = duplicate(true); |
| copy.setParam(key, value, true); |
| return copy; |
| } |
| |
| /** |
| * Sets a new key=value pair to the existing query |
| * data, overwriting any previous pair(s) that have |
| * the same key. |
| * |
| * @param key key of new query parameter |
| * @param value value of new query parameter |
| * @return a new instance of LinkTool |
| */ |
| public LinkTool set(Object key, Object value) |
| { |
| LinkTool copy = duplicate(true); |
| copy.setParam(key, value, false); |
| return copy; |
| } |
| |
| /** |
| * Returns a new LinkTool instance that has any |
| * value(s) under the specified key removed from the query data. |
| * |
| * @param key key of the query pair(s) to be removed |
| * @return a new instance of LinkTool |
| */ |
| public LinkTool remove(Object key) |
| { |
| LinkTool copy = duplicate(true); |
| copy.removeParam(key); |
| return copy; |
| } |
| |
| /** |
| * This method can do two different things. If you pass in a |
| * boolean, it will create a new LinkTool duplicate and call |
| * {@link #handleParamsBoolean(boolean)} on it. In this class, true |
| * values do nothing (subclasses may have use for them), but false |
| * values will clear out all params in the query for that instance. |
| * If you pass in a query string or a Map of parameters, those |
| * values will be added to the new LinkTool, either overwriting |
| * previous value(s) with those keys or appending to them, |
| * depending on the {@link #getAppendParams} value. |
| * |
| * @param parameters a boolean or new query data (either Map or query string) |
| * @return a new instance of LinkTool |
| */ |
| public LinkTool params(Object parameters) |
| { |
| // don't waste time with null/empty data |
| if (parameters == null) |
| { |
| return this; |
| } |
| if (parameters instanceof Boolean) |
| { |
| Boolean action = ((Boolean)parameters).booleanValue(); |
| LinkTool copy = duplicate(true); |
| copy.handleParamsBoolean(action); |
| return copy; |
| } |
| if (parameters instanceof Map && ((Map)parameters).isEmpty()) |
| { |
| return duplicate(false); |
| } |
| |
| LinkTool copy = duplicate(this.appendParams); |
| copy.setParams(parameters, this.appendParams); |
| return copy; |
| } |
| |
| public Map getParams() |
| { |
| if (this.query == null || this.query.isEmpty()) |
| { |
| return null; |
| } |
| return this.query; |
| } |
| |
| /** |
| * <p>Returns a copy of the link with the specified anchor to be |
| * added to the end of the generated hyperlink.</p> |
| * |
| * <p>Example:<br> |
| * <code><a href='$link.setAnchor("foo")'>Foo</a></code><br> |
| * produces something like<br> |
| * <code><a href="#foo">Foo</a></code></p> |
| * |
| * @param anchor an internal document reference |
| * @return a new instance of LinkTool with the set anchor |
| */ |
| public LinkTool anchor(Object anchor) |
| { |
| LinkTool copy = duplicate(); |
| copy.setFragment(anchor); |
| return copy; |
| } |
| |
| /** |
| * Returns the anchor (internal document reference) set for this link. |
| * @return anchor |
| */ |
| public String getAnchor() |
| { |
| return this.fragment; |
| } |
| |
| /** |
| * @return self |
| */ |
| public LinkTool getSelf() |
| { |
| // there are no self-params to bother with at this level, |
| return self; |
| } |
| |
| /** |
| * Returns the full URI reference that's been built with this tool, |
| * including the query string and anchor, e.g. |
| * <code>http://myserver.net/myapp/stuff/View.vm?id=42&type=blue#foo</code>. |
| * Typically, it is not necessary to call this method explicitely. |
| * Velocity will call the toString() method automatically to obtain |
| * a representable version of an object. |
| * @return string representation |
| */ |
| public String toString() |
| { |
| URI uri = createURI(); |
| if (uri == null) |
| { |
| return null; |
| } |
| if (query != null) |
| { |
| return decodeQueryPercents(uri.toString()); |
| } |
| return uri.toString(); |
| } |
| |
| /** |
| * This is an ugly (but fast) hack that's needed because URI encodes |
| * things that we don't need encoded while not encoding things |
| * that we do need encoded. So, we have to encode query data |
| * before creating the URI to ensure they are properly encoded, |
| * but then URI encodes all the % from that encoding. Here, |
| * we isolate the query data and manually decode the encoded |
| * %25 in that section back to %, without decoding anything else. |
| * @param url source url |
| * @return decoded url |
| */ |
| protected String decodeQueryPercents(String url) |
| { |
| StringBuilder out = new StringBuilder(url.length()); |
| boolean inQuery = false, havePercent = false, haveTwo = false; |
| for (int i=0; i<url.length(); i++) |
| { |
| char c = url.charAt(i); |
| if (inQuery) |
| { |
| if (havePercent) |
| { |
| if (haveTwo) |
| { |
| out.append('%'); |
| if (c != '5') |
| { |
| out.append('2').append(c); |
| } |
| havePercent = haveTwo = false; |
| } |
| else if (c == '2') |
| { |
| haveTwo = true; |
| } |
| else |
| { |
| out.append('%').append(c); |
| havePercent = false; |
| } |
| } |
| else if (c == '%') |
| { |
| havePercent = true; |
| } |
| else |
| { |
| out.append(c); |
| } |
| if (c == '#') |
| { |
| inQuery = false; |
| } |
| } |
| else |
| { |
| out.append(c); |
| if (c == '?') |
| { |
| inQuery = true; |
| } |
| } |
| } |
| // if things ended part way |
| if (havePercent) |
| { |
| out.append('%'); |
| if (haveTwo) |
| { |
| out.append('2'); |
| } |
| } |
| return out.toString(); |
| } |
| |
| /** |
| * This instance is considered equal to any |
| * LinkTool instance whose toString() method returns a |
| * String equal to that returned by this instance's toString() |
| * @see #toString() |
| * @param obj object to compare to |
| * @return equality |
| */ |
| @Override |
| public boolean equals(Object obj) |
| { |
| if (obj == null || !(obj instanceof LinkTool)) |
| { |
| return false; |
| } |
| // string value is all that ultimately matters |
| String that = obj.toString(); |
| if (that == null && toString() == null) |
| { |
| return true; |
| } |
| return that.equals(toString()); |
| } |
| |
| /** |
| * Returns the hash code for the result of toString(). |
| * If toString() returns {@code null} (yes, we do break that contract), |
| * this will return {@code -1}. |
| * @return hash code |
| */ |
| @Override |
| public int hashCode() |
| { |
| String hashme = toString(); |
| if (hashme == null) |
| { |
| return -1; |
| } |
| return hashme.hashCode(); |
| } |
| |
| |
| /** |
| * Delegates encoding of the specified url content to |
| * {@link URLEncoder#encode} using the configured character encoding. |
| * @param obj URL to encode |
| * @return String - the encoded url. |
| */ |
| public String encode(Object obj) |
| { |
| if (obj == null) |
| { |
| return null; |
| } |
| try |
| { |
| return URLEncoder.encode(String.valueOf(obj), charset); |
| } |
| catch (UnsupportedEncodingException uee) |
| { |
| getLog().error("Character encoding '{}' is unsupported", charset, uee); |
| return null; |
| } |
| } |
| |
| /** |
| * Delegates decoding of the specified url content to |
| * {@link URLDecoder#decode} using the configured character encoding. |
| * @param obj URL to decode |
| * @return String - the decoded url. |
| */ |
| public String decode(Object obj) |
| { |
| if (obj == null) |
| { |
| return null; |
| } |
| try |
| { |
| return URLDecoder.decode(String.valueOf(obj), charset); |
| } |
| catch (UnsupportedEncodingException uee) |
| { |
| getLog().error("Character encoding '{}' is unsupported", charset, uee); |
| return null; |
| } |
| } |
| |
| } |