=== modified file 'src/com/goldencode/p2j/ui/BrowseKey.java'
--- src/com/goldencode/p2j/ui/BrowseKey.java	2018-10-18 19:51:17 +0000
+++ src/com/goldencode/p2j/ui/BrowseKey.java	2019-09-17 20:05:17 +0000
@@ -65,11 +65,14 @@
 import java.io.*;
 import java.util.*;
 
+import com.fasterxml.jackson.annotation.*;
+
 /**
  * Key that uniquely identifies browse in an application using combination of the parent external
  * procedure name, browse name and set of column keys. If the parent external procedure name is
  * <code>null</code> then this key references to all browses.
  */
+@JsonInclude(JsonInclude.Include.NON_NULL)
 public class BrowseKey
 implements Serializable
 {
@@ -113,6 +116,7 @@
     * @param ehColumnKeys
     *        Set of column keys.
     */
+   @JsonCreator
    public BrowseKey(String ehKeyProcName,
                     String ehKeyBrowseName,
                     Set<String> ehColumnKeys)

=== modified file 'src/com/goldencode/p2j/ui/ClientExports.java'
--- src/com/goldencode/p2j/ui/ClientExports.java	2019-07-01 17:33:15 +0000
+++ src/com/goldencode/p2j/ui/ClientExports.java	2019-09-13 09:26:59 +0000
@@ -316,7 +316,7 @@
  * design policies are required to maximize the performance of the remote
  * UI as well as to keep the remote UI implementation as simple as possible.
  */
-public interface ClientExports
+public interface ClientExports extends ObjectStorage
 {
    /**
     * Rings a bell on the terminal.

=== added file 'src/com/goldencode/p2j/ui/ClientStorageProxy.java'
--- src/com/goldencode/p2j/ui/ClientStorageProxy.java	1970-01-01 00:00:00 +0000
+++ src/com/goldencode/p2j/ui/ClientStorageProxy.java	2019-09-19 19:38:57 +0000
@@ -0,0 +1,238 @@
+/*
+** Module   : ClientStorageProxy.java
+** Abstract : Implements the proxy for the client object storage that can be used on the server.
+**
+** Copyright (c) 2019, Golden Code Development Corporation.
+**
+** -#- -I- --Date-- ---------------------------------Description---------------------------------
+** 001 SBI 20190911 Created initial version.
+*/
+
+/*
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU Affero General Public License as
+** published by the Free Software Foundation, either version 3 of the
+** License, or (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+** GNU Affero General Public License for more details.
+**
+** You may find a copy of the GNU Affero GPL version 3 at the following
+** location: https://www.gnu.org/licenses/agpl-3.0.en.html
+** 
+** Additional terms under GNU Affero GPL version 3 section 7:
+** 
+**   Under Section 7 of the GNU Affero GPL version 3, the following additional
+**   terms apply to the works covered under the License.  These additional terms
+**   are non-permissive additional terms allowed under Section 7 of the GNU
+**   Affero GPL version 3 and may not be removed by you.
+** 
+**   0. Attribution Requirement.
+** 
+**     You must preserve all legal notices or author attributions in the covered
+**     work or Appropriate Legal Notices displayed by works containing the covered
+**     work.  You may not remove from the covered work any author or developer
+**     credit already included within the covered work.
+** 
+**   1. No License To Use Trademarks.
+** 
+**     This license does not grant any license or rights to use the trademarks
+**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
+**     of Golden Code Development Corporation. You are not authorized to use the
+**     name Golden Code, FWD, or the names of any author or contributor, for
+**     publicity purposes without written authorization.
+** 
+**   2. No Misrepresentation of Affiliation.
+** 
+**     You may not represent yourself as Golden Code Development Corporation or FWD.
+** 
+**     You may not represent yourself for publicity purposes as associated with
+**     Golden Code Development Corporation, FWD, or any author or contributor to
+**     the covered work, without written authorization.
+** 
+**   3. No Misrepresentation of Source or Origin.
+** 
+**     You may not represent the covered work as solely your work.  All modified
+**     versions of the covered work must be marked in a reasonable way to make it
+**     clear that the modified work is not originating from Golden Code Development
+**     Corporation or FWD.  All modified versions must contain the notices of
+**     attribution required in this license.
+*/
+
+package com.goldencode.p2j.ui;
+
+
+/**
+ * Represents the server side client storage proxy
+ */
+public class ClientStorageProxy
+implements ObjectStorage
+{
+   /** The client exported API */
+   private final ClientExports client;
+   
+   /**
+    * Creates a proxy object.
+    * 
+    * @param    client
+    *           The delegate that represents the client exported API.
+    */
+   public ClientStorageProxy(ClientExports client)
+   {
+      this.client = client;
+   }
+
+   /**
+    * Retrieves the number value of the given key from this storage.
+    * 
+    * @param    key
+    *           The given key
+    * 
+    * @return   The number value of the given key 
+    */
+   @Override
+   public Number getNumber(String key)
+   {
+      return client.getNumber(key);
+   }
+
+   /**
+    * Saves the number value of this key in this storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    value
+    *           The value of the given key
+    */
+   @Override
+   public void setNumber(String key, Number value)
+   {
+      client.setNumber(key, value);
+   }
+
+   /**
+    * Retrieves the boolean value of the given key from this storage.
+    * 
+    * @param    key
+    *           The given key
+    * 
+    * @return   The boolean value of the given key
+    */
+   @Override
+   public Boolean getBoolean(String key)
+   {
+      return client.getBoolean(key);
+   }
+
+   /**
+    * Saves the boolean value of this key in this storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    value
+    *           The value of the given key
+    */
+   @Override
+   public void setBoolean(String key, Boolean value)
+   {
+      client.setBoolean(key, value);
+   }
+
+   /**
+    * Retrieves the string value of the given key from this storage.
+    * 
+    * @param    key
+    *           The given key
+    * 
+    * @return   The string value of the given key
+    */
+   @Override
+   public String getString(String key)
+   {
+      return client.getString(key);
+   }
+
+   /**
+    * Saves the string value of this key in this storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    value
+    *           The value of the given key
+    */
+   @Override
+   public void setString(String key, String value)
+   {
+      client.setString(key, value);
+   }
+
+   /**
+    * Deletes the given key and its value from this storage.
+    * 
+    * @param    key
+    *           The given key
+    */
+   @Override
+   public void delete(String key)
+   {
+      client.delete(key);
+   }
+
+   /**
+    * Retrieves the object value of the given key from this storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    clazz
+    *           The java class that represents this object
+    * 
+    * @return   The object value of the given key
+    */
+   @SuppressWarnings("rawtypes")
+   @Override
+   public Object getObject(String key, Class clazz)
+   {
+      return client.getObject(key, clazz);
+   }
+
+   /**
+    * Sets the object value of the given key for this storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    value
+    *           The object value
+    */
+   @Override
+   public void setObject(String key, Object value)
+   {
+      client.setObject(key, value);
+   }
+
+   /**
+    * Returns the storage size.
+    * 
+    * @return   The storage size
+    */
+   @Override
+   public int getSize()
+   {
+      return client.getSize();
+   }
+
+   /**
+    * Returns the key by its index number in a sequence of all storage keys.
+    * 
+    * @param    index
+    *           The given index number in a sequence of all storage keys.
+    * 
+    * @return   The key
+    */
+   @Override
+   public String getKey(int index)
+   {
+      return client.getKey(index);
+   }
+}

=== modified file 'src/com/goldencode/p2j/ui/EnhancedBrowseConfig.java'
--- src/com/goldencode/p2j/ui/EnhancedBrowseConfig.java	2018-10-18 19:51:17 +0000
+++ src/com/goldencode/p2j/ui/EnhancedBrowseConfig.java	2019-09-17 20:06:41 +0000
@@ -67,8 +67,10 @@
 
 package com.goldencode.p2j.ui;
 
+import com.fasterxml.jackson.annotation.*;
 import com.goldencode.p2j.ui.client.*;
 import com.goldencode.p2j.ui.client.gui.*;
+
 import java.io.*;
 import java.util.*;
 
@@ -82,6 +84,7 @@
  *    <li>width, order and visibility of columns.</li>
  * </ul>
  */
+@JsonInclude(JsonInclude.Include.NON_NULL)
 public class EnhancedBrowseConfig
 implements Serializable
 {
@@ -229,6 +232,7 @@
     *
     * @return the key which identifies the underlying browse.
     */
+   @JsonIgnore
    public BrowseKey getKey()
    {
       return new BrowseKey(ehKeyProcName,
@@ -244,6 +248,7 @@
     *
     * @return the storage key of this configuration.
     */
+   @JsonIgnore
    public EnhancedBrowseConfigStorageKey getStorageKey()
    {
       return new EnhancedBrowseConfigStorageKey(userName, getKey());

=== modified file 'src/com/goldencode/p2j/ui/EnhancedBrowseConfigStorageKey.java'
--- src/com/goldencode/p2j/ui/EnhancedBrowseConfigStorageKey.java	2018-10-18 19:51:17 +0000
+++ src/com/goldencode/p2j/ui/EnhancedBrowseConfigStorageKey.java	2019-09-17 20:06:10 +0000
@@ -63,6 +63,8 @@
 
 import java.util.*;
 
+import com.fasterxml.jackson.annotation.*;
+
 /**
  * Enhanced browse configuration storage key. Extends {@link BrowseKey} with the
  * field containing a user name. Used for keying configurations loaded from the directory.
@@ -89,7 +91,9 @@
     * @param configKey
     *        Key that uniquely identifies browse in an application.
     */
-   public EnhancedBrowseConfigStorageKey(String userName, BrowseKey configKey)
+   @JsonCreator
+   public EnhancedBrowseConfigStorageKey(@JsonProperty("userName") String    userName,
+                                         @JsonProperty("configKey")BrowseKey configKey)
    {
       this.userName = userName;
       this.configKey = configKey;

=== added file 'src/com/goldencode/p2j/ui/KeyValueStorage.java'
--- src/com/goldencode/p2j/ui/KeyValueStorage.java	1970-01-01 00:00:00 +0000
+++ src/com/goldencode/p2j/ui/KeyValueStorage.java	2019-09-19 16:19:45 +0000
@@ -0,0 +1,156 @@
+/*
+** Module   : KeyValueStorage.java
+** Abstract : Defines methods to store and retrieve key and value pairs.
+**
+** Copyright (c) 2019, Golden Code Development Corporation.
+**
+** -#- -I- --Date-- ---------------------------------Description---------------------------------
+** 001 SBI 20190911 Created initial version.
+*/
+
+/*
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU Affero General Public License as
+** published by the Free Software Foundation, either version 3 of the
+** License, or (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+** GNU Affero General Public License for more details.
+**
+** You may find a copy of the GNU Affero GPL version 3 at the following
+** location: https://www.gnu.org/licenses/agpl-3.0.en.html
+** 
+** Additional terms under GNU Affero GPL version 3 section 7:
+** 
+**   Under Section 7 of the GNU Affero GPL version 3, the following additional
+**   terms apply to the works covered under the License.  These additional terms
+**   are non-permissive additional terms allowed under Section 7 of the GNU
+**   Affero GPL version 3 and may not be removed by you.
+** 
+**   0. Attribution Requirement.
+** 
+**     You must preserve all legal notices or author attributions in the covered
+**     work or Appropriate Legal Notices displayed by works containing the covered
+**     work.  You may not remove from the covered work any author or developer
+**     credit already included within the covered work.
+** 
+**   1. No License To Use Trademarks.
+** 
+**     This license does not grant any license or rights to use the trademarks
+**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
+**     of Golden Code Development Corporation. You are not authorized to use the
+**     name Golden Code, FWD, or the names of any author or contributor, for
+**     publicity purposes without written authorization.
+** 
+**   2. No Misrepresentation of Affiliation.
+** 
+**     You may not represent yourself as Golden Code Development Corporation or FWD.
+** 
+**     You may not represent yourself for publicity purposes as associated with
+**     Golden Code Development Corporation, FWD, or any author or contributor to
+**     the covered work, without written authorization.
+** 
+**   3. No Misrepresentation of Source or Origin.
+** 
+**     You may not represent the covered work as solely your work.  All modified
+**     versions of the covered work must be marked in a reasonable way to make it
+**     clear that the modified work is not originating from Golden Code Development
+**     Corporation or FWD.  All modified versions must contain the notices of
+**     attribution required in this license.
+*/
+
+package com.goldencode.p2j.ui;
+
+
+/**
+ * This interface defines access methods for a key value storage.
+ */
+public interface KeyValueStorage
+{
+   /**
+    * Retrieves the number value of the given key from this storage.
+    * 
+    * @param    key
+    *           The given key
+    * 
+    * @return   The number value of the given key 
+    */
+   Number getNumber(String key);
+   
+   /**
+    * Saves the number value of this key in this storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    value
+    *           The value of the given key
+    */
+   void setNumber(String key, Number value);
+   
+   /**
+    * Retrieves the boolean value of the given key from this storage.
+    * 
+    * @param    key
+    *           The given key
+    * 
+    * @return   The boolean value of the given key
+    */
+   Boolean getBoolean(String key);
+   
+   /**
+    * Saves the boolean value of this key in this storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    value
+    *           The value of the given key
+    */
+   void setBoolean(String key, Boolean value);
+   
+   /**
+    * Retrieves the string value of the given key from this storage.
+    * 
+    * @param    key
+    *           The given key
+    * 
+    * @return   The string value of the given key
+    */
+   String getString(String key);
+   
+   /**
+    * Saves the string value of this key in this storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    value
+    *           The value of the given key
+    */
+   void setString(String key, String value);
+   
+   /**
+    * Deletes the given key and its value from this storage.
+    * 
+    * @param    key
+    *           The given key
+    */
+   void delete(String key);
+   
+   /**
+    * Returns the storage size.
+    * 
+    * @return   The storage size
+    */
+   int getSize();
+   
+   /**
+    * Returns the key by its index number in a sequence of all storage keys.
+    * 
+    * @param    index
+    *           The given index number in a sequence of all storage keys.
+    * 
+    * @return   The key
+    */
+   String getKey(int index);
+}

=== added file 'src/com/goldencode/p2j/ui/KeyValueStorageAdapter.java'
--- src/com/goldencode/p2j/ui/KeyValueStorageAdapter.java	1970-01-01 00:00:00 +0000
+++ src/com/goldencode/p2j/ui/KeyValueStorageAdapter.java	2019-09-19 19:08:16 +0000
@@ -0,0 +1,285 @@
+/*
+** Module   : KeyValueStorageAdapter.java
+** Abstract : Implements the adapter for the KeyValueStorage backend and the client object storage.
+**
+** Copyright (c) 2019, Golden Code Development Corporation.
+**
+** -#- -I- --Date-- ---------------------------------Description---------------------------------
+** 001 SBI 20190911 Created initial version.
+*/
+
+/*
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU Affero General Public License as
+** published by the Free Software Foundation, either version 3 of the
+** License, or (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+** GNU Affero General Public License for more details.
+**
+** You may find a copy of the GNU Affero GPL version 3 at the following
+** location: https://www.gnu.org/licenses/agpl-3.0.en.html
+** 
+** Additional terms under GNU Affero GPL version 3 section 7:
+** 
+**   Under Section 7 of the GNU Affero GPL version 3, the following additional
+**   terms apply to the works covered under the License.  These additional terms
+**   are non-permissive additional terms allowed under Section 7 of the GNU
+**   Affero GPL version 3 and may not be removed by you.
+** 
+**   0. Attribution Requirement.
+** 
+**     You must preserve all legal notices or author attributions in the covered
+**     work or Appropriate Legal Notices displayed by works containing the covered
+**     work.  You may not remove from the covered work any author or developer
+**     credit already included within the covered work.
+** 
+**   1. No License To Use Trademarks.
+** 
+**     This license does not grant any license or rights to use the trademarks
+**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
+**     of Golden Code Development Corporation. You are not authorized to use the
+**     name Golden Code, FWD, or the names of any author or contributor, for
+**     publicity purposes without written authorization.
+** 
+**   2. No Misrepresentation of Affiliation.
+** 
+**     You may not represent yourself as Golden Code Development Corporation or FWD.
+** 
+**     You may not represent yourself for publicity purposes as associated with
+**     Golden Code Development Corporation, FWD, or any author or contributor to
+**     the covered work, without written authorization.
+** 
+**   3. No Misrepresentation of Source or Origin.
+** 
+**     You may not represent the covered work as solely your work.  All modified
+**     versions of the covered work must be marked in a reasonable way to make it
+**     clear that the modified work is not originating from Golden Code Development
+**     Corporation or FWD.  All modified versions must contain the notices of
+**     attribution required in this license.
+*/
+
+package com.goldencode.p2j.ui;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.databind.*;
+
+
+/**
+ * Implements a string key value user's storage backed by java user preferences file storage.
+ */
+public class KeyValueStorageAdapter
+implements ObjectStorage
+{
+   /** Object to JSON converter*/
+   private final ObjectMapper mapper;
+   
+   /** The backed key and value storage */
+   private KeyValueStorage backend;
+   
+   /**
+    * Creates this adapter.
+    */
+   public KeyValueStorageAdapter()
+   {
+      this.mapper = new ObjectMapper();
+      mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+   }
+   
+   /**
+    * Retrieves the number value of the given key from this storage.
+    * 
+    * @param    key
+    *           The given key
+    * 
+    * @return   The number value of the given key 
+    */
+   @Override
+   public Number getNumber(String key)
+   {
+      return this.backend.getNumber(key);
+   }
+
+   /**
+    * Saves the number value of this key in this storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    value
+    *           The value of the given key
+    */
+   @Override
+   public void setNumber(String key, Number value)
+   {
+      this.backend.setNumber(key, value);
+   }
+
+   /**
+    * Retrieves the boolean value of the given key from this storage.
+    * 
+    * @param    key
+    *           The given key
+    * 
+    * @return   The boolean value of the given key
+    */
+   @Override
+   public Boolean getBoolean(String key)
+   {
+      return this.backend.getBoolean(key);
+   }
+
+   /**
+    * Saves the boolean value of this key in this storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    value
+    *           The value of the given key
+    */
+   @Override
+   public void setBoolean(String key, Boolean value)
+   {
+      this.backend.setBoolean(key, value);
+   }
+
+   /**
+    * Retrieves the string value of the given key from this storage.
+    * 
+    * @param    key
+    *           The given key
+    * 
+    * @return   The string value of the given key
+    */
+   @Override
+   public String getString(String key)
+   {
+      return this.backend.getString(key);
+   }
+
+   @Override
+   public void setString(String key, String value)
+   {
+      this.backend.setString(key, value);
+   }
+
+   /**
+    * Deletes the given key and its value from this storage.
+    * 
+    * @param    key
+    *           The given key
+    */
+   @Override
+   public void delete(String key)
+   {
+      this.backend.delete(key);
+   }
+
+   /**
+    * Sets the backend key and value storage.
+    * 
+    * @param    backend
+    *           The backend key and value storage
+    */
+   public void setBackend(KeyValueStorage backend)
+   {
+      this.backend = backend;
+   }
+   
+   /**
+    * Retrieves the object value of the given key from this storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    clazz
+    *           The java class that represents this object
+    * 
+    * @return   The object value of the given key
+    */
+   @SuppressWarnings("rawtypes")
+   @Override
+   public Object getObject(String key, Class clazz)
+   {
+      return parseJSON(key, clazz);
+   }
+
+   /**
+    * Sets the object value of the given key for this storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    value
+    *           The object value
+    */
+   public void setObject(String key, Object value)
+   {
+      try
+      {
+         String json = mapper.writeValueAsString(value);
+         backend.setString(key, json);
+      }
+      catch (JsonProcessingException e)
+      {
+         e.printStackTrace();
+      }
+
+   }
+
+   /**
+    * The template method to get the object from its json presentation retrieved from the storage
+    * by the given key.
+    * 
+    * @param    key
+    *           The given key
+    * @param    clazz
+    *           The java class that represents this object
+    * 
+    * @return   The object value of the given key
+    */
+   private <T> T parseJSON(String key, Class<T> clazz)
+   {
+      String content = backend.getString(key);
+      
+      if (content != null)
+      {
+         try
+         {
+            return mapper.readValue(content, clazz);
+         }
+         catch (IOException e)
+         {
+            e.printStackTrace();
+         }
+      }
+      
+      return null;
+   }
+
+   /**
+    * Returns the storage size.
+    * 
+    * @return   The storage size
+    */
+   @Override
+   public int getSize()
+   {
+      return backend.getSize();
+   }
+
+   /**
+    * Returns the key by its index number in a sequence of all storage keys.
+    * 
+    * @param    index
+    *           The given index number in a sequence of all storage keys.
+    * 
+    * @return   The key
+    */
+   @Override
+   public String getKey(int index)
+   {
+      return backend.getKey(index);
+   }
+}

=== modified file 'src/com/goldencode/p2j/ui/LogicalTerminal.java'
--- src/com/goldencode/p2j/ui/LogicalTerminal.java	2019-05-22 03:03:31 +0000
+++ src/com/goldencode/p2j/ui/LogicalTerminal.java	2019-09-13 15:47:22 +0000
@@ -1216,6 +1216,9 @@
    /** Helper to use the TM without any context local lookups. */
    private TransactionManager.TransactionHelper tm;
    
+   /** The client local storage proxy */
+   private ObjectStorage clientLocalStorage;
+   
    /**
     * Constructor.
     */
@@ -1251,6 +1254,8 @@
          client = (ClientExports) RemoteObject.obtainLocalInstance(ClientExports.class, true);
       }
       
+      clientLocalStorage = new ClientStorageProxy(client);
+      
       ErrorManager.setSystemAlertBoxes(!client.isChui());
 
       // create the main window BUT DO NOT PUSH IT since a duplicate instance
@@ -7807,6 +7812,16 @@
    }
    
    /**
+    * Gets the client local storage.
+    * 
+    * @return   The client local storage
+    */
+   public static ObjectStorage getClientStorage()
+   {
+      return locate().clientLocalStorage;
+   }
+   
+   /**
     * Read the legacy text metrics for all the texts and each font defined in the font table, for
     * this user.
     *

=== added file 'src/com/goldencode/p2j/ui/ObjectStorage.java'
--- src/com/goldencode/p2j/ui/ObjectStorage.java	1970-01-01 00:00:00 +0000
+++ src/com/goldencode/p2j/ui/ObjectStorage.java	2019-09-13 09:27:00 +0000
@@ -0,0 +1,95 @@
+/*
+** Module   : ObjectStorage.java
+** Abstract : Extends a key and value storage to be used as a client object storage.
+**
+** Copyright (c) 2019, Golden Code Development Corporation.
+**
+** -#- -I- --Date-- ---------------------------------Description---------------------------------
+** 001 SBI 20190911 Created initial version.
+*/
+
+/*
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU Affero General Public License as
+** published by the Free Software Foundation, either version 3 of the
+** License, or (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+** GNU Affero General Public License for more details.
+**
+** You may find a copy of the GNU Affero GPL version 3 at the following
+** location: https://www.gnu.org/licenses/agpl-3.0.en.html
+** 
+** Additional terms under GNU Affero GPL version 3 section 7:
+** 
+**   Under Section 7 of the GNU Affero GPL version 3, the following additional
+**   terms apply to the works covered under the License.  These additional terms
+**   are non-permissive additional terms allowed under Section 7 of the GNU
+**   Affero GPL version 3 and may not be removed by you.
+** 
+**   0. Attribution Requirement.
+** 
+**     You must preserve all legal notices or author attributions in the covered
+**     work or Appropriate Legal Notices displayed by works containing the covered
+**     work.  You may not remove from the covered work any author or developer
+**     credit already included within the covered work.
+** 
+**   1. No License To Use Trademarks.
+** 
+**     This license does not grant any license or rights to use the trademarks
+**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
+**     of Golden Code Development Corporation. You are not authorized to use the
+**     name Golden Code, FWD, or the names of any author or contributor, for
+**     publicity purposes without written authorization.
+** 
+**   2. No Misrepresentation of Affiliation.
+** 
+**     You may not represent yourself as Golden Code Development Corporation or FWD.
+** 
+**     You may not represent yourself for publicity purposes as associated with
+**     Golden Code Development Corporation, FWD, or any author or contributor to
+**     the covered work, without written authorization.
+** 
+**   3. No Misrepresentation of Source or Origin.
+** 
+**     You may not represent the covered work as solely your work.  All modified
+**     versions of the covered work must be marked in a reasonable way to make it
+**     clear that the modified work is not originating from Golden Code Development
+**     Corporation or FWD.  All modified versions must contain the notices of
+**     attribution required in this license.
+*/
+
+package com.goldencode.p2j.ui;
+
+
+/**
+ * Added methods to persist object values of keys. Extends this key value storage interface to be
+ * an object storage interface. 
+ */
+public interface ObjectStorage extends KeyValueStorage
+{
+   /**
+    * Retrieves the object value of the given key from this storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    clazz
+    *           The java class that represents this object
+    * 
+    * @return   The object value of the given key
+    */
+   @SuppressWarnings("rawtypes")
+   Object getObject(String key, Class clazz);
+   
+   /**
+    * Sets the object value of the given key for this storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    value
+    *           The object value
+    */
+   void setObject(String key, Object value);
+}

=== modified file 'src/com/goldencode/p2j/ui/chui/ThinClient.java'
--- src/com/goldencode/p2j/ui/chui/ThinClient.java	2019-08-27 14:29:35 +0000
+++ src/com/goldencode/p2j/ui/chui/ThinClient.java	2019-09-19 19:06:21 +0000
@@ -23540,4 +23540,165 @@
          this.op = op;
       }
    }
+
+   /**
+    * Gets the client storage.
+    * 
+    * @return   The client storage
+    */
+   public ObjectStorage getClientStorage()
+   {
+      return getOutputManager().getDriver().getClientStorage();
+   }
+
+   /**
+    * Retrieves the object value of the given key from the client storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    clazz
+    *           The java class that represents this object
+    * 
+    * @return   The object value of the given key
+    */
+   @Override
+   public Object getObject(String key, Class clazz)
+   {
+      return getClientStorage().getObject(key, clazz);
+   }
+
+   /**
+    * Sets the object value of the given key for the client storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    value
+    *           The object value
+    */
+   @Override
+   public void setObject(String key, Object value)
+   {
+      getClientStorage().setObject(key, value);
+   }
+
+   /**
+    * Retrieves the number value of the given key from the client storage.
+    * 
+    * @param    key
+    *           The given key
+    * 
+    * @return   The number value of the given key 
+    */
+   @Override
+   public Number getNumber(String key)
+   {
+      return getClientStorage().getNumber(key);
+   }
+
+   /**
+    * Saves the number value of this key in the client storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    value
+    *           The value of the given key
+    */
+   @Override
+   public void setNumber(String key, Number value)
+   {
+      getClientStorage().setNumber(key, value);
+   }
+
+   /**
+    * Retrieves the boolean value of the given key from the client storage.
+    * 
+    * @param    key
+    *           The given key
+    * 
+    * @return   The boolean value of the given key
+    */
+   @Override
+   public Boolean getBoolean(String key)
+   {
+      return getClientStorage().getBoolean(key);
+   }
+
+   /**
+    * Saves the boolean value of this key in the client storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    value
+    *           The value of the given key
+    */
+   @Override
+   public void setBoolean(String key, Boolean value)
+   {
+      getClientStorage().setBoolean(key, value);
+   }
+
+   /**
+    * Retrieves the string value of the given key from the client storage.
+    * 
+    * @param    key
+    *           The given key
+    * 
+    * @return   The string value of the given key
+    */
+   @Override
+   public String getString(String key)
+   {
+      return getClientStorage().getString(key);
+   }
+
+   /**
+    * Saves the string value of this key in the client storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    value
+    *           The value of the given key
+    */
+   @Override
+   public void setString(String key, String value)
+   {
+      getClientStorage().setString(key, value);
+   }
+   
+   /**
+    * Deletes the given key and its value from the client storage.
+    * 
+    * @param    key
+    *           The given key
+    */
+   @Override
+   public void delete(String key)
+   {
+      getClientStorage().delete(key);
+   }
+
+   /**
+    * Returns the client storage size.
+    * 
+    * @return   The storage size
+    */
+   @Override
+   public int getSize()
+   {
+      return getClientStorage().getSize();
+   }
+
+   /**
+    * Returns the key by its index number in a sequence of all keys from the client storage.
+    * 
+    * @param    index
+    *           The given index number in a sequence of all storage keys.
+    * 
+    * @return   The key
+    */
+   @Override
+   public String getKey(int index)
+   {
+      return getClientStorage().getKey(index);
+   }
 }

=== added file 'src/com/goldencode/p2j/ui/client/UserKeyValueStorage.java'
--- src/com/goldencode/p2j/ui/client/UserKeyValueStorage.java	1970-01-01 00:00:00 +0000
+++ src/com/goldencode/p2j/ui/client/UserKeyValueStorage.java	2019-09-19 19:24:49 +0000
@@ -0,0 +1,463 @@
+/*
+** Module   : UserKeyValueStorage.java
+** Abstract : Represents an abstract key and value storage that can be used to persist users
+**            settings for storage backends with the capacity limit on the maximal value size.
+**
+** Copyright (c) 2019, Golden Code Development Corporation.
+**
+** -#- -I- --Date-- ---------------------------------Description---------------------------------
+** 001 SBI 20190916 Created initial version.
+*/
+
+/*
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU Affero General Public License as
+** published by the Free Software Foundation, either version 3 of the
+** License, or (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+** GNU Affero General Public License for more details.
+**
+** You may find a copy of the GNU Affero GPL version 3 at the following
+** location: https://www.gnu.org/licenses/agpl-3.0.en.html
+** 
+** Additional terms under GNU Affero GPL version 3 section 7:
+** 
+**   Under Section 7 of the GNU Affero GPL version 3, the following additional
+**   terms apply to the works covered under the License.  These additional terms
+**   are non-permissive additional terms allowed under Section 7 of the GNU
+**   Affero GPL version 3 and may not be removed by you.
+** 
+**   0. Attribution Requirement.
+** 
+**     You must preserve all legal notices or author attributions in the covered
+**     work or Appropriate Legal Notices displayed by works containing the covered
+**     work.  You may not remove from the covered work any author or developer
+**     credit already included within the covered work.
+** 
+**   1. No License To Use Trademarks.
+** 
+**     This license does not grant any license or rights to use the trademarks
+**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
+**     of Golden Code Development Corporation. You are not authorized to use the
+**     name Golden Code, FWD, or the names of any author or contributor, for
+**     publicity purposes without written authorization.
+** 
+**   2. No Misrepresentation of Affiliation.
+** 
+**     You may not represent yourself as Golden Code Development Corporation or FWD.
+** 
+**     You may not represent yourself for publicity purposes as associated with
+**     Golden Code Development Corporation, FWD, or any author or contributor to
+**     the covered work, without written authorization.
+** 
+**   3. No Misrepresentation of Source or Origin.
+** 
+**     You may not represent the covered work as solely your work.  All modified
+**     versions of the covered work must be marked in a reasonable way to make it
+**     clear that the modified work is not originating from Golden Code Development
+**     Corporation or FWD.  All modified versions must contain the notices of
+**     attribution required in this license.
+*/
+
+package com.goldencode.p2j.ui.client;
+
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.*;
+import com.goldencode.p2j.ui.*;
+
+
+/**
+ * Represents an abstract key and value storage that stores its data values into chunks in order
+ * to store large data within the same storage backend with the capacity limit on the maximal
+ * value size. 
+ */
+public abstract class UserKeyValueStorage
+implements KeyValueStorage
+{
+
+   /**
+    * Retrieves the value of the given key from this storage.
+    * 
+    * @param    key
+    *           The given key
+    * 
+    * @return   The number value of the given key 
+    */
+   @Override
+   public Number getNumber(String key)
+   {
+      Chunk head = getFirstChunk(key);
+      
+      if (head != null && head.head != null && head.head)
+      {
+         return head.number;
+      }
+      
+      return null;
+   }
+
+   /**
+    * Saves the number value of this key in this storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    value
+    *           The value of the given key
+    */
+   @Override
+   public void setNumber(String key, Number value)
+   {
+      if (isChunkKey(key))
+      {
+         return;
+      }
+      Chunk head = new Chunk();
+      head.number = value;
+      head.head  = true;
+      
+      saveHeadChunk(key, head);
+      flushChanges();
+   }
+
+   /**
+    * Retrieves the boolean value of the given key from this storage.
+    * 
+    * @param    key
+    *           The given key
+    * 
+    * @return   The boolean value of the given key
+    */
+   @Override
+   public Boolean getBoolean(String key)
+   {
+      Chunk head = getFirstChunk(key);
+      
+      if (head != null && head.head != null && head.head)
+      {
+         return head.bool;
+      }
+      
+      return null;
+   }
+
+   /**
+    * Saves the boolean value of this key in this storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    value
+    *           The value of the given key
+    */
+   @Override
+   public void setBoolean(String key, Boolean value)
+   {
+      if (isChunkKey(key))
+      {
+         return;
+      }
+      Chunk head = new Chunk();
+      head.bool = value;
+      head.head  = true;
+      saveHeadChunk(key, head);
+      flushChanges();
+   }
+
+   /**
+    * Retrieves the string value of the given key from this storage.
+    * 
+    * @param    key
+    *           The given key
+    * 
+    * @return   The string value of the given key
+    */
+   @Override
+   public String getString(String key)
+   {
+      Chunk head = getFirstChunk(key);
+      
+      if (head != null && head.head != null && head.head)
+      {
+         if (head.nextChunkKey != null)
+         {
+            return collectChunks(key, head);
+         }
+         else
+         {
+            return head.value;
+         }
+      }
+      
+      return null;
+   }
+
+   /**
+    * Saves the string value of this key in this storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    value
+    *           The string value of the given key
+    */
+   @Override
+   public void setString(String key, String value)
+   {
+      if (isChunkKey(key))
+      {
+         return;
+      }
+      saveByChunks(key, String.valueOf(value));
+   }
+
+   /**
+    * Collect stored chunks into the string value of the given key.
+    * 
+    * @param    key
+    *           The given key
+    * @param    head
+    *           The first chunk
+    * 
+    * @return   The string value of the given key
+    */
+   public String collectChunks(String key, Chunk head)
+   {
+      int size = head.size;
+      
+      char[] value = new char[size];
+      
+      int destPos = 0;
+      
+      int len = head.value.length();
+      head.value.getChars(0, len, value, destPos);
+      destPos += len;
+      
+      String nextChunkKey = head.nextChunkKey;
+      
+      while(nextChunkKey != null)
+      {
+         Chunk chunk = getNextChunk(head, nextChunkKey);
+
+         if (chunk != null)
+         {
+            len = chunk.value.length();
+            chunk.value.getChars(0, len, value, destPos);
+            destPos += len;
+            
+            nextChunkKey = chunk.nextChunkKey;
+         }
+      }
+      
+      return new String(value);
+   }
+
+   /**
+    * Saves the string value of this key in this storage by splitting it into chunks in order
+    * to fit value capacity limits of this storage backend.
+    * 
+    * @param    key
+    *           The given key
+    * @param    value
+    *           The string value of the given key
+    */
+   public void saveByChunks(String key, String value)
+   {
+      int maximalSize = getMaximalValueSize();
+      int size = value.length();
+      int curr = 0;
+      int end;
+      
+      if (size > maximalSize)
+      {
+         end = maximalSize;
+      }
+      else
+      {
+         end = size;
+      }
+      
+      LinkedList<Chunk> chunks = new LinkedList<>();
+      
+      Chunk head = new Chunk();
+      head.value = value.substring(curr, end);
+      head.size  = size;
+      head.head  = true;
+      
+      if (end < size)
+      {
+         setUniqueHeadName(head);
+      }
+      
+      chunks.add(head);
+      
+      int chunkNumber = 0;
+      
+      Chunk node = head;
+      
+      while(end < size)
+      {
+         node.nextChunkKey = getNextChunkKey(head, chunkNumber++);
+         
+         curr = end;
+         
+         int restSize = size - curr;
+         
+         if (restSize > maximalSize)
+         {
+            end = curr + maximalSize;
+         }
+         else
+         {
+            end = size;
+         }
+         
+         node = new Chunk();
+         node.value = value.substring(curr, end);
+         chunks.add(node);
+      }
+      
+      saveHeadChunk(key, head);
+      
+      node = head;
+      
+      for (int i = 1; i < chunks.size(); i++)
+      {
+         String nextChunkKey = node.nextChunkKey;
+         
+         node = chunks.get(i);
+         
+         saveNextChunk(head, nextChunkKey, node);
+      }
+      
+      flushChanges();
+   }
+
+   /**
+    * Sets the unique name to the given head chunk.
+    * 
+    * @param    head
+    *           The first chunk
+    */
+   public void setUniqueHeadName(Chunk head)
+   {
+      head.name = UUID.randomUUID().toString();
+   }
+
+   /**
+    * Returns the next chunk key.
+    * 
+    * @param    head
+    *           The first chunk of the stored data value
+    * @param    chunkNumber
+    *           The current chunk number
+    * 
+    * @return   The next chunk key;
+    */
+   public String getNextChunkKey(Chunk head, int chunkNumber)
+   {
+      StringBuilder chunkKey = new StringBuilder(64);
+      chunkKey.append(head.name).append('-').append(chunkNumber + 1);
+      
+      return chunkKey.toString();
+   }
+
+   /**
+    * Tests if the given key is used by a chunk that follows the head chunk.
+    * 
+    * @param    key
+    *           The given key to test
+    * 
+    * @return   Returns true if this key is used by a chunk.
+    */
+   public abstract boolean isChunkKey(String key);
+   
+   /**
+    * Stores the head chunk with the given key within this storage backend.
+    * 
+    * @param    key
+    *           The given key
+    * @param    head
+    *           The head chunk
+    */
+   public abstract void saveHeadChunk(String key, Chunk head);
+
+   /**
+    * Stores the given chunk with the given key within this storage backend.
+    * 
+    * @param    head
+    *           The head chunk
+    * @param    nextChunkKey
+    *           The given chunk key
+    * @param    node
+    *           The chunk
+    */
+   public abstract void saveNextChunk(Chunk head, String nextChunkKey, Chunk node);
+
+   /**
+    * Flushes the current changes into this storage backend.
+    */
+   public abstract void flushChanges();
+
+   /**
+    * Returns the maximal value size that can be stored into this storage backend.
+    * 
+    * @return   The maximal value size
+    */
+   public abstract int getMaximalValueSize();
+
+   /**
+    * Retrieves the first chunk node.
+    * 
+    * @param    key
+    *           The given key
+    * 
+    * @return   The first chunk node
+    */
+   public abstract Chunk getFirstChunk(String key);
+
+   /**
+    * Retrieves the next chunk of stored data.
+    * 
+    * @param    head
+    *           The first chunk of the stored data value
+    * @param    nextChunkKey
+    *           The next chunk key
+    * 
+    * @return   The next chunk
+    */
+   public abstract Chunk getNextChunk(Chunk head, String nextChunkKey);
+
+   /**
+    * Represents a chunk of a string data.
+    */
+   @JsonInclude(JsonInclude.Include.NON_NULL)
+   public static class Chunk
+   {
+      /** The chunk of the given value that can be safely stored by the backend.*/
+      public String value;
+      
+      /** The numeric value of the key */
+      public Number number;
+      
+      /** The boolean value of the key */
+      public Boolean bool;
+      
+      /** The value string length */
+      public Integer size;
+      
+      /** The unique name */
+      public String name;
+      
+      /** The true value indicates that key is prefixed with / character */
+      public Boolean hasRootPrefix;
+      
+      /** Indicates the first chunk */
+      public Boolean head;
+      
+      /** The key of the next chunk stored by the backend. */
+      public String nextChunkKey;
+   }
+
+}

=== added file 'src/com/goldencode/p2j/ui/client/UserPreferences.java'
--- src/com/goldencode/p2j/ui/client/UserPreferences.java	1970-01-01 00:00:00 +0000
+++ src/com/goldencode/p2j/ui/client/UserPreferences.java	2019-09-19 19:30:00 +0000
@@ -0,0 +1,435 @@
+/*
+** Module   : UserPreferences.java
+** Abstract : Implements the key and value storage that can be used to persist users settings.
+**
+** Copyright (c) 2019, Golden Code Development Corporation.
+**
+** -#- -I- --Date-- ---------------------------------Description---------------------------------
+** 001 SBI 20190911 Created initial version.
+*/
+
+/*
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU Affero General Public License as
+** published by the Free Software Foundation, either version 3 of the
+** License, or (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+** GNU Affero General Public License for more details.
+**
+** You may find a copy of the GNU Affero GPL version 3 at the following
+** location: https://www.gnu.org/licenses/agpl-3.0.en.html
+** 
+** Additional terms under GNU Affero GPL version 3 section 7:
+** 
+**   Under Section 7 of the GNU Affero GPL version 3, the following additional
+**   terms apply to the works covered under the License.  These additional terms
+**   are non-permissive additional terms allowed under Section 7 of the GNU
+**   Affero GPL version 3 and may not be removed by you.
+** 
+**   0. Attribution Requirement.
+** 
+**     You must preserve all legal notices or author attributions in the covered
+**     work or Appropriate Legal Notices displayed by works containing the covered
+**     work.  You may not remove from the covered work any author or developer
+**     credit already included within the covered work.
+** 
+**   1. No License To Use Trademarks.
+** 
+**     This license does not grant any license or rights to use the trademarks
+**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
+**     of Golden Code Development Corporation. You are not authorized to use the
+**     name Golden Code, FWD, or the names of any author or contributor, for
+**     publicity purposes without written authorization.
+** 
+**   2. No Misrepresentation of Affiliation.
+** 
+**     You may not represent yourself as Golden Code Development Corporation or FWD.
+** 
+**     You may not represent yourself for publicity purposes as associated with
+**     Golden Code Development Corporation, FWD, or any author or contributor to
+**     the covered work, without written authorization.
+** 
+**   3. No Misrepresentation of Source or Origin.
+** 
+**     You may not represent the covered work as solely your work.  All modified
+**     versions of the covered work must be marked in a reasonable way to make it
+**     clear that the modified work is not originating from Golden Code Development
+**     Corporation or FWD.  All modified versions must contain the notices of
+**     attribution required in this license.
+*/
+
+package com.goldencode.p2j.ui.client;
+
+import java.io.*;
+import java.util.*;
+import java.util.prefs.*;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.databind.*;
+
+/**
+ * Implements the user's key and value storage based on java user preferences API.
+ */
+public class UserPreferences
+extends UserKeyValueStorage
+{
+   /** The head key */
+   private static final String HEAD_KEY = "head_key";
+
+   /** The maximal string length for values */
+   private static final int MAXIMAL_STRING_LENGTH = Preferences.MAX_VALUE_LENGTH - 
+                                                    Preferences.MAX_KEY_LENGTH * 4;
+
+   /** The back end key and value storage */
+   private final Preferences backend = Preferences.userRoot();
+   
+   /** Maps an object into its json presentation and back */
+   private final ObjectMapper objectMapper = new ObjectMapper();
+
+   /** The collected keys */
+   private final List<String> collectedKeys = new LinkedList<>();
+
+   /** The Preferences node for the head chunk */
+   private Preferences headPreferences;
+
+   /**
+    * Deletes the given key and its value from this storage.
+    * 
+    * @param    key
+    *           The given key
+    */
+   @Override
+   public void delete(String key)
+   {
+      Chunk head = getFirstChunk(key);
+      
+      if (head != null)
+      {
+         Preferences node = headPreferences;
+         Preferences parent = node.parent();
+         
+         while (node != backend && parent != null)
+         {
+            try
+            {
+               node.removeNode();
+               node = parent;
+               parent = node.parent();
+            }
+            catch (BackingStoreException e)
+            {
+               e.printStackTrace();
+            }
+         }
+      }
+   }
+
+   /**
+    * Stores the head chunk with the given key within this storage backend.
+    * 
+    * @param    key
+    *           The given key
+    * @param    head
+    *           The head chunk
+    */
+   public void saveHeadChunk(String key, Chunk head)
+   {
+      try
+      {
+         headPreferences = getPreferencesFor(key);
+         
+         head.hasRootPrefix = key.startsWith("/");
+         
+         headPreferences.put(HEAD_KEY, objectMapper.writeValueAsString(head));
+      }
+      catch (JsonProcessingException e)
+      {
+         e.printStackTrace();
+      }
+   }
+
+   /**
+    * Stores the given chunk with the given key within this storage backend.
+    * 
+    * @param    head
+    *           The head chunk
+    * @param    nextChunkKey
+    *           The given chunk key
+    * @param    node
+    *           The chunk
+    */
+   public void saveNextChunk(Chunk head, String nextChunkKey, Chunk node)
+   {
+      try
+      {
+         headPreferences.put(nextChunkKey, objectMapper.writeValueAsString(node));
+      }
+      catch (JsonProcessingException e)
+      {
+         e.printStackTrace();
+      }
+   }
+
+   /**
+    * Flushes the current changes into this storage backend.
+    */
+   public void flushChanges()
+   {
+      try
+      {
+         backend.flush();
+      }
+      catch (BackingStoreException e)
+      {
+         e.printStackTrace();
+      }
+   }
+
+   /**
+    * Returns the maximal value size that can be stored into this storage backend.
+    * 
+    * @return   The maximal value size
+    */
+   public int getMaximalValueSize()
+   {
+      return MAXIMAL_STRING_LENGTH;
+   }
+
+   /**
+    * Retrieves the first chunk node.
+    * 
+    * @param    key
+    *           The given key
+    * 
+    * @return   The first chunk node
+    */
+   public Chunk getFirstChunk(String key)
+   {
+      Preferences node = getPreferencesFor(key);
+      
+      /* cache this node */
+      headPreferences = node;
+      
+      String stringValue = node.get(HEAD_KEY, null);
+      
+      if (stringValue == null || "null".equals(stringValue))
+      {
+         return null;
+      }
+      
+      try
+      {
+         return objectMapper.readValue(stringValue, Chunk.class);
+      }
+      catch (IOException e)
+      {
+         return null;
+      }
+   }
+
+   /**
+    * Retrieves the next chunk of stored data.
+    * 
+    * @param    head
+    *           The first chunk of the stored data value
+    * @param    nextChunkKey
+    *           The next chunk key
+    * 
+    * @return   The next chunk
+    */
+   public Chunk getNextChunk(Chunk head, String nextChunkKey)
+   {
+      String stringValue = headPreferences.get(nextChunkKey, null);
+      
+      if (stringValue == null || "null".equals(stringValue))
+      {
+         return null;
+      }
+      
+      try
+      {
+         return objectMapper.readValue(stringValue, Chunk.class);
+      }
+      catch (IOException e)
+      {
+         return null;
+      }
+   }
+
+   /**
+    * Returns the node assigned for the given key.
+    * 
+    * @param    key
+    *           The given key
+    * 
+    * @return   The assigned node
+    */
+   private Preferences getPreferencesFor(String key)
+   {
+      if (key == null)
+      {
+         return null;
+      }
+      
+      String[] names = key.split("/");
+      
+      Preferences node = backend;
+      
+      for (String name : names)
+      {
+         int nameLength = name.length();
+         
+         int curr = 0;
+         int end  = 0;
+         
+         while (curr < nameLength)
+         {
+            if (curr + Preferences.MAX_KEY_LENGTH < nameLength)
+            {
+               end = curr + Preferences.MAX_KEY_LENGTH;
+            }
+            else
+            {
+               end = nameLength;
+            }
+            String subname = name.substring(curr, end);
+            node = node.node(subname);
+            curr = end;
+         }
+      }
+      
+      return node;
+   }
+
+   /**
+    * Returns the storage size.
+    * 
+    * @return   The storage size
+    */
+   @Override
+   public int getSize()
+   {
+      KeysCollector collector = new KeysCollector();
+      collector.visit(backend);
+      
+      return collectedKeys.size();
+   }
+
+   /**
+    * Returns the key by its index number in a sequence of all storage keys.
+    * 
+    * @param    index
+    *           The given index number in a sequence of all storage keys.
+    * 
+    * @return   The key
+    */
+   @Override
+   public String getKey(int index)
+   {
+      if (index >= collectedKeys.size() || index < 0)
+      {
+         throw new IllegalArgumentException();
+      }
+      
+      String key = collectedKeys.get(index);
+      
+      Chunk head = getFirstChunk(key);
+      
+      if (head != null && head.hasRootPrefix != null && head.hasRootPrefix)
+      {
+         return "/" + key;
+      }
+      
+      return key;
+   }
+   
+   /**
+    * Collects stored keys.
+    */
+   private class KeysCollector
+   {
+      /**
+       * Creates this object and clears collected keys.
+       */
+      public KeysCollector()
+      {
+         collectedKeys.clear();
+      }
+      
+      /**
+       * Collects branches of the given node.
+       * 
+       * @param    node
+       *           The given node
+       */
+      public void visit(Preferences node)
+      {
+         try
+         {
+            String[] names = node.childrenNames();
+            
+            if (names.length == 0)
+            {
+               collectedKeys.add(buildPath(node));
+            }
+            else
+            {
+               for (String name : names)
+               {
+                  visit(node.node(name));
+               }
+            }
+         }
+         catch (BackingStoreException e)
+         {
+            e.printStackTrace();
+         }
+      }
+      
+      /**
+       * Builds the relative path from backend to the given leaf node.
+       * 
+       * @param    node
+       *           The given leaf node
+       * 
+       * @return   The path relative to backend.
+       */
+      private String buildPath(Preferences node)
+      {
+         LinkedList<String> path = new LinkedList<>();
+         
+         path.add(node.name());
+         
+         Preferences parent = node.parent();
+         
+         while(parent != null && parent != backend)
+         {
+            path.add(parent.name());
+            parent = parent.parent();
+         }
+         
+         StringBuilder key = new StringBuilder();
+         
+         while(!path.isEmpty())
+         {
+            key.append(path.pop());
+         }
+         
+         return key.toString();
+      }
+   }
+
+   /**
+    * Any string can be used as a key.
+    * 
+    * @return   false
+    */
+   @Override
+   public boolean isChunkKey(String key)
+   {
+      return false;
+   }
+}

=== modified file 'src/com/goldencode/p2j/ui/client/driver/AbstractDriver.java'
--- src/com/goldencode/p2j/ui/client/driver/AbstractDriver.java	2017-04-01 23:33:34 +0000
+++ src/com/goldencode/p2j/ui/client/driver/AbstractDriver.java	2019-09-13 09:27:00 +0000
@@ -62,7 +62,9 @@
 
 package com.goldencode.p2j.ui.client.driver;
 
+
 import com.goldencode.p2j.main.*;
+import com.goldencode.p2j.ui.*;
 import com.goldencode.p2j.ui.client.*;
 
 /**
@@ -92,6 +94,9 @@
    /** Terminal type name. */
    private String term;
    
+   /** The driver local storage that is persistent over sessions */
+   private KeyValueStorageAdapter storage;
+   
    /**
     * Construct a new instance.
     *
@@ -110,6 +115,9 @@
       this.direct  = direct;
       this.isChui  = isChui;
       this.term    = term;
+      storage = new KeyValueStorageAdapter();
+      // set the default key value storage
+      storage.setBackend(new UserPreferences());
    }
 
    /**
@@ -222,6 +230,17 @@
    }
 
    /**
+    * Returns the client key and value storage.
+    * 
+    * @return   The client key and value storage
+    */
+   @Override
+   public KeyValueStorageAdapter getClientStorage()
+   {
+      return storage;
+   }
+
+   /**
     * Assigns the flag determining if the current driver is running in batch mode.
     * 
     * @param    batch

=== modified file 'src/com/goldencode/p2j/ui/client/driver/ScreenDriver.java'
--- src/com/goldencode/p2j/ui/client/driver/ScreenDriver.java	2019-03-10 09:48:21 +0000
+++ src/com/goldencode/p2j/ui/client/driver/ScreenDriver.java	2019-09-13 09:27:00 +0000
@@ -303,6 +303,13 @@
    public ChildProcessFactory getChildProcessFactory();
    
    /**
+    * Returns the client key and value storage.
+    * 
+    * @return   The client key and value storage
+    */
+   public ObjectStorage getClientStorage();
+   
+   /**
     * Driver initialization.
     */
    public void init();

=== modified file 'src/com/goldencode/p2j/ui/client/driver/web/WebClientMessageTypes.java'
--- src/com/goldencode/p2j/ui/client/driver/web/WebClientMessageTypes.java	2019-04-08 20:55:02 +0000
+++ src/com/goldencode/p2j/ui/client/driver/web/WebClientMessageTypes.java	2019-09-19 17:39:57 +0000
@@ -411,6 +411,21 @@
    /** Sets the background image for the virtual desktop. */
    public static final byte MSG_SET_DESKTOP_IMAGE = (byte) 0xF5;
 
+   /** Sets the value of the key for the client key and value storage. */
+   public static final byte MSG_SET_KEY_VALUE = (byte) 0xF6;
+
+   /** Gets the value of the key from the client key and value storage. */
+   public static final byte MSG_GET_KEY_VALUE = (byte) 0xF7;
+
+   /** Deletes the given key and its value from the client key and value storage. */
+   public static final byte MSG_DELETE_KEY_VALUE = (byte) 0xF8;
+
+   /** Gets the size of the client key and value storage. */
+   public static final byte MSG_GET_STORAGE_SIZE = (byte) 0xF9;
+
+   /** Gets the n-th key from the client key and value storage. */
+   public static final byte MSG_GET_KEY = (byte) 0xFA;
+
    /** Wraps the large message from the client to the sequence of this partial type messages. */
    public static final byte MSG_PARTIAL = (byte) 0xFE;
    

=== modified file 'src/com/goldencode/p2j/ui/client/driver/web/res/p2j.socket.js'
--- src/com/goldencode/p2j/ui/client/driver/web/res/p2j.socket.js	2019-04-03 03:41:01 +0000
+++ src/com/goldencode/p2j/ui/client/driver/web/res/p2j.socket.js	2019-09-19 18:24:58 +0000
@@ -437,13 +437,13 @@
 
                   /** Destroy a driver-maintained widget. */
                   MSG_DESTROY_WIDGET : 0xB4,
-                
+                  
                   /** Place a driver-maintained widget. */
                   MSG_PLACE_WIDGET : 0xB5,
-               
+                  
                   /** Change the visibility for a driver-maintained widget. */
                   MSG_SET_WIDGET_VISIBILITY : 0xB6,
-               
+                  
                   /** Change the sensitivity for a driver-maintained widget. */
                   MSG_SET_WIDGET_SENSITIVITY : 0xB7,
    
@@ -462,6 +462,21 @@
                   /** Sets the background image for the virtual desktop. */
                   MSG_SET_DESKTOP_IMAGE : 0xF5,
                   
+                  /** Sets the value of the key for the client key and value storage. */
+                  MSG_SET_KEY_VALUE : 0xF6,
+
+                  /** Gets the value of the key from the client key and value storage. */
+                  MSG_GET_KEY_VALUE : 0xF7,
+
+                  /** Deletes the given key and its value from the client key and value storage.*/
+                  MSG_DELETE_KEY_VALUE : 0xF8,
+
+                  /** Gets the size of the client key and value storage. */
+                  MSG_GET_STORAGE_SIZE : 0xF9;
+
+                  /** Gets the n-th key from the client key and value storage. */
+                  MSG_GET_KEY : 0xFA;
+
                   /** Wraps the large message from the client to the sequence of this partial type messages. */
                   MSG_PARTIAL : 0xFE,
                   
@@ -2905,6 +2920,124 @@
             
             send(msg);
             break;
+         case types.MSG_GET_KEY_VALUE:
+            var offset = 1;
+
+            var msgId = me.readInt32BinaryMessage(message, offset);
+            offset = offset + 4;
+            
+            var keyLength = me.readInt32BinaryMessage(message, offset);
+            offset = offset + 4;
+
+            var key  = me.readStringBinaryMessageByLength(message, offset, keyLength);
+            offset = offset + keyLength * 2;
+            
+            var value = p2j.restoreObject(key, true);
+            var value = String(value);
+            var valueLength = value.length;
+            
+            // create the response message
+            var msg = new Uint8Array(1 + 4 + 4 + valueLength * 2);
+            
+            offset = 0;
+            msg[offset] = types.MSG_GET_KEY_VALUE;
+            offset += 1;
+
+            writeInt32BinaryMessage(msg, offset, msgId);
+            offset += 4;
+            
+            writeInt32BinaryMessage(msg, offset, valueLength);
+            offset += 4;
+            
+            writeStringBinaryMessage(msg, offset, value);
+            
+            send(msg);
+            break;
+         case types.MSG_SET_KEY_VALUE:
+            var offset = 1;
+
+            var msgId = me.readInt32BinaryMessage(message, offset);
+            offset = offset + 4;
+            
+            var keyLength = me.readInt32BinaryMessage(message, offset);
+            offset = offset + 4;
+
+            var key  = me.readStringBinaryMessageByLength(message, offset, keyLength);
+            offset = offset + keyLength * 2;
+            
+            var valueLength = me.readInt32BinaryMessage(message, offset);
+            offset = offset + 4;
+
+            var value  = me.readStringBinaryMessageByLength(message, offset, valueLength);
+            
+            p2j.saveObject(key, value, true);
+            break;
+         case types.MSG_DELETE_KEY_VALUE:
+            var offset = 1;
+
+            var msgId = me.readInt32BinaryMessage(message, offset);
+            offset = offset + 4;
+            
+            var keyLength = me.readInt32BinaryMessage(message, offset);
+            offset = offset + 4;
+
+            var key  = me.readStringBinaryMessageByLength(message, offset, keyLength);
+            
+            p2j.deleteObject(key, true);
+            break;
+         case types.MSG_GET_KEY:
+            var offset = 1;
+
+            var msgId = me.readInt32BinaryMessage(message, offset);
+            offset = offset + 4;
+            
+            var keyIndex = me.readInt32BinaryMessage(message, offset);
+            offset = offset + 4;
+            
+            var key = localStorage.key(keyIndex);
+            key = String(key);
+            var keyLength = key.length;
+            
+            // create the response message
+            var msg = new Uint8Array(1 + 4 + 4 + keyLength * 2);
+            
+            offset = 0;
+            msg[offset] = types.MSG_GET_KEY;
+            offset += 1;
+
+            writeInt32BinaryMessage(msg, offset, msgId);
+            offset += 4;
+            
+            writeInt32BinaryMessage(msg, offset, keyLength);
+            offset += 4;
+            
+            writeStringBinaryMessage(msg, offset, key);
+            
+            send(msg);
+            break;
+         case types.MSG_GET_STORAGE_SIZE:
+            var offset = 1;
+
+            var msgId = me.readInt32BinaryMessage(message, offset);
+            offset = offset + 4;
+            
+            var size = localStorage.length;
+            size = Integer(size);
+            
+            // create the response message
+            var msg = new Uint8Array(1 + 4 + 4);
+            
+            offset = 0;
+            msg[offset] = types.MSG_GET_STORAGE_SIZE;
+            offset += 1;
+
+            writeInt32BinaryMessage(msg, offset, msgId);
+            offset += 4;
+            
+            writeInt32BinaryMessage(msg, offset, size);
+            
+            send(msg);
+            break;
          case types.MSG_GET_TEXT_WIDTHS:
             // text widths
 

=== modified file 'src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebDriver.java'
--- src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebDriver.java	2019-05-10 18:14:14 +0000
+++ src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebDriver.java	2019-09-13 09:27:00 +0000
@@ -455,6 +455,7 @@
                                          new GenericWebSocketCreator<WebClientProtocol>(websock),
                                          cpres,
                                          hdlrs);
+      getClientStorage().setBackend(new WebClientKeyValueStorage(websock));
    }
 
    /**

=== modified file 'src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebSocket.java'
--- src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebSocket.java	2019-04-03 03:41:01 +0000
+++ src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebSocket.java	2019-09-19 18:07:39 +0000
@@ -3156,6 +3156,35 @@
          
          handled = true;
       }
+      else if (message[offset] == MSG_GET_KEY_VALUE || message[offset] == MSG_GET_KEY)
+      {
+         offset = offset + 1;
+
+         int msgId = readMessageInt32(message, offset);
+         offset = offset + 4;
+         
+         int valueLen = readMessageInt32(message, offset);
+         offset = offset + 4;
+         
+         String value = readMessageText(message, offset, offset + 2 * valueLen);
+         
+         receiveResult(msgId, value);
+         
+         handled = true;
+      }
+      else if (message[offset] == MSG_GET_STORAGE_SIZE)
+      {
+         offset = offset + 1;
+
+         int msgId = readMessageInt32(message, offset);
+         offset = offset + 4;
+         
+         int size = readMessageInt32(message, offset);
+         
+         receiveResult(msgId, Integer.valueOf(size));
+         
+         handled = true;
+      }
       
       return handled;
    }
@@ -3545,6 +3574,189 @@
    }
 
    /**
+    * Gets the string value of the given key from the browser local storage.
+    * 
+    * @param    key
+    *           The given key
+    * 
+    * @return   
+    */
+   public String getStringValue(String key)
+   {
+      int len = 1;   // type
+      len = len + 4; // msg ID
+      len = len + 4; // key.length()
+      int keyLen = key.length();
+      len = len + keyLen * 2; // key
+      
+      int offset = 0;
+      byte[] message = new byte[len];
+      int msgId = nextMessageId();
+
+      message[offset++] = MSG_GET_KEY_VALUE;
+
+      writeMessageInt32(message, offset, msgId);
+      offset += 4;
+
+      writeMessageInt32(message, offset, keyLen);
+      offset += 4;
+
+      writeMessageText(message, offset, key);
+
+      sendBinaryMessage(message);
+      
+      Object res = waitForResult(msgId);
+      
+      if (res instanceof String)
+      {
+         return (String) res;
+      }
+      
+      return null;
+   }
+
+   /**
+    * Sets the value of the given key into the browser local storage.
+    * 
+    * @param    key
+    *           The given key
+    * @param    value
+    *           The key value to be stored into the browser local storage.
+    */
+   public void setStringValue(String key, String value)
+   {
+      int len = 1;                // type
+      len = len + 4;              // msg ID
+      len = len + 4;              // key.length()
+      int keyLen = key.length();
+      len = len + keyLen * 2;     // key
+      len = len + 4;              // value.length()
+      int valueLen = value.length();
+      len = len + valueLen * 2;   // value
+      
+      int offset = 0;
+      byte[] message = new byte[len];
+      int msgId = nextMessageId();
+
+      message[offset++] = MSG_SET_KEY_VALUE;
+
+      writeMessageInt32(message, offset, msgId);
+      offset += 4;
+
+      writeMessageInt32(message, offset, keyLen);
+      offset += 4;
+
+      writeMessageText(message, offset, key);
+      offset += keyLen * 2;
+      
+      writeMessageInt32(message, offset, valueLen);
+      offset += 4;
+
+      writeMessageText(message, offset, value);
+
+      sendBinaryMessage(message);
+   }
+
+   /**
+    * Deletes the given key and its value from the browser local storage.
+    * 
+    * @param    key
+    *           The given key
+    */
+   public void deleteKeyAndValue(String key)
+   {
+      int len = 1;                // type
+      len = len + 4;              // msg ID
+      len = len + 4;              // key.length()
+      int keyLen = key.length();
+      len = len + keyLen * 2;     // key
+      
+      int offset = 0;
+      byte[] message = new byte[len];
+      int msgId = nextMessageId();
+
+      message[offset++] = MSG_DELETE_KEY_VALUE;
+
+      writeMessageInt32(message, offset, msgId);
+      offset += 4;
+
+      writeMessageInt32(message, offset, keyLen);
+      offset += 4;
+
+      writeMessageText(message, offset, key);
+      offset += keyLen * 2;
+
+      sendBinaryMessage(message);
+   }
+
+   /**
+    * Gets the size of the browser local storage.
+    * 
+    * @return   The storage size
+    */
+   public int getLocalStorageSize()
+   {
+      int len = 1;   // type
+      len = len + 4; // msg ID
+      
+      int offset = 0;
+      byte[] message = new byte[len];
+      int msgId = nextMessageId();
+
+      message[offset++] = MSG_GET_STORAGE_SIZE;
+
+      writeMessageInt32(message, offset, msgId);
+
+      sendBinaryMessage(message);
+      
+      Object res = waitForResult(msgId);
+      
+      if (res instanceof Integer)
+      {
+         return (Integer) res;
+      }
+      
+      return 0;
+   }
+
+   /**
+    * Gets the n-th key from the browser local storage.
+    * 
+    * @param    index
+    *           The key index
+    * 
+    * @return   The n-th key from the browser local storage
+    */
+   public String getLocalStorageKey(int index)
+   {
+      int len = 1;   // type
+      len = len + 4; // msg ID
+      len = len + 4; // index
+      
+      int offset = 0;
+      byte[] message = new byte[len];
+      int msgId = nextMessageId();
+
+      message[offset++] = MSG_GET_KEY;
+
+      writeMessageInt32(message, offset, msgId);
+      offset += 4;
+
+      writeMessageInt32(message, offset, index);
+
+      sendBinaryMessage(message);
+      
+      Object res = waitForResult(msgId);
+      
+      if (res instanceof String)
+      {
+         return (String) res;
+      }
+      
+      return null;
+   }
+
+   /**
     * Get the widths of the specified texts, using the given fonts.
     *
     * @param    fonts

=== added file 'src/com/goldencode/p2j/ui/client/gui/driver/web/WebClientKeyValueStorage.java'
--- src/com/goldencode/p2j/ui/client/gui/driver/web/WebClientKeyValueStorage.java	1970-01-01 00:00:00 +0000
+++ src/com/goldencode/p2j/ui/client/gui/driver/web/WebClientKeyValueStorage.java	2019-09-20 16:17:09 +0000
@@ -0,0 +1,318 @@
+/*
+** Module   : WebClientKeyValueStorage.java
+** Abstract : Implements the web client key and value storage.
+**
+** Copyright (c) 2019, Golden Code Development Corporation.
+**
+** -#- -I- --Date-- ---------------------------------Description---------------------------------
+** 001 SBI 20190911 Created initial version.
+*/
+
+/*
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU Affero General Public License as
+** published by the Free Software Foundation, either version 3 of the
+** License, or (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+** GNU Affero General Public License for more details.
+**
+** You may find a copy of the GNU Affero GPL version 3 at the following
+** location: https://www.gnu.org/licenses/agpl-3.0.en.html
+** 
+** Additional terms under GNU Affero GPL version 3 section 7:
+** 
+**   Under Section 7 of the GNU Affero GPL version 3, the following additional
+**   terms apply to the works covered under the License.  These additional terms
+**   are non-permissive additional terms allowed under Section 7 of the GNU
+**   Affero GPL version 3 and may not be removed by you.
+** 
+**   0. Attribution Requirement.
+** 
+**     You must preserve all legal notices or author attributions in the covered
+**     work or Appropriate Legal Notices displayed by works containing the covered
+**     work.  You may not remove from the covered work any author or developer
+**     credit already included within the covered work.
+** 
+**   1. No License To Use Trademarks.
+** 
+**     This license does not grant any license or rights to use the trademarks
+**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
+**     of Golden Code Development Corporation. You are not authorized to use the
+**     name Golden Code, FWD, or the names of any author or contributor, for
+**     publicity purposes without written authorization.
+** 
+**   2. No Misrepresentation of Affiliation.
+** 
+**     You may not represent yourself as Golden Code Development Corporation or FWD.
+** 
+**     You may not represent yourself for publicity purposes as associated with
+**     Golden Code Development Corporation, FWD, or any author or contributor to
+**     the covered work, without written authorization.
+** 
+**   3. No Misrepresentation of Source or Origin.
+** 
+**     You may not represent the covered work as solely your work.  All modified
+**     versions of the covered work must be marked in a reasonable way to make it
+**     clear that the modified work is not originating from Golden Code Development
+**     Corporation or FWD.  All modified versions must contain the notices of
+**     attribution required in this license.
+*/
+
+package com.goldencode.p2j.ui.client.gui.driver.web;
+
+import java.io.*;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.databind.*;
+import com.goldencode.p2j.ui.client.*;
+
+
+/**
+ * Represents the web client key and value storage.
+ */
+public class WebClientKeyValueStorage
+extends UserKeyValueStorage
+{
+   /** The maximal string length for values */
+   private static final int MAXIMAL_STRING_LENGTH = 4 * 1024 * 1024;
+
+   /** The web socket object that is responsible for the communication with the js client */
+   private final GuiWebSocket webSocket;
+   
+   /** Maps an object into its json presentation and back */
+   private final ObjectMapper objectMapper = new ObjectMapper();
+
+   /** The last retrieved chunk */
+   private Chunk lastRetrievedChunk;
+
+   /**
+    * Creates the web client storage.
+    * 
+    * @param    webSocket
+    *           The web socket object
+    */
+   public WebClientKeyValueStorage(GuiWebSocket webSocket)
+   {
+      this.webSocket = webSocket;
+   }
+
+   /**
+    * Deletes the given key and its value from this storage.
+    * 
+    * @param    key
+    *           The given key
+    */
+   @Override
+   public void delete(String key)
+   {
+      Chunk head = getFirstChunk(key);
+      
+      deleteChunks(head);
+      
+      if (head != null && head.head != null && head.head)
+      {
+         webSocket.deleteKeyAndValue(key);
+      }
+   }
+
+   /**
+    * Stores the head chunk with the given key within this storage backend.
+    * 
+    * @param    key
+    *           The given key
+    * @param    head
+    *           The head chunk
+    */
+   @Override
+   public void saveHeadChunk(String key, Chunk head)
+   {
+      try
+      {
+         // saveHeadChunk(..) follows getChunk(..)
+         if (lastRetrievedChunk != null)
+         {
+            // the key is already stored, it needs to delete all its chunks that follow the head 
+            deleteChunks(lastRetrievedChunk);
+         }
+         webSocket.setStringValue(key, objectMapper.writeValueAsString(head));
+      }
+      catch (JsonProcessingException e)
+      {
+         e.printStackTrace();
+      }
+   }
+
+   /**
+    * Stores the given chunk with the given key within this storage backend.
+    * 
+    * @param    head
+    *           The head chunk
+    * @param    nextChunkKey
+    *           The given chunk key
+    * @param    node
+    *           The chunk
+    */
+   @Override
+   public void saveNextChunk(Chunk head, String nextChunkKey, Chunk node)
+   {
+      saveHeadChunk(nextChunkKey, node);
+   }
+
+   /**
+    * Flushes the current changes into this storage backend.
+    */
+   @Override
+   public void flushChanges()
+   {
+   }
+
+   /**
+    * Returns the maximal value size that can be stored into this storage backend.
+    * 
+    * @return   The maximal value size
+    */
+   @Override
+   public int getMaximalValueSize()
+   {
+      return MAXIMAL_STRING_LENGTH;
+   }
+
+   /**
+    * Retrieves the first chunk node.
+    * 
+    * @param    key
+    *           The given key
+    * 
+    * @return   The first chunk node
+    */
+   @Override
+   public Chunk getFirstChunk(String key)
+   {
+      return getChunk(key, true);
+   }
+
+   /**
+    * Retrieves the next chunk of stored data.
+    * 
+    * @param    head
+    *           The first chunk of the stored data value
+    * @param    nextChunkKey
+    *           The next chunk key
+    * 
+    * @return   The next chunk
+    */
+   @Override
+   public Chunk getNextChunk(Chunk head, String nextChunkKey)
+   {
+      return getChunk(nextChunkKey, false);
+   }
+
+   /**
+    * Gets the storage size.
+    * 
+    * @return   The storage size
+    */
+   @Override
+   public int getSize()
+   {
+      return webSocket.getLocalStorageSize();
+   }
+
+   /**
+    * Returns the key by its index number in a sequence of all storage keys.
+    * 
+    * @param    index
+    *           The given index number in a sequence of all storage keys.
+    * 
+    * @return   The key
+    */
+   @Override
+   public String getKey(int index)
+   {
+      String key = webSocket.getLocalStorageKey(index);
+      
+      if (isChunkKey(key))
+      {
+         return null;
+      }
+      
+      return key;
+   }
+
+   /**
+    * Tests if the given key is used by a chunk that follows the head chunk.
+    * 
+    * @param    key
+    *           The given key to test
+    * 
+    * @return   Returns true if this key is used by a chunk.
+    */
+   @Override
+   public boolean isChunkKey(String key)
+   {
+      Chunk chunk = getChunk(key, false);
+      
+      return chunk != null && (chunk.head == null || !chunk.head);
+   }
+
+   /**
+    * Gets chunk by its key and checks if it is a head chunk.
+    * 
+    * @param    key
+    *           The given key
+    * @param    headOnly
+    *           The head chunk or the next chunk
+    * 
+    * @return   The target chunk
+    */
+   private Chunk getChunk(String key, boolean headOnly)
+   {
+      String stringValue = webSocket.getStringValue(key);
+      
+      if (stringValue == null || "null".equals(stringValue))
+      {
+         lastRetrievedChunk = null;
+         return null;
+      }
+      
+      try
+      {
+         Chunk chunk = objectMapper.readValue(stringValue, Chunk.class);
+         
+         lastRetrievedChunk = chunk;
+         
+         if (chunk != null && (!headOnly || (chunk.head != null && chunk.head))) 
+         {
+            return chunk;
+         }
+      }
+      catch (IOException e)
+      {
+         lastRetrievedChunk = null;
+      }
+      
+      return null;
+
+   }
+
+   /**
+    * Delete extended chunks that follow the head chunk.
+    * 
+    * @param    head
+    *           The head
+    */
+   private void deleteChunks(Chunk head)
+   {
+      Chunk chunk = head;
+      
+      while(chunk != null && chunk.nextChunkKey != null)
+      {
+         String nextChunkKey = chunk.nextChunkKey;
+         chunk = getNextChunk(head, nextChunkKey);
+         webSocket.deleteKeyAndValue(nextChunkKey);
+      }
+   }
+}

