Adding JMCR-Stable version
[Benchmarks_CSolver.git] / JMCR-Stable / real-world application / jigsaw / src / org / w3c / www / protocol / http / HttpManager.java
diff --git a/JMCR-Stable/real-world application/jigsaw/src/org/w3c/www/protocol/http/HttpManager.java b/JMCR-Stable/real-world application/jigsaw/src/org/w3c/www/protocol/http/HttpManager.java
new file mode 100644 (file)
index 0000000..fb6c910
--- /dev/null
@@ -0,0 +1,1160 @@
+// HttpManager.java\r
+// $Id: HttpManager.java,v 1.2 2010/06/15 17:53:12 smhuang Exp $\r
+// (c) COPYRIGHT MIT and INRIA, 1996.\r
+// Please first read the full copyright statement in file COPYRIGHT.html\r
+\r
+package org.w3c.www.protocol.http ;\r
+\r
+import java.util.Enumeration;\r
+import java.util.Hashtable;\r
+import java.util.Properties;\r
+\r
+import java.net.URL;\r
+\r
+import java.io.InputStream;\r
+import java.io.PrintStream;\r
+\r
+import org.w3c.www.mime.MimeHeaderHolder;\r
+import org.w3c.www.mime.MimeParser;\r
+import org.w3c.www.mime.MimeParserFactory;\r
+\r
+import org.w3c.util.LRUList;\r
+import org.w3c.util.ObservableProperties;\r
+import org.w3c.util.PropertyMonitoring;\r
+import org.w3c.util.SyncLRUList;\r
+\r
+class ManagerDescription {\r
+    HttpManager manager = null;\r
+    Properties  properties = null;\r
+\r
+    final HttpManager getManager() {\r
+       return manager;\r
+    }\r
+\r
+    final boolean sameProperties(Properties props) {\r
+       if (props.size() != properties.size())\r
+           return false;\r
+       Enumeration e = props.propertyNames();\r
+       while (e.hasMoreElements()) {\r
+           String name = (String) e.nextElement();\r
+           String prop = properties.getProperty(name);\r
+           if ((prop == null) || (! prop.equals(props.getProperty(name))))\r
+               return false;\r
+       }\r
+       return true;\r
+    }\r
+\r
+    ManagerDescription(HttpManager manager, Properties props) {\r
+       this.manager    = manager;\r
+       this.properties = (Properties)props.clone();\r
+    }\r
+}\r
+\r
+class ReplyFactory implements MimeParserFactory {\r
+\r
+    public MimeHeaderHolder createHeaderHolder(MimeParser parser) {\r
+       return new Reply(parser);\r
+    }\r
+\r
+}\r
+\r
+/**\r
+ * The client side HTTP request manager.\r
+ * This class is the user interface (along with the other public classes of\r
+ * this package) for the W3C client side library implementing HTTP. \r
+ * A typical request is launched though the following sequence:\r
+ * <pre>\r
+ * HttpManager     manager = HttpManager.getManager() ;\r
+ * Request request = manager.createRequest() ;\r
+ * request.setMethod(HTTP.GET) ;\r
+ * request.setURL(new URL("http://www.w3.org/pub/WWW/"));\r
+ * Reply    reply = manager.runRequest(request) ;\r
+ * // Get the reply input stream that contains the actual data:\r
+ * InputStream in = reply.getInputStream() ;\r
+ * ...\r
+ * </pre>\r
+ */\r
+\r
+public class HttpManager implements PropertyMonitoring {\r
+\r
+    private static final boolean debug = false;\r
+\r
+    private static final \r
+    String DEFAULT_SERVER_CLASS = "org.w3c.www.protocol.http.HttpBasicServer";\r
+\r
+    /**\r
+     * The name of the property indicating the class of HttpServer to use.\r
+     */\r
+    public static final\r
+    String SERVER_CLASS_P = "org.w3c.www.protocol.http.server";\r
+\r
+    /**\r
+     * The name of the property containing the ProprequestFilter to launch.\r
+     */\r
+    public static final \r
+    String FILTERS_PROP_P = "org.w3c.www.protocol.http.filters";\r
+    /**\r
+     * The maximum number of simultaneous connectionlrus.\r
+     */\r
+    public static final\r
+    String CONN_MAX_P = "org.w3c.www.protocol.http.connections.max";\r
+    /**\r
+     * The SO_TIMEOUT of the client socket.\r
+     */\r
+    public static final\r
+    String TIMEOUT_P = "org.w3c.www.protocol.http.connections.timeout";\r
+    /**\r
+     * The connection timeout of the client socket.\r
+     */\r
+    public static final\r
+    String CONN_TIMEOUT_P ="org.w3c.www.protocol.http.connections.connTimeout";\r
+    /**\r
+     * Header properties - The allowed drift for getting cached resources.\r
+     */\r
+    public static final \r
+    String MAX_STALE_P = "org.w3c.www.protocol.http.cacheControl.maxStale";\r
+    /**\r
+     * Header properties - The minium freshness required on cached resources.\r
+     */\r
+    public static final\r
+    String MIN_FRESH_P = "org.w3c.www.protocol.http.cacheControl.minFresh";\r
+    /**\r
+     * Header properties - Set the only if cached flag on requests.\r
+     */\r
+    public static final \r
+    String ONLY_IF_CACHED_P=\r
+                         "org.w3c.www.protocol.http.cacheControl.onlyIfCached";\r
+    /**\r
+     * Header properties - Set the user agent.\r
+     */\r
+    public static final \r
+    String USER_AGENT_P = "org.w3c.www.protocol.http.userAgent";\r
+    /**\r
+     * Header properties - Set the accept header.\r
+     */\r
+    public static final \r
+    String ACCEPT_P = "org.w3c.www.protocol.http.accept";\r
+    /**\r
+     * Header properties - Set the accept language.\r
+     */\r
+    public static final \r
+    String ACCEPT_LANGUAGE_P = "org.w3c.www.protocol.http.acceptLanguage";\r
+    /**\r
+     * Header properties - Set the accept encodings.\r
+     */\r
+    public static final \r
+    String ACCEPT_ENCODING_P = "org.w3c.www.protocol.http.acceptEncoding";\r
+    /**\r
+     * Header properties - are we parsing answers in a lenient way?\r
+     */\r
+    public static final \r
+    String LENIENT_P = "org.w3c.www.protocol.http.lenient";\r
+    /**\r
+     * Header properties - should we reuse a connection for POST?\r
+     */\r
+    public static final \r
+    String KEEPBODY_P = "org.w3c.www.protocol.http.keepbody";\r
+    /**\r
+     * Header properties - Should we use a proxy ?\r
+     */\r
+    public static final\r
+    String PROXY_SET_P = "proxySet";\r
+    /**\r
+     * Header properties - What is the proxy host name.\r
+     */\r
+    public static final\r
+    String PROXY_HOST_P = "proxyHost";\r
+    /**\r
+     * Header properties - What is the proxy port number.\r
+     */\r
+    public static final\r
+    String PROXY_PORT_P = "proxyPort";\r
+\r
+    /**\r
+     * The default value for the <code>Accept</code> header.\r
+     */\r
+    public static final\r
+    String DEFAULT_ACCEPT = "*/*";\r
+    /**\r
+     * The default value for the <code>User-Agent</code> header.\r
+     */\r
+    public static final\r
+    String DEFAULT_USER_AGENT = "Jigsaw/2.2.6";\r
+\r
+    /**\r
+     * This array keeps track of all the created managers.\r
+     * A new manager (kind of HTTP client side context) is created for each\r
+     * diffferent set of properties.\r
+     */\r
+    private static ManagerDescription managers[] = new ManagerDescription[4];\r
+\r
+    /**\r
+     * The class to instantiate to create new HttpServer instances.\r
+     */\r
+    protected Class serverclass = null;\r
+    /**\r
+     * The properties we initialized from.\r
+     */\r
+    ObservableProperties props = null;\r
+    /**\r
+     * The server this manager knows about, indexed by FQDN of target servers.\r
+     */\r
+    protected Hashtable servers = null;\r
+    /**\r
+     * The template request (the request we will clone to create new requests)\r
+     */\r
+    protected Request template = null ;\r
+    /**\r
+     * The LRU list of connections.\r
+     */\r
+    protected LRUList connectionsLru = null;\r
+    /**\r
+     * The filter engine attached to this manager.\r
+     */\r
+    FilterEngine filteng = null;\r
+\r
+    protected int timeout = 300000;\r
+    protected int conn_timeout = 3000;\r
+    protected int conn_count = 0;\r
+    protected int conn_max = 5;\r
+    protected boolean lenient = true;\r
+    protected boolean keepbody = false;\r
+\r
+    protected Hashtable _tmp_servers = null; // synced during creation\r
+\r
+    /**\r
+     * Update the proxy configuration to match current properties setting.\r
+     * @return A boolean, <strong>true</strong> if change was done,\r
+     * <strong>false</strong> otherwise.\r
+     */\r
+\r
+    protected boolean updateProxy() {\r
+       boolean set = props.getBoolean(PROXY_SET_P, false);\r
+       if ( set ) {\r
+           // Wow using a proxy now !\r
+           String host  = props.getString(PROXY_HOST_P, null);\r
+           int    port  = props.getInteger(PROXY_PORT_P, -1);\r
+           URL    proxy = null;\r
+           try {\r
+               proxy   = new URL("http", host, port, "/");\r
+           } catch (Exception ex) {\r
+               return false;\r
+           }\r
+           // Now if a proxy...\r
+           if (( proxy != null ) && (proxy.getHost() != null))\r
+               template.setProxy(proxy);\r
+       } else {\r
+           template.setProxy(null);\r
+       }\r
+       return true;\r
+    }\r
+\r
+    /**\r
+     * Get this manager properties.\r
+     * @return An ObservableProperties instance.\r
+     */\r
+\r
+    public final ObservableProperties getProperties() {\r
+       return props;\r
+    }\r
+\r
+    /**\r
+     * PropertyMonitoring implementation - Update properties on the fly !\r
+     * @param name The name of the property that has changed.\r
+     * @return A boolean, <strong>true</strong> if change is accepted,\r
+     * <strong>false</strong> otherwise.\r
+     */\r
+\r
+    public boolean propertyChanged(String name) {\r
+       Request tpl = template;\r
+       if ( name.equals(FILTERS_PROP_P) ) {\r
+           // FIXME\r
+           return true;\r
+           // return false;\r
+       } else if ( name.equals(TIMEOUT_P) ) {\r
+           setTimeout(props.getInteger(TIMEOUT_P, timeout));\r
+           return true;\r
+       } else if ( name.equals(CONN_TIMEOUT_P) ) {\r
+           setConnTimeout(props.getInteger(CONN_TIMEOUT_P, conn_timeout));\r
+           return true;\r
+       } else if ( name.equals(CONN_MAX_P) ) {\r
+           setMaxConnections(props.getInteger(CONN_MAX_P, conn_max));\r
+           return true;\r
+       } else if ( name.equals(MAX_STALE_P) ) {\r
+           int ival = props.getInteger(MAX_STALE_P, -1);\r
+           if ( ival >= 0 )\r
+               tpl.setMaxStale(ival);\r
+           return true;\r
+       } else if ( name.equals(MIN_FRESH_P) ) {\r
+           int ival = props.getInteger(MIN_FRESH_P, -1);\r
+           if ( ival >= 0 )\r
+               tpl.setMinFresh(ival);\r
+           return true;\r
+       } else if ( name.equals(LENIENT_P) ) {\r
+           lenient = props.getBoolean(LENIENT_P, lenient);\r
+           return true;\r
+       } else if ( name.equals(KEEPBODY_P) ) {\r
+           keepbody = props.getBoolean(KEEPBODY_P, keepbody);\r
+           return true;\r
+       } if ( name.equals(ONLY_IF_CACHED_P) ) {\r
+           tpl.setOnlyIfCached(props.getBoolean(ONLY_IF_CACHED_P, false));\r
+           return true;\r
+       } else if ( name.equals(USER_AGENT_P) ) {\r
+           tpl.setValue("user-agent"\r
+                        , props.getString(USER_AGENT_P\r
+                                          , DEFAULT_USER_AGENT));\r
+           return true;\r
+       } else if ( name.equals(ACCEPT_P) ) {\r
+           tpl.setValue("accept" \r
+                        , props.getString(ACCEPT_P, DEFAULT_ACCEPT));\r
+           return true;\r
+       } else if ( name.equals(ACCEPT_LANGUAGE_P) ) {\r
+           String sval = props.getString(ACCEPT_LANGUAGE_P, null);\r
+           if ( sval != null )\r
+               tpl.setValue("accept-language", sval);\r
+           return true;\r
+       } else if ( name.equals(ACCEPT_ENCODING_P) ) {\r
+           String sval = props.getString(ACCEPT_ENCODING_P, null);\r
+           if ( sval != null )\r
+               tpl.setValue("accept-encoding", sval);\r
+           return true;\r
+       } else if ( name.equals(PROXY_SET_P)\r
+                   || name.equals(PROXY_HOST_P)\r
+                   || name.equals(PROXY_PORT_P) ) {\r
+           return updateProxy();\r
+       } else {\r
+           return true;\r
+       } \r
+    }\r
+\r
+    /**\r
+     * Allow the manager to interact with the user if needed.\r
+     * This will, for example, allow prompting for paswords, etc.\r
+     * @param onoff Turn interaction on or off.\r
+     */\r
+\r
+    public void setAllowUserInteraction(boolean onoff) {\r
+       template.setAllowUserInteraction(onoff);\r
+    }\r
+\r
+    protected static synchronized \r
+       HttpManager getManager(Class managerclass, Properties p) \r
+    {\r
+       // Does such a manager exists already ?\r
+       for (int i = 0 ; i < managers.length ; i++) {\r
+           if ( managers[i] == null )\r
+               continue;\r
+           if ( managers[i].sameProperties(p) )\r
+               return managers[i].getManager();\r
+       }\r
+       // Get the props we will initialize from:\r
+       ObservableProperties props = null;\r
+       if ( p instanceof ObservableProperties )\r
+           props = (ObservableProperties) p;\r
+       else\r
+           props = new ObservableProperties(p);\r
+       // Create a new manager for this set of properties:\r
+       HttpManager manager = null;;\r
+       try {\r
+           Object o = managerclass.newInstance();\r
+           if (o instanceof HttpManager) {\r
+               manager = (HttpManager) o;\r
+           } else { // default value\r
+               manager = new HttpManager();\r
+           }\r
+       } catch (Exception ex) {\r
+           ex.printStackTrace();\r
+           manager = new HttpManager();\r
+       }\r
+       manager.props = props;\r
+       // Initialize this new manager filters:\r
+       String filters[] = props.getStringArray(FILTERS_PROP_P, null);\r
+       if ( filters != null ) {\r
+           for (int i = 0 ; i < filters.length ; i++) {\r
+               try {\r
+                   Class c = Class.forName(filters[i]);\r
+                   PropRequestFilter f = null;\r
+                   f = (PropRequestFilter) c.newInstance();\r
+                   //Added by Jeff Huang\r
+                   //TODO: FIXIT\r
+                   f.initialize(manager);\r
+               } catch (PropRequestFilterException ex) {\r
+                   System.out.println("Couldn't initialize filter \""\r
+                                      + filters[i]\r
+                                      + "\" init failed: "\r
+                                       + ex.getMessage());\r
+               } catch (Exception ex) {\r
+                   System.err.println("Error initializing prop filters:");\r
+                   System.err.println("Coulnd't initialize ["\r
+                                       + filters[i]\r
+                                      + "]: " + ex.getMessage());\r
+                   ex.printStackTrace();\r
+                   System.exit(1);\r
+               }\r
+           }\r
+       }\r
+       // The factory to create MIME reply holders:\r
+       manager.factory = manager.getReplyFactory();\r
+       // The class to create HttpServer instances from\r
+       String c = props.getString(SERVER_CLASS_P, DEFAULT_SERVER_CLASS);\r
+       try {\r
+         manager.serverclass = Class.forName(c);\r
+           //Added by Jeff Huang\r
+           //TODO: FIXIT\r
+       } catch (Exception ex) {\r
+           System.err.println("Unable to initialize HttpManager: ");\r
+           System.err.println("Class \""+c+"\" not found, from property "\r
+                               + SERVER_CLASS_P);\r
+           ex.printStackTrace();\r
+           System.exit(1);\r
+       }\r
+       // Setup the template request:\r
+       Request tpl = manager.template;\r
+       // Set some default headers value (from props)\r
+       // Check for a proxy ?\r
+       manager.updateProxy();\r
+       // CacheControl, only-if-cached\r
+       tpl.setOnlyIfCached(props.getBoolean(ONLY_IF_CACHED_P, false));\r
+       // CacheControl, maxstale\r
+       int ival = props.getInteger(MAX_STALE_P, -1);\r
+       if ( ival >= 0 )\r
+           tpl.setMaxStale(ival);\r
+       // CacheControl, minfresh:\r
+       ival = props.getInteger(MIN_FRESH_P, -1);\r
+       if ( ival >= 0 )\r
+           tpl.setMinFresh(ival);\r
+       // general, lenient\r
+       manager.lenient = props.getBoolean(LENIENT_P, true);\r
+       manager.keepbody = props.getBoolean(KEEPBODY_P, false);\r
+       // General, User agent\r
+       String sval;\r
+       tpl.setValue("user-agent"\r
+                    , props.getString(USER_AGENT_P\r
+                                      , DEFAULT_USER_AGENT));\r
+       // General, Accept\r
+       tpl.setValue("accept" \r
+                    , props.getString(ACCEPT_P, DEFAULT_ACCEPT));\r
+       // General, Accept-Language\r
+       sval = props.getString(ACCEPT_LANGUAGE_P, null);\r
+       if ( sval != null ) {\r
+           if (sval.trim().length() > 0) {\r
+               tpl.setValue("accept-language", sval);\r
+           }\r
+       }\r
+       // General, Accept-Encoding\r
+       sval = props.getString(ACCEPT_ENCODING_P, null);\r
+       if ( sval != null ) {\r
+           if (sval.trim().length() > 0) {\r
+               tpl.setValue("accept-encoding", sval);\r
+           }\r
+       }\r
+       // Maximum number of allowed connections:\r
+       manager.conn_max = props.getInteger(CONN_MAX_P, 5);\r
+       // timeout value\r
+       manager.timeout = props.getInteger(TIMEOUT_P, manager.timeout);\r
+       // connection timeout\r
+       manager.conn_timeout = props.getInteger(CONN_TIMEOUT_P, \r
+                                               manager.conn_timeout);\r
+       // Register ourself as a property observer:\r
+       props.registerObserver(manager);\r
+       // Register that manager in our knwon managers:\r
+       for (int i = 0 ; i < managers.length ; i++) {\r
+           if ( managers[i] == null ) {\r
+               managers[i] = new ManagerDescription(manager, p);\r
+               return manager;\r
+           }\r
+       }\r
+       ManagerDescription nm[] = new ManagerDescription[managers.length << 1];\r
+       System.arraycopy(managers, 0, nm, 0, managers.length);\r
+       nm[managers.length] = new ManagerDescription(manager, p);\r
+       managers = nm;\r
+       return manager;\r
+    }\r
+                                                      \r
+\r
+    /**\r
+     * Get an instance of the HTTP manager.\r
+     * This method returns an actual instance of the HTTP manager. It may\r
+     * return different managers, if it decides to distribute the load on\r
+     * different managers (avoid the HttpManager being a bottleneck).\r
+     * @return An application wide instance of the HTTP manager.\r
+     */\r
+\r
+    public static synchronized HttpManager getManager(Properties p) {\r
+       return getManager(HttpManager.class, p);\r
+    }\r
+\r
+    public static HttpManager getManager() {\r
+       return getManager(System.getProperties());\r
+    }\r
+\r
+    /**\r
+     * Get the String key for the server instance handling that request.\r
+     * This method takes care of any proxy setting (it will return the key\r
+     * to the proxy when required.)\r
+     * @return A uniq identifier for the handling server, as a String.\r
+     */\r
+\r
+    public final String getServerKey(Request request) {\r
+       URL    proxy  = request.getProxy();\r
+       URL    target = request.getURL();\r
+       String key   = null;\r
+       if ( proxy != null ) {\r
+           return ((proxy.getPort() == 80)\r
+                   ? proxy.getHost().toLowerCase()\r
+                   : (proxy.getHost().toLowerCase()+":"+proxy.getPort()));\r
+       } else {\r
+           return ((target.getPort() == 80)\r
+                   ? target.getHost().toLowerCase()\r
+                   : (target.getHost().toLowerCase()+":"+target.getPort()));\r
+       }\r
+    }\r
+\r
+    /**\r
+     * Get the appropriate server object for handling request to given target.\r
+     * @param key The server's key, as returned by <code>getServerKey</code>.\r
+     * @return An object complying to the HttpServer interface.\r
+     * @exception HttpException If the given host name couldn't be resolved.\r
+     */\r
+\r
+    protected HttpServer lookupServer(String host, int port)\r
+       throws HttpException\r
+    {\r
+       int    p  = (port == -1) ? 80 : port;\r
+       String id = ((p == 80) \r
+                    ? host.toLowerCase() \r
+                    : (host.toLowerCase() +":"+p));\r
+       // Check for an existing server:\r
+       HttpServer server = (HttpServer) servers.get(id);\r
+       if ( server != null ) {\r
+           return server;\r
+       }\r
+       // Create and register a new server:\r
+       synchronized (_tmp_servers) {\r
+           if (_tmp_servers.containsKey(id)) {\r
+               server = (HttpServer) _tmp_servers.get(id);\r
+               synchronized (server) {\r
+                   while (server.state == null || \r
+                          server.state.state != HttpServerState.PREINIT) {\r
+                       try {\r
+                           wait(100);\r
+                       } catch (InterruptedException ex) {\r
+                       } catch (IllegalMonitorStateException ex) {\r
+                           break;\r
+                       }\r
+                   }\r
+               }\r
+               if (server.state.state == HttpServerState.OK) {\r
+                   return server;\r
+               } else if (server.state.ex != null) {\r
+                   throw server.state.ex;\r
+               } else {\r
+                   throw new RuntimeException("Unexpected error "+\r
+                                              "in lookupServer");\r
+               }\r
+           } else {\r
+               try {\r
+                   server = (HttpServer) serverclass.newInstance();\r
+               } catch (Exception ex) {\r
+                   String msg = ("Unable to create an instance of \""\r
+                                 + serverclass.getName()\r
+                                 + "\", invalid config, check the "\r
+                                 + SERVER_CLASS_P + " property.");\r
+                   throw new HttpException(ex, msg);\r
+               } \r
+           }\r
+       }\r
+       try {\r
+           synchronized (server) {\r
+               server.initialize(this, new HttpServerState(server), host, p,\r
+                                 timeout, conn_timeout);\r
+               try {\r
+                   notifyAll();\r
+               } catch (IllegalMonitorStateException imse) {};\r
+           }\r
+           // FIXME for long running servers the growing hashtable  is\r
+           // a potential leak. This is a hard way of taking care of that\r
+//         if (servers.size() > conn_max) {\r
+//             closeAnyConnection();\r
+//             servers = new Hashtable(conn_max);\r
+//         }\r
+       } finally {\r
+           synchronized (_tmp_servers) {\r
+               if (server.state.state == HttpServerState.OK) {\r
+                   if (!servers.containsKey(id)) {\r
+                       servers.put(id, server);\r
+                   }\r
+               } else {\r
+//                 System.err.println("ERROR State is "+server.state.state\r
+//                                    +" for " + server);\r
+               }\r
+               _tmp_servers.remove(id);\r
+           }\r
+       }\r
+       return server;\r
+    }\r
+\r
+    /**\r
+     * The given connection is about to be used.\r
+     * Update our list of available servers.\r
+     * @param conn The idle connection.\r
+     */\r
+\r
+    public synchronized void notifyUse(HttpConnection conn) {\r
+       if (debug)\r
+           System.out.println("+++ connection used");\r
+       connectionsLru.remove(conn);\r
+    }\r
+\r
+    /**\r
+     * The given connection can be reused, but is now idle.\r
+     * @param conn The connection that is now idle.\r
+     */\r
+\r
+    public synchronized void notifyIdle(HttpConnection conn) {\r
+       if (debug)\r
+           System.out.println("+++ connection idle");\r
+       connectionsLru.toHead(conn);\r
+       notifyAll();\r
+    }\r
+\r
+    /**\r
+     * The given connection has just been created.\r
+     * @param conn The newly created connection.\r
+     */\r
+\r
+    protected synchronized void notifyConnection(HttpConnection conn) {\r
+       if (debug)\r
+           System.out.println("+++ notify conn_count " + (conn_count+1)\r
+                              + " / " + conn_max);\r
+       if ( ++conn_count > conn_max )\r
+           closeAnyConnection();\r
+    }\r
+\r
+    /**\r
+     * The given connection has been deleted.\r
+     * @param conn The deleted connection.\r
+     */\r
+\r
+    protected synchronized void deleteConnection(HttpConnection conn) {\r
+       --conn_count;\r
+       connectionsLru.remove(conn);\r
+       if (debug)\r
+           System.out.println("+++ delete conn_count: " + conn_count);\r
+       notifyAll();\r
+    }\r
+\r
+    protected synchronized boolean tooManyConnections() {\r
+       return conn_count >= conn_max;\r
+    }\r
+\r
+    /**\r
+     * Try reusing one of the idle connection of that server, if any.\r
+     * @param server The target server.\r
+     * @return An currently idle connection to the given server.\r
+     */\r
+\r
+    protected synchronized HttpConnection getConnection(HttpServer server) {\r
+       HttpServerState ss = server.getState();\r
+       HttpConnection hcn = ss.getConnection();\r
+       if (hcn != null) {\r
+           notifyUse(hcn);\r
+       }\r
+       return hcn;\r
+    }\r
+\r
+    /**\r
+     * Wait for a connection to come up.\r
+     * @param server, the target server.\r
+     * @exception InterruptedException If interrupted..\r
+     */\r
+\r
+    protected synchronized void waitForConnection(HttpServer server)\r
+       throws InterruptedException\r
+    {\r
+       wait(30000); // FIXME should be tunable, now set to 30s\r
+    }\r
+\r
+    /**\r
+     * Close some connections, but pickling the least recently used ones.\r
+     * One third of the max number of connection is cut. This is done to \r
+     * eliminate the old connections that should be broken already.\r
+     * (no Socket.isAlive());\r
+     * @return A boolean, <strong>true</strong> if a connection was closed\r
+     * <strong>false</strong> otherwise.\r
+     */\r
+\r
+    protected synchronized boolean closeAnyConnection() {\r
+       boolean saved = false;\r
+       int max = Math.max(conn_max/3, 1);\r
+       for (int i=0; i < max; i++) {\r
+           HttpConnection conn = (HttpConnection) connectionsLru.removeTail();\r
+           if ( conn != null ) {\r
+               conn.close();\r
+               if (debug)\r
+                   System.out.println("+++ close request");\r
+                   saved = true;\r
+           } else {\r
+               break;\r
+           }\r
+       }\r
+       // now purge the server Hashtable\r
+       synchronized (servers) {\r
+           Enumeration e = servers.keys();\r
+           if (debug) {\r
+               System.out.println("+++ hashtable purge starting: "\r
+                                  + servers.size() + " entries");\r
+           }\r
+           int nbconn = 0;\r
+           int rnbconn = 0;\r
+           int nbkept = 0;\r
+           while (e.hasMoreElements()) {\r
+               String id = (String) e.nextElement();\r
+               if (id != null) {\r
+                   HttpServer server = (HttpServer) servers.get(id);\r
+                   int conn_count = server.state.getConnectionCount();\r
+                   if (conn_count <= 0) {\r
+                       if (debug) {\r
+                           System.out.println("+++ hashtable purge: "+id);\r
+                       }   \r
+                       servers.remove(id);\r
+                   } else {\r
+                       if (debug) {\r
+                           nbkept++;\r
+                           nbconn += server.state.getConnectionCount();\r
+                           if (server.state.conns != null) {\r
+                               rnbconn += server.state.conns.size();\r
+                           }\r
+                           System.out.println("+++ hashtable keep: "+id\r
+                                              +" ( "\r
+                                            +server.state.getConnectionCount()\r
+                                              +" )");\r
+                       }\r
+                   }\r
+               }\r
+           }\r
+           if (debug) {\r
+               System.out.println("+++ hashtable purge done, keeping "\r
+                                  + servers.size() + " entries");\r
+               System.out.println("+++ hashtable stats, keeping "\r
+                                  + nbconn + " ( "+ rnbconn\r
+                                  + " ) connections for " + nbkept \r
+                                  + " servers ( "\r
+                                  + ((float)nbconn / (float)nbkept) + " )");\r
+           }\r
+           \r
+       }\r
+       return saved;\r
+    }\r
+\r
+    /**\r
+     * One of our server handler wants to open a connection.\r
+     * @param block A boolean indicating whether we should block the calling\r
+     * thread until a token is available (otherwise, the method will just\r
+     * peek at the connection count, and return the appropriate result).\r
+     * @return A boolean, <strong>true</strong> if the connection can be\r
+     * opened straight, <strong>false</strong> otherwise.\r
+     */\r
+\r
+    protected boolean negotiateConnection(HttpServer server) {\r
+       HttpServerState ss = server.getState();\r
+       if ( ! tooManyConnections() ) {\r
+           return true;\r
+       } else if ( ss.notEnoughConnections() ) {\r
+           return closeAnyConnection();\r
+       } else if ( servers.size() > conn_max ) {\r
+           return closeAnyConnection();\r
+       }\r
+       return false;\r
+    }\r
+\r
+    /**\r
+     * A new client connection has been established.\r
+     * This method will try to maintain a maximum number of established\r
+     * connections, by closing idle connections when possible.\r
+     * @param server The server that has established a new connection.\r
+     */\r
+\r
+    protected final synchronized void incrConnCount(HttpServer server) {\r
+       if ( ++conn_count > conn_max )\r
+           closeAnyConnection();\r
+       if (debug)\r
+           System.out.println("+++ incr conn_count: " + conn_count);\r
+    }\r
+\r
+    /**\r
+     * Decrement the number of established connections.\r
+     * @param server The server that has closed one connection to its target.\r
+     */\r
+\r
+    protected final synchronized void decrConnCount(HttpServer server) {\r
+       --conn_count;\r
+       if (debug)\r
+           System.out.println("+++ decr conn_count: " + conn_count);\r
+       if (conn_count < 0) {\r
+           System.err.println(this);\r
+       }\r
+    }\r
+\r
+    /**\r
+     * Run the given request, in synchronous mode.\r
+     * This method will launch the given request, and block the calling thread\r
+     * until the response headers are available.\r
+     * @param request The request to run.\r
+     * @return An instance of Reply, containing all the reply \r
+     * informations.\r
+     * @exception HttpException If something failed during request processing.\r
+     */\r
+\r
+    public Reply runRequest(Request request)\r
+       throws HttpException\r
+    {\r
+       Reply reply  = null;\r
+       int   fcalls = 0;\r
+       // Now run through the ingoing filters:\r
+       RequestFilter filters[] = filteng.run(request);\r
+       if ( filters != null ) {\r
+           for (int i = 0 ; i < filters.length ; i++) {\r
+               if ((reply = filters[fcalls].ingoingFilter(request)) != null)\r
+                   break;\r
+               fcalls++;\r
+           }\r
+       }\r
+       // Locate the appropriate target server:\r
+       URL target = request.getURL();\r
+       if ( reply == null ) {\r
+           HttpServer srv = null;\r
+           boolean    rtry ;\r
+           do {\r
+               rtry = false;\r
+               try {\r
+                   URL proxy  = request.getProxy();\r
+                   if ( proxy != null ) \r
+                       srv = lookupServer(proxy.getHost(), proxy.getPort());\r
+                   else\r
+                       srv = lookupServer(target.getHost(), target.getPort());\r
+                   request.setServer(srv);\r
+                   reply = srv.runRequest(request);\r
+               } catch (HttpException ex) {\r
+                   for (int i = 0; i < fcalls; i++)\r
+                       rtry = rtry || filters[i].exceptionFilter(request, ex);\r
+                   if ( ! rtry )\r
+                       throw ex;\r
+               } finally {\r
+//                 request.unsetServer();\r
+               }\r
+           } while (rtry);\r
+       }\r
+       // Apply the filters on the way back:\r
+       if ( filters != null ) {\r
+           while (--fcalls >= 0) {\r
+               Reply frep = filters[fcalls].outgoingFilter(request, reply);\r
+               if ( frep != null ) {\r
+                   reply = frep;\r
+                   break;\r
+               }\r
+           }\r
+       }\r
+       return reply;\r
+    }\r
+\r
+    /**\r
+     * Get this manager's reply factory.\r
+     * The Reply factory is used when prsing incomming reply from servers, it\r
+     * decides what object will be created to hold the actual reply from the \r
+     * server.\r
+     * @return An object compatible with the MimeParserFactory interface.\r
+     */\r
+\r
+    MimeParserFactory factory = null ;\r
+\r
+    public MimeParserFactory getReplyFactory() {\r
+       if (factory == null) {\r
+           factory = new ReplyFactory();\r
+       }\r
+       return factory;\r
+    }\r
+\r
+    /**\r
+     * Add a new request filter.\r
+     * Request filters are called <em>before</em> a request is launched, and\r
+     * <em>after</em> the reply headers are available. They allow applications\r
+     * to setup specific request headers (such as PICS, or PEP stuff) on the\r
+     * way in, and check the reply on the way out.\r
+     * <p>Request filters are application wide: if their scope matches\r
+     * the current request, then they will always be aplied.\r
+     * <p>Filter scopes are defined inclusively and exclusively\r
+     * @param incs The URL domains for which the filter should be triggered.\r
+     * @param exs The URL domains for which the filter should not be triggered.\r
+     * @param filter The request filter to add.\r
+     */\r
+\r
+    public void setFilter(URL incs[], URL exs[], RequestFilter filter) {\r
+       if ( incs != null ) {\r
+           for (int i = 0 ; i < incs.length ; i++)\r
+               filteng.setFilter(incs[i], true, filter);\r
+       }\r
+       if ( exs != null ) {\r
+           for (int i = 0 ; i < exs.length ; i++)\r
+               filteng.setFilter(exs[i], false, filter);\r
+       }\r
+       return;\r
+    }\r
+\r
+    /**\r
+     * Add a global filter.\r
+     * The given filter will <em>always</em> be invoked.\r
+     * @param filter The filter to install.\r
+     */\r
+\r
+    public void setFilter(RequestFilter filter) {\r
+       filteng.setFilter(filter);\r
+    }\r
+\r
+    /**\r
+     * Find back an instance of a global filter.\r
+     * This methods allow external classes to get a pointer to installed\r
+     * filters of a given class.\r
+     * @param cls The class of the filter to look for.\r
+     * @return A RequestFilter instance, or <strong>null</strong> if not\r
+     * found.\r
+     */\r
+\r
+    public RequestFilter getGlobalFilter(Class cls) {\r
+       return filteng.getGlobalFilter(cls);\r
+    }\r
+\r
+    /**\r
+     * Create a new default outgoing request.\r
+     * This method should <em>always</em> be used to create outgoing requests.\r
+     * It will initialize the request with appropriate default values for \r
+     * the various headers, and make sure that the request is enhanced by\r
+     * the registered request filters.\r
+     * @return An instance of Request, suitable to be launched.\r
+     */\r
+\r
+    public Request createRequest() {\r
+       return (Request) template.getDeeperClone() ;\r
+    }\r
+\r
+    /**\r
+     * Global settings - Set the max number of allowed connections.\r
+     * Set the maximum number of simultaneous connections that can remain\r
+     * opened. The manager will take care of queuing requests if this number\r
+     * is reached.\r
+     * <p>This value defaults to the value of the \r
+     * <code>org.w3c.www.http.connections.max</code> property.\r
+     * @param max_conn The allowed maximum simultaneous open connections.\r
+     */\r
+\r
+    public synchronized void setMaxConnections(int max_conn) {\r
+       this.conn_max = max_conn;\r
+    }\r
+\r
+    /**\r
+     * Global settings - Set the timeout on the socket\r
+     *\r
+     * <p>This value defaults to the value of the \r
+     * <code>org.w3c.www.http.connections.timeout</code> property.\r
+     * @param timeout The allowed maximum microsecond before a timeout.\r
+     */\r
+\r
+    public synchronized void setTimeout(int timeout) {\r
+       this.timeout = timeout;\r
+       Enumeration e = servers.elements();\r
+       while (e.hasMoreElements()) {\r
+           ((HttpServer) e.nextElement()).setTimeout(timeout);\r
+       }\r
+    }\r
+\r
+    /**\r
+     * Global settings - Set the connection timeout for the socket\r
+     *\r
+     * <p>This value defaults to the value of the \r
+     * <code>org.w3c.www.protocol.http.connections.connTimeout</code> property\r
+     * @param timeout The allowed maximum microsecond before a timeout.\r
+     */\r
+\r
+    public synchronized void setConnTimeout(int conn_timeout) {\r
+       this.conn_timeout = conn_timeout;\r
+       Enumeration e = servers.elements();\r
+       while (e.hasMoreElements()) {\r
+           ((HttpServer) e.nextElement()).setConnTimeout(conn_timeout);\r
+       }\r
+    }\r
+\r
+    /**\r
+     * Global settings - set the HTTP parsing lenient or not.\r
+     * @param lenient, true by default, false to detect wrong servers\r
+     */\r
+    public void setLenient(boolean lenient) {\r
+       this.lenient = lenient;\r
+    }\r
+\r
+    /**\r
+     * Is this manager parsing headers in a lenient way?\r
+     * @return A boolean.\r
+     */\r
+    public boolean isLenient() {\r
+       return lenient;\r
+    }\r
+\r
+    /**\r
+     * Global settings - Set an optional proxy to use.\r
+     * Set the proxy to which all requests should be targeted. If the\r
+     * <code>org.w3c.www.http.proxy</code> property is defined, it will be\r
+     * used as the default value.\r
+     * @param proxy The URL for the proxy to use.\r
+     */\r
+\r
+    public void setProxy(URL proxy) {\r
+       template.setProxy(proxy);\r
+    }\r
+\r
+    /**\r
+     * Does this manager uses a proxy to fulfill requests ?\r
+     * @return A boolean.\r
+     */\r
+\r
+    public boolean usingProxy() {\r
+       return template.hasProxy();\r
+    }\r
+\r
+    /**\r
+     * Global settings - Set the request timeout.\r
+     * Once a request has been emited, the HttpManager will sit for this \r
+     * given number of milliseconds before the request is declared to have\r
+     * timed-out.\r
+     * <p>This timeout value defaults to the value of the\r
+     * <code>org.w3c.www.http.requestTimeout</code> property value.\r
+     * @param ms The timeout value in milliseconds.\r
+     */\r
+\r
+    public void setRequestTimeout(int ms) {\r
+    }\r
+\r
+    /**\r
+     * Global settings - Define a global request header.\r
+     * Set a default value for some request header. Once defined, the\r
+     * header will automatically be defined on <em>all</em> outgoing requests\r
+     * created through the <code>createRequest</code> request.\r
+     * @param name The name of the header, case insensitive.\r
+     * @param value It's default value.\r
+     */\r
+\r
+    public void setGlobalHeader(String name, String value) {\r
+org.w3c.util.Trace.showTrace();\r
+System.err.println("**** " + name + " : " + value);\r
+       template.setValue(name, value);\r
+    }\r
+\r
+    /**\r
+     * Global settings - Get a global request header default value.\r
+     * @param name The name of the header to get.\r
+     * @return The value for that header, as a String, or <strong>\r
+     * null</strong> if undefined.\r
+     */\r
+\r
+    public String getGlobalHeader(String name) {\r
+       return template.getValue(name);\r
+    }\r
+\r
+   \r
+    /**\r
+     * Dump all in-memory cached state to persistent storage.\r
+     */\r
+\r
+    public void sync() {\r
+       filteng.sync();\r
+    }\r
+\r
+    /**\r
+     * Create a new HttpManager.\r
+     * FIXME Making this method protected breaks the static method\r
+     * to create HttpManager instances (should use a factory here)\r
+     * @param props The properties from which the manager should initialize \r
+     * itself, or <strong>null</strong> if none are available.\r
+     */\r
+\r
+    protected HttpManager() {\r
+       this.template       = new Request(this);\r
+       this.servers        = new Hashtable();\r
+       this._tmp_servers   = new Hashtable();\r
+       this.filteng        = new FilterEngine();\r
+       this.connectionsLru = new SyncLRUList();\r
+    }\r
+\r
+\r
+    /**\r
+     * DEBUGGING !\r
+     */\r
+\r
+    public synchronized String toString() {\r
+       StringBuffer sb = new StringBuffer();\r
+       HttpConnection hcn = (HttpConnection) connectionsLru.getHead();\r
+       sb.append("Connections: ");\r
+       sb.append(conn_count);\r
+       sb.append(" out of ");\r
+       sb.append(conn_max);\r
+       sb.append("\n\n");\r
+       if (hcn != null) {\r
+           sb.append("**** Idle Connections list ****\n");\r
+           while (hcn != null) {\r
+               sb.append("      ");\r
+               sb.append(hcn.toString());\r
+               sb.append('\n');\r
+               try {\r
+                   hcn = (HttpConnection) hcn.getNext();\r
+               } catch (ClassCastException ccex) {\r
+                   break;\r
+               }\r
+           }\r
+       } else { \r
+           sb.append ("*** NO IDLE CONNECTIONS ***\n");\r
+       }\r
+       sb.append(servers);\r
+       return sb.toString();\r
+    }\r
+\r
+    public static void main(String args[]) {\r
+       try {\r
+           // Get the manager, and define some global headers:\r
+           HttpManager manager = HttpManager.getManager();\r
+           manager.setGlobalHeader("User-Agent", DEFAULT_USER_AGENT);\r
+           manager.setGlobalHeader("Accept", "*/*;q=1.0");\r
+           manager.setGlobalHeader("Accept-Encoding", "gzip");\r
+           PropRequestFilter filter = \r
+             new org.w3c.www.protocol.http.cookies.CookieFilter();\r
+           filter.initialize(manager);\r
+           PropRequestFilter pdebug = \r
+             new org.w3c.www.protocol.http.DebugFilter();\r
+           pdebug.initialize(manager);\r
+           Request request = manager.createRequest();\r
+           request.setURL(new URL(args[0]));\r
+           request.setMethod("GET");\r
+           Reply       reply   = manager.runRequest(request);\r
+           //Display some infos:\r
+           System.out.println("last-modified: "+reply.getLastModified());\r
+           System.out.println("length       : "+reply.getContentLength());\r
+           // Display the returned body:\r
+           InputStream in = reply.getInputStream();\r
+           byte buf[] = new byte[4096];\r
+           int  cnt   = 0;\r
+           while ((cnt = in.read(buf)) >= 0) {\r
+//           System.out.print(new String(buf, 0, cnt));\r
+           }\r
+           System.out.println("-");\r
+           in.close();\r
+           manager.sync();\r
+           System.err.println(manager);\r
+       } catch (Exception ex) {\r
+           ex.printStackTrace();\r
+           if (ex instanceof HttpException) {\r
+               ((HttpException) ex).getException().printStackTrace();\r
+           }\r
+       }\r
+       System.exit(1);\r
+    }\r
+}\r
+\r
+\r