This is the second iteration in finding the right architecture for the 
preferences API.
_____
Notes
- Moved to a separate package: httpclient.config
- Configuration is not immutable.
- Configuration is now hierarchical and reflects changes in underlying 
Configurations immediately.
- The new ConfigManager links any object with a Configuration instance.
- If the user does nothing the default configuration is always used.

___________
Sample Code

The user can set the configuration from the outside for HttpClient, 
HttpMultiClient, HttpMethod, HttpConnection. The user should not try to 
configure other classes directly:
--user app
HttpClient client = new HttpClient();
Properties patch = new Properties();
patch.setProperty(ConfigKeys.USER_AGENT, "My HTTP Client 0.01");
Configuration clientConfig = new Configuration(Configuration.DEFAULT, 
patch);
ConfigManager.setConfiguration(client, clientConfig);

HttpClient configures HttpMethod automatically IF not yet configured. 
The same way a HttpConnection is configured:
--HttpClient
  public synchronized int executeMethod(HttpMethod method) {
    ...
    if (!ConfigManager.isConfigured(method)) {
     ConfigManager.setConfiguration(method, myConfig);
    }
    method.execute(getState(), connection);
    ...
  }


Low level objects use the configuration of a higher level object. The 
same applies to inner classes:
--ChunkedInputStream
  myConfig = ConfigManager.getConfiguration(method);
  ... myConfig.getBooleanValue(ConfigKeys.IGNORE_PROTOCOL_VIOLATION);

A static class must be configured by the caller using the meta object.
Users should never try to configure low-level classes:
--caller class
  ConfigManager.setConfiguration(NTLM.class, myConfig);

--NTLM
  Configuration myConfig = ConfigManager.getConfiguration(NTLM.class);
  String mySecurityProvider = 
Configuration.getStringValue(ConfigKeys.SECURITY_PROVIDER);


________
Problems

As you can see this approach generates a large overhead. This is mainly 
caused by one requirement: "Would there be a means to assign my own 
properties object to the HttpClient, HttpConnection and HttpMethod 
objects? So I could control the settings on a "client by client", 
"connection by connection",  or "method by method" basis?" by Mark R. 
Diggory, 2002-9-18.

Any single object (e.g. Cookie) must therefore know which Configuration 
applies to it. This means that the creator of an object must set its 
configuration in the ConfigManager. For static classes  the requirement 
can not be fulfilled at all. Not so nice, is it.

Please, if any of you has a good idea how to deal with this, drop me a note.


Odi



/*
 * $Header$
 * $Revision$
 * $Date$
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999-2002 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "HttpClient", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact [EMAIL PROTECTED]
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * [Additional notices, if required by prior licensing conditions]
 *
 */

package org.apache.commons.httpclient.config;

/**
  * Holds the property keys used to configure HttpClient.
  * @see Configuration
  *
  * @author Ortwin Glück
  *
  * @since 2.0
  */
public interface ConfigKeys {

     /** The HTTP version to use.  1.0 means HTTP/1.0, 1.1 means HTTP/1.1 */
     public static final String HTTP_VERSION = "http.version";

     /** Whether to use preemtive authorization or not. Boolean. */
     public static final String PREEMPT_AUTH = "preemtive.authorization";

     /** The maximum number of Location redirects until an Exception is thrown. 
Integer. */
     public static final String MAX_REDIRECT = "redirect.maximum";

     /** The user-agent string used to identify the client against the web server. 
String. */
     public static final String USER_AGENT = "user.agent";

     /** The default port for http requests. */
     public static final String HTTP_PORT = "http.default.port";

     /** The default port for secure https requests. */
     public static final String HTTPS_PORT = "https.default.port";

     /** The security provider to use for crypto alogrithm implementations. */
     public static final String SECURITY_PROVIDER = "security.provider";

     /** The maximum number of simultaneous HTTP/S sockets to be opened to the same 
host */
     public static final String CONNECTIONS_PER_SERVER = 
"simultaneous.connections.maximum";
}
/*
 * $Header$
 * $Revision$
 * $Date$
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999-2002 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "HttpClient", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact [EMAIL PROTECTED]
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * [Additional notices, if required by prior licensing conditions]
 *
 */

package org.apache.commons.httpclient.config;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
  * Holds a configuration for the httpclient package. A configuration can be
  * hierarchically based on another configuration and only overwrite (patch) some
  * of the values in the base configuration. Changes in the underlying base are
  * immediately reflected in derived configurations.
  *
  * @author Ortwin Glück
  *
  * @since 2.0
  */
public class Configuration {
    /**
     * The default configuration read from file.
     */
    public static final Configuration DEFAULT = new Configuration();
    /** Key of the System Property that is queried for the location of the properties 
file */
    public static final String SYSTEM_PROPERTY = 
"org.apache.commons.httpclient.configuration";
    /** Name of the properties file */
    private static final String PROPERTIES_FILE = "httpclient.properties";
    /** Location in a jar file where we expect a properies file */
    private static final String JAR_PATH = "META-INF/services/";
    /** The log */
    private static final Log log = LogFactory.getLog(Configuration.class);
    /** The actual configuration values */
    private Properties props = new Properties();
    /** The base configuration */
    private Configuration base = null;

    /**
    * Creates the default configuration.
    * The default values are read from the <tt>httpclient.properties</tt> which is
    * expected in the following locations:
    * 1. $JAVA_HOME/lib/ directory
    * 2. On the classpath
    * 3. In META-INF/services on the classpath
    *
    * For classpath lookups the following class loaders are probed in order:
    * 1. the context class loader of the current thread
    * 2. the class loader of this class
    * 3. the system class loader
    *
    * An alternative path and filename may be specified in the
    * <tt>org.apache.commons.httpclient.configuration</tt> System Property.
    */
    protected Configuration() {
        String filename = null;
        try {
            filename = System.getProperty(SYSTEM_PROPERTY);
        } catch(SecurityException e) {
        }

        if (filename == null) {
            String javahome = System.getProperty("java.home");
            filename = javahome + File.separator + "lib" + File.separator + 
PROPERTIES_FILE;
        }

        InputStream in = null;
        File file = new File(filename);
        if (file.exists()) {
            try {
                log.debug("Trying "+filename);
                in = new FileInputStream(file);
            } catch(Exception e) {
                log.error("Unable to load configuration file", e);
            }
        }

        if (in == null) {
            try {
             ClassLoader cl = getClassLoader();
                if (cl == null) {
                    log.debug("Trying last ressort class loader");
                    in = ClassLoader.getSystemResourceAsStream(JAR_PATH + 
PROPERTIES_FILE);
                } else {
                    log.debug("Trying class loader "+cl.toString());
                    in = cl.getResourceAsStream(JAR_PATH + PROPERTIES_FILE);
                }
            } catch(Exception e) {
             log.error("Error while probing class loaders", e);
            }
        }

        if (in != null) {
            try {
                props.load(in);
            } catch (IOException e) {
                log.error("Could not load "+ PROPERTIES_FILE, e);
            }
        } else {
            log.warn(PROPERTIES_FILE +" not found. No default values available.");
        }
    }

    /**
     * Returns the best class loader.
     * @return
     */
    private ClassLoader getClassLoader() {
        ClassLoader cl = null;
        try {
            cl = Thread.currentThread().getContextClassLoader();
            if (cl != null) return cl;
        } catch(Exception e) {
            log.warn("ClassLoader Exception: " + e.getMessage());
        }
        try {
            cl = Configuration.class.getClassLoader();
        } catch(Exception e) {
            log.warn("ClassLoader Exception: " + e.getMessage());
        }
        return cl;
    }

    /**
     * Creates a derived configuration based on a configuration base that is modified
     * by the patch values. Values in <tt>patch</tt> hide values in the <tt>base</tt>.
     * <tt>DEFAULT</tt> holds the default configuration which
     * is a good starting point.
     *
     * @param base The configuration base or <tt>null</tt>.
     * @param patch Values that are replaced in the base configuration.
     */
    public Configuration(Configuration base, Properties patch) {
        this.base = base;
        patch(patch);
    }

    /**
    * Convenience method to generate a patched configuration based on the current one.
    * @param patch Values that are replaced in the base configuration.
    * @return new Configuration(this, patch)
    */
    public void patch(Properties patch) {
        props.putAll(patch);
    }

    /**
     * Returns the value that is stored under <tt>key</tt>. If this is a derived
     * configuration and there is no local value for <tt>key</tt> the base
     * configuration will be queried.
     *
     * @param key The key to query
     * @return The value as a String. Empty string if no value was found.
     */
    public String getStringValue(String key) {
        if (base == null) {
            return props.getProperty(key, "").trim();
        } else {
            return props.getProperty(key, base.getStringValue(key));
        }
    }

    public long getLongValue(String key) {
        return Long.parseLong(getStringValue(key));
    }

    public long getLongHexValue(String key) {
        return Long.parseLong(getStringValue(key), 16);
    }

    public int getIntValue(String key) {
        return Integer.parseInt(getStringValue(key));
    }

    public int getIntHexValue(String key) {
        return Integer.parseInt(getStringValue(key), 16);
    }

    public float getFloatValue(String key) {
        return Float.parseFloat(getStringValue(key));
    }

    public double getDoubleValue(String key) {
        return Double.parseDouble(getStringValue(key));
    }

    public boolean getBooleanValue(String key) {
        return isEnabled(key);
    }


    /**
     * Returns true if the value is either yes, true, enabled, on or 1. The check
     * is not case sensitive.
     * @param key The key to check
     * @return
     */
    public boolean isEnabled(String key) {
        String val = getStringValue(key).toUpperCase();
        return (val.equals("YES") || val.equals("TRUE")
             || val.equals("ENABLED") || val.equals("ON")
             || val.equals("1"));
    }

    /**
     * Checks if a key is empty.
     * @param key
     * @return false if a key does not exist, it is the empty string or consits
     * solely of whitespace; true otherwise.
     */
    public boolean isEmpty(String key) {
        return getStringValue(key).equals("");
    }
}
/*
 * $Header$
 * $Revision$
 * $Date$
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999-2002 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "HttpClient", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact [EMAIL PROTECTED]
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * [Additional notices, if required by prior licensing conditions]
 *
 */

package org.apache.commons.httpclient.config;

import java.util.Map;
import java.util.WeakHashMap;

/**
 * Links configurations with objects. This implementation uses a WeakHashMap to
 * ensure that configurations are removed automatically when the linked object
 * is not used any more.
 *
 * @author Ortwin Glück
 *
 * @since 2.0
 */
public class ConfigManager {
    /** The default configuration (for convenience) */
    public static final Configuration DEFAULT = Configuration.DEFAULT;

    /** The singleton instance */
    private static ConfigManager instance = new ConfigManager();

    /** Stores (object, Configuration) pairs */
    private static Map configs = new WeakHashMap();

    /**
     * Singleton. Use static methods only.
     */
    protected ConfigManager() {
    }

    /**
     * Links a configuration to an object. It is stored until the object is reclaimed
     * by the garbage collector.
     * @param object The object to link <tt>configuration</tt> to.
     * @param configuration The configuration.
     */
    public static void setConfiguration(Object object, Configuration configuration) {
        configs.put(object, configuration);
    }

    /**
     * Gets the configuration that was linked to an object with the
     * <tt>setConfiuration</tt> method. If no configuration was set, the default
     * configuration is returned.
     * @param object The object whose configuration to get.
     * @return The configuration that was linked to <tt>object</tt> or the default one.
     */
    public static Configuration getConfiguration(Object object) {
        Configuration config = (Configuration) configs.get(object);
        if (config == null)  {
            return DEFAULT;
        } else {
            return config;
        }
    }

    /**
     * Checks if there is a specific Configuration for an object.
     * @param object The object that should be checked.
     * @return true if a specific Configuration exists, fals if the DEFAULT
     * is used for this object.
     */
    public static boolean isConfigured(Object object) {
        return (configs.get(object) != null);
    }
}
--
To unsubscribe, e-mail:   <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>

Reply via email to