11. 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.setPilot(pilot);
db.store(car);


// setSecondCar
Pilot pilot=new Pilot("Michael Schumacher",100);
Car car=new Car("Ferrari");
car.setPilot(pilot);
db.store(car);



    11.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.DB4OFILENAME,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.queryByExample(new Car(null)));
    client.close();


    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 result,int depth) {
        System.out.println(result.size());
        while(result.hasNext()) {
            Object obj = result.next();
            container.ext().refresh(obj, depth);
            System.out.println(obj);
        }
    }


    // demonstrateLocalReadCommitted
    ObjectContainer client1=server.openClient();
    ObjectContainer client2=server.openClient();
    Pilot pilot=new Pilot("David Coulthard",98);
    ObjectSet result=client1.queryByExample(new Car("BMW"));
    Car car=(Car)result.next();
    car.setPilot(pilot);
    client1.store(car);
    listResult(client1.queryByExample(new Car(null)));
    listResult(client2.queryByExample(new Car(null)));
    client1.commit();        
    listResult(client1.queryByExample(Car.class));
    listRefreshedResult(client2,client2.queryByExample(Car.class),2);
    client1.close();
    client2.close();


    Simple rollbacks just work as you might expect now.

    // demonstrateLocalRollback
    ObjectContainer client1=server.openClient();
    ObjectContainer client2=server.openClient();
    ObjectSet result=client1.queryByExample(new Car("BMW"));
    Car car=(Car)result.next();
    car.setPilot(new Pilot("Someone else",0));
    client1.store(car);
    listResult(client1.queryByExample(new Car(null)));
    listResult(client2.queryByExample(new Car(null)));
    client1.rollback();
    client1.ext().refresh(car,2);
    listResult(client1.queryByExample(new Car(null)));
    listResult(client2.queryByExample(new Car(null)));
    client1.close();
    client2.close();



    11.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.DB4OFILENAME,PORT);
    server.grantAccess(USER,PASSWORD);
    try {
        ObjectContainer client=Db4o.openClient("localhost",PORT,USER,PASSWORD);
        // 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.queryByExample(new Car(null)));
    client.close();


    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.queryByExample(new Car(null));
    Car car=(Car)result.next();
    car.setPilot(pilot);
    client1.store(car);
    listResult(client1.queryByExample(new Car(null)));
    listResult(client2.queryByExample(new Car(null)));
    client1.commit();
    listResult(client1.queryByExample(new Car(null)));
    listRefreshedResult(client2,client2.queryByExample(Car.class),2);
    client1.close();
    client2.close();


    // demonstrateRemoteRollback
    ObjectContainer client1=Db4o.openClient("localhost",port,user,password);
    ObjectContainer client2=Db4o.openClient("localhost",port,user,password);
    ObjectSet result=client1.queryByExample(new Car(null));
    Car car=(Car)result.next();
    car.setPilot(new Pilot("Someone else",0));
    client1.store(car);
    listResult(client1.queryByExample(new Car(null)));
    listResult(client2.queryByExample(new Car(null)));
    client1.rollback();
    client1.ext().refresh(car,2);
    listResult(client1.queryByExample(new Car(null)));
    listResult(client2.queryByExample(new Car(null)));
    client1.close();
    client2.close();



    11.3. Native Queries in Client/Server mode


    A quite subtle problem may occur if you're using Native Queries as anonymous (or just non-static) inner classes in Client/Server mode. Anonymous/non-static inner class instances carry a synthetic field referencing their outer class instance - this is just Java's way of implementing inner class access to fields or final method locals of the outer class without introducing any notion of inner classes at all at the bytecode level. If such a non-static inner class predicate cannot be converted to S.O.D.A. form on the client, it will be wrapped into an evaluation and passed to the server, introducing the same problem already mentioned in the evaluation chapter : db4o will try to transfer the evaluation, using the standard platform serialization mechanism. If this fails, it will just try to pass it to the server via db4o marshalling. However, this may fail, too, for example if the outer class references any local db4o objects like ObjectContainer, etc., resulting in an ObjectNotStorableException.

    So to be on the safe side with NQs in C/S mode, you should declare Predicates as top-level or static inner classes only. Alternatively, you could either make sure that the outer classes containing Predicate definitions could principally be persisted to db4o, too, and don't add significant overhead to the predicate (the appropriate value for 'significant'
    being your choice) or ensure during development that all predicates used actually can be optimized to S.O.D.A. form.



    11.4. 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(){
    synchronized(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().clientServer().setMessageRecipient(this);
      
      // to identify the thread in a debugger
      Thread.currentThread().setName(this.getClass().getName());
      
      // We only need low priority since the db4o server has
      // it's own thread.
      Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
      try {
          if(! stop){
            // wait forever for notify() from close()
            this.wait(Long.MAX_VALUE);   
          }
      } catch (Exception e) {
        e.printStackTrace();
      }
      db4oServer.close();
    }
      }


    The message is received and processed by a processMessage() method:

    public void processMessage(MessageContext con, Object message) {
    if(message instanceof StopServer){
      close();
    }
      }


    Db4o allows a client to send an arbitrary signal or message to a server by sending a plain 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) {
      e.printStackTrace();
    }
    if(objectContainer != null){
      // get the messageSender for the ObjectContainer
      MessageSender messageSender = objectContainer.ext().configure()
                    .clientServer().getMessageSender();
      
      // send an instance of a StopServer object
      messageSender.send(new StopServer());
      
      // close the ObjectContainer
      objectContainer.close();
    }
      }



    11.5. 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:

    package com.db4o.f1.chapter5;
    /**
    * Configuration used for {@link StartServer} and {@link StopServer}.
    */
    public interface ServerConfiguration {
      
      /**
       * the host to be used.
       * <br>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.
       */
      public String   HOST = "localhost";  
       
      /**
       * the database file to be used by the server.
       */
      public String   FILE = "formula1.db4o";
      
      /**
       * the port to be used by the server.
       */
      public int    PORT = 4488;
      
      /**
       * the user name for access control.
       */
      public String   USER = "db4o";
      
      /**
       * the pasword for access control.
       */
      public String   PASS = "db4o";
    }


    Now we'll create the server:

    package com.db4o.f1.chapter5;
    import com.db4o.*;
    import com.db4o.messaging.*;
    /**
    * starts a db4o server with the settings from {@link ServerConfiguration}.
    * <br><br>This is a typical setup for a long running server.
    * <br><br>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.
    * <br><br>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.
    */
    public class StartServer
        implements ServerConfiguration, MessageRecipient {
      
      /**
       * setting the value to true denotes that the server should be closed
       */
      private boolean stop = false;
      
      /**
       * starts a db4o server using the configuration from
       * {@link ServerConfiguration}.
       */
      public static void main(String[] arguments) {
        new StartServer().runServer();
      }
      
      /**
       * opens the ObjectServer, and waits forever until close() is called
       * or a StopServer message is being received.
       */
      public void runServer(){
        synchronized(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().clientServer().setMessageRecipient(this);
          
          // to identify the thread in a debugger
          Thread.currentThread().setName(this.getClass().getName());
          
          // We only need low priority since the db4o server has
          // it's own thread.
          Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
          try {
              if(! stop){
                // wait forever for notify() from close()
                this.wait(Long.MAX_VALUE);   
              }
          } catch (Exception e) {
            e.printStackTrace();
          }
          db4oServer.close();
        }
      }
      
      /**
       * messaging callback
       * @see com.db4o.messaging.MessageRecipient#processMessage(MessageContext, Object)
       */
      public void processMessage(MessageContext con, Object message) {
        if(message instanceof StopServer){
          close();
        }
      }
      
      /**
       * closes this server.
       */
      public void close(){
        synchronized(this){
          stop = true;
          this.notify();
        }
      }
    }


    And last but not least, the client that stops the server.

    package com.db4o.f1.chapter5;
    import com.db4o.*;
    import com.db4o.messaging.*;
    /**
    * stops the db4o Server started with {@link StartServer}.
    * <br><br>This is done by opening a client connection
    * to the server and by sending a StopServer object as
    * a message. {@link StartServer} will react in it's
    * processMessage method.
    */
    public class StopServer implements ServerConfiguration {
      /**
       * stops a db4o Server started with StartServer.
       * @throws 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) {
          e.printStackTrace();
        }
        
        if(objectContainer != null){
        
          // get the messageSender for the ObjectContainer
          MessageSender messageSender = objectContainer.ext().configure()
                        .clientServer().getMessageSender();
          
          // send an instance of a StopServer object
          messageSender.send(new StopServer());
          
          // close the ObjectContainer
          objectContainer.close();
        }
      }
    }



    11.6. 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. A much more thorough documentation is provided in the reference that you should have also received with the download or online.

    We hope that this tutorial has helped to get you started with db4o. How should you continue now?

    - You could browse the remaining chapters. They are a selection of themes from the reference that very frequently come up as questions in our http://forums.db4o.com/forums/.

    -(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 Jira or visit our forums at http://forums.db4o.com/forums/.



    11.7. Full source


    package com.db4o.f1.chapter5;
    import java.io.*;
    import com.db4o.*;
    import com.db4o.f1.*;

    public class ClientServerExample extends Util {
        private final static int PORT=0xdb40;
        private final static String USER="user";
        private final static String PASSWORD="password";
        
        public static void main(String[] args) throws IOException {
            new File(Util.DB4OFILENAME).delete();
            accessLocalServer();
            new File(Util.DB4OFILENAME).delete();
            ObjectContainer db=Db4o.openFile(Util.DB4OFILENAME);
            try {
                setFirstCar(db);
                setSecondCar(db);
            }
            finally {
                db.close();
            }
            configureDb4o();
            ObjectServer server=Db4o.openServer(Util.DB4OFILENAME,0);
            try {
                queryLocalServer(server);
                demonstrateLocalReadCommitted(server);
                demonstrateLocalRollback(server);
            }
            finally {
                server.close();
            }
            accessRemoteServer();
            server=Db4o.openServer(Util.DB4OFILENAME,PORT);
            server.grantAccess(USER,PASSWORD);
            try {
                queryRemoteServer(PORT,USER,PASSWORD);
                demonstrateRemoteReadCommitted(PORT,USER,PASSWORD);
                demonstrateRemoteRollback(PORT,USER,PASSWORD);
            }
            finally {
                server.close();
            }
        }
            
        public static void setFirstCar(ObjectContainer db) {
            Pilot pilot=new Pilot("Rubens Barrichello",99);
            Car car=new Car("BMW");
            car.setPilot(pilot);
            db.store(car);
        }
        public static void setSecondCar(ObjectContainer db) {
            Pilot pilot=new Pilot("Michael Schumacher",100);
            Car car=new Car("Ferrari");
            car.setPilot(pilot);
            db.store(car);
        }
        public static void accessLocalServer() {
            ObjectServer server=Db4o.openServer(Util.DB4OFILENAME,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.queryByExample(new Car(null)));
            client.close();
        }
        public static void configureDb4o() {
            Db4o.configure().objectClass(Car.class).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.queryByExample(new Car("BMW"));
            Car car=(Car)result.next();
            car.setPilot(pilot);
            client1.store(car);
            listResult(client1.queryByExample(new Car(null)));
            listResult(client2.queryByExample(new Car(null)));
            client1.commit();        
            listResult(client1.queryByExample(Car.class));
            listRefreshedResult(client2,client2.queryByExample(Car.class),2);
            client1.close();
            client2.close();
        }
        public static void demonstrateLocalRollback(ObjectServer server) {
            ObjectContainer client1=server.openClient();
            ObjectContainer client2=server.openClient();
            ObjectSet result=client1.queryByExample(new Car("BMW"));
            Car car=(Car)result.next();
            car.setPilot(new Pilot("Someone else",0));
            client1.store(car);
            listResult(client1.queryByExample(new Car(null)));
            listResult(client2.queryByExample(new Car(null)));
            client1.rollback();
            client1.ext().refresh(car,2);
            listResult(client1.queryByExample(new Car(null)));
            listResult(client2.queryByExample(new Car(null)));
            client1.close();
            client2.close();
        }
        public static void accessRemoteServer() throws IOException {
            ObjectServer server=Db4o.openServer(Util.DB4OFILENAME,PORT);
            server.grantAccess(USER,PASSWORD);
            try {
                ObjectContainer client=Db4o.openClient("localhost",PORT,USER,PASSWORD);
                // Do something with this client, or open more clients
                client.close();
            }
            finally {
                server.close();
            }
        }
        public static void queryRemoteServer(int port,String user,String password) throws IOException {
            ObjectContainer client=Db4o.openClient("localhost",port,user,password);
            listResult(client.queryByExample(new Car(null)));
            client.close();
        }
        public static void demonstrateRemoteReadCommitted(int port,String user,String password) throws IOException {
            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.queryByExample(new Car(null));
            Car car=(Car)result.next();
            car.setPilot(pilot);
            client1.store(car);
            listResult(client1.queryByExample(new Car(null)));
            listResult(client2.queryByExample(new Car(null)));
            client1.commit();
            listResult(client1.queryByExample(new Car(null)));
            listRefreshedResult(client2,client2.queryByExample(Car.class),2);
            client1.close();
            client2.close();
        }
        public static void demonstrateRemoteRollback(int port,String user,String password) throws IOException {
            ObjectContainer client1=Db4o.openClient("localhost",port,user,password);
            ObjectContainer client2=Db4o.openClient("localhost",port,user,password);
            ObjectSet result=client1.queryByExample(new Car(null));
            Car car=(Car)result.next();
            car.setPilot(new Pilot("Someone else",0));
            client1.store(car);
            listResult(client1.queryByExample(new Car(null)));
            listResult(client2.queryByExample(new Car(null)));
            client1.rollback();
            client1.ext().refresh(car,2);
            listResult(client1.queryByExample(new Car(null)));
            listResult(client2.queryByExample(new Car(null)));
            client1.close();
            client2.close();
        }
    }




    www.db4o.com