JDK-6306820 : Extend Java's 'URL parameters' manipulation capabilites
  • Type: Enhancement
  • Component: core-libs
  • Sub-Component: java.net
  • Affected Version: 6,6u10
  • Priority: P3
  • Status: Open
  • Resolution: Unresolved
  • OS: generic,windows_2000,windows_xp
  • CPU: generic,x86
  • Submitted: 2005-08-05
  • Updated: 2017-05-19
Related Reports
Duplicate :  
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.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.

Comments
EVALUATION Contribution-Forum:https://jdk-collaboration.dev.java.net/servlets/ProjectForumMessageView?forumID=1463&messageID=10348
06-12-2005

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.
28-10-2005