9. Client/Server

Now that we have seen how transactions work in db4o conceptually, we are prepared to tackle concurrently executing transactions.
We start by preparing our database in the familiar way.
[setFirstCar]
Pilot pilot = new Pilot("Rubens Barrichello", 99);
    Car car = new Car("BMW");
    car.Pilot = pilot;
    db.set(car);

[setSecondCar]
Pilot pilot = new Pilot("Michael Schumacher", 100);
    Car car = new Car("Ferrari");
    car.Pilot = pilot;
    db.set(car);


    9.1. Embedded server

    From the API side, there's no real difference between transactions executing concurrently within the same VM and transactions executed against a remote server. To use concurrent transactions within a single VM, we just open a db4o server on our database file, directing it to run on port 0, thereby declaring that no networking will take place.
    [accessLocalServer]
    ObjectServer server = Db4o.openServer(Util.YapFileName, 0);
        try
        {
            ObjectContainer client = server.openClient();
            // Do something with this client, or open more clients
            client.close();
        }
        finally
        {
            server.close();
        }

    Again, we will delegate opening and closing the server to our environment to focus on client interactions.
    [queryLocalServer]
    ObjectContainer client = server.openClient();
        listResult(client.get(new Car(null)));
        client.close();
    OUTPUT:
    2
    BMW[Rubens Barrichello/99]/0
    Ferrari[Michael Schumacher/100]/0
    [db4o 4.6.004   2005-07-29 05:00:01]
    Connection closed by client %.
    [db4o 4.6.004   2005-07-29 05:00:01]
    '/tmp/formula157712.yap' close request
    [db4o 4.6.004   2005-07-29 05:00:01]
    '/tmp/formula157712.yap' closed

The transaction level in db4o is read committed . However, each client container maintains its own weak reference cache of already known objects. To make all changes committed by other clients immediately, we have to explicitly refresh known objects from the server. We will delegate this task to a specialized version of our listResult() method.
public static void listRefreshedResult(ObjectContainer container, ObjectSet items, int depth)
    {
        Console.WriteLine(items.size());
        while (items.hasNext())
        {
            object item = items.next();
            container.ext().refresh(item, depth);
            Console.WriteLine(item);
        }
    }

[demonstrateLocalReadCommitted]
ObjectContainer client1 =server.openClient();
    ObjectContainer client2 =server.openClient();
    Pilot pilot = new Pilot("David Coulthard", 98);
    ObjectSet result = client1.get(new Car("BMW"));
    Car car = (Car)result.next();
    car.Pilot = pilot;
    client1.set(car);
    listResult(client1.get(new Car(null)));
    listResult(client2.get(new Car(null)));
    client1.commit();
    listResult(client1.get(typeof(Car)));            
    listRefreshedResult(client2, client2.get(typeof(Car)), 2);
    client1.close();
    client2.close();
OUTPUT:
2
BMW[David Coulthard/98]/0
Ferrari[Michael Schumacher/100]/0
2
BMW[Rubens Barrichello/99]/0
Ferrari[Michael Schumacher/100]/0
2
BMW[David Coulthard/98]/0
Ferrari[Michael Schumacher/100]/0
2
BMW[David Coulthard/98]/0
Ferrari[Michael Schumacher/100]/0
[db4o 4.6.004   2005-07-29 05:00:01]
Connection closed by client %.
[db4o 4.6.004   2005-07-29 05:00:01]
Connection closed by client %.
[db4o 4.6.004   2005-07-29 05:00:01]
'/tmp/formula157712.yap' close request
[db4o 4.6.004   2005-07-29 05:00:01]
'/tmp/formula157712.yap' closed

Simple rollbacks just work as you might expect now.
[demonstrateLocalRollback]
ObjectContainer client1 = server.openClient();
    ObjectContainer client2 = server.openClient();
    ObjectSet result = client1.get(new Car("BMW"));
    Car car = (Car)result.next();
    car.Pilot = new Pilot("Someone else", 0);
    client1.set(car);
    listResult(client1.get(new Car(null)));
    listResult(client2.get(new Car(null)));
    client1.rollback();
    client1.ext().refresh(car, 2);
    listResult(client1.get(new Car(null)));
    listResult(client2.get(new Car(null)));
    client1.close();
    client2.close();
OUTPUT:
2
BMW[Someone else/0]/0
Ferrari[Michael Schumacher/100]/0
2
BMW[David Coulthard/98]/0
Ferrari[Michael Schumacher/100]/0
2
BMW[David Coulthard/98]/0
Ferrari[Michael Schumacher/100]/0
2
BMW[David Coulthard/98]/0
Ferrari[Michael Schumacher/100]/0
[db4o 4.6.004   2005-07-29 05:00:02]
Connection closed by client %.
[db4o 4.6.004   2005-07-29 05:00:02]
Connection closed by client %.
[db4o 4.6.004   2005-07-29 05:00:02]
'/tmp/formula157712.yap' close request
[db4o 4.6.004   2005-07-29 05:00:02]
'/tmp/formula157712.yap' closed


9.2. Networking

From here it's only a small step towards operating db4o over a TCP/IP network. We just specify a port number greater than zero and set up one or more accounts for our client(s).
[accessRemoteServer]
ObjectServer server = Db4o.openServer(Util.YapFileName, ServerPort);
    server.grantAccess(ServerUser, ServerPassword);
    try
    {
        ObjectContainer client = Db4o.openClient("localhost", ServerPort, ServerUser, ServerPassword);
        // Do something with this client, or open more clients
        client.close();
    }
    finally
    {
        server.close();
    }

The client connects providing host, port, user name and password.
[queryRemoteServer]
ObjectContainer client = Db4o.openClient("localhost", port, user, password);
    listResult(client.get(new Car(null)));
    client.close();
OUTPUT:
[db4o 4.6.004   2005-07-29 05:00:02]
Server listening on port: '56128'
[db4o 4.6.004   2005-07-29 05:00:02]
Client 'user' connected.
2
BMW[David Coulthard/98]/0
Ferrari[Michael Schumacher/100]/0
[db4o 4.6.004   2005-07-29 05:00:02]
Connection closed by client 'user'.
[db4o 4.6.004   2005-07-29 05:00:02]
'/tmp/formula157712.yap' close request
[db4o 4.6.004   2005-07-29 05:00:02]
'/tmp/formula157712.yap' closed

Everything else is absolutely identical to the local server examples above.
[demonstrateRemoteReadCommitted]
ObjectContainer client1 = Db4o.openClient("localhost", port, user, password);
    ObjectContainer client2 = Db4o.openClient("localhost", port, user, password);
    Pilot pilot = new Pilot("Jenson Button", 97);
    ObjectSet result = client1.get(new Car(null));
    Car car = (Car)result.next();
    car.Pilot = pilot;
    client1.set(car);
    listResult(client1.get(new Car(null)));
    listResult(client2.get(new Car(null)));
    client1.commit();
    listResult(client1.get(new Car(null)));
    listResult(client2.get(new Car(null)));
    client1.close();
    client2.close();
OUTPUT:
[db4o 4.6.004   2005-07-29 05:00:02]
Server listening on port: '56128'
[db4o 4.6.004   2005-07-29 05:00:02]
Client 'user' connected.
[db4o 4.6.004   2005-07-29 05:00:02]
Client 'user' connected.
2
BMW[Jenson Button/97]/0
Ferrari[Michael Schumacher/100]/0
2
BMW[David Coulthard/98]/0
Ferrari[Michael Schumacher/100]/0
2
BMW[Jenson Button/97]/0
Ferrari[Michael Schumacher/100]/0
2
BMW[Jenson Button/97]/0
Ferrari[Michael Schumacher/100]/0
[db4o 4.6.004   2005-07-29 05:00:02]
Connection closed by client 'user'.
[db4o 4.6.004   2005-07-29 05:00:02]
Connection closed by client 'user'.
[db4o 4.6.004   2005-07-29 05:00:02]
'/tmp/formula157712.yap' close request
[db4o 4.6.004   2005-07-29 05:00:02]
'/tmp/formula157712.yap' closed

[demonstrateRemoteRollback]
ObjectContainer client1 = Db4o.openClient("localhost", port, user, password);
    ObjectContainer client2 = Db4o.openClient("localhost", port, user, password);
    ObjectSet result = client1.get(new Car(null));
    Car car = (Car)result.next();
    car.Pilot = new Pilot("Someone else", 0);
    client1.set(car);
    listResult(client1.get(new Car(null)));
    listResult(client2.get(new Car(null)));
    client1.rollback();
    client1.ext().refresh(car,2);
    listResult(client1.get(new Car(null)));
    listResult(client2.get(new Car(null)));
    client1.close();
    client2.close();
OUTPUT:
[db4o 4.6.004   2005-07-29 05:00:02]
Server listening on port: '56128'
[db4o 4.6.004   2005-07-29 05:00:02]
Client 'user' connected.
[db4o 4.6.004   2005-07-29 05:00:02]
Client 'user' connected.
2
BMW[Someone else/0]/0
Ferrari[Michael Schumacher/100]/0
2
BMW[Jenson Button/97]/0
Ferrari[Michael Schumacher/100]/0
2
BMW[Jenson Button/97]/0
Ferrari[Michael Schumacher/100]/0
2
BMW[Jenson Button/97]/0
Ferrari[Michael Schumacher/100]/0
[db4o 4.6.004   2005-07-29 05:00:03]
Connection closed by client 'user'.
[db4o 4.6.004   2005-07-29 05:00:03]
Connection closed by client 'user'.
[db4o 4.6.004   2005-07-29 05:00:03]
'/tmp/formula157712.yap' close request
[db4o 4.6.004   2005-07-29 05:00:03]
'/tmp/formula157712.yap' closed


9.3. Out-of-band signalling

Sometimes a client needs to send a special message to a server in order to tell the server to do something.  The server may need to be signalled to perform a defragment or it may need to be signalled to shut itself down gracefully.
This is configured by calling setMessageRecipient(), passing the object that will process client-initiated messages.
public void runServer()
    {
        lock(this)
        {
            ObjectServer db4oServer = Db4o.openServer(FILE, PORT);
            db4oServer.grantAccess(USER, PASS);
            
            // Using the messaging functionality to redirect all
            // messages to this.processMessage
            db4oServer.ext().configure().setMessageRecipient(this);
            try
            {
                if (! stop)
                {
                    // wait forever for notify() from close()
                    Monitor.Wait(this);   
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
            db4oServer.close();
        }
    }

The message is received and processed by a processMessage() method:
public void processMessage(ObjectContainer con, object message)
    {
        if (message is StopServer)
        {
            close();
        }
    }

Db4o allows a client to send an arbitrary signal or message to a server by sending a plain Java object to the server.  The server will receive a callback message, including the object that came from the client. The server can interpret this message however it wants.
public static void Main(string[] args)
    {
        ObjectContainer objectContainer = null;
        try
        {
            // connect to the server
            objectContainer = Db4o.openClient(HOST, PORT, USER, PASS);
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
        
        if (objectContainer != null)
        {
            // get the messageSender for the ObjectContainer
            MessageSender messageSender = objectContainer.ext()
                .configure().getMessageSender();
            // send an instance of a StopServer object
            messageSender.send(new StopServer());
            
            // close the ObjectContainer
            objectContainer.close();
        }
    }


9.4. Putting it all together: a simple but complete db4o server

Let's put all of this information together now to implement a simple standalone db4o server with a special client that can tell the server to shut itself down gracefully on demand.
First, both the client and the server need some shared configuration information.  We will provide this using an interface:
namespace com.db4o.f1.chapter5
{
    /// <summary>
    /// Configuration used for StartServer and StopServer.
    /// </summary>
    public class ServerConfiguration
    {
        /// <summary>
        /// the host to be used.
        /// If you want to run the client server examples on two computers,
        /// enter the computer name of the one that you want to use as server.
        /// </summary>
        public const string HOST = "localhost";  
        /// <summary>
        /// the database file to be used by the server.
        /// </summary>
        public const string FILE = "formula1.yap";
        /// <summary>
        /// the port to be used by the server.
        /// </summary>
        public const int PORT = 4488;
        /// <summary>
        /// the user name for access control.
        /// </summary>
        public const string USER = "db4o";
    
        /// <summary>
        /// the pasword for access control.
        /// </summary>
        public const string PASS = "db4o";
    }
}

Now we'll create the server:
using System;
using System.Threading;
using com.db4o;
using com.db4o.messaging;
using j4o.lang;
namespace com.db4o.f1.chapter5
{
    /// <summary>
    /// starts a db4o server with the settings from ServerConfiguration.
    /// This is a typical setup for a long running server.
    /// The Server may be stopped from a remote location by running
    /// StopServer. The StartServer instance is used as a MessageRecipient
    /// and reacts to receiving an instance of a StopServer object.
    /// Note that all user classes need to be present on the server side
    /// and that all possible Db4o.configure() calls to alter the db4o
    /// configuration need to be executed on the client and on the server.
    /// </summary>
    public class StartServer : ServerConfiguration, MessageRecipient
    {
        /// <summary>
        /// setting the value to true denotes that the server should be closed
        /// </summary>
        private bool stop = false;
        /// <summary>
        /// starts a db4o server using the configuration from
        /// ServerConfiguration.
        /// </summary>
        public static void Main(string[] arguments)
        {
            new StartServer().runServer();
        }
        /// <summary>
        /// opens the ObjectServer, and waits forever until close() is called
        /// or a StopServer message is being received.
        /// </summary>
        public void runServer()
        {
            lock(this)
            {
                ObjectServer db4oServer = Db4o.openServer(FILE, PORT);
                db4oServer.grantAccess(USER, PASS);
                
                // Using the messaging functionality to redirect all
                // messages to this.processMessage
                db4oServer.ext().configure().setMessageRecipient(this);
                try
                {
                    if (! stop)
                    {
                        // wait forever for notify() from close()
                        Monitor.Wait(this);   
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                }
                db4oServer.close();
            }
        }
        /// <summary>
        /// messaging callback
        /// see com.db4o.messaging.MessageRecipient#processMessage()
        /// </summary>
        public void processMessage(ObjectContainer con, object message)
        {
            if (message is StopServer)
            {
                close();
            }
        }
        /// <summary>
        /// closes this server.
        /// </summary>
        public void close()
        {
            lock(this)
            {
                stop = true;
                Monitor.PulseAll(this);
            }
        }
    }
}

And last but not least, the client that stops the server.
using System;
using com.db4o;
using com.db4o.messaging;
namespace com.db4o.f1.chapter5
{
    /// <summary>
    /// stops the db4o Server started with StartServer.
    /// This is done by opening a client connection
    /// to the server and by sending a StopServer object as
    /// a message. StartServer will react in it's
    /// processMessage method.
    /// </summary>
    public class StopServer : ServerConfiguration
    {
        /// <summary>
        /// stops a db4o Server started with StartServer.
        /// </summary>
        /// <exception cref="Exception" />
        public static void Main(string[] args)
        {
            ObjectContainer objectContainer = null;
            try
            {
                // connect to the server
                objectContainer = Db4o.openClient(HOST, PORT, USER, PASS);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
            
            if (objectContainer != null)
            {
                // get the messageSender for the ObjectContainer
                MessageSender messageSender = objectContainer.ext()
                    .configure().getMessageSender();
                // send an instance of a StopServer object
                messageSender.send(new StopServer());
                
                // close the ObjectContainer
                objectContainer.close();
            }
        }
    }
}


9.5. Conclusion

That's it, folks. No, of course it isn't. There's much more to db4o we haven't covered yet: schema evolution, custom persistence for your classes, writing your own query objects, etc. The following, more loosely coupled chapters will look into more advanced db4o features.
This tutorial is work in progress. We will successively add chapters and incorporate feedback from the community into the existing chapters.
We hope that this tutorial has helped to get you started with db4o. How should you continue now?
- Browse the remaining chapters.
-(Interactive version only)While this tutorial is basically sequential in nature, try to switch back and forth between the chapters and execute the sample snippets in arbitrary order. You will be working with the same database throughout; sometimes you may just get stuck or even induce exceptions, but you can always reset the database via the console window.
- The examples we've worked through are included in your db4o distribution in full source code. Feel free to experiment with it.
- I you're stuck, see if the FAQ can solve your problem, browse the information on our web site, check if your problem is submitted to Bugzilla or join our newsgroup at news://news.db4odev.com/db4o.users.


9.6. Full source

namespace com.db4o.f1.chapter5
{
    using System;
    using System.IO;
    using com.db4o;
    using com.db4o.f1;
    public class ClientServerExample : Util
    {
        public static void Main(string[] args)
        {
            File.Delete(Util.YapFileName);
            accessLocalServer();
            File.Delete(Util.YapFileName);
            ObjectContainer db = Db4o.openFile(Util.YapFileName);
            try
            {
                setFirstCar(db);
                setSecondCar(db);
            }
            finally
            {
                db.close();
            }
            
            configureDb4o();
            ObjectServer server = Db4o.openServer(Util.YapFileName, 0);
            try
            {
                queryLocalServer(server);
                demonstrateLocalReadCommitted(server);
                demonstrateLocalRollback(server);
            }
            finally
            {
                server.close();
            }
            
            accessRemoteServer();
            server = Db4o.openServer(Util.YapFileName, ServerPort);
            server.grantAccess(ServerUser, ServerPassword);
            try
            {
                queryRemoteServer(ServerPort, ServerUser, ServerPassword);
                demonstrateRemoteReadCommitted(ServerPort, ServerUser, ServerPassword);
                demonstrateRemoteRollback(ServerPort, ServerUser, ServerPassword);
            }
            finally
            {
                server.close();
            }
        }
            
        public static void setFirstCar(ObjectContainer db)
        {
            Pilot pilot = new Pilot("Rubens Barrichello", 99);
            Car car = new Car("BMW");
            car.Pilot = pilot;
            db.set(car);
        }
    
        public static void setSecondCar(ObjectContainer db)
        {
            Pilot pilot = new Pilot("Michael Schumacher", 100);
            Car car = new Car("Ferrari");
            car.Pilot = pilot;
            db.set(car);
        }
    
        public static void accessLocalServer()
        {
            ObjectServer server = Db4o.openServer(Util.YapFileName, 0);
            try
            {
                ObjectContainer client = server.openClient();
                // Do something with this client, or open more clients
                client.close();
            }
            finally
            {
                server.close();
            }
        }
    
        public static void queryLocalServer(ObjectServer server)
        {
            ObjectContainer client = server.openClient();
            listResult(client.get(new Car(null)));
            client.close();
        }
        
        public static void configureDb4o()
        {
         Db4o.configure().objectClass(typeof(Car)).updateDepth(3);
        }
    
        public static void demonstrateLocalReadCommitted(ObjectServer server)
        {
            ObjectContainer client1 =server.openClient();
            ObjectContainer client2 =server.openClient();
            Pilot pilot = new Pilot("David Coulthard", 98);
            ObjectSet result = client1.get(new Car("BMW"));
            Car car = (Car)result.next();
            car.Pilot = pilot;
            client1.set(car);
            listResult(client1.get(new Car(null)));
            listResult(client2.get(new Car(null)));
            client1.commit();
            listResult(client1.get(typeof(Car)));
            listRefreshedResult(client2, client2.get(typeof(Car)), 2);
            client1.close();
            client2.close();
        }
    
        public static void demonstrateLocalRollback(ObjectServer server)
        {
            ObjectContainer client1 = server.openClient();
            ObjectContainer client2 = server.openClient();
            ObjectSet result = client1.get(new Car("BMW"));
            Car car = (Car)result.next();
            car.Pilot = new Pilot("Someone else", 0);
            client1.set(car);
            listResult(client1.get(new Car(null)));
            listResult(client2.get(new Car(null)));
            client1.rollback();
            client1.ext().refresh(car, 2);
            listResult(client1.get(new Car(null)));
            listResult(client2.get(new Car(null)));
            client1.close();
            client2.close();
        }
    
        public static void accessRemoteServer()
        {
            ObjectServer server = Db4o.openServer(Util.YapFileName, ServerPort);
            server.grantAccess(ServerUser, ServerPassword);
            try
            {
                ObjectContainer client = Db4o.openClient("localhost", ServerPort, ServerUser, ServerPassword);
                // Do something with this client, or open more clients
                client.close();
            }
            finally
            {
                server.close();
            }
        }
    
        public static void queryRemoteServer(int port, string user, string password)
        {
            ObjectContainer client = Db4o.openClient("localhost", port, user, password);
            listResult(client.get(new Car(null)));
            client.close();
        }
    
        public static void demonstrateRemoteReadCommitted(int port, string user, string password)
        {
            ObjectContainer client1 = Db4o.openClient("localhost", port, user, password);
            ObjectContainer client2 = Db4o.openClient("localhost", port, user, password);
            Pilot pilot = new Pilot("Jenson Button", 97);
            ObjectSet result = client1.get(new Car(null));
            Car car = (Car)result.next();
            car.Pilot = pilot;
            client1.set(car);
            listResult(client1.get(new Car(null)));
            listResult(client2.get(new Car(null)));
            client1.commit();
            listResult(client1.get(new Car(null)));
            listResult(client2.get(new Car(null)));
            client1.close();
            client2.close();
        }
    
        public static void demonstrateRemoteRollback(int port, string user, string password)
        {
            ObjectContainer client1 = Db4o.openClient("localhost", port, user, password);
            ObjectContainer client2 = Db4o.openClient("localhost", port, user, password);
            ObjectSet result = client1.get(new Car(null));
            Car car = (Car)result.next();
            car.Pilot = new Pilot("Someone else", 0);
            client1.set(car);
            listResult(client1.get(new Car(null)));
            listResult(client2.get(new Car(null)));
            client1.rollback();
            client1.ext().refresh(car,2);
            listResult(client1.get(new Car(null)));
            listResult(client2.get(new Car(null)));
            client1.close();
            client2.close();
        }
    }
}



--
generated by
Doctor courtesy of db4objects Inc.