You are viewing a plain text version of this content. The canonical link for it is here.
Posted to jdo-commits@db.apache.org by mb...@apache.org on 2005/05/22 20:40:21 UTC

svn commit: r171355 [4/31] - in /incubator/jdo/trunk/fostore20: ./ src/ src/conf/ src/java/ src/java/org/ src/java/org/apache/ src/java/org/apache/jdo/ src/java/org/apache/jdo/impl/ src/java/org/apache/jdo/impl/fostore/ test/ test/conf/ test/fsuid2/ test/fsuid2/org/ test/fsuid2/org/apache/ test/fsuid2/org/apache/jdo/ test/fsuid2/org/apache/jdo/pc/ test/java/ test/java/org/ test/java/org/apache/ test/java/org/apache/jdo/ test/java/org/apache/jdo/impl/ test/java/org/apache/jdo/impl/fostore/ test/java/org/apache/jdo/pc/ test/java/org/apache/jdo/pc/appid/ test/java/org/apache/jdo/pc/empdept/ test/java/org/apache/jdo/pc/serializable/ test/java/org/apache/jdo/pc/xempdept/ test/java/org/apache/jdo/test/ test/java/org/apache/jdo/test/query/ test/java/org/apache/jdo/test/util/ test/jdo/ test/jdo/org/ test/jdo/org/apache/ test/jdo/org/apache/jdo/ test/jdo/org/apache/jdo/pc/ test/jdo/org/apache/jdo/pc/appid/ test/jdo/org/apache/jdo/pc/empdept/ test/jdo/org/apache/jdo/pc/serializable/ test/jdo/org/apache/jdo/pc/xempdept/

Added: incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/DumpHandler.java
URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/DumpHandler.java?rev=171355&view=auto
==============================================================================
--- incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/DumpHandler.java (added)
+++ incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/DumpHandler.java Sun May 22 11:40:13 2005
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at 
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+
+package org.apache.jdo.impl.fostore;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.TreeSet;
+
+import org.apache.jdo.util.I18NHelper;
+
+
+/**
+* Process dump requests. A dump request provides information about the
+* store. Currently, two options are supported:
+* <ul>
+* <li>dbInfo: Provide information about all classes stored in the store.</li>
+* <li>ClassMetadata: Provide the stored metadata information for the class
+* <code>className</code>.</li>
+* </ul>
+*
+* @see org.apache.jdo.impl.fostore.DumpOption
+* @author Markus Fuchs
+*/
+// This is server-side code.  It does not need to live in the client.
+class DumpHandler extends RequestHandler {
+
+    /** Maps <code>DumpOptions</code> to
+     * <code>DumpOptionSubHandlers</code>. The option table must match
+     * the <code>optionTable</code> in <code>Dumper</code>.
+     * <p>
+     * Because the <code>DumpOptionSubHandler</code> are bound to 
+     * the state of the enclosing <code>DumpHander</code> instance, the
+     * <code>optionTable</code> has to be non-static. */
+    private static HashMap optionTable = new HashMap();
+
+    /** List of results. A SubRequest might report more than one result. */
+    private final ArrayList results = new ArrayList();
+
+    /** Return status. */
+    private Status status;
+
+    /** I18N support. */
+    private static final I18NHelper msg = I18NHelper.getInstance(I18N.NAME);
+
+    /** Initializes the <code>optionTable</code>.
+     * The option table must match the <code>optionTable</code> in
+     * <code>Dumper</code>.
+     * @see Dumper
+     */
+    // Note that there is no entry for DumpOption.CLASS_INSTANCES: that option
+    // is handled entirely by the Dumper on the client side, with help of
+    // FOStoreStoreManager.getExtent.
+    private void initOptionTable() {
+        optionTable.put(DumpOption.DBINFO,
+                        new DBInfoHandler());
+        optionTable.put(DumpOption.CLASS_METADATA,
+                        new ClassMetadataHandler());
+        optionTable.put(DumpOption.CLASS_SUBCLASSES,
+                        new ClassSubclassesHandler());
+    }
+
+    private DumpHandler(Reply reply, int length,
+                         FOStoreServerConnection con) {
+
+        super(reply, length, con);
+        initOptionTable();
+    }
+    
+    public static final HandlerFactory factory =
+        new HandlerFactory() {
+                public RequestHandler getHandler(Reply reply, int length,
+                                             FOStoreServerConnection con) {
+                return new DumpHandler(reply, length, con);
+            }};
+
+    RequestFinisher handleRequest()
+        throws IOException, FOStoreDatabaseException {
+
+        FOStoreInput in = con.getInputFromClient();
+        FOStoreDatabase db = con.getDatabase();
+
+        // read arguments
+        DumpOption option = DumpOption.read(in);
+        String className = in.readUTF();
+        
+        DumpOptionSubHandler h = 
+            (DumpOptionSubHandler) optionTable.get(option);
+        if (null == h) {
+            reply.setStatus(Status.FATAL,msg.msg("ERR_NoSubHandler", option)); // NOI18N
+        } else {
+            h.run(db, className);
+
+            int size = results.size();
+            reply.writeInt(size);
+            if (size > 0) {
+                for (int i = 0; i < size; i++) {
+                    Object o = results.get(i);
+                    if (null != o) {
+                        reply.writeUTF(o.toString());
+                    }
+                }
+            }
+            reply.setStatus(status);
+        }
+
+        return null;
+    }
+
+    /**
+     * Abstract class for dumping database information. The server creates
+     * instances of subclasses and then invoke <code>run</code>. Subclasses
+     * implement <code>run</code> method to do the real work.
+     */
+    abstract class DumpOptionSubHandler {
+
+        /** Must be implemented to dump particular kind of info.
+         */
+        protected abstract void run(FOStoreDatabase db, String className)
+            throws IOException, FOStoreDatabaseException;
+    }
+
+    /**
+     * OptionHandler that dumps DBInfo.
+     */
+    class DBInfoHandler extends DumpOptionSubHandler {
+      protected void run(FOStoreDatabase db, String className) 
+          throws IOException, FOStoreDatabaseException {
+
+          results.add(db.getDBInfo());
+          status = Status.OK;
+        }
+    }
+
+    /**
+     * Abstract OptionHandler that assists in dumping information about
+     * classes.
+     */
+    abstract class ClassHandler extends DumpOptionSubHandler {
+        protected void run(FOStoreDatabase db, String className) 
+            throws IOException, FOStoreDatabaseException {
+
+            if (null == className) {
+                reply.setStatus(
+                    Status.ERROR,
+                    msg.msg("MSG_MissingParameter", className)); //NOI18N
+            } else {
+                _run(db, className);
+            }
+        }
+
+        DBClass[] getDBClasses(FOStoreDatabase db, String className)
+            throws IOException, FOStoreDatabaseException {
+
+            DBClass rc[] = null;
+
+            // look for class info in store
+            DBInfo dbInfo = db.getDBInfo();
+            ArrayList found = new ArrayList();
+            for (Iterator i = dbInfo.getDBClasses(); i.hasNext();) {
+                DBClass dbClass = (DBClass)i.next();
+                if (className.equals(dbClass.getName())) {
+                    found.add(dbClass);
+                }
+            }
+            int size = found.size();
+            if (size > 0) {
+                rc = new DBClass[size];
+                for (int i = 0; i < size; i++) {
+                    rc[i] = (DBClass)found.get(i);
+                }
+            }
+            return rc;
+        }
+
+        /** Subclasses must implement, to dump their particular kind of info.
+        */
+        abstract void _run(FOStoreDatabase db, String className) 
+            throws IOException, FOStoreDatabaseException;
+    }
+
+
+    /**
+     * OptionHandler that dumps metadata about a class.
+     */
+    class ClassMetadataHandler extends ClassHandler {
+        protected void _run(FOStoreDatabase db, String className) 
+            throws IOException, FOStoreDatabaseException {
+
+            DBClass dbClasses[] = getDBClasses(db, className);
+            for (int i = 0; i < dbClasses.length; i++) {
+                results.add(dbClasses[i]);
+            }
+            status = Status.OK;
+        }
+    }
+
+    /**
+    * OptionHandler that dumps information about the subclasses of a class.
+    */
+    class ClassSubclassesHandler extends ClassHandler {
+        protected void _run(FOStoreDatabase db, String className)
+            throws IOException, FOStoreDatabaseException {
+
+            // Sort results in String order.
+            TreeSet sorted = new TreeSet();
+
+            DBClass dbClasses[] = getDBClasses(db, className);
+            if (null == dbClasses || 0 == dbClasses.length){
+                results.add(
+                    msg.msg("MSG_DbClassNotFound", className)); // NOI18N
+            } else {
+                // For each DBClass known by className, get all of its
+                // subclasses.
+                for (int i = 0; i < dbClasses.length; i++) {
+                    String result = null;
+                    CLID clid = dbClasses[i].getCLID();
+                    OID ssOID = DBInfo.getSubclassSetOID(clid);
+                    SubclassSet sl = (SubclassSet)db.getIfExists(ssOID);
+                    if (null == sl) {
+                        result = msg.msg("MSG_NoSubclasses", className); // NOI18N
+                    } else {
+                        result = msg.msg("MSG_Subclasses", className); // NOI18N
+                        // Get results into the TreeSet
+                        for (Iterator j = sl.iterator(); j.hasNext();) {
+                            CLID subCLID = (CLID)j.next();
+                            OID subDBClassOID = DBInfo.getDBClassOID(subCLID);
+                            DBClass subDBClass =
+                                (DBClass)db.get(subDBClassOID);
+                            sorted.add("\n\t" + subDBClass.getName()); // NOI18N
+                        }
+
+                        // Put sorted subclasses from TreeSet to string
+                        for (Iterator k = sorted.iterator(); k.hasNext();) {
+                            result += (String)k.next();
+                        }
+                    }
+                    results.add(result);
+                }
+            }
+            status = Status.OK;
+        }
+    }
+}

Added: incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/DumpOption.java
URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/DumpOption.java?rev=171355&view=auto
==============================================================================
--- incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/DumpOption.java (added)
+++ incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/DumpOption.java Sun May 22 11:40:13 2005
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at 
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+
+package org.apache.jdo.impl.fostore;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.HashMap;
+
+import javax.jdo.JDOUserException;
+
+import org.apache.jdo.util.I18NHelper;
+
+
+/** Diagnostic code to identify a dump request to the store. Currently,
+* there are the following options supported: 
+* <ul>
+* <li>DBINFO: Provide information about all classes in the store.</li>
+* <li>CLASS_METADATA: Provide metadata information about a special class.</li>
+* <li>CLASS_INSTANCES: List all instances of a special class.</li>
+* </ul>
+*
+* @author Markus Fuchs
+*/
+class DumpOption {
+    /** Value of this DumpOption. */
+    private final int value;
+
+    /** String name of this option. */
+    private final String name;
+
+    /** Map from name to DumpOption. */
+    private static final HashMap options = new HashMap();
+
+    /** Provide information about all classes in the store. */
+    static final DumpOption DBINFO =
+        new DumpOption(1, "dbInfo"); // NOI18N
+
+    /** Provide metadata information about a particular class.*/
+    static final DumpOption CLASS_METADATA =
+        new DumpOption(2, "classMetadata"); // NOI18N
+
+    /** List all instances of a particular class. */
+    static final DumpOption CLASS_INSTANCES =
+    new DumpOption(3, "classInstances"); // NOI18N
+
+    /** List all subclasses of a particular class. */
+    static final DumpOption CLASS_SUBCLASSES =
+    new DumpOption(4, "classSubclasses"); // NOI18N
+
+    /** I18N support. */
+    private static final I18NHelper msg = I18NHelper.getInstance(I18N.NAME);
+
+    /**
+     * Create a DumpOption with the given value and name.
+     */
+    private DumpOption(int value, String name) {
+        this.value = value;
+        this.name = name;
+        options.put(name, this);
+    }
+
+    /** Provide a DumpOption given a name. */
+    static DumpOption forName(String name) {
+        DumpOption rc = (DumpOption)options.get(name);
+        if (null == rc) {
+            throw new JDOUserException(
+                msg.msg("EXC_BadValue", name)); // NOI18N
+        }
+        return rc;
+    }
+
+    //
+    // I/O support
+    // 
+
+    /**
+     * Write this DumpOption's value.
+     */
+    void write(DataOutput out) throws IOException {
+        out.writeUTF(name);
+    }
+
+    /**
+     * Return the instance of a DumpOption that corresponds to the value read
+     * from the DataInput.
+     * @throws IOException if there is an IOException reading the value.
+     * <em>or</em> if the value read does not correspond to a DumpOption.
+     */
+    static DumpOption read(DataInput in) throws IOException {
+        String optionName = in.readUTF();
+        DumpOption rc = forName(optionName);
+        if (null == rc) {
+            throw new IOException(
+                msg.msg("EXC_BadValue", optionName)); // NOI18N
+        }
+        return rc;
+    }
+
+    //
+    // Override java.lang.Object methods
+    //
+    
+    public String toString() {
+        return name;
+    }
+    
+    public boolean equals(Object o) {
+        boolean rc = false;
+        if ((null != o) && (o instanceof DumpOption)) {
+            rc = (value == ((DumpOption)o).value);
+        }
+        return rc;
+    }
+
+    public int hashCode() {
+        // Same as for java.lang.Integer
+        return value;
+    }
+}

Added: incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/DumpRequest.java
URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/DumpRequest.java?rev=171355&view=auto
==============================================================================
--- incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/DumpRequest.java (added)
+++ incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/DumpRequest.java Sun May 22 11:40:13 2005
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at 
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+
+package org.apache.jdo.impl.fostore;
+
+import java.io.DataInput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import javax.jdo.Extent;
+import javax.jdo.JDOHelper;
+import javax.jdo.JDOUserException;
+import javax.jdo.PersistenceManager;
+
+import org.apache.jdo.pm.PersistenceManagerInternal;
+import org.apache.jdo.state.StateManagerInternal;
+
+
+/**
+ * Represents a request to dump information from the store.
+ *
+ * @author Markus Fuchs
+ */
+//
+// This is client-side code.  It does not need to live in the server.
+//
+class DumpRequest extends AbstractRequest {
+    /** The DumpOption.
+     */
+    private final DumpOption option;
+
+    /** The name of the class queried by this request.
+     */
+    private final String className;
+
+    /** Returned dump information.
+     */
+    private String dump;
+
+    DumpRequest(DumpOption option, String className, Message m,
+                     FOStorePMF pmf) {
+
+        super(m, pmf);
+        this.option = option;
+        this.className = className;
+    }
+
+    protected void doRequestBody() throws IOException {
+        //
+        // The format of this request is:
+        //
+        // int: option code
+        // String: className
+        //
+
+        option.write(out);
+        out.writeUTF((null != className) ? className : ""); // NOI18N
+        if (logger.isDebugEnabled()) {
+            logger.debug("DR.dRB: " + option // NOI18N
+                           + " class=" + className); // NOI18N
+        }
+    }
+
+     public void handleReply(Status status, DataInput in, int length) throws IOException {
+         //
+         // The format of the reply is:
+         // int: number of objects (<= one)
+         // String: information dump
+         //
+         // The status might be Status.WARN, in which case either
+         // * The class to be queried wasn't found.
+         // * The className parameter was null.
+
+         int count = in.readInt();
+         if (logger.isDebugEnabled()) {
+             logger.debug("DR.hR/0: count=" + count); // NOI18N
+         }
+
+         StringBuffer results = new StringBuffer();
+         for (int i = 0; i < count; i++) {
+             results.append(in.readUTF());
+         }
+         dump = results.toString();
+         
+         if (logger.isDebugEnabled()) {
+             logger.debug("DR.hR/1: dump=" + dump); // NOI18N
+         }
+    }
+
+    String getDump() {
+        return dump;
+    }
+}

Added: incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/Dumper.java
URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/Dumper.java?rev=171355&view=auto
==============================================================================
--- incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/Dumper.java (added)
+++ incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/Dumper.java Sun May 22 11:40:13 2005
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at 
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+
+package org.apache.jdo.impl.fostore;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.StringTokenizer;
+
+import javax.jdo.Extent;
+import javax.jdo.JDOUserException;
+import javax.jdo.PersistenceManager;
+
+import org.apache.jdo.impl.pm.PersistenceManagerWrapper;
+import org.apache.jdo.pm.PersistenceManagerInternal;
+import org.apache.jdo.util.I18NHelper;
+
+
+/**
+* Provides information such as metadata, extents, objects about the store.
+* <pre>
+* FOStore dumper program usage:
+*     -DdumpOption=OPTION -DclassNames=CLASSNAMES
+* OPTION parameter can be one of the following:
+*     dbInfo: prints general information about the store.
+*     classMetadata: prints the metadata for the classes CLASSNAMES.
+*     classInstances: prints all instances of the classes CLASSNAMES.
+*     classSubclasses: prints all information about the subclasses 
+*     of the classes CLASSNAMES.
+* </pre>
+* This class is <code>public</code> because it has a <code>main</code> entry
+* point for running as a standalone program.
+*
+* @author Markus Fuchs
+* @author Dave Bristor
+*/
+public class Dumper {
+
+    /** Maps <code>DumpOptions</code> to
+     * <code>DumpOptionSubRequests</code>. The option table must match
+     * the <code>optionTable</code> in <code>DumpHandler</code>.
+     * <p>
+     * Because there is no non-static state to be shared between
+     * <code>Dumper</code> and the <code>DumpOptionSubRequest</code>s, 
+     * the <code>optionTable</code> can be initilialzed only once.
+     * @see org.apache.jdo.impl.fostore.DumpOption
+     */
+    private static final HashMap optionTable = new HashMap();
+
+    private static FOStorePMF pmf;
+
+    /** Class names to dump informations about. */
+    private static String classNames;
+
+    /** I18N support. */
+    private static final I18NHelper msg = I18NHelper.getInstance(I18N.NAME);
+
+    /**
+     * Initializes the <code>optionTable</code>.
+     * The option table must match the <code>optionTable</code> in
+     * <code>DumpHandler</code>.
+     * @see org.apache.jdo.impl.fostore.DumpHandler
+     */
+    static {
+        optionTable.put(DumpOption.DBINFO,
+                        new DBInfoRequest());
+        optionTable.put(DumpOption.CLASS_METADATA,
+                        new ClassMetadataRequest());
+        optionTable.put(DumpOption.CLASS_INSTANCES,
+                        new ClassInstancesRequest());
+        optionTable.put(DumpOption.CLASS_SUBCLASSES,
+                        new ClassSubclassesRequest());
+    }
+
+    /**
+     * Given a command line argument that specifies what information
+     * to dump, gets that information from the database and prints it
+     * on standard output.
+     * @param args the command line arguments
+     */
+    public static void main(String args[]) {
+
+        String optionName = System.getProperty("dumpOption"); // NOI18N
+        if (null == optionName) {
+            exit(msg.msg("MSG_MissingDumpOption")); // NOI18N
+        }
+        classNames = System.getProperty("classNames"); // NOI18N
+
+        try {
+            DumpOption option = DumpOption.forName(optionName);
+            
+            DumpOptionSubRequest r = 
+                (DumpOptionSubRequest)optionTable.get(option);
+            r.run();
+        } catch (Exception ex) {
+            exit(msg.msg("MSG_DumperException", ex)); // NOI18N
+        }
+    }
+
+    /**
+     * Abstract class for dumping database information. Clients create
+     * instances of subclasses and then invoke <code>run</code>. Subclasses
+     * implement <code>dump</code> method to do the real work.
+     */
+    static abstract class DumpOptionSubRequest {
+        protected PersistenceManager pm;
+        protected FOStoreStoreManager srm;
+        protected StringTokenizer st;
+
+        /** Called by clients to cause information to be dumped.
+         */
+        void run() {
+            try {
+                setupPMF();
+                pm = pmf.getPersistenceManager();
+                srm = (FOStoreStoreManager)pmf.getStoreManager(pm);
+                dump();
+            } catch (Exception ex) {
+                exit(msg.msg("MSG_DumperException", ex)); // NOI18N
+            }
+        }
+
+        /** Subclasses must implement, to dump their particular kind of info.
+         */
+        protected abstract void dump();
+    }
+
+    /**
+     * DumpRequest that can dump DBInfo.
+     */
+    static class DBInfoRequest extends DumpOptionSubRequest {
+        protected void dump() {
+            println(srm.dump(DumpOption.DBINFO, "")); // NOI18N
+        }
+    }
+
+    /**
+     * Abstract DumpRequest that assists in dumping
+     * information about classes.
+     */
+    static abstract class ClassRequest extends DumpOptionSubRequest {
+        protected void dump() {
+            StringTokenizer st = 
+                new StringTokenizer(classNames, ","); // NOI18N
+            while (st.hasMoreElements()) {
+                subDump((String)st.nextElement());
+            }
+        }
+
+        /** Subclasses must implement, to dump their particular kind
+         * of info.
+         */
+        protected abstract void subDump(String className);
+    }
+
+    /**
+     * DumpRequest that dumps metadata about a class.
+     */
+    static class ClassMetadataRequest extends ClassRequest {
+        protected void subDump(String className) {
+            println(srm.dump(DumpOption.CLASS_METADATA, className));
+        }
+    }
+
+    /**
+     * DumpRequest that dumps information about the instances
+     * of a class.
+     */
+    static class ClassInstancesRequest extends ClassRequest {
+        protected void subDump(String className) {
+            int objCount = 0;
+            Class cls = null;
+            try {
+                cls = Class.forName(className);
+            } catch (ClassNotFoundException ex) {
+                throw new JDOUserException(
+                  msg.msg("EXC_InstantiateClass", className)); // NOI18N
+            }
+
+            println("\n" + msg.msg("MSG_ExtentName", className)); // NOI18N
+            Extent e = pm.getExtent(cls, false);
+            for (Iterator i = e.iterator(); i.hasNext();) {
+                Object pc = i.next();
+                println("" + pc); // NOI18N
+                objCount++;
+            }
+            println(msg.msg("MSG_ExtentCount", className, // NOI18N 
+                            new Integer(objCount)));
+        }
+    }
+
+    /**
+     * DumpRequest that dumps information about the subclasses
+     * of a class.
+     */
+    static class ClassSubclassesRequest extends ClassRequest {
+        protected void subDump(String className) {
+            int objCount = 0;
+            Class cls = null;
+            try {
+                cls = Class.forName(className);
+            } catch (ClassNotFoundException ex) {
+                throw new JDOUserException(
+                  msg.msg("EXC_InstantiateClass", className)); // NOI18N
+            }
+
+            println(srm.dump(DumpOption.CLASS_SUBCLASSES, className));
+        }
+    }
+
+    /** Print an error message and exit.
+     */
+    private static void exit(String message) {
+        println(message);
+        usage();
+        System.exit(1);
+    }
+
+    /** Print the usage message on standard output.
+     */    
+    private static void usage() {
+        println(msg.msg("MSG_Usage")); // NOI18N
+    }
+        
+    /**
+     * Configures a PMF with some basic properties, and creates the
+     * corresponding database.
+     */
+    private static void setupPMF() throws Exception {
+        pmf = new FOStorePMF();
+        pmf.setConnectionCreate(false);
+
+        pmf.setConnectionUserName(System.getProperty("user", // NOI18N
+                                                     "fred")); // NOI18N
+        pmf.setConnectionPassword(System.getProperty("password", // NOI18N
+                                                     "wombat")); // NOI18N
+
+        // Create url in string form.
+        String path = ""; // NOI18N
+
+        String dir = System.getProperty("dir"); // NOI18N
+        if (null == dir) {
+            dir = System.getProperty("user.dir"); // NOI18N
+        }
+
+        String name = System.getProperty("name"); // NOI18N
+        if (null == name) {
+            name = "FOStoreTestDB"; // NOI18N
+        }
+
+        path += dir + File.separator;
+        path += name;
+        
+        path = "fostore:" + path; // NOI18N
+
+        pmf.setConnectionURL(path);
+    }
+
+    /** Print a message on the standard output.
+     * @param s the message to print.
+     */    
+    private static void println(String s) {
+        System.out.println(s);
+    }
+
+}

Added: incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreAbstractMethodException.java
URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreAbstractMethodException.java?rev=171355&view=auto
==============================================================================
--- incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreAbstractMethodException.java (added)
+++ incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreAbstractMethodException.java Sun May 22 11:40:13 2005
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at 
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+
+package org.apache.jdo.impl.fostore;
+
+import javax.jdo.JDOFatalInternalException;
+
+/**
+ * This exception means that a subclass didn't override a method that it should
+ * have.  When thrown, it means there is an implementation bug.
+ *
+ * @author Dave Bristor
+ */
+class FOStoreAbstractMethodException extends JDOFatalInternalException {
+    FOStoreAbstractMethodException(String msg) {
+        super(msg); // NOI18N
+    }
+}

Added: incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreBtreeStorage.java
URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreBtreeStorage.java?rev=171355&view=auto
==============================================================================
--- incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreBtreeStorage.java (added)
+++ incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreBtreeStorage.java Sun May 22 11:40:13 2005
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at 
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+
+package org.apache.jdo.impl.fostore;
+
+import java.util.HashMap;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.netbeans.mdr.persistence.MOFID;
+import org.netbeans.mdr.persistence.ObjectResolver;
+import org.netbeans.mdr.persistence.StorageException;
+import org.netbeans.mdr.persistence.btreeimpl.btreestorage.BtreeFactory;
+import org.netbeans.mdr.persistence.btreeimpl.btreestorage.BtreeStorage;
+
+/**
+ * FOStore specific BtreeStorage subclass. This class manages MOFIDs for a
+ * FOStore datastore. A MOFID consists of two parts: a storageId of type
+ * String and a serial number of type long. When storing on disk the
+ * storageId is converted into a number and stored as 16-bit value. Only 48
+ * bits of the serial number get represented on disk. FOStore uses the
+ * serial number to encode the uid part of an OID and the storageId for the
+ * class id of an OID. The btree class MOFIDInfo converts a MOFID into a
+ * byte array of 8 bytes that is stored on disk. The two high order bytes
+ * represent the storageId. Class MOFIDInfo calls method storageIdToNumber
+ * to convert a String storageId into a number. The remaining 6 bytes
+ * represent the serial number.  
+ *
+ * @author Michael Bouschen
+ * @since 1.1
+ * @version 1.1
+ */
+public class FOStoreBtreeStorage extends BtreeStorage {
+    
+    /** 
+     * Dummy ObjectResolver instance required by BtreeStorage create and 
+     * open methods.
+     */
+    private static final ObjectResolver resolver = new Resolver();
+
+    /** Prefix for the storageId generated from an class id. */
+    private static final String CLID_PREFIX = "FOSTORE_CLID";
+
+    /** Length of class id prefix. */
+    private static final int CLID_PREFIX_LENGTH = 12;
+
+    /** Logger */
+    static final Log logger = LogFactory.getFactory().getInstance(
+        "org.apache.jdo.impl.fostore"); // NOI18N
+
+    /**
+     * Creates a new FOStoreBtreeStorage instance. The constructor creates
+     * or opens a storage based on the specified argument <code>isNew</code>.
+     * @param name the name of the storage.
+     * @param isNew true if the database is being created
+     */
+    public FOStoreBtreeStorage(String name, boolean isNew) 
+        throws StorageException {
+        super(name);
+        if (isNew) {
+            create(true, resolver);
+        } 
+        else {
+            open(false, resolver);
+        }
+    }
+    
+    /**
+     * Returns Creates a MOFID based on the class id and uid taken from a
+     * FOStore OID. The storageId of the returned MOFID represents the
+     * class id and the serial number represents the uid.
+     * @param clid the class id 
+     * @param uid the unique id
+     * @return MOFID representing class id and uid taken from  FOStore OID.
+     */
+    public MOFID createMOFID(int clid, long uid) {
+        // Add FIRST_EXTERNAL_ID to avoid clashes with 
+        // internal btree serial numbers
+        long serialNumber = uid + BtreeFactory.FIRST_EXTERNAL_ID;
+        // The prefix CLID_PREFIX check whether the storageId denotes a
+        // fostore class id. Add FIRST_EXTERNAL_CODE to avoid clashes with  
+        // internal btree storageId codes
+        String storageId = 
+            CLID_PREFIX + (clid + BtreeFactory.FIRST_EXTERNAL_CODE);
+        return new MOFID(serialNumber, storageId);
+    }
+    
+    /** 
+     * Converts a storageId to an int. If the specified storageId starts
+     * with CLID_PREFIX, then the storageId represents a class id. In this
+     * case use the class id as the numeric reresentation. 
+     * @param storageId the storageId as String
+     * @return the numeric representation of the storageId 
+     */
+    public int storageIdToNumber(String storageId) throws StorageException {
+        if (storageId.startsWith(CLID_PREFIX))
+            return Integer.parseInt(storageId.substring(CLID_PREFIX_LENGTH));
+        else 
+            return super.storageIdToNumber(storageId);
+    }
+    
+    /**
+     * Creates a storage id from an int. It returns the FOStore specific
+     * String representation for a storageId representing a class id.
+     * @param number the numberic representation of the storageId 
+     * @return the storageId as String
+     */
+    public String numberToStorageId(int number) throws StorageException {
+        if (number >= BtreeFactory.FIRST_EXTERNAL_CODE)
+            return CLID_PREFIX + number;
+        else
+            return super.numberToStorageId(number);
+    }
+
+    /** Dummy implementation. */
+    static class Resolver implements ObjectResolver {
+        
+        /** */
+        public Object resolve(String storageID, Object key) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("FOStoreBtreeStorage: called Resolver.resolve(" +
+                             storageID + ", " + key + ")");
+            }
+            return new Object();
+        }
+    }
+}
+

Added: incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreClientConnection.java
URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreClientConnection.java?rev=171355&view=auto
==============================================================================
--- incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreClientConnection.java (added)
+++ incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreClientConnection.java Sun May 22 11:40:13 2005
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at 
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+
+package org.apache.jdo.impl.fostore;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Represents the connection as seen by the client.  This contains two types
+ * of interface: that used by the ConnectionFactory, and that used by the
+ * Message.  It provides a means for managing the data sent and received
+ * from the store, as well as for being managed in a pool.
+ *
+ * @author Dave Bristor
+ */
+interface FOStoreClientConnection extends FOStoreConnection {
+
+    /** Connect to the data source.
+     */
+    public void connect() throws IOException;
+    
+    /**
+    * Provides DataInput from which the client can read replies from
+    * the server.
+    * @return DataInput from which the client can read replies.
+    */
+    public DataInput getInputFromServer() throws IOException;
+
+    /**
+     * Indicates that the client has finished writing.  The data is sent to the
+     * server and processed there.
+     */
+    public void sendToStore(byte[] buffer, int start, int length) throws IOException;
+
+    /**
+     * Put this connection back into the connection pool managed by the 
+     * ConnectionFactory.
+     */
+    public void close() throws IOException;
+    
+    /** Set the connection id used to create this connection.  This is used
+     * to determine which connection pool is used.
+     */
+    public void setConnectionId (FOStoreConnectionId connectionId);
+    
+    /** Get the connection id used to create this connection.  Connections
+     * are pooled based on this connection id.
+     */
+    public FOStoreConnectionId getConnectionId();
+    
+    /** Set the connection id used to create this connection.  This is used
+     * to determine which connection pool is used.
+     */
+    public void setConnectionFactory (FOStoreConnectionFactory connectionFactory);
+    
+    /** Get the connection Factory used to create this connection.  Connections
+     * are pooled based on this connection Factory.
+     */
+    public FOStoreConnectionFactory getConnectionFactory();
+    
+    /** Close the database associated with this connection.  In the remote case,
+     * close the socket.
+     */
+    public void closeDatabase() throws IOException, FOStoreDatabaseException ;
+    
+}

Added: incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreClientConnectionImpl.java
URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreClientConnectionImpl.java?rev=171355&view=auto
==============================================================================
--- incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreClientConnectionImpl.java (added)
+++ incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreClientConnectionImpl.java Sun May 22 11:40:13 2005
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at 
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+
+/*
+ * FOStoreClientConnectionImpl.java
+ *
+ * Created on June 7, 2001, 3:17 PM
+ */
+
+package org.apache.jdo.impl.fostore;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.OutputStream;
+
+import java.net.URLConnection;
+import java.net.URL;
+import java.net.Socket;
+
+import java.util.Date;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Implementation of the client side of a FOStore connection.
+ *
+ * @author  Craig Russell
+ * @version 1.0
+ */
+abstract class FOStoreClientConnectionImpl extends URLConnection implements FOStoreClientConnection {
+
+    /** Message to handle connection-to-connection messages (login).
+     */
+    Message message = new Message();
+    
+    FOStoreConnectionFactory connectionFactory;
+    
+    FOStoreConnectionId connectionId;
+    
+    /** Flag set while logging in.  This flag, when set, causes close() to
+     * retain the connection instead of returning the connection to the pool.
+     */
+    private boolean loggingIn = true;
+
+    /** Logger */
+    static final Log logger = LogFactory.getFactory().getInstance(
+        "org.apache.jdo.impl.fostore"); // NOI18N
+    
+    /** Creates new FOStoreClientConnectionImpl */
+    public FOStoreClientConnectionImpl(URL url) {
+        super (url);
+        if (logger.isDebugEnabled()) logger.debug("FOCCI<init>" +  // NOI18N
+            " Host: " + url.getHost() + // NOI18N
+            " Port: " + url.getPort() + // NOI18N
+            " Path: " + url.getPath()); // NOI18N
+    }
+
+    /**
+     * Return this connection to the connection pool.
+     */
+    public void close() throws IOException {
+        if (!loggingIn) {
+            if (logger.isDebugEnabled()) {
+                logger.debug ("FOCCI.close: closing"); // NOI18N
+            }
+            connectionFactory.closeConnection(this);
+        } else {
+            if (logger.isDebugEnabled()) {
+                logger.debug ("FOCCI.close: loggingIn; not closing"); // NOI18N
+            }
+        }
+    }
+        
+    /** Log in to the datastore.  This will be done for both local and
+     * remote connections.
+     */
+    protected void login() throws IOException {
+        String path = localizePath(url.getPath());
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("FOCCI:login " +  // NOI18N
+                           " Database: " + path + // NOI18N
+                           " User: " + connectionId.getUser() + // NOI18N
+                           " Password: " + connectionId.getPassword() + // NOI18N
+                           " Create: " + connectionId.getCreate()); // NOI18N
+        }
+        LoginRequest logreq =
+            new LoginRequest(
+                message, connectionFactory.getPMF(),
+                path, connectionId.getUser(), connectionId.getPassword(),
+                connectionId.getCreate());
+        logreq.doRequest();
+        // false => NOT ok to close (this) connection
+        message.processInStore(this, false);
+        loggingIn = false;
+    }
+        
+    public void setConnectionFactory(FOStoreConnectionFactory cf) {
+        this.connectionFactory = cf;
+    }
+    
+    /** Get the connection Factory used to create this connection.  Connections
+     * are pooled based on this connection Factory.
+     */
+    public FOStoreConnectionFactory getConnectionFactory() {
+        return connectionFactory;
+    }
+    
+    /** Set the connection id used to create this connection.  This is used
+     * to determine which connection pool is used.
+     */
+    public void setConnectionId (FOStoreConnectionId id) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("FOCCI.setConnectionId(): " + // NOI18N
+            " URL: " + id.getUrl() + // NOI18N
+            " User: " + id.getUser() + // NOI18N
+            " Password: " + id.getPassword() + // NOI18N 
+            " Create: " + id.getCreate()); // NOI18N
+        }
+        this.connectionId = id;
+    }
+    
+    /** Get the connection id used to create this connection.  The id
+     * encapsulates URL, user, and password.  Connections
+     * are pooled based on this connection id.
+     */
+    public FOStoreConnectionId getConnectionId() {
+        return connectionId;
+    }
+    
+    /** 
+     * @return The path, modified as required for the kind of connection.
+     */
+    abstract protected String localizePath(String path);
+}

Added: incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreConnection.java
URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreConnection.java?rev=171355&view=auto
==============================================================================
--- incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreConnection.java (added)
+++ incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreConnection.java Sun May 22 11:40:13 2005
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at 
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+
+package org.apache.jdo.impl.fostore;
+
+/**
+* Tagging interface at root of all kinds of FOStore connections.
+*
+* @author Dave Bristor
+*/
+interface FOStoreConnection {
+}

Added: incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreConnectionFactory.java
URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreConnectionFactory.java?rev=171355&view=auto
==============================================================================
--- incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreConnectionFactory.java (added)
+++ incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreConnectionFactory.java Sun May 22 11:40:13 2005
@@ -0,0 +1,715 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at 
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+
+package org.apache.jdo.impl.fostore;
+
+import java.io.PrintWriter;
+import java.io.Serializable;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.UnknownHostException;
+import java.net.ConnectException;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.jdo.JDOFatalException;
+import javax.jdo.JDOFatalInternalException;
+import javax.jdo.JDOFatalDataStoreException;
+import javax.jdo.JDOUserException;
+import javax.jdo.JDOFatalUserException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.jdo.pm.Accessor;
+import org.apache.jdo.util.I18NHelper;
+import org.apache.jdo.util.Pool;
+
+/**
+ * A connection factory for FOStore.  Allows both same- and remote-address
+ * space connections.  For the same address space-connections, the URL must
+ * not include the Host (Server) parameter.  For remote address space
+ * connections, the URL's protocol is ignored.
+ * <p>
+ * This class is <code>public</code> so that clients can create instances of it
+ * with <code>new</code>.
+ *
+ * @author Dave Bristor
+ */
+public class FOStoreConnectionFactory implements Serializable {
+    private String url;
+    private String userName;
+    private String password;
+    private String driverName;
+    private boolean create;
+    
+    private FOStorePMF pmf;
+
+    private int loginTimeout;
+
+    private transient PrintWriter logWriter;
+    
+    /** Connections are created by the FOStoreURLStreamHandler.
+     */
+    private final FOStoreURLStreamHandler streamHandler = 
+        FOStoreURLStreamHandler.getInstance();
+
+    /** Connections are pooled.  Each unique combination of url, 
+     * user, password has its own pool.  The hashmap associates 
+     * a FOStoreConnectionId with its pool of connections.
+     */
+    private final HashMap connectionMap = new HashMap();
+    
+    /** For now, set the pool size to 1.
+     */
+    // XXX this needs to be configurable...
+    private static final int poolSize = 1;
+
+    private FOStoreConnectionId defaultConnectionId;
+    
+    private FOStoreConnectionId userConnectionId;
+
+    /** True until setConfigured has been invoked.  Allows properties to be
+     * set if true.
+     */
+    private boolean configurable = true;
+
+    /** This table maps from names to CFAccessors.  The names are the same as the
+     * persistence manager factory's property names, but with 
+     * org.apache.jdo.FOStoreConnectionFactory.option prepended.
+     */
+    protected static HashMap CFpropsAccessors = new HashMap(9);
+
+    /** I18N support. */
+    private static final I18NHelper msg = I18NHelper.getInstance(I18N.NAME);
+
+    /** Logger */
+    static final Log logger = LogFactory.getFactory().getInstance(
+        "org.apache.jdo.impl.fostore"); // NOI18N
+
+    /**
+     * First time a FOStoreConnectionFactory is created, initialize accessors
+     * which are used to store/save instances via JNDI.
+     */
+    public FOStoreConnectionFactory() {
+        if (logger.isDebugEnabled()) logger.debug("FOCF()"); // NOI18N
+            initPropsAccessors();
+    }
+    
+    /** Set the url, user, and password into the ConnectionIds for this
+     * connection factory.
+     */
+    private void setConfigured() {
+        if (logger.isDebugEnabled()) {
+            logger.debug("FOCF.setConfigured: URL: " + url); // NOI18N
+        }
+        configurable = false;
+        defaultConnectionId =
+            new FOStoreConnectionId(url, userName, password, create);
+        userConnectionId = new FOStoreConnectionId(url, null, null, false);
+    }
+    
+    private void assertConfigurable() {
+        if (configurable) return;
+        throw new JDOUserException (
+            msg.msg("EXC_AssertConfigurableFailure")); // NOI18N
+    }
+
+    /**
+     * Provides a connection to the database using the given userName and
+     * password.  The first time a connection is made, the
+     * factory can no longer be configured.
+     * @return A FOStoreClientConnection
+     */
+    public synchronized FOStoreClientConnection getConnection(
+            String user, String password) {
+        setConfigured(); // initializes userConnectionId with url.
+        FOStoreConnection rc = null;
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("FOCF.getConnection(" + user + ", " + 
+                           password + "): " + hashCode()); // NOI18N
+        }
+        // We reuse the same userConnectionId until we need to create a new
+        // Pool for it.  The we create a new one for next time.
+        FOStoreConnectionId connectionId = userConnectionId;
+        connectionId.setUser (user);
+        connectionId.setPassword (password);
+        
+        // Try to find the existing connection in the pool.
+        // First time we get here, database is not yet open and connections
+        // are not yet created/in-pool.
+        Pool pool = (Pool) connectionMap.get (connectionId);
+        if (null == pool) {
+            pool = createPool (connectionId);
+            userConnectionId = new FOStoreConnectionId (url, null, null);
+        }
+        try {
+            // blocks until a connection is available.
+            return (FOStoreClientConnection) pool.get(); 
+        } catch (InterruptedException ex) {
+            throw new JDOFatalInternalException(
+                msg.msg("ERR_PoolGet"), ex); // NOI18N
+        }
+    }
+
+    /** Create a new pool of connections for this combination of url, user,
+     * and password.  This might be either the default or for a specific user.
+     */
+     Pool createPool (FOStoreConnectionId id) {
+        Pool pool = new Pool(poolSize);
+        if (connectionMap.put (id, pool) != null) {
+            throw new JDOFatalInternalException (
+                msg.msg("ERR_DuplicatePool")); // NOI18N
+        }
+        try {
+            if (logger.isDebugEnabled()) {
+                logger.debug("FOCF: first time; filling pool"); // NOI18N
+            }
+            for (int i = 0; i < poolSize; i++) {
+                FOStoreClientConnection connection =
+                    createConnection(id);
+                pool.put(connection);
+            }
+        } catch (InterruptedException ex) {
+            throw new JDOFatalInternalException(
+                msg.msg("ERR_PoolPutFill"), ex); // NOI18N
+        }
+        return pool;
+        }
+
+    /**
+     * Provides a connection to the database using the configured userName,
+     * password, and url.  The first time a connection is made, the
+     * factory can no longer be configured.
+     * @return A FOStoreClientConnection
+     */
+    public synchronized FOStoreClientConnection getConnection() {
+        setConfigured();
+        FOStoreConnection rc = null;
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("FOCF.getConnection(): " + hashCode()); // NOI18N
+        }
+
+        // First time we get here, database is not yet open and connections
+        // are not yet created/in-pool.
+        
+        Pool pool = (Pool) connectionMap.get (defaultConnectionId);
+        if (null == pool) {
+            pool = createPool (defaultConnectionId);
+        }
+        try {
+            // blocks until a connection is available.
+            return (FOStoreClientConnection) pool.get(); 
+        } catch (InterruptedException ex) {
+            throw new JDOFatalInternalException(
+                msg.msg("ERR_PoolGet"), ex); // NOI18N
+        }
+    }
+            
+    /** 
+     * This method requires permission to perform the following requests:
+     * Create new URL with the specified StreamHandler.
+     * Delete old database id create flag is set to true.
+     */
+    private FOStoreClientConnection createConnection(
+        final FOStoreConnectionId id) {
+        final FOStoreConnectionFactory cf = this;
+        return (FOStoreClientConnection) AccessController.doPrivileged (
+            new PrivilegedAction () {
+                public Object run () {
+                    URL uRL = null;
+                    try {
+                        uRL = new URL (null, url, streamHandler);
+                        FOStoreClientConnection connection =
+                            (FOStoreClientConnection) streamHandler.openConnection(uRL);
+                        connection.setConnectionFactory(cf);
+                        connection.setConnectionId(id);
+                        connection.connect();
+                        return connection;
+                    } catch (SecurityException se) {
+                        throw new JDOFatalUserException(
+                            msg.msg("EXC_CannotSpecifyStreamHandler"), se); //NOI18N
+                    } catch (UnknownHostException ioe) {
+                        throw new JDOFatalUserException (
+                        msg.msg("EXC_UnknownHostException", uRL.getHost()), ioe); // NOI18N
+                    } catch (ConnectException ioe) {
+                        int port = uRL.getPort();
+                        if (port == -1) 
+                            port = FOStoreRemoteConnection.DEFAULT_PORT;
+                        throw new JDOFatalUserException(
+                            msg.msg("EXC_ConnectException", // NOI18N
+                                    uRL.getHost(), new Integer(port)),
+                            ioe); 
+                    } catch (IOException ioe) {
+                        throw new JDOFatalUserException (
+                        msg.msg("EXC_CannotCreateConnection", url), ioe); // NOI18N
+                    }
+                }
+            }
+        );
+    }
+         
+
+    /**
+     * Returns a connection to the pool
+     * @param connection Connection to be returned to the pool.
+     */
+    void closeConnection(FOStoreClientConnection connection) {
+        FOStoreConnectionId id = connection.getConnectionId();
+        Pool pool = (Pool) connectionMap.get(id);
+        try {
+            pool.put(connection);
+        } catch (InterruptedException ex) {
+            throw new JDOFatalInternalException(
+                msg.msg("ERR_CloseConnectionpoolPut"), ex); // NOI18N
+        }
+    }
+
+    /**
+     * Close the database.  This really means close all connections that
+     * have been opened.  Closing the last connection on a database actually 
+     * closes the database, whether local or remote.
+     */
+    public synchronized void closeDatabase() {
+        Object timer = Tester.startTime();
+        try {
+            for (Iterator hmi = connectionMap.values().iterator();
+                 hmi.hasNext();) {
+                
+                Pool dbPool = (Pool) hmi.next();
+                // we know that there are poolSize entries in each pool
+                for (int i = 0; i < poolSize; ++i) {
+                    FOStoreClientConnection focci =
+                        (FOStoreClientConnection) dbPool.get();
+                    focci.closeDatabase();
+                }
+            }
+            if (logger.isTraceEnabled()) {
+                Tester.printTime(timer, "Time to close database"); // NOI18N
+            }
+        } catch (FOStoreDatabaseException ex) {
+            throw new FOStoreFatalInternalException(
+                this.getClass(),
+                "close", msg.msg("ERR_CannotClose", url), ex); // NOI18N
+        } catch (IOException ex) {
+            throw new FOStoreFatalInternalException(
+                this.getClass(),
+                "close", msg.msg("ERR_CannotClose", url), ex); // NOI18N
+        } catch (InterruptedException ex) {
+            throw new FOStoreFatalInternalException(
+                this.getClass(),
+                "close", msg.msg("ERR_CannotClose", url), ex); // NOI18N
+        }
+        if (logger.isDebugEnabled()) {
+            logger.debug("FOCF: closed " + url); // NOI18N
+        }
+    }
+
+    /**
+    * Sets name of the driver for connections
+    * @param driverName driver name
+    */
+    public void setDriverName(String driverName){
+        assertConfigurable();
+        this.driverName = driverName;
+    }
+
+    /**
+    * Provides PersistenceManagerFactory for connections
+    * @return PMF
+    */
+    public FOStorePMF getPMF() {
+        return pmf;
+    }
+  
+    /**
+    * Sets PersistenceManagerFactory for connections
+    * @param pmf PersistenceManagerFactory
+    */
+    public void setPMF(FOStorePMF pmf){
+        assertConfigurable();
+        this.pmf = pmf;
+    }
+
+    /**
+    * Provides name of driver used for connections
+    * @return driver name
+    */
+    public String getDriverName() {
+        return driverName;
+    }
+  
+    /**
+    * Sets connection URL
+    * @param url connection URL
+    */
+    public void setURL(String url) {
+        assertConfigurable();
+        this.url = url;
+    }
+
+    /**
+    * Returns connection URL
+    * @return      connection URL
+    */
+    public String getURL() {
+        return url;
+    }
+  
+    /**
+    * Sets database user
+    * @param userName      database user
+    */
+    public void setUserName(String userName) {
+        assertConfigurable();
+        this.userName = userName;
+    }
+
+    /**
+    * Returns database user name
+    * @return      current database user name
+    */
+    public String getUserName() {
+        return userName;
+    }
+  
+    /**
+    * Sets database user password
+    * @param password      database user password
+    */
+    public void setPassword(String password) {
+        assertConfigurable();
+        this.password = password;
+    }
+  
+    /**
+    * Sets minimum number of connections in the connection pool
+    * @param minPool       minimum number of connections
+    */
+    public void setMinPool(int minPool) { 
+        assertConfigurable();
+    }
+
+    /**
+    * Returns minimum number of connections in the connection pool
+    * @return      connection minPool
+    */
+    public int getMinPool() {
+        return 1;
+    }
+  
+    /**
+    * Sets maximum number of connections in the connection pool
+    * @param maxPool       maximum number of connections
+    */
+    public void setMaxPool(int maxPool) {
+        assertConfigurable();
+    }
+
+    /**
+    * Returns maximum number of connections in the connection pool
+    * @return      connection maxPool
+    */
+    public int getMaxPool() {
+        return 1;
+    }
+  
+    /**
+    * Sets the amount of time, in milliseconds, between the connection
+    * manager's attempts to get a pooled connection.
+    * @param msInterval    the interval between attempts to get a database
+    *                      connection, in milliseconds.
+    *
+    */
+    public void setMsInterval(int msInterval) { }
+
+    /**
+    * Returns the amount of time, in milliseconds, between the connection
+    * manager's attempts to get a pooled connection.
+    * @return      the length of the interval between tries in milliseconds
+    */
+    public int getMsInterval() {
+        return 0;
+    }
+  
+    /**
+    * Sets the number of milliseconds to wait for an available connection
+    * from the connection pool before throwing an exception
+    * @param msWait        number in milliseconds
+    */
+    public void setMsWait(int msWait) { 
+        assertConfigurable();
+    }
+
+    /**
+    * Returns the number of milliseconds to wait for an available connection
+    * from the connection pool before throwing an exception
+    * @return      number in milliseconds
+    */
+    public int getMsWait() {
+        return 0;
+    }
+  
+    /**
+    * Sets the LogWriter to which messages should be sent
+    * @param logWriter            logWriter
+    */
+    public void setLogWriter(PrintWriter logWriter) {
+        this.logWriter = logWriter;
+    }
+
+    /**
+    * Returns the LogWriter to which messages should be sent
+    * @return      logWriter
+    */
+    public PrintWriter getLogWriter() {
+        return logWriter;
+    }
+ 
+    /**
+    * Sets the number of seconds to wait for a new connection to be
+    * established to the data source
+    * @param loginTimeout           wait time in seconds
+    */
+    public void setLoginTimeout(int loginTimeout) {
+        assertConfigurable();
+        this.loginTimeout = loginTimeout;
+    }
+
+    /**
+    * Returns the number of seconds to wait for a new connection to be
+    * established to the data source
+    * @return      wait time in seconds
+    */
+    public int getLoginTimeout() {
+        return loginTimeout;
+    }
+
+    /**
+    * Sets whether to create the database.
+    * @param create whether to create the database.
+    */
+    public void setCreate(boolean create) {
+        assertConfigurable();
+        this.create = create;
+    }
+
+    /**
+    * Sets whether to create the database.
+    * @param create whether to create the database.
+    */
+    public void setCreate(String create) {
+        assertConfigurable();
+        this.create = Boolean.valueOf (create).booleanValue();
+    }
+
+    /**
+    * Returns whether to create the database.
+    * @return whether to create the database
+    */
+    public boolean getCreate() {
+        return create;
+    }
+
+    //
+    // Support for JNDI: we want to save/restore a FOStoreConnectionFactory
+    // via JNDI, and have the stored representation be via properties.
+    //
+
+    /** CFAccessor implementation instances allow copying values to/from a
+     * FOStoreConnectionFactory
+     * and a Properties.  They do the proper type translation too.
+     */
+    interface CFAccessor extends Accessor {
+        /** @return String form of a value in a FOStoreConnectionFactory.
+         */
+        public String get(FOStoreConnectionFactory focf);
+
+        /** @return String form of a value in a FOStoreConnectionFactory if
+         * is not a default value.
+         */
+        public String getNonDefault(FOStoreConnectionFactory focf);
+
+        /** @param s String form of a value in a FOStoreConnectionFactory.
+         */
+        public void set(FOStoreConnectionFactory focf, String s);
+    }
+
+    // Initialize the property accessors map.
+    protected static void initPropsAccessors() {
+        synchronized (CFpropsAccessors) {
+            if (CFpropsAccessors.size() == 0) {
+
+                CFpropsAccessors.put(
+                    "org.apache.jdo.FOStoreConnectionFactory.option.URL", // NOI18N
+                    new CFAccessor() {
+                    public String get(FOStoreConnectionFactory focf) { return focf.getURL(); }
+                    public String getNonDefault(FOStoreConnectionFactory focf) { return focf.getURL(); }
+                    public String getDefault() {return null;}
+                    public void set(FOStoreConnectionFactory focf, String s) { focf.setURL(s); }
+                });
+                CFpropsAccessors.put(
+                    "org.apache.jdo.FOStoreConnectionFactory.option.UserName", // NOI18N
+                    new CFAccessor() {
+                    public String get(FOStoreConnectionFactory focf) { return focf.getUserName(); }
+                    public String getNonDefault(FOStoreConnectionFactory focf) { return focf.getUserName(); }
+                    public String getDefault() {return null;}
+                    public void set(FOStoreConnectionFactory focf, String s) { focf.setUserName(s); }
+                });
+                CFpropsAccessors.put(
+                    "org.apache.jdo.FOStoreConnectionFactory.option.Password", // NOI18N
+                    new CFAccessor() {
+                    public String get(FOStoreConnectionFactory focf) { return FOStorePMF.doEncrypt(focf.password); }
+                    public String getNonDefault(FOStoreConnectionFactory focf) { return FOStorePMF.doEncrypt(focf.password); }
+                    public String getDefault() {return null;}
+                    public void set(FOStoreConnectionFactory focf, String s) { focf.setPassword(FOStorePMF.doDecrypt(s)); }
+                });
+                CFpropsAccessors.put(
+                    "org.apache.jdo.FOStoreConnectionFactory.option.DriverName", // NOI18N
+                    new CFAccessor() {
+                    public String get(FOStoreConnectionFactory focf) { return focf.getDriverName(); }
+                    public String getNonDefault(FOStoreConnectionFactory focf) { return focf.getDriverName(); }
+                    public String getDefault() {return null;}
+                    public void set(FOStoreConnectionFactory focf, String s) { focf.setDriverName(s); }
+                });
+                CFpropsAccessors.put(
+                    "org.apache.jdo.FOStoreConnectionFactory.option.LoginTimeout", // NOI18N
+                    new CFAccessor() {
+                    public String get(FOStoreConnectionFactory focf) { return focf.getDriverName(); }
+                    public String getNonDefault(FOStoreConnectionFactory focf) { return focf.getDriverName(); }
+                    public String getDefault() {return null;}
+                    public void set(FOStoreConnectionFactory focf, String s) { focf.setDriverName(s); }
+                });
+                CFpropsAccessors.put(
+                    "javax.jdo.FOStoreConnectionFactory.option.MinPool", // NOI18N
+                    new CFAccessor() {
+                    public String get(FOStoreConnectionFactory focf) { return Integer.toString(focf.getMinPool()); }
+                    public String getNonDefault(FOStoreConnectionFactory focf) { return (focf.getMinPool()==1)?null:Integer.toString(focf.getMinPool()); }
+                    public String getDefault() { return "1"; } // NOI18N
+                    public void set(FOStoreConnectionFactory focf, String s) { focf.setMinPool(toInt(s)); }
+                });
+                CFpropsAccessors.put(
+                    "javax.jdo.FOStoreConnectionFactory.option.MaxPool", // NOI18N
+                    new CFAccessor() {
+                    public String get(FOStoreConnectionFactory focf) { return Integer.toString(focf.getMaxPool()); }
+                    public String getNonDefault(FOStoreConnectionFactory focf) { return (focf.getMaxPool()==1)?null:Integer.toString(focf.getMaxPool()); }
+                    public String getDefault() { return "1"; } // NOI18N
+                    public void set(FOStoreConnectionFactory focf, String s) { focf.setMaxPool(toInt(s)); }
+                });
+                CFpropsAccessors.put(
+                    "javax.jdo.FOStoreConnectionFactory.option.MsWait", // NOI18N
+                    new CFAccessor() {
+                    public String get(FOStoreConnectionFactory focf) { return Integer.toString(focf.getMsWait()); }
+                    public String getNonDefault(FOStoreConnectionFactory focf) { return (focf.getMsWait()==0)?null:Integer.toString(focf.getMsWait()); }
+                    public String getDefault() { return "0"; } // NOI18N
+                    public void set(FOStoreConnectionFactory focf, String s) { focf.setMsWait(toInt(s)); }
+                });
+                CFpropsAccessors.put(
+                    "javax.jdo.FOStoreConnectionFactory.option.Create", // NOI18N
+                    new CFAccessor() {
+                    public String get(FOStoreConnectionFactory focf) { return new Boolean(focf.getCreate()).toString(); }
+                    public String getNonDefault(FOStoreConnectionFactory focf) { return (!focf.getCreate())?null:"true"; } // NOI18N
+                    public String getDefault() { return "false"; } // NOI18N
+                    public void set(FOStoreConnectionFactory focf, String s) { focf.setCreate(Boolean.valueOf(s).booleanValue()); }
+                });
+            }
+        }
+    }
+
+    /**
+     * It should *never* be the case that our translation process encounters
+     * a NumberFormatException.  If so, tell the user in the JDO-approved
+     * manner.
+     */ 
+    private static int toInt(String s) {
+        int rc = 0;
+        try {
+            rc = new Integer(s).intValue();
+        } catch (NumberFormatException ex) {
+            throw new JDOFatalInternalException(
+                msg.msg("ERR_Badformat")); // NOI18N
+        }
+        return rc;
+    }
+
+    /**
+     * Sets properties as per the property values in the connection factory.
+     * For each CFAccessor in the given HashMap, gets the corresponding value
+     * from the FOStoreConnectionFactory and puts it in the given 
+     * Properties object.
+     */
+    void setProperties(Properties p) {
+        Set s = CFpropsAccessors.entrySet();
+        for (Iterator i = s.iterator(); i.hasNext();) {
+            Map.Entry e = (Map.Entry)i.next();
+            String key = (String)e.getKey();
+            CFAccessor a = (CFAccessor)e.getValue();
+            String value = (String)a.getNonDefault(this);
+            if (null != value) {
+                p.put(key, value);
+            }
+        }
+    }
+
+    /**
+     * Configures a FOStoreConnectionFactory from the given Properties.
+     * For each Accessor in the given HashMap, gets the corresponding value
+     * from the Properties and sets that value in the PMF.
+     * This is public so that a test program can create a
+     * FOSToreConnectionFactory, and configure it from a Properties object.
+     */
+    public void setFromProperties(Properties p) {
+        Set s = CFpropsAccessors.entrySet();
+        for (Iterator i = s.iterator(); i.hasNext();) {
+            Map.Entry e = (Map.Entry)i.next();
+            String key = (String)e.getKey();
+            String value = p.getProperty(key);
+            if (null != value) {
+                CFAccessor a = (CFAccessor)e.getValue();
+                if (logger.isDebugEnabled()) {
+                    logger.debug("FOStoreConnectionFactory setting property: " + key + " to: " + value); // NOI18N
+                }
+                a.set(this, value);
+            }
+        }
+    }
+    
+    /**
+     * Returns true if this connection factory has been configured with a URL.
+     */
+    public boolean isConfigured() {
+        if (logger.isDebugEnabled()) 
+            logger.debug("FOStoreConnectionFactory url is: " + url); // NOI18N
+        return (url != null);
+    }
+
+    public String toString() {
+        return "" + // NOI18N
+            "FOCF.url: " + url + "\n" + // NOI18N
+            "FOCF.userName: " + userName + "\n" + // NOI18N
+            "FOCF.password: " + password + "\n" + // NOI18N
+            "FOCF.driverName: " + driverName + "\n" + // NOI18N
+            "FOCF.loginTimeout: " + loginTimeout; // NOI18N
+   }        
+}

Added: incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreConnectionId.java
URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreConnectionId.java?rev=171355&view=auto
==============================================================================
--- incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreConnectionId.java (added)
+++ incubator/jdo/trunk/fostore20/src/java/org/apache/jdo/impl/fostore/FOStoreConnectionId.java Sun May 22 11:40:13 2005
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at 
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+
+/*
+ * FOStoreConnectionId.java
+ *
+ * Created on June 15, 2001, 1:30 PM
+ */
+
+package org.apache.jdo.impl.fostore;
+
+/**
+ * Represents the information required to connect to a database.
+ * @author  Craig Russell
+ * @version 1.0
+ */
+class FOStoreConnectionId {
+    
+    /** The URL of this connection.  Must not be null.
+     */    
+    private String url;
+    
+    /** The user id of this connection.  May be null.
+     */    
+    private String user;
+    
+    /** The password of this connection.  May be null.
+     */    
+    private String password;
+    
+    /** The flag telling whether to create.
+     */
+    private boolean create;
+
+    /** Creates new FOStoreConnectionId.
+     * @param url the URL of the connection.
+     * @param user the user id of the connection.
+     * @param password the password of the connection.
+     * @param create the flag whether to create the database.
+     */
+    public FOStoreConnectionId(String url, String user, String password, boolean create) {
+        if (url == null) {
+            throw new NullPointerException();
+        }
+        this.url = url;
+        this.user = user;
+        this.password = password;
+        this.create = create;
+    }
+    
+
+    /** Creates new FOStoreConnectionId.
+     * @param url the URL of the connection.
+     * @param user the user id of the connection.
+     * @param password the password of the connection.
+     */
+    public FOStoreConnectionId(String url, String user, String password) {
+        if (url == null) {
+            throw new NullPointerException();
+        }
+        this.url = url;
+        this.user = user;
+        this.password = password;
+        this.create = false;
+    }
+    
+    public void setUrl (String url) {
+        if (url == null) {
+            throw new NullPointerException();
+        }
+        this.url = url;
+    }
+    
+    public String getUrl () {
+        return url;
+    }
+    
+    public void setUser (String user) {
+        this.user = user;
+    }
+    
+    public String getUser() {
+        return user;
+    }
+    
+    public void setPassword (String password) {
+        this.password = password;
+    }
+    
+    public String getPassword () {
+        return password;
+    }
+    
+    public void setCreate (boolean create) {
+        this.create = create;
+    }
+    
+    public boolean getCreate() {
+        return create;
+    }
+
+    /** Combine the hashCodes of URL, user, and password.
+     * @return the combined hashCode.
+     * Note that the create flag is not part of the hashCode.
+     */    
+    public int hashCode () {
+        int urlHashCode = url.hashCode();
+        int userHashCode = user==null?0:user.hashCode();
+        int passwordHashCode = password==null?0:password.hashCode();
+        return urlHashCode + userHashCode + passwordHashCode;
+    }
+    
+    /** Returns true if this represents the same URL,
+     * user, and password as the other.
+     * @param other another connection id.
+     * @return true if this represents the same URL,
+     * user, and password as the other.
+     * Note that the create flag is not part of the identity.
+     */    
+    public boolean equals (Object other) {
+        if (this.getClass() != other.getClass()) return false;
+        FOStoreConnectionId foci = (FOStoreConnectionId) other;
+        return this.url.equals(foci.url) 
+            & ((this.user!=null&foci.user!=null)?this.user.equals(foci.user):this.user==foci.user)
+            & ((this.password!=null&foci.password!=null)?this.password.equals(foci.password):this.password==foci.password);
+    }
+    
+    // XXX this 'main' really belongs in a test class...
+    public static void main (String argv[]) {
+        FOStoreConnectionId id0 = new FOStoreConnectionId("url", null, null); // NOI18N
+        FOStoreConnectionId id1 = new FOStoreConnectionId("url", "1", null); // NOI18N
+        FOStoreConnectionId id2 = new FOStoreConnectionId("url", null, "2"); // NOI18N
+        FOStoreConnectionId id3 = new FOStoreConnectionId("url", "1", "2"); // NOI18N
+        FOStoreConnectionId id4 = new FOStoreConnectionId("url2", "1", "2"); // NOI18N
+        FOStoreConnectionId id5 = new FOStoreConnectionId("url2", "1", "2", true); // NOI18N
+        try {
+            FOStoreConnectionId id = new FOStoreConnectionId (null, null, null);
+            System.out.println ("Failure."); // NOI18N
+            return;
+        } catch (NullPointerException npe) {
+            // good catch
+        } catch (Throwable t) {
+            System.out.println ("Failure."); // NOI18N
+            return;
+        }
+        if    (!id0.equals(id1)
+            & (!id0.equals(id2))
+            & (!id0.equals(id3))
+            & (!id0.equals(id4))
+            & (!id1.equals(id0))
+            & (!id2.equals(id0))
+            & (!id3.equals(id0))
+            & (!id4.equals(id0))
+             & (id0.equals(id0))
+             & (id1.equals(id1))
+             & (id2.equals(id2))
+             & (id3.equals(id3))
+             & (id4.equals(id4))
+             & (id4.equals(id5))
+                                ) {
+            System.out.println ("Success."); // NOI18N
+        } else {
+            System.out.println ("Failure."); // NOI18N
+        }
+    }
+
+}