|
Description
|
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. customer .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.
Posted Date : 2005-08-05 06:36:36.0
Peabody community member xxxxx@xxxxx 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.
Posted Date : 2005-11-17 22:26:08.0
|
|
Comments
|
Submitted On 30-OCT-2005
kennardconsulting
I believe the evaluation above, when it states 'too late for Mustang', is a misunderstanding.
I have already submitted, via the Mustang Peabody initiative, an implementation for this RFE. The implementation is considerably more mature than the example above, which was only representative.
It was accepted into Peabody six weeks ago.
Submitted On 11-FEB-2009
grima
FYI, if anyone finds this on Google -- this RFE never made it into Java 6. FWIW, I think this is still a noticeable omission from the API.
Submitted On 11-FEB-2009
RichardKennard
At the prompting of grima, I have released the final implementation of this RFE under a BSD license at https://urlencodedquerystring.dev.java.net
Perhaps it will make it into the JDK one day :)
PLEASE NOTE: JDK6 is formerly known as Project Mustang
|