You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nlpcraft.apache.org by se...@apache.org on 2021/05/17 16:06:16 UTC

[incubator-nlpcraft] branch NLPCRAFT-319 created (now 69f665a)

This is an automated email from the ASF dual-hosted git repository.

sergeykamov pushed a change to branch NLPCRAFT-319
in repository https://gitbox.apache.org/repos/asf/incubator-nlpcraft.git.


      at 69f665a  WIP.

This branch includes the following new commits:

     new 69f665a  WIP.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


[incubator-nlpcraft] 01/01: WIP.

Posted by se...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sergeykamov pushed a commit to branch NLPCRAFT-319
in repository https://gitbox.apache.org/repos/asf/incubator-nlpcraft.git

commit 69f665a207b18e8d4f3b2b2b8b6b7bbbc6b1c888
Author: Sergey Kamov <sk...@gmail.com>
AuthorDate: Mon May 17 19:05:45 2021 +0300

    WIP.
---
 nlpcraft/src/main/resources/sql/create_schema.sql  |  27 +-
 nlpcraft/src/main/resources/sql/drop_schema.sql    |   2 -
 .../org/apache/nlpcraft/common/util/NCUtils.scala  |  44 +++-
 .../model/tools/embedded/NCEmbeddedResult.java     |   5 +
 .../model/tools/test/NCTestClientBuilder.java      |   1 +
 .../probe/mgrs/nlp/NCProbeEnrichmentManager.scala  |  13 +
 .../probe/mgrs/nlp/impl/NCRequestImpl.scala        |  15 +-
 .../nlpcraft/server/company/NCCompanyManager.scala |  22 +-
 .../apache/nlpcraft/server/mdo/NCCompanyMdo.scala  |   1 +
 .../nlpcraft/server/mdo/NCCompanyPropertyMdo.scala |  41 ---
 .../nlpcraft/server/mdo/NCQueryStateMdo.scala      |   4 +-
 .../org/apache/nlpcraft/server/mdo/NCUserMdo.scala |   7 +-
 .../nlpcraft/server/mdo/NCUserPropertyMdo.scala    |  41 ---
 .../nlpcraft/server/probe/NCProbeManager.scala     |  28 +-
 .../server/proclog/NCProcessLogManager.scala       |   3 +
 .../nlpcraft/server/query/NCQueryManager.scala     |  29 ++-
 .../nlpcraft/server/rest/NCBasicRestApi.scala      | 191 ++++++++------
 .../apache/nlpcraft/server/sql/NCSqlManager.scala  | 284 ++++++++-------------
 .../nlpcraft/server/user/NCUserManager.scala       |  22 +-
 .../apache/nlpcraft/server/rest/NCRestSpec.scala   |   3 +
 openapi/nlpcraft_swagger.yml                       |   8 +-
 sql/mysql/drop_schema.sql                          |   2 -
 sql/mysql/schema.sql                               |  29 +--
 sql/oracle/drop_schema.sql                         |  16 --
 sql/oracle/schema.sql                              |  33 +--
 sql/postgres/drop_schema.sql                       |   2 -
 sql/postgres/schema.sql                            |  33 +--
 27 files changed, 335 insertions(+), 571 deletions(-)

diff --git a/nlpcraft/src/main/resources/sql/create_schema.sql b/nlpcraft/src/main/resources/sql/create_schema.sql
index 570e215..1965ca0 100644
--- a/nlpcraft/src/main/resources/sql/create_schema.sql
+++ b/nlpcraft/src/main/resources/sql/create_schema.sql
@@ -30,6 +30,7 @@ CREATE TABLE nc_company (
     postal_code VARCHAR,
     auth_token VARCHAR NOT NULL, -- Unique.
     auth_token_hash VARCHAR NOT NULL, -- Unique.
+    properties_gzip VARCHAR NULL,
     created_on TIMESTAMP NOT NULL,
     last_modified_on TIMESTAMP NOT NULL
 ) WITH "template=replicated, atomicity=transactional";
@@ -38,18 +39,6 @@ CREATE INDEX nc_company_idx_1 ON nc_company(name);
 CREATE INDEX nc_company_idx_2 ON nc_company(auth_token);
 CREATE INDEX nc_company_idx_3 ON nc_company(auth_token_hash);
 
-DROP TABLE IF EXISTS nc_company_property;
-CREATE TABLE nc_company_property (
-    id LONG PRIMARY KEY,
-    company_id LONG NOT NULL, -- Foreign key nc_company.id.
-    property VARCHAR NOT NULL,
-    value VARCHAR NULL,
-    created_on TIMESTAMP NOT NULL,
-    last_modified_on TIMESTAMP NOT NULL
-) WITH "template=replicated, atomicity=transactional";
-
-CREATE INDEX nc_company_property_idx_1 ON nc_company_property(company_id);
-
 DROP TABLE IF EXISTS nc_user;
 CREATE TABLE nc_user (
     id LONG PRIMARY KEY,
@@ -61,6 +50,7 @@ CREATE TABLE nc_user (
     last_name VARCHAR NULL,
     is_admin BOOL NOT NULL,
     passwd_salt VARCHAR NULL,
+    properties_gzip VARCHAR NULL,
     created_on TIMESTAMP NOT NULL,
     last_modified_on TIMESTAMP NOT NULL
 ) WITH "template=replicated, atomicity=transactional";
@@ -69,18 +59,6 @@ CREATE INDEX nc_user_idx_1 ON nc_user(email);
 CREATE INDEX nc_user_idx_2 ON nc_user(company_id, ext_id);
 CREATE INDEX nc_user_idx_3 ON nc_user(company_id);
 
-DROP TABLE IF EXISTS nc_user_property;
-CREATE TABLE nc_user_property (
-    id LONG PRIMARY KEY,
-    user_id LONG NOT NULL, -- Foreign key nc_user.id.
-    property VARCHAR NOT NULL,
-    value VARCHAR NULL,
-    created_on TIMESTAMP NOT NULL,
-    last_modified_on TIMESTAMP NOT NULL
-) WITH "template=replicated, atomicity=transactional";
-
-CREATE INDEX nc_user_property_idx_1 ON nc_user_property(user_id);
-
 DROP TABLE IF EXISTS passwd_pool;
 CREATE TABLE passwd_pool (
     id LONG PRIMARY KEY,
@@ -103,6 +81,7 @@ CREATE TABLE proc_log (
     cancel_tstamp TIMESTAMP NULL,
     res_type VARCHAR NULL,
     res_body_gzip VARCHAR NULL,
+    res_meta_gzip VARCHAR NULL,
     intent_id VARCHAR NULL,
     error VARCHAR NULL,
     probe_token VARCHAR NULL,
diff --git a/nlpcraft/src/main/resources/sql/drop_schema.sql b/nlpcraft/src/main/resources/sql/drop_schema.sql
index b1ce0b9..f985bae 100644
--- a/nlpcraft/src/main/resources/sql/drop_schema.sql
+++ b/nlpcraft/src/main/resources/sql/drop_schema.sql
@@ -16,9 +16,7 @@
 --
 
 DROP TABLE IF EXISTS proc_log;
-DROP TABLE IF EXISTS nc_user_property;
 DROP TABLE IF EXISTS nc_user;
-DROP TABLE IF EXISTS nc_company_property;
 DROP TABLE IF EXISTS nc_company;
 DROP TABLE IF EXISTS passwd_pool;
 DROP TABLE IF EXISTS feedback;
\ No newline at end of file
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala
index 0248b56..2b3131a 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala
@@ -1149,14 +1149,21 @@ object NCUtils extends LazyLogging {
       * @param rawStr String to compress.
       * @return Compressed Base64-encoded string.
       */
+    @throws[NCE]
     def compress(rawStr: String): String = {
         val arr = new ByteArrayOutputStream(1024)
 
-        managed(new GOS(arr)) acquireAndGet { zip ⇒
-            zip.write(rawStr.getBytes)
-        }
+        try {
+            managed(new GOS(arr)) acquireAndGet { zip ⇒
+                zip.write(rawStr.getBytes)
+            }
 
-        Base64.encodeBase64String(arr.toByteArray)
+            Base64.encodeBase64String(arr.toByteArray)
+        }
+        catch {
+            // TODO: text.
+            case e: Exception ⇒ throw new NCE("Error compressing data", e)
+        }
     }
 
     /**
@@ -1165,8 +1172,14 @@ object NCUtils extends LazyLogging {
       * @param zipStr Compressed string.
       * @return Uncompressed string.
       */
+    @throws[NCE]
     def uncompress(zipStr: String): String =
-        IOUtils.toString(new GIS(new ByteArrayInputStream(Base64.decodeBase64(zipStr))), Charset.defaultCharset())
+        try
+            IOUtils.toString(new GIS(new ByteArrayInputStream(Base64.decodeBase64(zipStr))), Charset.defaultCharset())
+        catch {
+            // TODO: text.
+            case e: Exception ⇒ throw new NCE("Error decompressing data", e)
+        }
 
     /**
       * Sleeps number of milliseconds properly handling exceptions.
@@ -1672,6 +1685,7 @@ object NCUtils extends LazyLogging {
      * @param json JSON to convert.
      * @return
      */
+    @throws[Exception]
     def jsonToScalaMap(json: String): Map[String, Object] =
         GSON.fromJson(json, classOf[java.util.HashMap[String, Object]]).asScala.toMap
 
@@ -1681,8 +1695,15 @@ object NCUtils extends LazyLogging {
      * @param json JSON to convert.
      * @return
      */
-    def jsonToJavaMap(json: String): java.util.Map[String, Object] =
-        GSON.fromJson(json, classOf[java.util.HashMap[String, Object]])
+    @throws[NCE]
+    def jsonToJavaMap(json: String): java.util.Map[String, Object] = {
+        try
+            GSON.fromJson(json, classOf[java.util.HashMap[String, Object]])
+        catch {
+            // TODO: text
+            case e: Exception ⇒ throw new NCE(s"Cannot deserialize JSON to map: '$json'", e)
+        }
+    }
 
     /**
      *
@@ -1690,9 +1711,14 @@ object NCUtils extends LazyLogging {
      * @param field
      * @return
      */
-    @throws[Exception]
+    @throws[NCE]
     def getJsonBooleanField(json: String, field: String): Boolean =
-        GSON.getAdapter(classOf[JsonElement]).fromJson(json).getAsJsonObject.get(field).getAsBoolean
+        try
+            GSON.getAdapter(classOf[JsonElement]).fromJson(json).getAsJsonObject.get(field).getAsBoolean
+        catch {
+            // TODO: text
+            case e: Exception ⇒ throw new NCE(s"Cannot deserialize JSON to map: '$json'", e)
+        }
 
     /**
      *
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/embedded/NCEmbeddedResult.java b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/embedded/NCEmbeddedResult.java
index 3ece971..8d20981 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/embedded/NCEmbeddedResult.java
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/embedded/NCEmbeddedResult.java
@@ -18,6 +18,8 @@
 package org.apache.nlpcraft.model.tools.embedded;
 
 import org.apache.nlpcraft.model.*;
+
+import java.util.Map;
 import java.util.function.*;
 
 /**
@@ -68,6 +70,9 @@ public interface NCEmbeddedResult {
      */
     String getBody();
 
+    // TODO:
+    Map<String, Object> getMeta();
+
     /**
      * Gets optional result type. Note that either both result body and type are set or
      * error message and error code are set, but not both pairs.
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/test/NCTestClientBuilder.java b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/test/NCTestClientBuilder.java
index df39787..550d0a3 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/test/NCTestClientBuilder.java
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/test/NCTestClientBuilder.java
@@ -192,6 +192,7 @@ public class NCTestClientBuilder {
         @SerializedName("usrId") private long userId;
         @SerializedName("resType") private String resType;
         @SerializedName("resBody") private Object resBody;
+        @SerializedName("resMeta") private Object resMeta;
         @SerializedName("status") private String status;
         @SerializedName("error") private String error;
         @SerializedName("createTstamp") private long createTstamp;
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/NCProbeEnrichmentManager.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/NCProbeEnrichmentManager.scala
index 398d1c2..e2d949a 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/NCProbeEnrichmentManager.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/NCProbeEnrichmentManager.scala
@@ -275,6 +275,7 @@ object NCProbeEnrichmentManager extends NCService with NCOpenCensusModelStats {
           *
           * @param resType Result type.
           * @param resBody Result body.
+          * @param resMeta Result meta.
           * @param errMsg Error message.
           * @param errCode Error code.
           * @param msgName Message name.
@@ -284,6 +285,7 @@ object NCProbeEnrichmentManager extends NCService with NCOpenCensusModelStats {
         def respond(
             resType: Option[String],
             resBody: Option[String],
+            resMeta: Option[JavaMeta],
             errMsg: Option[String],
             errCode: Option[Int],
             msgName: String,
@@ -302,6 +304,10 @@ object NCProbeEnrichmentManager extends NCService with NCOpenCensusModelStats {
                 if (vOpt.isDefined)
                     msg += name → vOpt.get
 
+            def addMeta(name: String, vOpt: Option[JavaMeta]): Unit =
+                if (vOpt.isDefined)
+                    msg += name → vOpt.get.asInstanceOf[Serializable]
+
             if (resBody.isDefined && resBody.get.length > Config.resultMaxSize) {
                 addOptional("error", Some("Result is too big. Model result must be corrected."))
                 addOptional("errorCode", Some(RESULT_TOO_BIG))
@@ -311,6 +317,7 @@ object NCProbeEnrichmentManager extends NCService with NCOpenCensusModelStats {
                 addOptional("errorCode", errCode.map(Integer.valueOf))
                 addOptional("resType", resType)
                 addOptional("resBody", resBody)
+                addMeta("resMeta", resMeta)
                 addOptional("log", log)
                 addOptional("intentId", intentId)
             }
@@ -322,6 +329,7 @@ object NCProbeEnrichmentManager extends NCService with NCOpenCensusModelStats {
                     override val getOriginalText: String = txt
                     override val getUserId: Long = usrId
                     override val getBody: String = msg.dataOpt[String]("resBody").orNull
+                    override val getMeta: JavaMeta = msg.dataOpt[JavaMeta]("resMeta").orNull
                     override val getType: String = msg.dataOpt[String]("resType").orNull
                     override val getErrorMessage: String = msg.dataOpt[String]("error").orNull
                     override val getErrorCode: Int = msg.dataOpt[Int]("errorCode").getOrElse(0)
@@ -394,6 +402,7 @@ object NCProbeEnrichmentManager extends NCService with NCOpenCensusModelStats {
             respond(
                 None,
                 None,
+                None,
                 Some(errMsg),
                 Some(errCode),
                 "P2S_ASK_RESULT",
@@ -531,6 +540,7 @@ object NCProbeEnrichmentManager extends NCService with NCOpenCensusModelStats {
                 respond(
                     None,
                     None,
+                    None,
                     Some(errMsg),
                     Some(errCode),
                     "P2S_ASK_RESULT",
@@ -621,6 +631,7 @@ object NCProbeEnrichmentManager extends NCService with NCOpenCensusModelStats {
         def respondWithResult(res: NCResult, log: Option[String]): Unit = respond(
             Some(res.getType),
             Some(res.getBody),
+            Some(res.getMetadata),
             None,
             None,
             "P2S_ASK_RESULT",
@@ -693,6 +704,7 @@ object NCProbeEnrichmentManager extends NCService with NCOpenCensusModelStats {
                             respond(
                                 None,
                                 None,
+                                None,
                                 Some(e.getMessage), // User provided rejection message.
                                 Some(MODEL_REJECTION),
                                 "P2S_ASK_RESULT",
@@ -722,6 +734,7 @@ object NCProbeEnrichmentManager extends NCService with NCOpenCensusModelStats {
                             respond(
                                 None,
                                 None,
+                                None,
                                 Some("Processing failed with unexpected error."), // System error message.
                                 Some(UNEXPECTED_ERROR),
                                 "P2S_ASK_RESULT",
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/impl/NCRequestImpl.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/impl/NCRequestImpl.scala
index d828961..394a96e 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/impl/NCRequestImpl.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/impl/NCRequestImpl.scala
@@ -17,14 +17,13 @@
 
 package org.apache.nlpcraft.probe.mgrs.nlp.impl
 
-import java.util.{Collections, Optional}
 import org.apache.nlpcraft.common._
 import org.apache.nlpcraft.model._
 import org.apache.nlpcraft.model.impl._
 
-import java.util
-import scala.collection._
+import java.util.{Collections, Optional}
 import scala.collection.JavaConverters._
+import scala.collection._
 import scala.compat.java8.OptionConverters._
 
 /**
@@ -55,7 +54,7 @@ case class NCRequestImpl(nlpMeta: Map[String, Any], srvReqId: String) extends NC
         getOpt("COMPANY_CITY"),
         getOpt("COMPANY_ADDRESS"),
         getOpt("COMPANY_POSTAL"),
-        getMap("COMPANY_META")
+        getOpt("COMPANY_META").orElse(Collections.emptyMap())
     )
     override lazy val getUser: NCUser = new NCUserImpl(
         nlpMeta("USER_ID").asInstanceOf[Long],
@@ -63,7 +62,7 @@ case class NCRequestImpl(nlpMeta: Map[String, Any], srvReqId: String) extends NC
         getOpt("LAST_NAME"),
         getOpt("EMAIL"),
         getOpt("AVATAR_URL"),
-        getMap("META"),
+        getOpt("META").orElse(Collections.emptyMap()),
         nlpMeta("IS_ADMIN").asInstanceOf[Boolean],
         nlpMeta("SIGNUP_TSTAMP").asInstanceOf[Long]
     )
@@ -73,10 +72,4 @@ case class NCRequestImpl(nlpMeta: Map[String, Any], srvReqId: String) extends NC
             case Some(v) ⇒ Optional.of(v.asInstanceOf[T])
             case None ⇒ Optional.empty()
         }
-
-    private def getMap(key: String): util.Map[String, AnyRef] = {
-        val m: Optional[JavaMeta] = getOpt(key)
-
-        if (m.isPresent) m.get() else Collections.emptyMap()
-    }
 }
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/company/NCCompanyManager.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/company/NCCompanyManager.scala
index 41a6444..0e3937f 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/company/NCCompanyManager.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/company/NCCompanyManager.scala
@@ -21,7 +21,7 @@ import io.opencensus.trace.Span
 import org.apache.ignite.{IgniteAtomicSequence, IgniteSemaphore}
 import org.apache.nlpcraft.common.{NCService, _}
 import org.apache.nlpcraft.server.ignite.NCIgniteInstance
-import org.apache.nlpcraft.server.mdo.{NCCompanyMdo, NCCompanyPropertyMdo}
+import org.apache.nlpcraft.server.mdo.NCCompanyMdo
 import org.apache.nlpcraft.server.sql.{NCSql, NCSqlManager}
 import org.apache.nlpcraft.server.user.NCUserManager
 
@@ -168,7 +168,7 @@ object NCCompanyManager extends NCService with NCIgniteInstance {
         city: Option[String],
         address: Option[String],
         postalCode: Option[String],
-        props: Option[Map[String, String]],
+        props: Option[String],
         parent: Span = null
     ): Unit =
         startScopedSpan("updateCompany", parent, "id" → id) { span ⇒
@@ -282,7 +282,7 @@ object NCCompanyManager extends NCService with NCIgniteInstance {
         adminFirstName: String,
         adminLastName: String,
         adminAvatarUrl: Option[String],
-        props: Option[Map[String, String]],
+        props: Option[String],
         mkToken: () ⇒ String,
         parent: Span = null
     ): NCCompanyCreationData = {
@@ -371,7 +371,7 @@ object NCCompanyManager extends NCService with NCIgniteInstance {
         adminFirstName: String,
         adminLastName: String,
         adminAvatarUrl: Option[String],
-        props: Option[Map[String, String]],
+        props: Option[String],
         parent: Span = null
     ): NCCompanyCreationData = startScopedSpan("addCompany", parent, "name" → name) { _ ⇒
         addCompany0(
@@ -391,18 +391,4 @@ object NCCompanyManager extends NCService with NCIgniteInstance {
             () ⇒ mkToken()
         )
     }
-
-    /**
-      * Gets company properties for given company ID.
-      *
-      * @param id User ID.
-      * @param parent Optional parent span.
-      */
-    @throws[NCE]
-    def getCompanyProperties(id: Long, parent: Span = null): Seq[NCCompanyPropertyMdo] =
-        startScopedSpan("getCompanyProperties", parent, "companyId" → id) { span ⇒
-            NCSql.sql {
-                NCSqlManager.getCompanyProperties(id, span)
-            }
-        }
 }
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCCompanyMdo.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCCompanyMdo.scala
index c4aa5a8..2405bf2 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCCompanyMdo.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCCompanyMdo.scala
@@ -37,6 +37,7 @@ case class NCCompanyMdo(
     @NCMdoField(column = "postal_code") postalCode: Option[String],
     @NCMdoField(column = "auth_token") authToken: String,
     @NCMdoField(column = "auth_token_hash") authTokenHash: String,
+    @NCMdoField(column = "properties_gzip") propertiesGzip: Option[String],
     @NCMdoField(column = "created_on") createdOn: Timestamp,
     @NCMdoField(column = "last_modified_on") lastModifiedOn: Timestamp
 ) extends NCAnnotatedMdo[NCCompanyMdo]
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCCompanyPropertyMdo.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCCompanyPropertyMdo.scala
deleted file mode 100644
index 695a41c..0000000
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCCompanyPropertyMdo.scala
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.nlpcraft.server.mdo
-
-import org.apache.nlpcraft.server.mdo.impl._
-import org.apache.nlpcraft.server.sql.NCSql.Implicits.RsParser
-
-import java.sql.Timestamp
-
-/**
-  * Company property MDO.
-  */
-@NCMdoEntity(table = "nc_company_property")
-case class NCCompanyPropertyMdo(
-    @NCMdoField(column = "id", pk = true) id: Long,
-    @NCMdoField(column = "company_id") userId: Long,
-    @NCMdoField(column = "property") property: String,
-    @NCMdoField(column = "value") value: String,
-    @NCMdoField(column = "created_on") createdOn: Timestamp,
-    @NCMdoField(column = "last_modified_on") lastModifiedOn: Timestamp
-) extends NCAnnotatedMdo[NCCompanyPropertyMdo]
-
-object NCCompanyPropertyMdo {
-    implicit val x: RsParser[NCCompanyPropertyMdo] =
-        NCAnnotatedMdo.mkRsParser(classOf[NCCompanyPropertyMdo])
-}
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCQueryStateMdo.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCQueryStateMdo.scala
index 27f440a..7677b36 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCQueryStateMdo.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCQueryStateMdo.scala
@@ -17,8 +17,9 @@
 
 package org.apache.nlpcraft.server.mdo
 
-import java.sql.Timestamp
+import org.apache.nlpcraft.common.JavaMeta
 
+import java.sql.Timestamp
 import org.apache.nlpcraft.server.sql.NCSql.Implicits.RsParser
 import org.apache.nlpcraft.server.mdo.impl._
 
@@ -45,6 +46,7 @@ case class NCQueryStateMdo(
     // Query OK.
     @NCMdoField var resultType: Option[String] = None,
     @NCMdoField var resultBody: Option[String] = None,
+    @NCMdoField var resultMeta: Option[JavaMeta] = None,
     // Query ERROR.
     @NCMdoField var error: Option[String] = None,
     @NCMdoField var errorCode: Option[Int] = None
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCUserMdo.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCUserMdo.scala
index 9d7be9d..063a4be 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCUserMdo.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCUserMdo.scala
@@ -36,6 +36,7 @@ case class NCUserMdo(
     @NCMdoField(column = "last_name") lastName: Option[String],
     @NCMdoField(column = "avatar_url") avatarUrl: Option[String],
     @NCMdoField(column = "passwd_salt") passwordSalt: Option[String],
+    @NCMdoField(column = "properties_gzip") propertiesGzip: Option[String],
     @NCMdoField(column = "is_admin") isAdmin: Boolean,
     @NCMdoField(column = "created_on") createdOn: Timestamp,
     @NCMdoField(column = "last_modified_on") lastModifiedOn: Timestamp
@@ -54,11 +55,12 @@ object NCUserMdo {
         lastName: Option[String],
         avatarUrl: Option[String],
         passwordSalt: Option[String],
+        propertiesGzip: Option[String],
         isAdmin: Boolean
     ): NCUserMdo = {
         val now = U.nowUtcTs()
 
-        NCUserMdo(id, companyId, extId, email, firstName, lastName, avatarUrl, passwordSalt, isAdmin, now, now)
+        NCUserMdo(id, companyId, extId, email, firstName, lastName, avatarUrl, passwordSalt, propertiesGzip, isAdmin, now, now)
     }
 
     def apply(
@@ -70,11 +72,12 @@ object NCUserMdo {
         lastName: Option[String],
         avatarUrl: Option[String],
         passwordSalt: Option[String],
+        propertiesGzip: Option[String],
         isAdmin: Boolean,
         createdOn: Timestamp
     ): NCUserMdo = {
         require(createdOn != null, "Created date cannot be null.")
 
-        NCUserMdo(id, companyId, extId, email, firstName, lastName, avatarUrl, passwordSalt, isAdmin, createdOn, createdOn)
+        NCUserMdo(id, companyId, extId, email, firstName, lastName, avatarUrl, passwordSalt, propertiesGzip, isAdmin, createdOn, createdOn)
     }
 }
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCUserPropertyMdo.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCUserPropertyMdo.scala
deleted file mode 100644
index f1813cd..0000000
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/mdo/NCUserPropertyMdo.scala
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.nlpcraft.server.mdo
-
-import java.sql.Timestamp
-
-import org.apache.nlpcraft.server.mdo.impl._
-import org.apache.nlpcraft.server.sql.NCSql.Implicits.RsParser
-
-/**
-  * User property MDO.
-  */
-@NCMdoEntity(table = "nc_user_property")
-case class NCUserPropertyMdo(
-    @NCMdoField(column = "id", pk = true) id: Long,
-    @NCMdoField(column = "user_id") userId: Long,
-    @NCMdoField(column = "property") property: String,
-    @NCMdoField(column = "value") value: String,
-    @NCMdoField(column = "created_on") createdOn: Timestamp,
-    @NCMdoField(column = "last_modified_on") lastModifiedOn: Timestamp
-) extends NCAnnotatedMdo[NCUserPropertyMdo]
-
-object NCUserPropertyMdo {
-    implicit val x: RsParser[NCUserPropertyMdo] =
-        NCAnnotatedMdo.mkRsParser(classOf[NCUserPropertyMdo])
-}
\ No newline at end of file
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/probe/NCProbeManager.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/probe/NCProbeManager.scala
index 34b654b..9452b83 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/probe/NCProbeManager.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/probe/NCProbeManager.scala
@@ -722,6 +722,7 @@ object NCProbeManager extends NCService {
                         val errCodeOpt = probeMsg.dataOpt[Int]("errorCode")
                         val resTypeOpt = probeMsg.dataOpt[String]("resType")
                         val resBodyOpt = probeMsg.dataOpt[String]("resBody")
+                        val resMetaOpt = probeMsg.dataOpt[JavaMeta]("resMeta")
                         val logJson = probeMsg.dataOpt[String]("log")
                         val intentId = probeMsg.dataOpt[String]("intentId")
 
@@ -740,13 +741,11 @@ object NCProbeManager extends NCService {
                         else { // OK result.
                             require(resTypeOpt.isDefined && resBodyOpt.isDefined, "Result defined")
                      
-                            val resType = resTypeOpt.get
-                            val resBody = resBodyOpt.get
-                     
                             NCQueryManager.setResult(
                                 srvReqId,
-                                resType,
-                                resBody,
+                                resTypeOpt.get,
+                                resBodyOpt.get,
+                                resMetaOpt,
                                 logJson,
                                 intentId
                             )
@@ -849,22 +848,11 @@ object NCProbeManager extends NCService {
         usrAgent: Option[String],
         rmtAddr: Option[String],
         data: Option[String],
-        usrMeta: Option[Map[String, String]],
-        companyMeta: Option[Map[String, String]],
+        usrMeta: Option[JavaMeta],
+        companyMeta: Option[JavaMeta],
         enableLog: Boolean,
         parent: Span = null): Unit = {
         startScopedSpan("askProbe", parent, "srvReqId" → srvReqId, "usrId" → usr.id, "mdlId" → mdlId, "txt" → txt) { span ⇒
-            def convertMeta(metaOpt: Option[Map[String, String]]): util.HashMap[String, String] =
-                metaOpt match {
-                    case Some(meta) ⇒
-                        val map = new util.HashMap[String, String]()
-
-                        meta.foreach { case (k, v) ⇒ map.put(k, v) }
-
-                        map
-                    case None ⇒ null
-                }
-
             val senMeta = new util.HashMap[String, java.io.Serializable]()
 
             Map(
@@ -880,7 +868,7 @@ object NCProbeManager extends NCService {
                 "IS_ADMIN" → usr.isAdmin,
                 "AVATAR_URL" → usr.avatarUrl.orNull,
                 "DATA" → data.orNull,
-                "META" → convertMeta(usrMeta),
+                "META" → usrMeta.orNull,
                 "COMPANY_ID" → company.id,
                 "COMPANY_NAME" → company.name,
                 "COMPANY_WEBSITE" → company.website.orNull,
@@ -889,7 +877,7 @@ object NCProbeManager extends NCService {
                 "COMPANY_CITY" → company.city.orNull,
                 "COMPANY_ADDRESS" → company.address.orNull,
                 "COMPANY_POSTAL" → company.postalCode.orNull,
-                "COMPANY_META" → convertMeta(companyMeta)
+                "COMPANY_META" → companyMeta.orNull
             ).
                 filter(_._2 != null).
                 foreach(p ⇒ senMeta.put(p._1, p._2.asInstanceOf[java.io.Serializable]))
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/proclog/NCProcessLogManager.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/proclog/NCProcessLogManager.scala
index f7958b9..436a0e1 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/proclog/NCProcessLogManager.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/proclog/NCProcessLogManager.scala
@@ -91,6 +91,7 @@ object NCProcessLogManager extends NCService with NCIgniteInstance {
       * @param errMsg
       * @param resType
       * @param resBody
+      * @param resMeta
       * @param intentId
       * @param parent Optional parent span.
       */
@@ -101,6 +102,7 @@ object NCProcessLogManager extends NCService with NCIgniteInstance {
         errMsg: Option[String],
         resType: Option[String],
         resBody: Option[String],
+        resMeta: Option[JavaMeta],
         intentId: Option[String],
         parent: Span = null
     ): Unit =
@@ -115,6 +117,7 @@ object NCProcessLogManager extends NCService with NCIgniteInstance {
                     errMsg.orNull,
                     resType.orNull,
                     resBody.orNull,
+                    resMeta.orNull,
                     intentId.orNull,
                     tstamp,
                     span
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/query/NCQueryManager.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/query/NCQueryManager.scala
index ad6fdb1..2b762f3 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/query/NCQueryManager.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/query/NCQueryManager.scala
@@ -22,6 +22,7 @@ import org.apache.ignite.IgniteCache
 import org.apache.ignite.events.{CacheEvent, EventType}
 import org.apache.nlpcraft.common.ascii.NCAsciiTable
 import org.apache.nlpcraft.common.pool.NCThreadPoolManager
+import org.apache.nlpcraft.common.util.NCUtils.{jsonToJavaMap, uncompress}
 import org.apache.nlpcraft.common.{NCService, _}
 import org.apache.nlpcraft.server.apicodes.NCApiStatusCode._
 import org.apache.nlpcraft.server.company.NCCompanyManager
@@ -221,18 +222,6 @@ object NCQueryManager extends NCService with NCIgniteInstance with NCOpenCensusS
         val usr = NCUserManager.getUserById(usrId, parent).getOrElse(throw new NCE(s"Unknown user ID: $usrId"))
         val company = NCCompanyManager.getCompany(usr.companyId, parent).getOrElse(throw new NCE(s"Unknown company ID: ${usr.companyId}"))
 
-        val usrMeta = {
-            val m = NCUserManager.getUserProperties(usrId, parent)
-
-            if (m.isEmpty) None else Some(m.map(p ⇒ p.property → p.value).toMap)
-        }
-
-        val compMeta = {
-            val m = NCCompanyManager.getCompanyProperties(usr.companyId, parent)
-
-            if (m.isEmpty) None else Some(m.map(p ⇒ p.property → p.value).toMap)
-        }
-
         // Check input length.
         if (txt0.split(" ").length > MAX_WORDS)
             throw new NCE(s"User input is too long (max is $MAX_WORDS words).")
@@ -285,6 +274,13 @@ object NCQueryManager extends NCService with NCIgniteInstance with NCOpenCensusS
 
                 val enabledBuiltInToks = NCProbeManager.getModel(mdlId, span).enabledBuiltInTokens
 
+                @throws[NCE]
+                def unzipProperties(gzipOpt: Option[String]): Option[JavaMeta] =
+                    gzipOpt match {
+                        case Some(gzip) ⇒ Some(jsonToJavaMap(uncompress(gzip)))
+                        case None ⇒ None
+                    }
+
                 // Enrich the user input and send it to the probe.
                 NCProbeManager.askProbe(
                     srvReqId,
@@ -296,8 +292,8 @@ object NCQueryManager extends NCService with NCIgniteInstance with NCOpenCensusS
                     usrAgent,
                     rmtAddr,
                     data,
-                    usrMeta,
-                    compMeta,
+                    unzipProperties(usr.propertiesGzip),
+                    unzipProperties(company.propertiesGzip),
                     enableLog,
                     span
                 )
@@ -369,6 +365,7 @@ object NCQueryManager extends NCService with NCIgniteInstance with NCOpenCensusS
                     None,
                     None,
                     None,
+                    None,
                     span
                 )
         }
@@ -379,6 +376,7 @@ object NCQueryManager extends NCService with NCIgniteInstance with NCOpenCensusS
       * @param srvReqId Server request ID.
       * @param resType
       * @param resBody
+      * @param resMeta
       * @param logJson
       * @param intentId
       * @param parent Optional parent span.
@@ -388,6 +386,7 @@ object NCQueryManager extends NCService with NCIgniteInstance with NCOpenCensusS
         srvReqId: String,
         resType: String,
         resBody: String,
+        resMeta: Option[JavaMeta],
         logJson: Option[String],
         intentId: Option[String],
         parent: Span = null
@@ -407,6 +406,7 @@ object NCQueryManager extends NCService with NCIgniteInstance with NCOpenCensusS
                         copy.status = QRY_READY.toString
                         copy.resultType = Some(resType)
                         copy.resultBody = Some(resBody)
+                        copy.resultMeta = resMeta
                         copy.logJson = logJson
                         copy.intentId = intentId
     
@@ -430,6 +430,7 @@ object NCQueryManager extends NCService with NCIgniteInstance with NCOpenCensusS
                     None,
                     resType = Some(resType),
                     resBody = Some(resBody),
+                    resMeta = resMeta,
                     intentId = intentId,
                     span
                 )
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/rest/NCBasicRestApi.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/rest/NCBasicRestApi.scala
index a681a8f..c80c0ac 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/rest/NCBasicRestApi.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/rest/NCBasicRestApi.scala
@@ -24,14 +24,17 @@ import akka.http.scaladsl.model._
 import akka.http.scaladsl.model.headers._
 import akka.http.scaladsl.server.Directives.{entity, _}
 import akka.http.scaladsl.server._
-import com.google.gson.Gson
+import com.fasterxml.jackson.core.JsonProcessingException
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.scala.DefaultScalaModule
 import com.typesafe.scalalogging.LazyLogging
 import io.opencensus.stats.Measure
 import io.opencensus.trace.{Span, Status}
 import org.apache.commons.validator.routines.UrlValidator
 import org.apache.nlpcraft.common.opencensus.NCOpenCensusTrace
 import org.apache.nlpcraft.common.pool.NCThreadPoolManager
-import org.apache.nlpcraft.common.{NCE, U}
+import org.apache.nlpcraft.common.util.NCUtils.{jsonToJavaMap, uncompress}
+import org.apache.nlpcraft.common.{JavaMeta, NCE, U}
 import org.apache.nlpcraft.model.NCModelView
 import org.apache.nlpcraft.server.apicodes.NCApiStatusCode.{API_OK, _}
 import org.apache.nlpcraft.server.company.NCCompanyManager
@@ -52,12 +55,10 @@ import scala.concurrent.{ExecutionContext, Future}
   * REST API default implementation.
   */
 class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace with NCOpenCensusServerStats {
-    protected final val GSON = new Gson()
     protected final val URL_VALIDATOR = new UrlValidator(Array("http", "https"), UrlValidator.ALLOW_LOCAL_URLS)
 
     final val API_VER = 1
     final val API = "api" / s"v$API_VER"
-
     /** */
     private final val CORS_HDRS = List(
         `Access-Control-Allow-Origin`.*,
@@ -65,6 +66,10 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
         `Access-Control-Allow-Headers`("Authorization", "Content-Type", "X-Requested-With")
     )
 
+    private final val JS_MAPPER = new ObjectMapper()
+
+    JS_MAPPER.registerModule(DefaultScalaModule)
+
     /*
      * General control exception.
      * Note that these classes must be public because scala 2.11 internal errors (compilations problems).
@@ -132,6 +137,21 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
 
     /**
       *
+      * @param o
+      * @throws
+      * @return
+      */
+    @throws[NCE]
+    private def toJs(o: AnyRef): String =
+        try
+            JS_MAPPER.writeValueAsString(o)
+        catch {
+            // TODO: text
+            case e: JsonProcessingException ⇒ throw new NCE("Serialization error", e)
+        }
+
+    /**
+      *
       * @param acsTkn Access token to check.
       * @param shouldBeAdmin Admin flag.
       * @return
@@ -220,28 +240,38 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
                 else
                     s.resultBody.orNull
                 ),
+            "resMeta" → s.resultMeta.orNull,
             "error" → s.error.orNull,
             "errorCode" → s.errorCode.map(Integer.valueOf).orNull,
             "logHolder" → (if (s.logJson.isDefined) U.jsonToObject(s.logJson.get) else null),
             "intentId" → s.intentId.orNull
         ).filter(_._2 != null).asJava
 
+
     /**
-      * Checks properties.
+      * Extracts and checks JSON.
       *
-      * @param propsOpt Optional properties.
+      * @param jsOpt JSON value. Optional.
+      * @param name Property name.
       */
     @throws[TooLargeField]
-    private def checkProperties(propsOpt: Option[Map[String, String]]): Unit =
-        propsOpt match {
-            case Some(props) ⇒
-                props.foreach { case (k, v) ⇒
-                    checkLength(k, k, 64)
-
-                    if (v != null && v.nonEmpty && v.length > 512)
-                        throw TooLargeField(v, 512)
+    @throws[InvalidField]
+    private def extractJson(jsOpt: Option[spray.json.JsValue], name: String): Option[String] =
+        jsOpt match {
+            case Some(js) ⇒
+                val s = js.compactPrint
+
+                checkLength(name, s, 512000)
+
+                // Validates.
+                try
+                    U.jsonToJavaMap(s)
+                catch {
+                    case _: NCE ⇒ throw InvalidField(name)
                 }
-            case None ⇒ // No-op.
+
+                Some(s)
+            case None ⇒ None
         }
 
     /**
@@ -526,9 +556,28 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
       *
       * @param fut
       */
-    private def successWithJs(fut: Future[String]): Route = onSuccess(fut) {
-        js ⇒ complete(HttpResponse(entity = HttpEntity(ContentTypes.`application/json`, js)))
-    }
+    private def successWithJs(fut: Future[String]): Route =
+        onSuccess(fut) {
+            js ⇒ complete(HttpResponse(entity = HttpEntity(ContentTypes.`application/json`, js)))
+        }
+
+    /**
+      *
+      * @param o
+      */
+    private def completeJs(o: Object): Route =
+        complete(HttpResponse(entity = HttpEntity(ContentTypes.`application/json`, toJs(o))))
+
+    /**
+      *
+      * @param gzipOpt
+      */
+    @throws[NCE]
+    private def unzipProperties(gzipOpt: Option[String]): Option[JavaMeta] =
+        gzipOpt match {
+            case Some(gzip) ⇒ Some(jsonToJavaMap(uncompress(gzip)))
+            case None ⇒ None
+        }
 
     /**
       *
@@ -559,14 +608,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
                     "acsTok" → req.acsTok, "usrExtId" → req.usrExtId, "mdlId" → req.mdlId, "txt" → req.txt
                 )
 
-                val dataJsOpt =
-                    req.data match {
-                        case Some(data) ⇒ Some(data.compactPrint)
-                        case None ⇒ None
-                    }
-
-                checkLengthOpt("data", dataJsOpt,512000)
-
+                val dataJs = extractJson(req.data, "data")
                 val acsUsr = authenticate(req.acsTok)
 
                 checkModelId(req.mdlId, acsUsr.companyId)
@@ -579,7 +621,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
                             mdlId = req.mdlId,
                             usrAgent = usrAgent,
                             rmtAddr = getAddress(rmtAddr),
-                            data = dataJsOpt,
+                            data = dataJs,
                             req.enableLog.getOrElse(false),
                             parent = span
                         ))
@@ -640,8 +682,8 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
 
                 successWithJs(
                     fut.collect {
-                        // We have to use GSON (not spray) here to serialize 'resBody' field.
-                        case res ⇒ GSON.toJson(
+                        // We have to use Jackson (not spray) here to serialize 'resBody' field.
+                        case res ⇒ toJs(
                             Map(
                                 "status" → API_OK.toString,
                                 "state" → queryStateToMap(res)
@@ -724,8 +766,8 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
                         toSeq.sortBy(-_.createTstamp.getTime).
                         take(req.maxRows.getOrElse(Integer.MAX_VALUE))
 
-                // We have to use GSON (not spray) here to serialize 'resBody' field.
-                val js = GSON.toJson(
+                // We have to use Jackson (not spray) here to serialize 'resBody' field.
+                val js = toJs(
                     Map(
                         "status" → API_OK.toString,
                         "states" → states.map(queryStateToMap).asJava
@@ -772,8 +814,8 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
 
                 successWithJs(
                     fut.collect {
-                        // We have to use GSON (not spray) here to serialize 'result' field.
-                        case res ⇒ GSON.toJson(Map("status" → API_OK.toString, "result" → res).asJava)
+                        // We have to use Jackson (not spray) here to serialize 'result' field.
+                        case res ⇒ toJs(Map("status" → API_OK.toString, "result" → res).asJava)
                     }
                 )
             }
@@ -881,7 +923,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
             adminFirstName: String,
             adminLastName: String,
             adminAvatarUrl: Option[String],
-            properties: Option[Map[String, String]]
+            properties: Option[spray.json.JsValue]
         )
         case class Res$Company$Add(
             status: String,
@@ -911,7 +953,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
                     "adminAvatarUrl" → req.adminAvatarUrl
                 )
 
-                checkProperties(req.properties)
+                val propsJs = extractJson(req.properties, "properties")
 
                 // Via REST only administrators of already created companies can create new companies.
                 authenticateAsAdmin(req.acsTok)
@@ -929,7 +971,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
                     req.adminFirstName,
                     req.adminLastName,
                     req.adminAvatarUrl,
-                    req.properties,
+                    propsJs,
                     span
                 )
 
@@ -958,11 +1000,10 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
             city: Option[String],
             address: Option[String],
             postalCode: Option[String],
-            properties: Option[Map[String, String]]
+            properties: Option[JavaMeta]
         )
 
         implicit val reqFmt: RootJsonFormat[Req$Company$Get] = jsonFormat1(Req$Company$Get)
-        implicit val resFmt: RootJsonFormat[Res$Company$Get] = jsonFormat10(Res$Company$Get)
 
         entity(as[Req$Company$Get]) { req ⇒
             startScopedSpan("company$get", "acsTok" → req.acsTok) { span ⇒
@@ -974,9 +1015,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
                     getCompany(acsUsr.companyId, span).
                     getOrElse(throw new NCE(s"Company not found: ${acsUsr.companyId}"))
 
-                val props = NCCompanyManager.getCompanyProperties(acsUsr.companyId, span)
-
-                complete {
+                completeJs {
                     Res$Company$Get(API_OK,
                         company.id,
                         company.name,
@@ -986,7 +1025,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
                         company.city,
                         company.address,
                         company.postalCode,
-                        if (props.isEmpty) None else Some(props.map(p ⇒ p.property → p.value).toMap)
+                        unzipProperties(company.propertiesGzip)
                     )
                 }
             }
@@ -1010,7 +1049,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
             city: Option[String],
             address: Option[String],
             postalCode: Option[String],
-            properties: Option[Map[String, String]]
+            properties: Option[spray.json.JsValue]
         )
         case class Res$Company$Update(
             status: String
@@ -1031,8 +1070,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
                     "postalCode" → req.postalCode
                 )
 
-                checkProperties(req.properties)
-
+                val propsJs = extractJson(req.properties, "properties")
                 val admUsr = authenticateAsAdmin(req.acsTok)
 
                 NCCompanyManager.updateCompany(
@@ -1044,7 +1082,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
                     req.city,
                     req.address,
                     req.postalCode,
-                    req.properties,
+                    propsJs,
                     span
                 )
 
@@ -1314,7 +1352,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
             lastName: String,
             avatarUrl: Option[String],
             isAdmin: Boolean,
-            properties: Option[Map[String, String]],
+            properties: Option[spray.json.JsValue],
             usrExtId: Option[String]
         )
         case class Res$User$Add(
@@ -1337,8 +1375,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
                     "usrExtId" → req.usrExtId
                 )
 
-                checkProperties(req.properties)
-
+                val propsJs = extractJson(req.properties, "properties")
                 val admUsr = authenticateAsAdmin(req.acsTok)
 
                 val id = NCUserManager.addUser(
@@ -1349,7 +1386,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
                     req.lastName,
                     req.avatarUrl,
                     req.isAdmin,
-                    req.properties,
+                    propsJs,
                     req.usrExtId,
                     span
                 )
@@ -1375,7 +1412,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
             firstName: String,
             lastName: String,
             avatarUrl: Option[String],
-            properties: Option[Map[String, String]]
+            properties: Option[spray.json.JsValue]
         )
         case class Res$User$Update(
             status: String
@@ -1393,8 +1430,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
                     "avatarUrl" → req.avatarUrl
                 )
 
-                checkProperties(req.properties)
-
+                val propsJs = extractJson(req.properties, "properties")
                 val acsUsr = authenticate(req.acsTok)
 
                 NCUserManager.updateUser(
@@ -1402,7 +1438,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
                     req.firstName,
                     req.lastName,
                     req.avatarUrl,
-                    req.properties,
+                    propsJs,
                     span
                 )
 
@@ -1454,7 +1490,6 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
 
                     NCUserManager.
                         getAllUsers(acsUsr.companyId, span).
-                        keys.
                         filter(_.id != acsUsr.id).
                         map(_.id).
                         foreach(delete)
@@ -1575,7 +1610,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
             avatarUrl: Option[String],
             isAdmin: Boolean,
             companyId: Long,
-            properties: Option[Map[String, String]]
+            properties: Option[JavaMeta]
         )
         case class Res$User$All(
             status: String,
@@ -1583,8 +1618,6 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
         )
 
         implicit val reqFmt: RootJsonFormat[Req$User$All] = jsonFormat1(Req$User$All)
-        implicit val usrFmt: RootJsonFormat[ResUser_User$All] = jsonFormat9(ResUser_User$All)
-        implicit val resFmt: RootJsonFormat[Res$User$All] = jsonFormat2(Res$User$All)
 
         entity(as[Req$User$All]) { req ⇒
             startScopedSpan("user$All", "acsTok" → req.acsTok) { span ⇒
@@ -1592,23 +1625,23 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
 
                 val admUSr = authenticateAsAdmin(req.acsTok)
 
-                val usrLst =
-                    NCUserManager.getAllUsers(admUSr.companyId, span).map { case (u, props) ⇒
-                        ResUser_User$All(
-                            u.id,
-                            u.email,
-                            u.extId,
-                            u.firstName,
-                            u.lastName,
-                            u.avatarUrl,
-                            u.isAdmin,
-                            u.companyId,
-                            if (props.isEmpty) None else Some(props.map(p ⇒ p.property → p.value).toMap)
+                completeJs {
+                    Res$User$All(
+                        API_OK,
+                        NCUserManager.getAllUsers(admUSr.companyId, span).map(u ⇒
+                            ResUser_User$All(
+                                u.id,
+                                u.email,
+                                u.extId,
+                                u.firstName,
+                                u.lastName,
+                                u.avatarUrl,
+                                u.isAdmin,
+                                u.companyId,
+                                unzipProperties(u.propertiesGzip)
+                            )
                         )
-                    }.toSeq
-
-                complete {
-                    Res$User$All(API_OK, usrLst)
+                    )
                 }
             }
         }
@@ -1634,11 +1667,10 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
             lastName: Option[String],
             avatarUrl: Option[String],
             isAdmin: Boolean,
-            properties: Option[Map[String, String]]
+            properties: Option[JavaMeta]
         )
 
         implicit val reqFmt: RootJsonFormat[Req$User$Get] = jsonFormat3(Req$User$Get)
-        implicit val resFmt: RootJsonFormat[Res$User$Get] = jsonFormat9(Res$User$Get)
 
         entity(as[Req$User$Get]) { req ⇒
             startScopedSpan(
@@ -1653,9 +1685,8 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
                     throw AdminRequired(acsUsr.email.get)
 
                 val usr = NCUserManager.getUserById(usrId, span).getOrElse(throw new NCE(s"User not found: $usrId"))
-                val props = NCUserManager.getUserProperties(usrId, span)
 
-                complete {
+                completeJs {
                     Res$User$Get(API_OK,
                         usr.id,
                         usr.email,
@@ -1664,7 +1695,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
                         usr.lastName,
                         usr.avatarUrl,
                         usr.isAdmin,
-                        if (props.isEmpty) None else Some(props.map(p ⇒ p.property → p.value).toMap)
+                        unzipProperties(usr.propertiesGzip)
                     )
                 }
             }
@@ -1771,7 +1802,7 @@ class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace w
                     status = statusCode,
                     entity = HttpEntity(
                         ContentTypes.`application/json`,
-                        GSON.toJson(Map("code" → errCode, "msg" → errMsg).asJava)
+                        toJs(Map("code" → errCode, "msg" → errMsg).asJava)
                     )
                 )
             )
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/sql/NCSqlManager.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/sql/NCSqlManager.scala
index 00fe81b..3ef2551 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/sql/NCSqlManager.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/sql/NCSqlManager.scala
@@ -17,8 +17,10 @@
 
 package org.apache.nlpcraft.server.sql
 
+import com.fasterxml.jackson.core.JsonProcessingException
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.scala.DefaultScalaModule
 import io.opencensus.trace.Span
-import org.apache.ignite.IgniteAtomicSequence
 import org.apache.nlpcraft.common.config.NCConfigurable
 import org.apache.nlpcraft.common.{NCService, _}
 import org.apache.nlpcraft.server.apicodes.NCApiStatusCode._
@@ -27,7 +29,6 @@ import org.apache.nlpcraft.server.mdo._
 import org.apache.nlpcraft.server.sql.NCSql.Implicits._
 
 import java.sql.Timestamp
-import scala.util.control.Exception.catching
 
 /**
   * Provides basic CRUD and often used operations on RDBMS.
@@ -35,7 +36,7 @@ import scala.util.control.Exception.catching
   */
 object NCSqlManager extends NCService with NCIgniteInstance {
     private final val DB_TABLES = Seq(
-        "nc_company", "nc_company_property", "nc_user", "nc_user_property", "passwd_pool", "proc_log", "feedback"
+        "nc_company", "nc_user", "passwd_pool", "proc_log", "feedback"
     )
     private final val CACHE_2_CLEAR = Seq(
         "user-token-signin-cache",
@@ -45,13 +46,46 @@ object NCSqlManager extends NCService with NCIgniteInstance {
         "stanford-cache",
         "opennlp-cache"
     )
-    
+
+    private final val JS_MAPPER = new ObjectMapper()
+
+    JS_MAPPER.registerModule(DefaultScalaModule)
+
     private object Config extends NCConfigurable {
         def init: Boolean = getBoolOpt("nlpcraft.server.database.igniteDbInitialize").getOrElse(false)
     }
 
-    @volatile private var usersPropsSeq: IgniteAtomicSequence = _
-    @volatile private var compPropsSeq: IgniteAtomicSequence = _
+    /**
+      *
+      * @param opt
+      */
+    @throws[NCE]
+    private def gzip(opt: Option[String]): String =
+        opt match {
+            case Some(s) ⇒ U.compress(s)
+            case None ⇒ null
+        }
+
+    /**
+      *
+      * @param nullable
+      */
+    @throws[NCE]
+    private def gzip(nullable: String): String = if (nullable != null) U.compress(nullable) else null
+
+    /**
+      *
+      * @param m
+      */
+    @throws[NCE]
+    private def gzip(m: JavaMeta): String = {
+        try
+            if (m != null) U.compress(JS_MAPPER.writeValueAsString(m)) else null
+        catch {
+            // TODO: text
+            case e: JsonProcessingException ⇒ throw new NCE("Serialization error", e)
+        }
+    }
 
     /**
      *
@@ -68,11 +102,6 @@ object NCSqlManager extends NCService with NCIgniteInstance {
         if (NCSql.isIgniteDb)
             prepareIgniteSchema()
      
-        catching(wrapIE) {
-            usersPropsSeq = NCSql.mkSeq(ignite, "usersPropsSeq", "nc_user_property", "id")
-            compPropsSeq = NCSql.mkSeq(ignite, "companiesPropsSeq", "nc_company_property", "id")
-        }
-     
         ackStarted()
     }
 
@@ -163,7 +192,6 @@ object NCSqlManager extends NCService with NCIgniteInstance {
     @throws[NCE]
     def deleteUser(id: Long, parent: Span): Int =
         startScopedSpan("deleteUser", parent, "usrId" → id) { _ ⇒
-            NCSql.delete("DELETE FROM nc_user_property WHERE user_id = ?", id)
             NCSql.delete("DELETE FROM nc_user WHERE id = ?", id)
         }
 
@@ -176,7 +204,6 @@ object NCSqlManager extends NCService with NCIgniteInstance {
     @throws[NCE]
     def deleteCompany(id: Long, parent: Span): Int =
         startScopedSpan("deleteCompany", parent, "compId" → id) { _ ⇒
-            NCSql.delete("DELETE FROM nc_user_property WHERE user_id IN (SELECT id FROM nc_user WHERE company_id = ?)", id)
             NCSql.delete("DELETE FROM nc_user WHERE company_id = ?", id)
             NCSql.delete("DELETE FROM nc_company WHERE id = ?", id)
         }
@@ -197,33 +224,28 @@ object NCSqlManager extends NCService with NCIgniteInstance {
         firstName: String,
         lastName: String,
         avatarUrl: Option[String],
-        propsOpt: Option[Map[String, String]],
+        propsOpt: Option[String],
         parent: Span
     ): Int =
-        startScopedSpan("updateUser", parent, "usrId" → id) { span ⇒
-            val n =
-                NCSql.update(
-                    s"""
-                       |UPDATE nc_user
-                       |SET
-                       |    first_name = ?,
-                       |    last_name = ?,
-                       |    avatar_url = ?,
-                       |    last_modified_on = ?
-                       |WHERE id = ?
-                        """.stripMargin,
-                    firstName,
-                    lastName,
-                    avatarUrl.orNull,
-                    U.nowUtcTs(),
-                    id
-                )
-         
-            NCSql.delete("DELETE FROM nc_user_property WHERE user_id = ?", id)
-         
-            addUserProperties(id, propsOpt, span)
-         
-            n
+        startScopedSpan("updateUser", parent, "usrId" → id) { _ ⇒
+            NCSql.update(
+                s"""
+                   |UPDATE nc_user
+                   |SET
+                   |    first_name = ?,
+                   |    last_name = ?,
+                   |    avatar_url = ?,
+                   |    properties_gzip = ?,
+                   |    last_modified_on = ?
+                   |WHERE id = ?
+                    """.stripMargin,
+                firstName,
+                lastName,
+                avatarUrl.orNull,
+                gzip(propsOpt),
+                U.nowUtcTs(),
+                id
+            )
         }
 
     /**
@@ -246,97 +268,32 @@ object NCSqlManager extends NCService with NCIgniteInstance {
         firstName: String,
         lastName: String,
         avatarUrl: Option[String],
-        propsOpt: Option[Map[String, String]],
+        propsOpt: Option[String],
         parent: Span
     ): Int =
         startScopedSpan("updateUser", parent, "usrId" → id) { span ⇒
-            val n =
-                NCSql.update(
-                    s"""
-                       |UPDATE nc_user
-                       |SET
-                       |    email = ?,
-                       |    passwd_salt = ?,
-                       |    first_name = ?,
-                       |    last_name = ?,
-                       |    avatar_url = ?,
-                       |    last_modified_on = ?
-                       |WHERE id = ?
-                        """.stripMargin,
-                    email,
-                    passwdSalt,
-                    firstName,
-                    lastName,
-                    avatarUrl.orNull,
-                    U.nowUtcTs(),
-                    id
-                )
-
-            NCSql.delete("DELETE FROM nc_user_property WHERE user_id = ?", id)
-
-            addUserProperties(id, propsOpt, span)
-
-            n
-        }
-
-    /**
-      *
-      * @param id
-      * @param propsOpt
-      * @param parent Optional parent span.
-      */
-    private def addUserProperties(id: Long, propsOpt: Option[Map[String, String]], parent: Span): Unit =
-        startScopedSpan("addUserProperties", parent, "usrId" → id) { _ ⇒
-            propsOpt match {
-                case Some(props) ⇒
-                    val now = U.nowUtcTs()
-         
-                    props.foreach { case (k, v) ⇒
-                        NCSql.insert(
-                            s"""
-                               |INSERT INTO nc_user_property (id, user_id, property, value, created_on, last_modified_on)
-                               |VALUES(?, ?, ?, ?, ?, ?)
-                            """.stripMargin,
-                            usersPropsSeq.getAndIncrement(),
-                            id,
-                            k,
-                            v,
-                            now,
-                            now
-                        )
-                    }
-                case None ⇒ // No-op.
-            }
-        }
-
-    /**
-      *
-      * @param id
-      * @param propsOpt
-      * @param parent Optional parent span.
-      */
-    private def addCompanyProperties(id: Long, propsOpt: Option[Map[String, String]], parent: Span): Unit =
-        startScopedSpan("addCompanyProperties", parent, "companyId" → id) { _ ⇒
-            propsOpt match {
-                case Some(props) ⇒
-                    val now = U.nowUtcTs()
-
-                    props.foreach { case (k, v) ⇒
-                        NCSql.insert(
-                            s"""
-                               |INSERT INTO nc_company_property (id, company_id, property, value, created_on, last_modified_on)
-                               |VALUES(?, ?, ?, ?, ?, ?)
-                            """.stripMargin,
-                            compPropsSeq.getAndIncrement(),
-                            id,
-                            k,
-                            v,
-                            now,
-                            now
-                        )
-                    }
-                case None ⇒ // No-op.
-            }
+            NCSql.update(
+                s"""
+                   |UPDATE nc_user
+                   |SET
+                   |    email = ?,
+                   |    passwd_salt = ?,
+                   |    first_name = ?,
+                   |    last_name = ?,
+                   |    avatar_url = ?,
+                   |    properties_gzip = ?,
+                   |    last_modified_on = ?
+                   |WHERE id = ?
+                    """.stripMargin,
+                email,
+                passwdSalt,
+                firstName,
+                lastName,
+                avatarUrl.orNull,
+                gzip(propsOpt),
+                U.nowUtcTs(),
+                id
+            )
         }
 
     /**
@@ -387,11 +344,11 @@ object NCSqlManager extends NCService with NCIgniteInstance {
         city: Option[String],
         address: Option[String],
         postalCode: Option[String],
-        propsOpt: Option[Map[String, String]],
+        propsOpt: Option[String],
         parent: Span
     ): Int =
         startScopedSpan("updateCompany", parent, "compId" → id) { _ ⇒
-            val res = NCSql.update(
+            NCSql.update(
                 s"""
                    |UPDATE nc_company
                    |SET
@@ -402,6 +359,7 @@ object NCSqlManager extends NCService with NCIgniteInstance {
                    |    city = ?,
                    |    address = ?,
                    |    postal_code = ?,
+                   |    properties_gzip = ?,
                    |    last_modified_on = ?
                    |WHERE id = ?
                     """.stripMargin,
@@ -412,15 +370,10 @@ object NCSqlManager extends NCService with NCIgniteInstance {
                 city.orNull,
                 address.orNull,
                 postalCode.orNull,
+                gzip(propsOpt),
                 U.nowUtcTs(),
                 id
             )
-
-            NCSql.delete("DELETE FROM nc_company_property WHERE company_id = ?", id)
-
-            addCompanyProperties(id, propsOpt, parent)
-
-            res
         }
 
     /**
@@ -530,34 +483,6 @@ object NCSqlManager extends NCService with NCIgniteInstance {
         }
 
     /**
-      * Gets user properties for given ID.
-      *
-      * @param id User ID.
-      * @param parent Optional parent span.
-      * @return User properties.
-      *
-      */
-    @throws[NCE]
-    def getUserProperties(id: Long, parent: Span): Seq[NCUserPropertyMdo] =
-        startScopedSpan("getUserProperties", parent, "usrId" → id) { _ ⇒
-            NCSql.select[NCUserPropertyMdo]("SELECT * FROM nc_user_property WHERE user_id = ?", id)
-        }
-
-    /**
-      * Gets company properties for given ID.
-      *
-      * @param id User ID.
-      * @param parent Optional parent span.
-      * @return Company properties.
-      *
-      */
-    @throws[NCE]
-    def getCompanyProperties(id: Long, parent: Span): Seq[NCCompanyPropertyMdo] =
-        startScopedSpan("getCompanyProperties", parent, "usrId" → id) { _ ⇒
-            NCSql.select[NCCompanyPropertyMdo]("SELECT * FROM nc_company_property WHERE company_id = ?", id)
-        }
-
-    /**
       * Gets user properties for given external ID.
       *
       * @param companyId Company ID.
@@ -593,15 +518,9 @@ object NCSqlManager extends NCService with NCIgniteInstance {
       * @param parent Optional parent span.
       */
     @throws[NCE]
-    def getAllUsers(compId: Long, parent: Span): Map[NCUserMdo, Seq[NCUserPropertyMdo]] =
+    def getAllUsers(compId: Long, parent: Span): Seq[NCUserMdo] =
         startScopedSpan("getAllUsers", parent, "compId" → compId) { _ ⇒
-            val props = NCSql.select[NCUserPropertyMdo](
-                "SELECT * FROM nc_user_property WHERE user_id IN (SELECT id FROM nc_user WHERE company_id = ?)",
-                compId
-            ).groupBy(_.userId)
-         
-            NCSql.select[NCUserMdo]("SELECT * FROM nc_user WHERE company_id = ?", compId).
-                map(p ⇒ p → props.getOrElse(p.id, Nil)).toMap
+            NCSql.select[NCUserMdo]("SELECT * FROM nc_user WHERE company_id = ?", compId)
         }
 
     /**
@@ -643,7 +562,7 @@ object NCSqlManager extends NCService with NCIgniteInstance {
         city: Option[String],
         address: Option[String],
         postalCode: Option[String],
-        propsOpt: Option[Map[String, String]],
+        propsOpt: Option[String],
         parent: Span
     ): Unit =
         startScopedSpan("addCompany", parent, "compId" → id, "name" → name, "tkn" → tkn) { _ ⇒
@@ -663,10 +582,11 @@ object NCSqlManager extends NCService with NCIgniteInstance {
                   |    postal_code,
                   |    auth_token,
                   |    auth_token_hash,
+                  |    properties_gzip,
                   |    created_on,
                   |    last_modified_on
                   |)
-                  | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+                  | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
               """.stripMargin,
                 id,
                 name,
@@ -678,11 +598,10 @@ object NCSqlManager extends NCService with NCIgniteInstance {
                 postalCode.orNull,
                 tkn,
                 U.mkSha256Hash(tkn),
+                gzip(propsOpt),
                 now,
                 now
             )
-
-            addCompanyProperties(id, propsOpt, parent)
         }
 
     /**
@@ -711,7 +630,7 @@ object NCSqlManager extends NCService with NCIgniteInstance {
         avatarUrl: Option[String],
         passwdSalt: Option[String],
         isAdmin: Boolean,
-        propsOpt: Option[Map[String, String]],
+        propsOpt: Option[String],
         parent: Span
     ): Unit = {
         require(usrExtId.isDefined ^ email.isDefined)
@@ -739,11 +658,12 @@ object NCSqlManager extends NCService with NCIgniteInstance {
                   |    email,
                   |    passwd_salt,
                   |    avatar_url,
+                  |    properties_gzip,
                   |    is_admin,
                   |    created_on,
                   |    last_modified_on
                   | )
-                  | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+                  | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                 """.stripMargin,
                 id,
                 compId,
@@ -753,12 +673,11 @@ object NCSqlManager extends NCService with NCIgniteInstance {
                 email.orNull,
                 passwdSalt.orNull,
                 avatarUrl.orNull,
+                gzip(propsOpt),
                 isAdmin,
                 now,
                 now
             )
-
-            addUserProperties(id, propsOpt, span)
         }
     }
     
@@ -852,6 +771,7 @@ object NCSqlManager extends NCService with NCIgniteInstance {
       * @param errMsg
       * @param resType
       * @param resBody
+      * @param resMeta
       * @param intentId
       * @param tstamp
       * @param parent Optional parent span.
@@ -862,9 +782,11 @@ object NCSqlManager extends NCService with NCIgniteInstance {
         errMsg: String,
         resType: String,
         resBody: String,
+        resMeta: JavaMeta,
         intentId: String,
         tstamp: Timestamp,
-        parent: Span): Unit =
+        parent: Span
+    ): Unit =
         startScopedSpan("updateReadyProcessingLog", parent, "srvReqId" → srvReqId) { _ ⇒
             NCSql.update(
                 """
@@ -874,6 +796,7 @@ object NCSqlManager extends NCService with NCIgniteInstance {
                   |    error = ?,
                   |    res_type = ?,
                   |    res_body_gzip = ?,
+                  |    res_meta_gzip = ?,
                   |    intent_id = ?,
                   |    resp_tstamp = ?
                   |WHERE srv_req_id = ?
@@ -881,7 +804,8 @@ object NCSqlManager extends NCService with NCIgniteInstance {
                 QRY_READY.toString,
                 errMsg,
                 resType,
-                if (resBody == null) null else U.compress(resBody),
+                gzip(resBody),
+                gzip(resMeta),
                 intentId,
                 tstamp,
                 srvReqId
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/user/NCUserManager.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/user/NCUserManager.scala
index 4471b31..7107085 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/user/NCUserManager.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/user/NCUserManager.scala
@@ -27,7 +27,7 @@ import org.apache.nlpcraft.common.config.NCConfigurable
 import org.apache.nlpcraft.common.{NCService, _}
 import org.apache.nlpcraft.server.ignite.NCIgniteHelpers._
 import org.apache.nlpcraft.server.ignite.NCIgniteInstance
-import org.apache.nlpcraft.server.mdo.{NCUserMdo, NCUserPropertyMdo}
+import org.apache.nlpcraft.server.mdo.NCUserMdo
 import org.apache.nlpcraft.server.sql.{NCSql, NCSqlManager}
 import org.apache.nlpcraft.server.tx.NCTxManager
 
@@ -190,7 +190,7 @@ object NCUserManager extends NCService with NCIgniteInstance {
       * @param parent Optional parent span.
       */
     @throws[NCE]
-    def getAllUsers(compId: Long, parent: Span = null): Map[NCUserMdo, Seq[NCUserPropertyMdo]] =
+    def getAllUsers(compId: Long, parent: Span = null): Seq[NCUserMdo] =
         NCSql.sql {
             NCSqlManager.getAllUsers(compId, parent)
         }
@@ -313,20 +313,6 @@ object NCUserManager extends NCService with NCIgniteInstance {
         }
 
     /**
-      * Gets user properties for given user ID.
-      *
-      * @param id User ID.
-      * @param parent Optional parent span.
-      */
-    @throws[NCE]
-    def getUserProperties(id: Long, parent: Span = null): Seq[NCUserPropertyMdo] =
-        startScopedSpan("getUserProperties", parent, "usrId" → id) { span ⇒
-            NCSql.sql {
-                NCSqlManager.getUserProperties(id, span)
-            }
-        }
-
-    /**
       *
       * @param email User email (as username).
       * @param passwd User password.
@@ -391,7 +377,7 @@ object NCUserManager extends NCService with NCIgniteInstance {
         firstName: String,
         lastName: String,
         avatarUrl: Option[String],
-        props: Option[Map[String, String]],
+        props: Option[String],
         parent: Span = null
     ): Unit =
         startScopedSpan("updateUser", parent, "usrId" → id) { span ⇒
@@ -488,7 +474,7 @@ object NCUserManager extends NCService with NCIgniteInstance {
         lastName: String,
         avatarUrl: Option[String],
         isAdmin: Boolean,
-        props: Option[Map[String, String]],
+        props: Option[String],
         usrExtIdOpt: Option[String],
         parent: Span = null
     ): Long =
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/server/rest/NCRestSpec.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/server/rest/NCRestSpec.scala
index 98bc498..098b582 100644
--- a/nlpcraft/src/test/scala/org/apache/nlpcraft/server/rest/NCRestSpec.scala
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/server/rest/NCRestSpec.scala
@@ -48,6 +48,9 @@ object NCRestSpec {
             val code = resp.getStatusLine.getStatusCode
             val js = mkJs(code, resp.getEntity)
 
+            if (js == null)
+                throw new RuntimeException(s"Unexpected response [code=$code, response=$js]")
+
             code match {
                 case 200 ⇒ GSON.fromJson(js, TYPE_RESP)
 
diff --git a/openapi/nlpcraft_swagger.yml b/openapi/nlpcraft_swagger.yml
index f8b0114..fbf0f1c 100644
--- a/openapi/nlpcraft_swagger.yml
+++ b/openapi/nlpcraft_swagger.yml
@@ -871,7 +871,7 @@ paths:
                 maxLength: 512000
               properties:
                 type: object
-                description: <em>Optional.</em> Additional user properties.
+                description: <em>Optional.</em> Additional user properties with maximum JSON length of 512000 bytes.
                 additionalProperties:
                   type: string
       responses:
@@ -1064,7 +1064,7 @@ paths:
                 description: Admin flag.
               properties:
                 type: object
-                description: <em>Optional.</em> Additional user properties.
+                description: <em>Optional.</em> Additional user properties with maximum JSON length of 512000 bytes.
                 additionalProperties:
                   type: string
               usrExtId:
@@ -1240,7 +1240,7 @@ paths:
                 maxLength: 512000
               properties:
                 type: object
-                description: <em>Optional.</em> Additional company properties.
+                description: <em>Optional.</em> Additional company properties with maximum JSON length of 512000 bytes.
                 additionalProperties:
                   type: string
       responses:
@@ -1323,7 +1323,7 @@ paths:
                 maxLength: 512
               properties:
                 type: object
-                description: <em>Optional.</em> Additional company properties.
+                description: <em>Optional.</em> Additional company properties with maximum JSON length of 512000 bytes.
                 additionalProperties:
                   type: string
       responses:
diff --git a/sql/mysql/drop_schema.sql b/sql/mysql/drop_schema.sql
index fba01aa..e1ead27 100644
--- a/sql/mysql/drop_schema.sql
+++ b/sql/mysql/drop_schema.sql
@@ -18,9 +18,7 @@
 USE nlpcraft;
 
 DROP TABLE IF EXISTS proc_log;
-DROP TABLE IF EXISTS nc_user_property;
 DROP TABLE IF EXISTS nc_user;
-DROP TABLE IF EXISTS nc_company_property;
 DROP TABLE IF EXISTS nc_company;
 DROP TABLE IF EXISTS passwd_pool;
 DROP TABLE IF EXISTS feedback;
diff --git a/sql/mysql/schema.sql b/sql/mysql/schema.sql
index cac0bf8..9022ab6 100644
--- a/sql/mysql/schema.sql
+++ b/sql/mysql/schema.sql
@@ -39,6 +39,7 @@ CREATE TABLE nc_company (
     postal_code VARCHAR(32),
     auth_token VARCHAR(64) NOT NULL,
     auth_token_hash VARCHAR(64) NOT NULL,
+    properties_gzip TEXT NULL,
     created_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
     last_modified_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3)
 );
@@ -48,19 +49,6 @@ CREATE UNIQUE INDEX nc_company_idx_2 ON nc_company(auth_token);
 CREATE UNIQUE INDEX nc_company_idx_3 ON nc_company(auth_token_hash);
 
 --
--- Company properties table.
---
-CREATE TABLE nc_company_property (
-    id SERIAL PRIMARY KEY,
-    company_id BIGINT UNSIGNED NOT NULL,
-    property VARCHAR(64) NOT NULL,
-    value VARCHAR(512) NULL,
-    created_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
-    last_modified_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
-    FOREIGN KEY (company_id) REFERENCES nc_company(id)
-);
-
---
 -- User table.
 --
 CREATE TABLE nc_user (
@@ -73,6 +61,7 @@ CREATE TABLE nc_user (
     last_name VARCHAR(64) NULL,
     is_admin BOOLEAN NOT NULL, -- Whether or not created with admin token.
     passwd_salt VARCHAR(64) NULL,
+    properties_gzip TEXT NULL,
     created_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
     last_modified_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
     FOREIGN KEY (company_id) REFERENCES nc_company(id)
@@ -82,19 +71,6 @@ CREATE UNIQUE INDEX nc_user_idx_1 ON nc_user(email);
 CREATE UNIQUE INDEX nc_user_idx_2 ON nc_user(company_id, ext_id);
 
 --
--- User properties table.
---
-CREATE TABLE nc_user_property (
-    id SERIAL PRIMARY KEY,
-    user_id BIGINT UNSIGNED NOT NULL,
-    property VARCHAR(64) NOT NULL,
-    value VARCHAR(512) NULL,
-    created_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
-    last_modified_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
-    FOREIGN KEY (user_id) REFERENCES nc_user(id)
-);
-
---
 -- Pool of password hashes.
 --
 CREATE TABLE passwd_pool (
@@ -122,6 +98,7 @@ CREATE TABLE proc_log (
     -- Result parts.
     res_type VARCHAR(32) NULL,
     res_body_gzip TEXT NULL, -- GZIP-ed result body.
+    res_body_meta TEXT NULL, -- GZIP-ed result meta.
     intent_id VARCHAR(256) NULL,
     error TEXT NULL,
     -- Probe information for this request.
diff --git a/sql/oracle/drop_schema.sql b/sql/oracle/drop_schema.sql
index 652bdb8..78bd027 100644
--- a/sql/oracle/drop_schema.sql
+++ b/sql/oracle/drop_schema.sql
@@ -24,14 +24,6 @@ EXCEPTION WHEN OTHERS THEN
 END;
 
 BEGIN
-  EXECUTE IMMEDIATE 'DROP TABLE nc_user_property';
-EXCEPTION WHEN OTHERS THEN
-  IF SQLCODE != -942 THEN
-    RAISE;
-  END IF;
-END;
-
-BEGIN
   EXECUTE IMMEDIATE 'DROP TABLE nc_user';
 EXCEPTION WHEN OTHERS THEN
   IF SQLCODE != -942 THEN
@@ -40,14 +32,6 @@ EXCEPTION WHEN OTHERS THEN
 END;
 
 BEGIN
-  EXECUTE IMMEDIATE 'DROP TABLE nc_company_property';
-EXCEPTION WHEN OTHERS THEN
-  IF SQLCODE != -942 THEN
-    RAISE;
-END IF;
-END;
-
-BEGIN
   EXECUTE IMMEDIATE 'DROP TABLE nc_company';
 EXCEPTION WHEN OTHERS THEN
   IF SQLCODE != -942 THEN
diff --git a/sql/oracle/schema.sql b/sql/oracle/schema.sql
index 1ad495c..da243b9 100644
--- a/sql/oracle/schema.sql
+++ b/sql/oracle/schema.sql
@@ -37,6 +37,7 @@ CREATE TABLE nc_company (
     postal_code VARCHAR2(32),
     auth_token VARCHAR2(64) NOT NULL,
     auth_token_hash VARCHAR2(64) NOT NULL,
+    properties_gzip CLOB NULL,
     created_on DATE DEFAULT sysdate NOT NULL,
     last_modified_on DATE DEFAULT sysdate NOT NULL
 );
@@ -46,21 +47,6 @@ CREATE UNIQUE INDEX nc_company_idx_2 ON nc_company(auth_token);
 CREATE UNIQUE INDEX nc_company_idx_3 ON nc_company(auth_token_hash);
 
 --
--- Company properties table.
---
-CREATE TABLE nc_company_property (
-    id NUMBER PRIMARY KEY,
-    company_id NUMBER NOT NULL,
-    property VARCHAR2(64) NOT NULL,
-    value VARCHAR2(512) NULL,
-    created_on DATE DEFAULT sysdate NOT NULL,
-    last_modified_on DATE DEFAULT sysdate NOT NULL,
-    CONSTRAINT fk_company_id_company_property FOREIGN KEY (company_id) REFERENCES nc_company(id)
-);
-
-CREATE INDEX nc_company_property_idx1 ON nc_company_property(company_id);
-
---
 -- User table.
 --
 CREATE TABLE nc_user (
@@ -73,6 +59,7 @@ CREATE TABLE nc_user (
     last_name VARCHAR2(64) NULL,
     is_admin NUMBER(1) NOT NULL, -- Whether or not created with admin token.
     passwd_salt VARCHAR2(64) NULL,
+    properties_gzip CLOB NULL,
     created_on DATE DEFAULT sysdate NOT NULL,
     last_modified_on DATE DEFAULT sysdate NOT NULL,
     CONSTRAINT fk_company_id_user FOREIGN KEY (company_id) REFERENCES nc_company(id)
@@ -83,21 +70,6 @@ CREATE UNIQUE INDEX nc_user_idx_2 ON nc_user(company_id, ext_id);
 CREATE INDEX nc_user_idx_3 ON nc_user(company_id);
 
 --
--- User properties table.
---
-CREATE TABLE nc_user_property (
-    id NUMBER PRIMARY KEY,
-    user_id NUMBER NOT NULL,
-    property VARCHAR2(64) NOT NULL,
-    value VARCHAR2(512) NULL,
-    created_on DATE DEFAULT sysdate NOT NULL,
-    last_modified_on DATE DEFAULT sysdate NOT NULL,
-    CONSTRAINT fk_user_id_user_property FOREIGN KEY (user_id) REFERENCES nc_user(id)
-);
-
-CREATE INDEX nc_user_property_idx1 ON nc_user_property(user_id);
-
---
 -- Pool of password hashes.
 --
 CREATE TABLE passwd_pool (
@@ -125,6 +97,7 @@ CREATE TABLE proc_log (
     -- Result parts.
     res_type VARCHAR2(32) NULL,
     res_body_gzip CLOB NULL, -- GZIP-ed result body.
+    res_body_meta CLOB NULL, -- GZIP-ed result meta.
     intent_id VARCHAR2(256) NULL,
     error CLOB NULL,
     -- Probe information for this request.
diff --git a/sql/postgres/drop_schema.sql b/sql/postgres/drop_schema.sql
index b1ce0b9..f985bae 100644
--- a/sql/postgres/drop_schema.sql
+++ b/sql/postgres/drop_schema.sql
@@ -16,9 +16,7 @@
 --
 
 DROP TABLE IF EXISTS proc_log;
-DROP TABLE IF EXISTS nc_user_property;
 DROP TABLE IF EXISTS nc_user;
-DROP TABLE IF EXISTS nc_company_property;
 DROP TABLE IF EXISTS nc_company;
 DROP TABLE IF EXISTS passwd_pool;
 DROP TABLE IF EXISTS feedback;
\ No newline at end of file
diff --git a/sql/postgres/schema.sql b/sql/postgres/schema.sql
index 561418e..8329ed7 100644
--- a/sql/postgres/schema.sql
+++ b/sql/postgres/schema.sql
@@ -37,6 +37,7 @@ CREATE TABLE nc_company (
     postal_code VARCHAR(32),
     auth_token VARCHAR(64) NOT NULL,
     auth_token_hash VARCHAR(64) NOT NULL,
+    properties_gzip TEXT NULL,
     created_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
     last_modified_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3)
 );
@@ -46,21 +47,6 @@ CREATE UNIQUE INDEX nc_company_idx_2 ON nc_company(auth_token);
 CREATE UNIQUE INDEX nc_company_idx_3 ON nc_company(auth_token_hash);
 
 --
--- Company properties table.
---
-CREATE TABLE nc_company_property (
-    id SERIAL PRIMARY KEY,
-    company_id BIGINT NOT NULL,
-    property VARCHAR(64) NOT NULL,
-    value VARCHAR(512) NULL,
-    created_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
-    last_modified_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
-    FOREIGN KEY (company_id) REFERENCES nc_company(id)
-);
-
-CREATE INDEX nc_company_property_idx1 ON nc_company_property(company_id);
-
---
 --
 -- User table.
 --
@@ -74,6 +60,7 @@ CREATE TABLE nc_user (
     last_name VARCHAR(64) NULL,
     is_admin BOOL NOT NULL, -- Whether or not created with admin token.
     passwd_salt VARCHAR(64) NULL,
+    properties_gzip TEXT NULL,
     created_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
     last_modified_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
     FOREIGN KEY (company_id) REFERENCES nc_company(id)
@@ -92,21 +79,6 @@ CREATE TABLE passwd_pool (
 );
 
 --
--- User properties table.
---
-CREATE TABLE nc_user_property (
-    id SERIAL PRIMARY KEY,
-    user_id BIGINT NOT NULL,
-    property VARCHAR(64) NOT NULL,
-    value VARCHAR(512) NULL,
-    created_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
-    last_modified_on TIMESTAMP(3) NOT NULL DEFAULT current_timestamp(3),
-    FOREIGN KEY (user_id) REFERENCES nc_user(id)
-);
-
-CREATE INDEX nc_user_property_idx1 ON nc_user_property(user_id);
-
---
 -- Processing log.
 --
 CREATE TABLE proc_log (
@@ -126,6 +98,7 @@ CREATE TABLE proc_log (
     -- Result parts.
     res_type VARCHAR(32) NULL,
     res_body_gzip TEXT NULL, -- GZIP-ed result body.
+    res_body_meta TEXT NULL, -- GZIP-ed result body.
     intent_id VARCHAR(256) NULL,
     error TEXT NULL,
     -- Probe information for this request.