Java Solaris Communities Sun Store Join SDN My Profile Why Join?
 
Bug Database
Bug Detail
Quick Lists
Top 25 Bugs
Top 25 RFE's
Recently Closed Bugs
Printable Page Printable Page


Bug Database
Bug ID: 6306820
Votes 16
Synopsis Extend Java's 'URL parameters' manipulation capabilites
Category java:classes_net
Reported Against
Release Fixed
State 3-Accepted, request for enhancement
Priority: 3-Medium
Related Bugs 6599890
Submit Date 05-AUG-2005
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
Work Around
N/A
Evaluation
This kind of API would be useful. Since it would be used by both client
and server side apps, it probably belongs in J2SE in java.net.
It's too late for mustang. As the submitter says applications typically
roll their own implementations of this kind of facility.
We should be able to do it for dolphin though.
Posted Date : 2005-10-28 10:11:15.0

Contribution-Forum:https://jdk-collaboration.dev.java.net/servlets/ProjectForumMessageView?forumID=1463&messageID=10348
Posted Date : 2005-12-06 17:40:15.0
Comments
  
  Include a link with my name & email   

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