A DESCRIPTION OF THE REQUEST :
URL manipulation is such a common requirement in today's Web applications, and so error prone, that there should be tools for it in the JDK.
Either java.net.URL should be extended with parameter manipulation capabilities (though I believe it's meant to be immutable, so maybe not) or new tools should be introduced.
JUSTIFICATION :
I frequently come across code in JSPs like...
<a href="mypage.jsp?name=${name}">
...or in Java like...
bufUrl.append( strUrl );
bufUrl.append( "?name=" );
bufUrl.append( strName );
This code is both prone to poor URL encoding (if, for example, strName contained spaces) and generating invalid URLs (if, for example, the URL already contains parameters, you cannot append a second '?').
This is partially addressed by JSTL's URL tag, but it's behaviour is lacking. For example:
<c:url var="newurl" value="${url}">
<c:param name="name" value="${name}">
</c:url>
If the ${url} already contains the 'name' parameter, <c:url> does not attempt to parse out ${url} and correctly replace it.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
I enclose an example class that I use extensively in my own applications called UrlBuilder. I would gladly work to make this class suitable for Mustang if this RFE was accepted.
ACTUAL -
Tools that remove common pitfalls for programmers doing URL manipulation. Specifically:
* Safely adding new parameters (only using one '?' and multiple '&'s)
* Safely removing existing parameters
* Correctly URL encoding all parameters
* Replacing (rather than duplicating) parameter names
---------- BEGIN SOURCE ----------
import java.beans.PropertyDescriptor;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.apache.commons.beanutils.PropertyUtils;
/**
* Given a URL and a set of parameters, provide abilites to add and
* remove parameters (either directly, from a Map, or from a Bean),
* and finally reconstruct the proper URL. For example:
*
* <code>
* UrlBuilder builder = new UrlBuilder()
* .setUrl( strUrl ))
* .addParameters( p_request.getParameterMap() );
* .removeParameter( "id" )
* .addParameter( "session", strSession );
* </code>
*
* Note that this class currently does not support URL rewriting of
* cookie strings (though it should).
*/
public class UrlBuilder
{
//
//
// Private statics
//
//
private final static List RESTRICTED = Arrays.asList( new String[] { "j_username", "j_password", "username", "password" } );
//
//
// Private members
//
//
private String m_strUrlWithoutParameters;
private Map m_mapParameters = new HashMap();
//
//
// Public methods
//
//
public UrlBuilder setUrl( String p_strUrl )
{
// Nothing to do?
if ( p_strUrl == null || p_strUrl.length() == 0 )
{
m_strUrlWithoutParameters = "";
return this;
}
// Split off the given URL from its query string
StringTokenizer tokenizerQueryString = new StringTokenizer( p_strUrl, "?" );
m_strUrlWithoutParameters = tokenizerQueryString.nextToken();
// Parse the query string (if any) into name/value pairs
if ( tokenizerQueryString.hasMoreTokens() )
{
String strQueryString = tokenizerQueryString.nextToken();
if ( strQueryString != null )
{
StringTokenizer tokenizerNameValuePair = new StringTokenizer( strQueryString, "&" );
while ( tokenizerNameValuePair.hasMoreTokens() )
{
try
{
String strNameValuePair = tokenizerNameValuePair.nextToken();
StringTokenizer tokenizerValue = new StringTokenizer( strNameValuePair, "=" );
String strName = tokenizerValue.nextToken();
String strValue = tokenizerValue.nextToken();
m_mapParameters.put( strName, strValue );
}
catch ( Throwable t )
{
// If we cannot parse a parameter, ignore it
}
}
}
}
return this;
}
/**
* Add parameters from a map
*/
public UrlBuilder addParameters( Map p_mapParameters )
{
// Nothing to do?
if ( p_mapParameters == null )
return this;
for ( Iterator i = p_mapParameters.keySet().iterator(); i.hasNext(); )
{
String strName = (String) i.next();
Object objValue = p_mapParameters.get( strName );
if ( objValue == null )
continue;
if ( objValue instanceof Object[] )
{
m_mapParameters.put( strName, URLEncoder.encode( ( (Object[]) objValue )[0].toString() ) );
continue;
}
m_mapParameters.put( strName, URLEncoder.encode( objValue.toString() ) );
}
return this;
}
/**
* Add parameters defined by a bean
*/
public UrlBuilder addBeanParameters( Object p_obj )
{
PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors( p_obj );
for ( int iLoop = 0; iLoop < descriptors.length; iLoop++ )
{
try
{
if ( descriptors[iLoop].isHidden() )
continue;
String strName = descriptors[iLoop].getName();
Object objValue = PropertyUtils.getSimpleProperty( p_obj, strName );
if ( objValue == null )
continue;
m_mapParameters.put( strName, URLEncoder.encode( objValue.toString() ) );
}
catch ( Throwable t )
{
// If we can't retrieve a property, no biggie
}
}
return this;
}
/**
* Add a single parameter
*/
public UrlBuilder addParameter( String p_strName, String p_strValue )
{
// Nothing to do?
if ( p_strName == null || p_strValue == null )
return this;
m_mapParameters.put( p_strName, URLEncoder.encode( p_strValue ) );
return this;
}
public UrlBuilder removeParameter( String p_strName )
{
m_mapParameters.remove( p_strName );
return this;
}
public String getParameter( String p_strName )
{
return (String) m_mapParameters.get( p_strName );
}
public String toString()
{
// Construct the final query string
StringBuffer bufQueryString = new StringBuffer();
boolean bFirstTime = true;
for ( Iterator i = m_mapParameters.keySet().iterator(); i.hasNext(); )
{
String strName = (String) i.next();
String strValue = (String) m_mapParameters.get( strName );
// Some names are to be excluded
if ( RESTRICTED.contains( strName ))
continue;
// (ignore empty parameters)
if ( strValue.length() == 0 )
continue;
// (and anything that evaluates to zero)
try
{
if ( Float.parseFloat( strValue ) == 0 )
continue;
}
catch( NumberFormatException e )
{
// If there's an exception, assume strValue is non-zero
}
if ( bFirstTime )
bFirstTime = false;
else
bufQueryString.append( "&" );
bufQueryString.append( strName );
bufQueryString.append( "=" );
bufQueryString.append( strValue );
}
// Reconstruct the URL
if ( bufQueryString.length() > 0 )
{
bufQueryString.insert( 0, '?' );
bufQueryString.insert( 0, m_strUrlWithoutParameters );
return bufQueryString.toString();
}
return m_strUrlWithoutParameters;
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Write your own UrlBuilder class, such as the one above.
Peabody community member ###@###.### sent me an updated
implementation (with fewer imports) and a Junit test. Refer to the attached
files URIBuilder.java and URIBuilderTests.java.
He writes:
As discussed, I would be most grateful if somebody could review the
quality of the submission and consider it for inclusion in Mustang. I am
very, very willing to make any changes my Responsible Engineer suggests.