NanoDBConnect - NanoDB Part II

NanoDB - NanoDBConnect - Part II

NanoDB allows you to create an object-oriented database in Java, where the objects can be either serializable or non-serializable (e.g. a simple String). However, NanoDB can be used directly as an (embedded) API or indirectly via the NanoDBConnect API when used as an OODB core.

Example: NanoDBdirect.java, which uses NanoDB API directly. It’s a SWING application. In this app you will learn how to call NanoDB methods and pass them with parameters, as well as how to handle the returned values. When you start the app, you will see some instructions that you should read carefully. The processing times given depend on the instance, which are often longer when called for the first time than when called successively or thereafter.

import java.io.*;
import java.awt.*;
import java.net.URL;
import javax.swing.*;
import javax.swing.text.DefaultCaret;
//
import nanodb.NanoDB;
// Joe T. Schwarz(C)
public class NanoDBdirect extends JFrame {
  private NanoDB nano;
  private boolean isPeople;
  private java.util.List<String> keys;
  private String nanoDB = "People", userID = "NanoDB";
  //
  public NanoDBdirect() {
    setTitle("NanoDB");
    setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
    //
    JTextArea jta = new JTextArea(35, 55);
    DefaultCaret caret = (DefaultCaret) jta.getCaret();
    caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);  
     jta.setText("NOTE:\n"+
                "- When a NanoDB is created and filled with add, it must be committed or set to \n"+
                "  autoCommit before closing. Otherwise everything will be lost.\n"+
                "- Before each transaction, the key must be locked and then released for other users.\n"+
                "- Transaction like add, delete and update must be commit or rollback before close.\n"+
                "  Otherwise this transaction will be lost.\n"+
                "- If Object is added, its key is locked and must be unlocked for other users.\n"+
                "- If Object is deleted, its key is kept locked and must be unlocked for release.\n");
    JScrollPane sp =  new JScrollPane(jta,
                                      ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                                      ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED
                                     );
    jta.setEditable(false);
    JButton open = new JButton("OPEN");
    open.setPreferredSize(new Dimension(300, 30));
    open.addActionListener(a -> { // open NanoDB
      nanoDB = JOptionPane.showInputDialog(this, "NanoDB ?", nanoDB);
      if (nanoDB == null) return;
      isPeople = nanoDB.equals("People");
      accMode = "NanoDB "+nanoDB+": ";
      open.setText("OPEN "+nanoDB);
      try {
        long t0 = System.nanoTime();
        nano = new NanoDB(nanoDB);
        nano.open();
        double d = ((double)System.nanoTime()-t0)/1000000;
        jta.append(String.format("%s open(%s) by %s. Elapsed time: %.03f milliSec.\n",
                   accMode,nanoDB,userID, d));
        open.setEnabled(false);
        enabled(true);
      } catch (Exception ex) {
        ex.printStackTrace();
        System.exit(0);
      }
    });
    lst = new JButton("GET KEYS");
    lst.setPreferredSize(new Dimension(300, 30));
    lst.addActionListener(a -> {
        long t0 = System.nanoTime();
        keys = nano.getKeys();
        double d = ((double)System.nanoTime()-t0)/1000000;
        jta.append(String.format("%s getKeys(). Elapsed time: %.03f milliSec.\n",accMode,d));
        for (String key:keys) jta.append("- "+key+"\n");
    });
    read = new JButton("READ");
    read.setPreferredSize(new Dimension(300, 30));
    read.addActionListener(a -> {
      String key = JOptionPane.showInputDialog(this, "Key Name:");
      if (key != null) {
        try {
          long t0 = System.nanoTime();
          byte[] bb = nano.readObject(userID, key);
          double d = ((double)System.nanoTime()-t0)/1000000;
          String time = String.format("%s read(%s, %s). Elapsed time: %.03f milliSec.\n",
                                      accMode,userID, key,d);
          Object obj = toObject(bb);
          jta.append(time+"Data from key:"+key+"\n"+
                          (isPeople?((People)obj).toString():(String)obj+"\n"));
          if (obj instanceof People) ((People)obj).picture(this);
        } catch (Exception ex) {
          JOptionPane.showMessageDialog(this, "Unable to read key:"+key+". Reason:"+ex.toString());
        }
      }
    });
    add = new JButton("ADD");
    add.setPreferredSize(new Dimension(300, 30));
    add.addActionListener(a -> {
      String key = JOptionPane.showInputDialog(this, "Key Name:");
      if (key != null) {
        String inp;
        if (isPeople) {
          inp = JOptionPane.showInputDialog(this, "Image URL:");
        } else {
          inp = JOptionPane.showInputDialog(this, "Any text:");
        }
        if (inp == null) return;
        try {
          long t0 = 0;
          double d = 0;
          if (isPeople) {
            t0 = System.nanoTime();
            nano.addObject(userID, key, new People(key, inp));
            d = ((double)System.nanoTime()-t0)/1000000;
          } else {
            t0 = System.nanoTime();
            nano.addObject(userID, key, inp.getBytes());
            d = ((double)System.nanoTime()-t0)/1000000;
          }
          jta.append(String.format("%s add(%s, %s). Elapsed time: %.03f milliSec.\n",
                                   accMode,userID, key,d));
        } catch (Exception ex) {
          JOptionPane.showMessageDialog(this, "Unable to add key:"+key+". Reason:"+ex.toString());
        }
      }
    });
    del = new JButton("DELETE");
    del.setPreferredSize(new Dimension(300, 30));
    del.addActionListener(a -> {
      String key = JOptionPane.showInputDialog(this, "Key Name:");
      if (key != null) {
        try {
          long t0 = System.nanoTime();
          nano.deleteObject(userID, key);
          double d = ((double)System.nanoTime()-t0)/1000000;
          jta.append(String.format("%s delete(%s, %s). Elapsed time: %.03f milliSec.\n",
                     accMode,userID, key,d));
        } catch (Exception ex) {
          JOptionPane.showMessageDialog(this, "Unable to delete key:"+key+". Reason:"+ex.toString());
        }
      }
    });
    upd = new JButton("UPDATE");
    upd.setPreferredSize(new Dimension(300, 30));
    upd.addActionListener(a -> {
      String key = JOptionPane.showInputDialog(this, "Key Name:");
      if (key != null) {
        try {
          String data = null;
          if (isPeople) {
            String inp[] = ((People)toObject(nano.readObject(userID, key))).getData();
            data = JOptionPane.showInputDialog(this, "Image URL:", inp[1]);
          } else {
            data = new String(nano.readObject(userID, key));
            data = JOptionPane.showInputDialog(this, "Any text:", data);
          }
          if (data == null) return;
          long t0 = 0;
          double d = 0;
          if (isPeople) { // People
            t0 = System.nanoTime();
            nano.updateObject(userID, key, new People(key, data));
            d = (double)(System.nanoTime()-t0)/1000000;
          } else { // String
            t0 = System.nanoTime();
            nano.updateObject(userID, key, data.getBytes());
            d = (double)(System.nanoTime()-t0)/1000000;
          }
          jta.append(String.format("%s update(%s, %s). Elapsed time: %.03f milliSec.\n",
                     accMode, userID, key,d));
        } catch (Exception ex) {
          JOptionPane.showMessageDialog(this, "Unable to update key:"+key+". Reason:"+ex.toString());
        }
      }
    });
    loop = new JButton("LOCK/READ/UPDATE/COMMIT/UNLOCK");
    loop.setPreferredSize(new Dimension(300, 30));
    loop.addActionListener(a -> {
      keys = nano.getKeys();
      if (keys != null && keys.size() > 0) {
        jta.append("Read & Update "+keys.size()+" Keys\n");
        long t0 = System.nanoTime();
        for (String key : keys) {
          try {
            nano.lock(userID, key);
            nano.updateObject(userID, key, nano.readObject(userID, key));
            nano.commit(userID, key);
            nano.unlock(userID, key);
          } catch (Exception ex) {
            JOptionPane.showMessageDialog(this, "Unable to eork with key:"+key+". Reason:"+ex.toString());
          }
        }
        double d = (double)(System.nanoTime()-t0)/1000000;
        jta.append(String.format("%s LOCK/READ/UPDATE/COMMIT/UNLOCK. Elapsed time: %.03f milliSec.\n"+
                                 "Average: %.03f milliSec.\n",accMode,d,(d/keys.size())));
      }
    });
    aut = new JButton("AUTOCOMMIT");
    aut.setPreferredSize(new Dimension(300, 30));
    aut.addActionListener(a -> {
      int au = JOptionPane.showConfirmDialog(this, "autoCommit", "NanoDB", JOptionPane.YES_NO_OPTION);
      boolean bool = au == JOptionPane.YES_OPTION;
      long t0 = System.nanoTime();
      nano.autoCommit(bool);
      double d = ((double)System.nanoTime()-t0)/1000000;
      jta.append(String.format("%s autoCommit(%b). Elapsed time: %.03f milliSec.\n",accMode,bool,d));
    });
    com = new JButton("COMMIT");
    com.setPreferredSize(new Dimension(300, 30));
    com.addActionListener(a -> {
      String key = JOptionPane.showInputDialog(this, "Key Name:");
      if (key != null) {
        long t0 = System.nanoTime();
        boolean b = nano.commit(userID, key);
        double d = ((double)System.nanoTime()-t0)/1000000;
        jta.append(String.format("%s commit(%s, %s): %b. Elapsed time: %.03f milliSec.\n",
                                 accMode,userID, key,b, d));
      }
    });
    call = new JButton("COMMIT ALL");
    call.setPreferredSize(new Dimension(250, 25));
    call.addActionListener(a -> {
      try {
        long t0 = System.nanoTime();
        nano.commitAll(userID);
        double d = ((double)System.nanoTime()-t0)/1000000;
        jta.append(String.format("%s commitAll(%s). Elapsed time: %.03f milliSec.\n",
                                 accMode,nanoDB, d));
      } catch (Exception ex) {
        jta.append(ex.toString()+"\n");
      }
    });
    roll = new JButton("ROLLBACK");
    roll.setPreferredSize(new Dimension(300, 30));
    roll.addActionListener(a -> {
      String key = JOptionPane.showInputDialog(this, "Key Name:");
      if (key != null) {
        long t0 = System.nanoTime();
        boolean b = nano.rollback(userID, key);
        double d = ((double)System.nanoTime()-t0)/1000000;
        jta.append(String.format("%s rollback(%s, %s): %b. Elapsed time: %.03f milliSec.\n",
                   accMode,userID, key,b,d));
      }
    });
    rall = new JButton("ROLLBACK ALL");
    rall.setPreferredSize(new Dimension(250, 25));
    rall.addActionListener(a -> {
      try {
        long t0 = System.nanoTime();
        nano.rollbackAll(userID);
        double d = ((double)System.nanoTime()-t0)/1000000;
        jta.append(String.format("%s rollbackAll(%s). Elapsed time: %.03f milliSec.\n",
                   accMode,nanoDB, d));
      } catch (Exception ex) {
        jta.append(ex.toString()+"\n");
      }
    });
    isAut = new JButton("isAutoCommit");
    isAut.setPreferredSize(new Dimension(300, 30));
    isAut.addActionListener(a -> {
      long t0 = System.nanoTime();
      boolean b = nano.isAutoCommit( );
      double d = ((double)System.nanoTime()-t0)/1000000;
      jta.append(String.format("%s isAutoCommit(): %b. Elapsed time: %.03f milliSec.\n",accMode,b,d));
    });
    isLck = new JButton("isLocked");
    isLck.setPreferredSize(new Dimension(300, 30));
    isLck.addActionListener(a -> {
      String key = JOptionPane.showInputDialog(this, "Key Name:");
      if (key == null) return;
      long t0 = System.nanoTime();
      boolean b = nano.isLocked(key);
      double d = ((double)System.nanoTime()-t0)/1000000;
      jta.append(String.format("%s isLocked(): %b. Elapsed time: %.03f milliSec.\n",accMode,b,d));
    });
    isExt = new JButton("isExisted");
    isExt.setPreferredSize(new Dimension(300, 30));
    isExt.addActionListener(a -> {
      String key = JOptionPane.showInputDialog(this, "Key Name:");
      if (key != null) {
        long t0 = System.nanoTime();
        boolean b = nano.isExisted(key);
        double d = ((double)System.nanoTime()-t0)/1000000;
        jta.append(String.format("%s isExisted(%s): %b. Elapsed time: %.03f milliSec.\n",
                   accMode,key,b,d));
      }
    });
    isDel = new JButton("isKeyDeleted");
    isDel.setPreferredSize(new Dimension(300, 30));
    isDel.addActionListener(a -> {
      String key = JOptionPane.showInputDialog(this, "Key Name:");
      if (key != null) {
        long t0 = System.nanoTime();
        boolean b = nano.isKeyDeleted(key);
        double d = ((double)System.nanoTime()-t0)/1000000;
        jta.append(String.format("%s isKeyDeleted(%s): %b. Elapsed time: %.03f milliSec.\n",
                   accMode,key,b,d));
      }
    });
    lck = new JButton("LOCK");
    lck.setPreferredSize(new Dimension(300, 30));
    lck.addActionListener(a -> {
      String key = JOptionPane.showInputDialog(this, "Key Name:");
      if (key == null) return;
      long t0 = System.nanoTime();
      boolean b = nano.lock(userID, key);
      double d = ((double)System.nanoTime()-t0)/1000000;
      jta.append(String.format("%s lock(%s, %s): %b. Elapsed time: %.03f milliSec.\n",
                 accMode,userID, key, b, d));
    });
    unlck = new JButton("UNLOCK");
    unlck.setPreferredSize(new Dimension(300, 30));
    unlck.addActionListener(a -> {
      String key = JOptionPane.showInputDialog(this, "Key Name:");
      if (key == null) return;
      long t0 = System.nanoTime();
      boolean b = nano.unlock(userID, key);
      double d = ((double)System.nanoTime()-t0)/1000000;
      jta.append(String.format("%s unlock(%s, %s): %b. Elapsed time: %.03f milliSec.\n",
                 accMode,userID, key, b, d));
    });
    close = new JButton("CLOSE");
    close.setPreferredSize(new Dimension(300, 30));
    close.addActionListener(a -> {
      if (nano != null) try {
        nano.removeLockedKeys(userID);
        nano.close( );
      } catch (Exception ex) { }
      open.setText("OPEN");
      open.setEnabled(true);
      enabled(false);
      nano = null;
    });   
    JButton exit = new JButton("EXIT");
    exit.setPreferredSize(new Dimension(300, 30));
    exit.addActionListener(a -> {
      if (nano != null) try {
        nano.close();
      } catch (Exception ex) { }
      System.exit(0);
    });
    JPanel bPanel = new JPanel();
    bPanel.setLayout(new GridLayout(21,1));
    bPanel.add(new JLabel("Access Mode"));
    bPanel.add(open); bPanel.add(lst); bPanel.add(read); bPanel.add(add); bPanel.add(del);
    bPanel.add(upd); bPanel.add(loop);bPanel.add(aut); bPanel.add(com); bPanel.add(call);
    bPanel.add(roll); bPanel.add(rall); bPanel.add(isAut); bPanel.add(isExt); bPanel.add(isDel);
    bPanel.add(isLck); bPanel.add(lck); bPanel.add(unlck); bPanel.add(close); bPanel.add(exit);
    
    Container contentPanel = getContentPane();  
    GroupLayout groupLayout = new GroupLayout(contentPanel);  
  
    contentPanel.setLayout(groupLayout);  
    groupLayout.setHorizontalGroup(  
                    groupLayout.createSequentialGroup()  
                               .addComponent(bPanel) 
                               .addGap(5)                                
                               .addComponent(sp));  
                                 
    groupLayout.setVerticalGroup(  
                    groupLayout.createParallelGroup(GroupLayout.Alignment.BASELINE)  
                               .addComponent(bPanel)  
                               .addComponent(sp));  
      
    setLocation(0, 0);
    pack();
    enabled(false);
    setVisible(true);
  }
  //
  private Object toObject(byte[] bb) throws Exception {
    if (bb[0] != (byte)0xAC || bb[1] != (byte)0xED) return new String(bb); 
    // is a serialized object
    ObjectInputStream oi = new ObjectInputStream(new ByteArrayInputStream(bb));
    Object obj = oi.readObject();
    oi.close();
    return obj;
  }
  //
  private void enabled(boolean boo) {
    read.setEnabled(boo); add.setEnabled(boo); del.setEnabled(boo);
    upd.setEnabled(boo); loop.setEnabled(boo); com.setEnabled(boo);
    roll.setEnabled(boo); isLck.setEnabled(boo); isExt.setEnabled(boo);
    unlck.setEnabled(boo); lst.setEnabled(boo); isDel.setEnabled(boo);
    lck.setEnabled(boo); close.setEnabled(boo); aut.setEnabled(boo);
    isAut.setEnabled(boo); call.setEnabled(boo); rall.setEnabled(boo); 
  }
  //
  private String accMode;
  private JButton lst, read, add, del, upd, loop, aut, com, call, roll, rall;
  private JButton isAut, isDel, isLck, isExt, lck, unlck, close;
  //
  public static void main(String... args) throws Exception {
    UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
    new NanoDBdirect();
  }
}

NanoDBdirect
The classic and best implementation of a multi-user database system based on the client-server principle. A server running independently from the clients provides DB access services for all client DB requests. As mentioned, the server manages the DB requests using a key locking mechanism by assigning each client an unique user ID that serves as the owner of the locked keys. The unique user ID can be a login ID or a part of it combined with a unique random number. In this Package, the user ID is generated by the server. Only the owner of the locked keys can update, delete, commit or revert the key-based changes before the keys can be unlocked. If you decide to build your own server that requires user authentication, it is up to you to implement your approach yourself. However, your server must include NanoDBWorker to complete the chain between NanoDBConnect and NanoDB files via NanoDBManager.

The server service is usually a thread created by the server, runs in a thread pool (or as an independent thread - not recommended) and accesses NanoDB on behalf of its client using the provided methods like READ, DELETE, etc., keeps track of it and ensures DB integrity with the key locking mechanism in sync with other client threads. In this NanoDB package, this is the NanoDBWorker. Usually client data is either serialized POJO or plain text (string). It is better and easier for clients to send DB requests in byte arrays. The reason is that serialized objects (POJO) with their POJO classes usually only exist on the client side, so object management on the server side does not make sense. Example (on the client side). If your app uses the NanoDB API directly, the returned byte array must be converted to an object or plain text. Such conversion is not required when using it with NanoDBServer, as this is done by NanoDBConnect.

public class people implements java.io.Serializable {
  private static final long serialVersionUID = 1234L;
  private String name, image;
  public People(String name, String image) {
    this.name = name;
    this.image = image;
  }
  public String toString() {
    return "People: "+name+", image Link:"+image;
  }
  // some getters and setters
  ...
}

Converting the byte array from readObject() to a serialized object used by NanoDBdirect.

  private Object toObject(byte[] bb) throws Exception {
    if (bb[0] != (byte)0xAC || bb[1] != (byte)0xED) return new String(bb); 
    // is a serialized object
    ObjectInputStream oi = new ObjectInputStream(new ByteArrayInputStream(bb));
    Object obj = oi.readObject();
    oi.close();
    return obj;
  }

Instead of struggling with object conversion, you can work with NanoDBServer indirectly, like all other DB systems do. The only thing you need to do is “cast” the objects into the appropriate POJOs. In this NanoDB package, this is NanoDBConnect. This API is a SocketChannel-based network module that communicates directly with the appropriate thread spawned by the server on the server side (NanoDBWorker) and provides the client app “surrogate” NanoDB methods: The client API sends a DB request using the “surrogate” methods as if it were NanoDB itself. The assigned NanoDBWorker processes this request and sends the result back to its partner NanoDBConnect and the client app just has to work with it.

NanoDBConnect.java. This API automatically closes the connection if something unexpected happens on the client site (e.g. sudden shutdown, power outage, etc.).

package nanodb;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;
//
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.Charset;
/**
NanoDBConnect. Interface for NanoDB's clients to NanoDBServer
@author Joe T. Schwarz (c)
*/
public class NanoDBConnect {
  /**
  contructor. API for Client app
  @param host  String, NanoDB Server hostname or IP
  @param port  int, NanoDB Server's port
  @exception Exception thrown by java
  */
  public NanoDBConnect(String host, int port) throws Exception {
    soc = SocketChannel.open(new InetSocketAddress(host, port));
    soc.socket().setReceiveBufferSize(65536); // 32KB
    soc.socket().setSendBufferSize(65536);
    // start Shutdown listener
    Runtime.getRuntime().addShutdownHook(new Thread() {
      public void run() {
        if (soc != null) try {
          disconnect();
        } catch (Exception ex) { }
      }
    });
  }  
  /**
  @param dbName String
  @param charsetName String
  @return String assigned userID to this connected dbName
  @exception Exception thrown by java
  */
  public String open(String dbName, String charsetName) throws Exception {
    if (!dbLst.contains(dbName)) {
      dbLst.add(dbName);
      return new String(send(dbName, 0, charsetName));
    } else throw new Exception(dbName+" is already opened.");
  }
  /**
  @param dbName String
  @exception Exception thrown by java
  */
  public void close(String dbName) throws Exception {
    send(dbName, 1);
    dbLst.remove(dbName);
  }
  /**
  @param dbName String
  @return List of key Strings
  @exception Exception thrown by java
  */
  public List<String> getKeys(String dbName) throws Exception {
    byte[] bb = send(dbName, 2);
    List<String> keys = new ArrayList<>();
    if (bb[1] != (byte) 0x00 || bb[2] != (byte) 0x00) {
      for (int l = 0, i = 1; i < bb.length; i += (2+l)) {
        l = ((int)(bb[i] & 0xFF) << 8) | (int)(bb[i+1] & 0xFF);
        keys.add(new String(bb, i+2, l));
      }
    }
    return keys;
  }
  /**
  @param dbName String
  @param boo boolean, true: set, false: reset
  @exception Exception thrown by java
  */
  public void autoCommit(String dbName, boolean boo) throws Exception {
    if (boo) send(dbName, 3, ""+(char)0x00);
    else     send(dbName, 3, ""+(char)0x01);
  }
  /**
  @param dbName String
  @return boolean true: set, false: not set
  @exception Exception thrown by java
  */
  public boolean isAutoCommit(String dbName) throws Exception {
    return send(dbName, 4)[1] == (byte)0x00;
  }
  /**
  @param dbName String
  @param key String
  @return boolean true: locked, false: no locked
  @exception Exception thrown by java
  */
  public boolean lock(String dbName, String key) throws Exception {
    return send(dbName, 5, key)[1] == (byte)0x00;
  }
  /**
  @param dbName String
  @param key String
  @return boolean true: locked, false: no locked
  @exception Exception thrown by java
  */
  public boolean unlock(String dbName, String key) throws Exception {
    return send(dbName, 6, key)[1] == (byte)0x00;
  }
  /**
  @param dbName String
  @param key String
  @return boolean true: locked, false: no locked
  @exception Exception thrown by java
  */
  public boolean isLocked(String dbName, String key) throws Exception {
    return send(dbName, 7, key)[1] == (byte)0x00;
  }
  /**
  @param dbName String
  @param key String
  @return boolean true: locked, false: no locked
  @exception Exception thrown by java
  */
  public boolean isExisted(String dbName, String key) throws Exception {
    return send(dbName, 8, key)[1] == (byte)0x00;
  }
  /**
  @param dbName String
  @param key String
  @return boolean true: locked, false: no locked
  @exception Exception thrown by java
  */
  public boolean isKeyDeleted(String dbName, String key) throws Exception {
    return send(dbName, 9, key)[1] == (byte)0x00;
  }
  /**
  @param dbName String
  @param key String
  @return Object either as serialized Object or as byte array
  @exception Exception thrown by java
  */
  public Object readObject(String dbName, String key) throws Exception {
    byte[] buf = send(dbName, 10, key); // ignore buf[0] as OK-byte 
    if (buf[1] != (byte)0xAC || buf[2] != (byte)0xED) return buf; // is a serialized object   
    ObjectInputStream oi = new ObjectInputStream(new ByteArrayInputStream(buf, 1, buf.length-1));
    Object obj = oi.readObject();
    oi.close();
    return obj;
  }
  /**
  @param dbName String
  @param key String
  @param obj serializable Object
  @exception Exception thrown by java
  */
  public void addObject(String dbName, String key, Object obj) throws Exception {
    ByteArrayOutputStream bao = new ByteArrayOutputStream();
    ObjectOutputStream oo = new ObjectOutputStream(bao);
    oo.writeObject(obj);
    oo.flush();
    oo.close();
    send(dbName, 11, key, bao.toByteArray());
  }
  /**
  @param dbName String
  @param key String
  @param buf byte array
  @exception Exception thrown by java
  */
  public void addObject(String dbName, String key, byte[] buf) throws Exception {
    send(dbName, 11, key, buf);
  }
  /**
  @param dbName String
  @param key String
  @return Object either as serialized Object or as byte array
  @exception Exception thrown by java
  */
  public void deleteObject(String dbName, String key) throws Exception {
    send(dbName, 12, key);
  }
  /**
  @param dbName String
  @param key String
  @param obj serializable Object
  @exception Exception thrown by java
  */
  public void updateObject(String dbName, String key, Object obj) throws Exception {
    ByteArrayOutputStream bao = new ByteArrayOutputStream();
    ObjectOutputStream oo = new ObjectOutputStream(bao);
    oo.writeObject(obj);
    oo.flush();
    oo.close();
    send(dbName, 13, key, bao.toByteArray());
  }
  /**
  @param dbName String
  @param key String
  @param buf byte array
  @exception Exception thrown by java
  */
  public void updateObject(String dbName, String key, byte[] buf) throws Exception {
    send(dbName, 13, key, buf);
  }
  /**
  @param dbName String
  @param key String
  @return boolean true: committed, false: failed
  @exception Exception thrown by java
  */
  public boolean commit(String dbName, String key) throws Exception {
    return send(dbName, 14, key)[1] == (byte)0x00;
  }
  /**
  @param dbName String
  @param key String
  @exception Exception thrown by java
  */
  public void commitAll(String dbName) throws Exception {
    send(dbName, 15);
  }
  /**
  @param dbName String
  @param key String
  @return boolean true: rolled back, false: failed
  @exception Exception thrown by java
  */
  public boolean rollback(String dbName, String key) throws Exception {
    return send(dbName, 16, key)[1] == (byte)0x00;
  }
  /**
  @param dbName String
  @param key String
  @exception Exception thrown by java
  */
  public void rollbackAll(String dbName) throws Exception {
    send(dbName, 17);
  }
  /**
  @param dbName String
  @exception Exception thrown by java
  */
  public void disconnect() throws Exception {
    send("*", 18);
    soc.close();
    bao.close();
    soc = null;
  }
  //-------------------------------------------------------------------------------------
  //
  // buf format: 1st byte: cmd, 2 bytes: dbName length, 2 bytes: key length, 4 bytes: data Length, dbName, key, data
  //
  private byte[] send(String dbName, int cmd, String key, byte[] obj) throws Exception {
    if (!dbLst.contains(dbName)) throw new Exception("Unknown dbName "+dbName);
    int kl = key.length(), dl = dbName.length();
    byte[] buf = new byte[9+kl+dl+obj.length];
    buf[0] = (byte)cmd;
    buf[1] = (byte)(((int)dl & 0xFF00) >> 8); buf[2] = (byte)((int)dl & 0xFF);
    buf[3] = (byte)(((int)kl & 0xFF00) >> 8); buf[4] = (byte)((int)kl & 0xFF);
    buf[5] = (byte)(((int)obj.length & 0xFF000000) >> 24); buf[6] = (byte)(((int)obj.length & 0xFF0000) >> 16);
    buf[7] = (byte)(((int)obj.length & 0xFF00) >> 8); buf[8] = (byte)((int)obj.length & 0xFF);
    System.arraycopy(dbName.getBytes(), 0, buf, 9, dl);
    System.arraycopy(key.getBytes(), 0, buf, 9+dl, kl);
    System.arraycopy(obj, 0, buf, 9+dl+kl, obj.length);
    soc.write(ByteBuffer.wrap(buf, 0, buf.length));
    return readChannel();
  }
  //
  // buf format: 1st byte: cmd, 2 bytes: dbName length, 2 bytes: key/data length, dbName, keye/data
  //
  private byte[] send(String dbName, int cmd, String key) throws Exception {
    if (!dbLst.contains(dbName)) throw new Exception("Unknown dbName "+dbName);
    int kl = key.length(), dl = dbName.length();
    byte[] buf = new byte[5+kl+dl];
    buf[0] = (byte)cmd;
    buf[1] = (byte)(((int)dl & 0xFF00) >> 8); buf[2] = (byte)((int)dl & 0xFF);
    buf[3] = (byte)(((int)kl & 0xFF00) >> 8); buf[4] = (byte)((int)kl & 0xFF);
    System.arraycopy(dbName.getBytes(), 0, buf, 5, dl);
    System.arraycopy(key.getBytes(), 0, buf, 5+dl, kl);
    soc.write(ByteBuffer.wrap(buf, 0, buf.length));
    return readChannel();
  }
  //
  // buf format: 1st byte: cmd, 2 bytes: key length, key or name
  //
  private byte[] send(String dbName, int cmd) throws Exception {
    if (!dbLst.contains(dbName)) throw new Exception("Unknown dbName "+dbName);
    int dl = dbName.length();
    byte[] buf = new byte[3+dl];
    buf[0] = (byte)cmd;
    buf[1] = (byte)(((int)dl & 0xFF00) >> 8); buf[2] = (byte)((int)dl & 0xFF);
    System.arraycopy(dbName.getBytes(), 0, buf, 3, dl);
    soc.write(ByteBuffer.wrap(buf, 0, buf.length));
    return readChannel();
  }
  //-------------------------------------------------------------------------------
  private byte[] readChannel() throws Exception {
    bao.reset();
    int le = 0;
    do {
      bbuf.clear();
      le = soc.read(bbuf);
      bao.write(bbuf.flip().array(), 0, le);
    } while (le >= 65536);
    byte[] bb = bao.toByteArray();
    if (bb[0] == (byte)00) return bb;
    throw new Exception(new String(bb, 1, bb.length-1));
  }
  //------------------------------------------------------------------------------
  private SocketChannel soc;
  private ByteBuffer bbuf = ByteBuffer.allocate(65536);
  private ByteArrayOutputStream bao = new ByteArrayOutputStream(65536);
  private List<String> dbLst = Collections.synchronizedList(new ArrayList<>());
}

End of Part II

Comments and suggestions are welcome.

83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?