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/09/11 06:40:12 UTC

[incubator-nlpcraft] branch master updated: NCElement definition annotations added.

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 6b12761  NCElement definition annotations added.
6b12761 is described below

commit 6b12761177ba8b60601c730e4721eb9127f7baa6
Author: Sergey Kamov <sk...@gmail.com>
AuthorDate: Sat Sep 11 09:34:39 2021 +0300

    NCElement definition annotations added.
---
 .../org/apache/nlpcraft/model/NCAddElement.java    |  51 ++++
 .../apache/nlpcraft/model/NCAddElementClass.java   |  51 ++++
 .../probe/mgrs/deploy/NCDeployManager.scala        | 339 +++++++++++++++++----
 .../apache/nlpcraft/probe/mgrs/deploy/readme.txt   |  18 ++
 .../mgrs/deploy1/NCElementAnnotationsSpec.scala    | 122 ++++++++
 .../mgrs/deploy1/pack/NCElementAnnotations2.scala  |  34 +++
 .../apache/nlpcraft/probe/mgrs/deploy1/readme.txt  |  18 ++
 7 files changed, 567 insertions(+), 66 deletions(-)

diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCAddElement.java b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCAddElement.java
new file mode 100644
index 0000000..b6aadfd
--- /dev/null
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCAddElement.java
@@ -0,0 +1,51 @@
+/*
+ * 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.model;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+// TODO: json or yaml
+@Documented
+@Retention(value=RUNTIME)
+@Target(value=METHOD)
+@Repeatable(NCAddElement.NCAddElementList.class)
+public @interface NCAddElement {
+    /**
+     * ID of the intent term.
+     *
+     * @return ID of the intent term.
+     */
+    String value();
+
+    /**
+     *
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(value=METHOD)
+    @Documented
+    @interface NCAddElementList {
+        NCAddElement[] value();
+    }
+}
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCAddElementClass.java b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCAddElementClass.java
new file mode 100644
index 0000000..fa0d345
--- /dev/null
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCAddElementClass.java
@@ -0,0 +1,51 @@
+/*
+ * 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.model;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+// TODO: class name
+@Documented
+@Retention(value=RUNTIME)
+@Target(value=METHOD)
+@Repeatable(NCAddElementClass.NCAddElementClassList.class)
+public @interface NCAddElementClass {
+    /**
+     * ID of the intent term.
+     *
+     * @return ID of the intent term.
+     */
+    Class<?> value();
+
+    /**
+     * 
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(value=METHOD)
+    @Documented
+    @interface NCAddElementClassList {
+        NCAddElementClass[] value();
+    }
+}
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/deploy/NCDeployManager.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/deploy/NCDeployManager.scala
index fddc682..490b62d 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/deploy/NCDeployManager.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/deploy/NCDeployManager.scala
@@ -17,6 +17,9 @@
 
 package org.apache.nlpcraft.probe.mgrs.deploy
 
+import com.fasterxml.jackson.core.JsonParser
+import com.fasterxml.jackson.databind.{DeserializationFeature, ObjectMapper}
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
 import com.google.common.reflect.ClassPath
 
 import java.io._
@@ -35,17 +38,19 @@ import org.apache.nlpcraft.common.nlp.core.{NCNlpCoreManager, NCNlpPorterStemmer
 import org.apache.nlpcraft.common.util.NCUtils.{IDL_FIX, REGEX_FIX}
 import org.apache.nlpcraft.model._
 import org.apache.nlpcraft.model.factories.basic.NCBasicModelFactory
+import org.apache.nlpcraft.model.impl.json.NCElementJson
+import org.apache.nlpcraft.model.intent._
 import org.apache.nlpcraft.model.intent.compiler.NCIdlCompiler
 import org.apache.nlpcraft.model.intent.solver.NCIntentSolver
-import org.apache.nlpcraft.model.intent._
 import org.apache.nlpcraft.probe.mgrs.NCProbeSynonymChunkKind.{IDL, REGEX, TEXT}
 import org.apache.nlpcraft.probe.mgrs.{NCProbeModel, NCProbeModelCallback, NCProbeSynonym, NCProbeSynonymChunk, NCProbeSynonymsWrapper}
 
 import java.lang.annotation.Annotation
+import java.util.Optional
 import scala.util.Using
 import scala.compat.java8.OptionConverters._
 import scala.collection.mutable
-import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, MapHasAsScala, SetHasAsScala}
+import scala.jdk.CollectionConverters.{ListHasAsScala, MapHasAsJava, MapHasAsScala, SeqHasAsJava, SetHasAsJava, SetHasAsScala}
 import scala.util.control.Exception._
 
 /**
@@ -63,6 +68,8 @@ object NCDeployManager extends NCService {
     private final val CLS_SAMPLE_REF = classOf[NCIntentSampleRef]
     private final val CLS_MDL_CLS_REF = classOf[NCModelAddClasses]
     private final val CLS_MDL_PKGS_REF = classOf[NCModelAddPackage]
+    private final val CLS_ELEM_DEF = classOf[NCAddElement]
+    private final val CLS_ELEM_DEF_CLASS = classOf[NCAddElementClass]
 
     // Java and scala lists.
     private final val CLS_SCALA_SEQ = classOf[Seq[_]]
@@ -92,6 +99,13 @@ object NCDeployManager extends NCService {
     private final val SEPARATORS = Seq('?', ',', '.', '-', '!')
     private final val SUSP_SYNS_CHARS = Seq("?", "*", "+")
 
+    private val MAPPER_YAML = new ObjectMapper(new YAMLFactory)
+    private val MAPPER_JSON = new ObjectMapper
+
+    MAPPER_JSON.enable(JsonParser.Feature.ALLOW_COMMENTS)
+    MAPPER_JSON.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
+    MAPPER_YAML.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
+
     @volatile private var data: mutable.ArrayBuffer[NCProbeModel] = _
     @volatile private var mdlFactory: NCModelFactory = _
 
@@ -170,29 +184,96 @@ object NCDeployManager extends NCService {
 
         val mdlId = mdl.getId
 
-        for (elm <- mdl.getElements.asScala) {
+        val annElems = scanElementsAnnotations(mdl)
+
+        val wrappedMdl =
+            if (annElems.nonEmpty)
+                new NCModel {
+                    private val allElems =
+                        (annElems ++ (if (mdl.getElements == null) Set.empty else mdl.getElements.asScala)).
+                            asJava
+
+                    // One wrapped method.
+                    override def getElements: util.Set[NCElement] = allElems
+
+                    // Other methods delegated.
+                    override def getId: String = mdl.getId
+                    override def getName: String = mdl.getName
+                    override def getVersion: String = mdl.getVersion
+
+                    override def onParsedVariant(`var`: NCVariant): Boolean = mdl.onParsedVariant(`var`)
+                    override def onContext(ctx: NCContext): NCResult = mdl.onContext(ctx)
+                    override def onMatchedIntent(ctx: NCIntentMatch): Boolean = mdl.onMatchedIntent(ctx)
+                    override def onResult(ctx: NCIntentMatch, res: NCResult): NCResult = mdl.onResult(ctx, res)
+                    override def onRejection(ctx: NCIntentMatch, e: NCRejection): NCResult = mdl.onRejection(ctx, e)
+                    override def onError(ctx: NCContext, e: Throwable): NCResult = mdl.onError(ctx, e)
+                    override def getDescription: String = mdl.getDescription
+                    override def getOrigin: String = mdl.getOrigin
+                    override def getMaxUnknownWords: Int = mdl.getMaxUnknownWords
+                    override def getMaxFreeWords: Int = mdl.getMaxFreeWords
+                    override def getMaxSuspiciousWords: Int = mdl.getMaxSuspiciousWords
+                    override def getMinWords: Int = mdl.getMinWords
+                    override def getMaxWords: Int = mdl.getMaxWords
+                    override def getMinTokens: Int = mdl.getMinTokens
+                    override def getMaxTokens: Int = mdl.getMaxTokens
+                    override def getMinNonStopwords: Int = mdl.getMinNonStopwords
+                    override def isNonEnglishAllowed: Boolean = mdl.isNonEnglishAllowed
+                    override def isNotLatinCharsetAllowed: Boolean = mdl.isNotLatinCharsetAllowed
+                    override def isSwearWordsAllowed: Boolean = mdl.isSwearWordsAllowed
+                    override def isNoNounsAllowed: Boolean = mdl.isNoNounsAllowed
+                    override def isPermutateSynonyms: Boolean = mdl.isPermutateSynonyms
+                    override def isDupSynonymsAllowed: Boolean = mdl.isDupSynonymsAllowed
+                    override def getMaxTotalSynonyms: Int = mdl.getMaxTotalSynonyms
+                    override def isNoUserTokensAllowed: Boolean = mdl.isNoUserTokensAllowed
+                    override def isSparse: Boolean = mdl.isSparse
+                    override def getMetadata: util.Map[String, AnyRef] = mdl.getMetadata
+                    override def getAdditionalStopWords: util.Set[String] = mdl.getAdditionalStopWords
+                    override def getExcludedStopWords: util.Set[String] = mdl.getExcludedStopWords
+                    override def getSuspiciousWords: util.Set[String] = mdl.getSuspiciousWords
+                    override def getMacros: util.Map[String, String] = mdl.getMacros
+                    override def getParsers: util.List[NCCustomParser] = mdl.getParsers
+                    override def getEnabledBuiltInTokens: util.Set[String] = mdl.getEnabledBuiltInTokens
+                    override def getAbstractTokens: util.Set[String] = mdl.getAbstractTokens
+                    override def getMaxElementSynonyms: Int = mdl.getMaxElementSynonyms
+                    override def isMaxSynonymsThresholdError: Boolean = mdl.isMaxSynonymsThresholdError
+                    override def getConversationTimeout: Long = mdl.getConversationTimeout
+                    override def getConversationDepth: Int = mdl.getConversationDepth
+                    override def getRestrictedCombinations: util.Map[String, util.Set[String]] =
+                        mdl.getRestrictedCombinations
+
+                    override def onInit(): Unit = mdl.onInit()
+                    override def onDiscard(): Unit = mdl.onDiscard()
+
+                    override def metaOpt[T](prop: String): Optional[T] = mdl.metaOpt(prop)
+                    override def meta[T](prop: String): T = mdl.meta(prop)
+                    override def metax[T](prop: String): T = mdl.metax(prop)
+                    override def meta[T](prop: String, dflt: T): T = mdl.meta(prop, dflt)
+                }
+            else
+                mdl
+
+        for (elm <- wrappedMdl.getElements.asScala)
             if (!elm.getId.matches(ID_REGEX))
                 throw new NCE(
-                s"Model element ID does not match regex [" +
+                    s"Model element ID does not match regex [" +
                     s"mdlId=$mdlId, " +
                     s"elmId=${elm.getId}, " +
                     s"regex=$ID_REGEX" +
-                s"]"
+                    s"]"
             )
-        }
 
-        checkMacros(mdl)
+        checkMacros(wrappedMdl)
 
         val parser = new NCMacroParser
 
         // Initialize macro parser.
-        mdl.getMacros.asScala.foreach(t => parser.addMacro(t._1, t._2))
+        wrappedMdl.getMacros.asScala.foreach(t => parser.addMacro(t._1, t._2))
 
-        for (elm <- mdl.getElements.asScala)
-            checkElement(mdl, elm)
+        for (elm <- wrappedMdl.getElements.asScala)
+            checkElement(wrappedMdl, elm)
 
-        checkElementIdsDups(mdl)
-        checkCyclicDependencies(mdl)
+        checkElementIdsDups(wrappedMdl)
+        checkCyclicDependencies(wrappedMdl)
 
         /**
          *
@@ -211,9 +292,9 @@ object NCDeployManager extends NCService {
                 else
                     NCNlpCoreManager.stem(word)
 
-        val addStopWords = checkAndStemmatize(mdl.getAdditionalStopWords, "additionalStopWords")
-        val exclStopWords = checkAndStemmatize(mdl.getExcludedStopWords, "excludedStopWords")
-        val suspWords = checkAndStemmatize(mdl.getSuspiciousWords, "suspiciousWord")
+        val addStopWords = checkAndStemmatize(wrappedMdl.getAdditionalStopWords, "additionalStopWords")
+        val exclStopWords = checkAndStemmatize(wrappedMdl.getExcludedStopWords, "excludedStopWords")
+        val suspWords = checkAndStemmatize(wrappedMdl.getSuspiciousWords, "suspiciousWord")
 
         checkStopwordsDups(mdlId, addStopWords, exclStopWords)
 
@@ -224,10 +305,10 @@ object NCDeployManager extends NCService {
         def sparse(syns: Set[SynonymHolder], sp: Boolean): Set[SynonymHolder] = syns.filter(s => ok(s.syn.sparse, sp))
 
         var cnt = 0
-        val maxCnt = mdl.getMaxTotalSynonyms
+        val maxCnt = wrappedMdl.getMaxTotalSynonyms
 
         // Process and check elements.
-        for (elm <- mdl.getElements.asScala) {
+        for (elm <- wrappedMdl.getElements.asScala) {
             val elmId = elm.getId
 
             // Checks before macros processing.
@@ -242,8 +323,8 @@ object NCDeployManager extends NCService {
                     s"]"
                 )
 
-            val sparseElem = elm.isSparse.orElse(mdl.isSparse)
-            val permuteElem = elm.isPermutateSynonyms.orElse(mdl.isPermutateSynonyms)
+            val sparseElem = elm.isSparse.orElse(wrappedMdl.isSparse)
+            val permuteElem = elm.isPermutateSynonyms.orElse(wrappedMdl.isPermutateSynonyms)
 
             def addSynonym(
                 isElementId: Boolean,
@@ -260,7 +341,7 @@ object NCDeployManager extends NCService {
                     if (syns.add(holder)) {
                         cnt += 1
 
-                        if (mdl.isMaxSynonymsThresholdError && cnt > maxCnt)
+                        if (wrappedMdl.isMaxSynonymsThresholdError && cnt > maxCnt)
                             throw new NCE(s"Too many total synonyms detected [" +
                                 s"mdlId=$mdlId, " +
                                 s"cnt=$cnt, " +
@@ -348,7 +429,7 @@ object NCDeployManager extends NCService {
 
                 chunks ++= U.splitTrimFilter(x.substring(start), " ")
 
-                chunks.map(mkChunk(mdl, _))
+                chunks.map(mkChunk(wrappedMdl, _))
             }
 
             /**
@@ -392,10 +473,13 @@ object NCDeployManager extends NCService {
             val vals =
                 (if (elm.getValues != null) elm.getValues.asScala else Seq.empty) ++
                 (
-                    elm.getValueLoader.asScala match {
-                        case Some(ldr) => ldr.load(elm).asScala
-                        case None => Seq.empty
-                    }
+                    if (elm.getValueLoader == null)
+                        Seq.empty
+                    else
+                        elm.getValueLoader.asScala match {
+                            case Some(ldr) => ldr.load(elm).asScala
+                            case None => Seq.empty
+                        }
                 )
 
             // Add value synonyms.
@@ -436,7 +520,7 @@ object NCDeployManager extends NCService {
             }
         }
 
-        if (cnt > maxCnt && !mdl.isMaxSynonymsThresholdError)
+        if (cnt > maxCnt && !wrappedMdl.isMaxSynonymsThresholdError)
             logger.warn(
                 s"Too many total synonyms detected [" +
                   s"mdlId=$mdlId, " +
@@ -445,7 +529,7 @@ object NCDeployManager extends NCService {
               s"]")
 
         // Discard value loaders.
-        for (elm <- mdl.getElements.asScala)
+        for (elm <- wrappedMdl.getElements.asScala if elm.getValueLoader != null)
             elm.getValueLoader.ifPresent(_.onDiscard())
 
         val allAliases = syns
@@ -463,7 +547,7 @@ object NCDeployManager extends NCService {
                 s"dups=${allAliases.diff(allAliases.distinct).mkString(", ")}" +
             s"]")
 
-        val idAliasDups = mdl.getElements.asScala.map(_.getId).intersect(allAliases.toSet)
+        val idAliasDups = wrappedMdl.getElements.asScala.map(_.getId).intersect(allAliases.toSet)
 
         // Check that IDL aliases don't intersect with element IDs.
         if (idAliasDups.nonEmpty)
@@ -486,7 +570,7 @@ object NCDeployManager extends NCService {
         }
 
         if (dupSyns.nonEmpty) {
-            if (mdl.isDupSynonymsAllowed) {
+            if (wrappedMdl.isDupSynonymsAllowed) {
                 val tbl = NCAsciiTable("Elements", "Dup Synonym")
 
                 dupSyns.foreach(row => tbl += (
@@ -506,7 +590,7 @@ object NCDeployManager extends NCService {
         }
 
         // Scan for intent annotations in the model class.
-        val intents = scanIntents(mdl)
+        val intents = scanIntents(mdl, wrappedMdl)
 
         var solver: NCIntentSolver = null
 
@@ -516,7 +600,7 @@ object NCDeployManager extends NCService {
                 case ids if ids.nonEmpty =>
                     throw new NCE(s"Duplicate intent IDs [" +
                         s"mdlId=$mdlId, " +
-                        s"origin=${mdl.getOrigin}, " +
+                        s"origin=${wrappedMdl.getOrigin}, " +
                         s"ids=${ids.mkString(",")}" +
                     s"]")
                 case _ => ()
@@ -535,7 +619,7 @@ object NCDeployManager extends NCService {
         val simple = idl(syns.toSet, idl = false)
 
         NCProbeModel(
-            model = mdl,
+            model = wrappedMdl,
             solver = solver,
             intents = intents.map(_._1).toSeq,
             callbacks = intents.map(kv => (
@@ -552,8 +636,8 @@ object NCDeployManager extends NCService {
             addStopWordsStems = addStopWords,
             exclStopWordsStems = exclStopWords,
             suspWordsStems = suspWords,
-            elements = mdl.getElements.asScala.map(elm => (elm.getId, elm)).toMap,
-            samples = scanSamples(mdl)
+            elements = wrappedMdl.getElements.asScala.map(elm => (elm.getId, elm)).toMap,
+            samples = scanSamples(wrappedMdl)
         )
     }
 
@@ -1534,17 +1618,130 @@ object NCDeployManager extends NCService {
       * @param mdl
       */
     @throws[NCE]
-    private def scanIntents(mdl: NCModel): Set[Intent] = {
-        val cl = Thread.currentThread().getContextClassLoader
+    private def scanElementsAnnotations(mdl: NCModel): Set[NCElement] = {
+        val elems = mutable.HashSet.empty[NCElement]
 
-        val mdlId = mdl.getId
+        def scan(claxx: Class[_]): Unit = {
+            val allClassAnns = mutable.ArrayBuffer.empty[NCAddElement]
+
+            def add(anns: Array[NCAddElement]): Unit = if (anns != null) allClassAnns ++= anns.toSeq
+
+            // Class.
+            add(claxx.getAnnotationsByType(CLS_ELEM_DEF))
+
+            // All class's methods.
+            getAllMethods(claxx).foreach(m => add(m.getAnnotationsByType(CLS_ELEM_DEF)))
+
+            allClassAnns.foreach(a => {
+                val body = a.value().strip
+                val expectedJson = body.head == '{'
+
+                val jsElem =
+                    try
+                        (if (expectedJson) MAPPER_JSON else MAPPER_YAML).readValue(body, classOf[NCElementJson])
+                    catch {
+                        case e: Exception =>
+                            // TODO:
+                            throw new NCE(s"Error parsing element[" +
+                                s"modelId=${mdl.getId}, " +
+                                s"definitionClass=${claxx.getName}, " +
+                                s"element='$body', " +
+                                s"expectedFormat=${if (expectedJson) "JSON" else "YAML"}" +
+                                s"]",
+                                e
+                            )
+                    }
+
+                val elem =
+                    new NCElement {
+                        private var loader: NCValueLoader = _
+
+                        private def nvl[T](arr: Array[T]): Seq[T] = if (arr == null) Seq.empty else arr.toSeq
+
+                        override def getId: String = jsElem.getId
+                        override def getDescription: String = jsElem.getDescription
+                        override def getParentId: String = jsElem.getParentId
+
+                        override def getGroups: util.List[String] = nvl(jsElem.getGroups).asJava
+                        override def getMetadata: util.Map[String, AnyRef] = jsElem.getMetadata
+                        override def getSynonyms: util.List[String] = nvl(jsElem.getSynonyms).asJava
+
+                        override def getValues: util.List[NCValue] =
+                            nvl(jsElem.getValues).map(v => new NCValue {
+                                override def getName: String = v.getName
+                                override def getSynonyms: util.List[String] = nvl(v.getSynonyms).asJava
+                            }).asJava
+
+                        override def getValueLoader: Optional[NCValueLoader] =
+                            if (jsElem.getValueLoader != null) {
+                                if (loader == null) {
+                                    loader  = U.mkObject(jsElem.getValueLoader)
+
+                                    loader.onInit()
+                                }
+
+                                Optional.of(loader)
+                            }
+                            else
+                                Optional.empty()
+
+                        override def isPermutateSynonyms: Optional[java.lang.Boolean] =
+                            Optional.ofNullable(jsElem.isPermutateSynonyms)
+                        override def isSparse: Optional[java.lang.Boolean] =
+                            Optional.ofNullable(jsElem.isSparse)
+                    }
+
+                elems += elem
+
+                val allClassDefAnns = mutable.ArrayBuffer.empty[NCAddElementClass]
+
+                def addClass(anns: Array[NCAddElementClass]): Unit = if (anns != null) allClassDefAnns ++= anns.toSeq
+
+                addClass(claxx.getAnnotationsByType(CLS_ELEM_DEF_CLASS))
+                getAllMethods(claxx).foreach(m => addClass(m.getAnnotationsByType(CLS_ELEM_DEF_CLASS)))
+
+                allClassDefAnns.foreach(cl =>
+                    try
+                        elems += cl.value().getDeclaredConstructor().newInstance().asInstanceOf[NCElement]
+                    catch {
+                        case e: Exception => throw new NCE(s"Failed to instantiate element for: ${cl.value()}", e)
+                    }
+                )
+            })
+        }
+
+        val claxx = Class.forName(mdl.meta[String](MDL_META_MODEL_CLASS_KEY))
+
+        scan(claxx)
+
+        val classesRef = claxx.getAnnotationsByType(CLS_MDL_CLS_REF)
+
+        if (classesRef != null && classesRef.nonEmpty)
+            classesRef.head.value().foreach(scan)
+
+        val packRef = claxx.getAnnotationsByType(CLS_MDL_PKGS_REF)
+
+        if (packRef != null && packRef.nonEmpty)
+            packRef.head.value().flatMap(pack => getPackageClasses(mdl, pack)).foreach(scan)
+
+        elems.toSet
+    }
+
+    /**
+      *
+      * @param mdl
+      * @param wrappedMdl
+      */
+    @throws[NCE]
+    private def scanIntents(mdl: NCModel, wrappedMdl: NCModel): Set[Intent] = {
+        val mdlId = wrappedMdl.getId
         val intentDecls = mutable.Buffer.empty[NCIdlIntent]
         val intents = mutable.Buffer.empty[Intent]
 
         // First, get intent declarations from the JSON/YAML file, if any.
         mdl match {
             case adapter: NCModelFileAdapter =>
-                intentDecls ++= adapter.getIntents.asScala.flatMap(NCIdlCompiler.compileIntents(_, mdl, mdl.getOrigin))
+                intentDecls ++= adapter.getIntents.asScala.flatMap(NCIdlCompiler.compileIntents(_, wrappedMdl, wrappedMdl.getOrigin))
             case _ => ()
         }
 
@@ -1553,12 +1750,12 @@ object NCDeployManager extends NCService {
                 try
                     for (
                         ann <- cls.getAnnotationsByType(CLS_INTENT);
-                        intent <- NCIdlCompiler.compileIntents(ann.value(), mdl, cls.getName)
+                        intent <- NCIdlCompiler.compileIntents(ann.value(), wrappedMdl, cls.getName)
                     )
                         if (intentDecls.exists(_.id == intent.id))
                             throw new NCE(s"Duplicate intent ID [" +
                                 s"mdlId=$mdlId, " +
-                                s"origin=${mdl.getOrigin}, " +
+                                s"origin=${wrappedMdl.getOrigin}, " +
                                 s"class=$cls, " +
                                 s"id=${intent.id}" +
                             s"]")
@@ -1580,13 +1777,13 @@ object NCDeployManager extends NCService {
                 if (intents.exists(i => i._1.id == intent.id && i._2.id != cb.id))
                     throw new NCE(s"The intent cannot be bound to more than one callback [" +
                         s"mdlId=$mdlId, " +
-                        s"origin=${mdl.getOrigin}, " +
+                        s"origin=${wrappedMdl.getOrigin}, " +
                         s"class=${mo.objClassName}, " +
                         s"intentId=${intent.id}" +
                     s"]")
                 else {
                     intentDecls += intent
-                    intents += (intent -> prepareCallback(mo, mdl, intent))
+                    intents += (intent -> prepareCallback(mo, wrappedMdl, intent))
                 }
 
             def existsForOtherMethod(id: String): Boolean =
@@ -1598,28 +1795,28 @@ object NCDeployManager extends NCService {
             // Process inline intent declarations by @NCIntent annotation.
             for (
                 ann <- m.getAnnotationsByType(CLS_INTENT);
-                intent <- NCIdlCompiler.compileIntents(ann.value(), mdl, mtdStr)
+                intent <- NCIdlCompiler.compileIntents(ann.value(), wrappedMdl, mtdStr)
             )
                 if (intentDecls.exists(_.id == intent.id && existsForOtherMethod(intent.id)))
                     throw new NCE(s"Duplicate intent ID [" +
                         s"mdlId=$mdlId, " +
-                        s"origin=${mdl.getOrigin}, " +
+                        s"origin=${wrappedMdl.getOrigin}, " +
                         s"callback=$mtdStr, " +
                         s"id=${intent.id}" +
                     s"]")
                 else
-                    bindIntent(intent, prepareCallback(mo, mdl, intent))
+                    bindIntent(intent, prepareCallback(mo, wrappedMdl, intent))
 
             // Process intent references from @NCIntentRef annotation.
             for (ann <- m.getAnnotationsByType(CLS_INTENT_REF)) {
                 val refId = ann.value().trim
 
                 intentDecls.find(_.id == refId) match {
-                    case Some(intent) => bindIntent(intent, prepareCallback(mo, mdl, intent))
+                    case Some(intent) => bindIntent(intent, prepareCallback(mo, wrappedMdl, intent))
                     case None => throw new NCE(
                         s"""@NCIntentRef("$refId") references unknown intent ID [""" +
                             s"mdlId=$mdlId, " +
-                            s"origin=${mdl.getOrigin}, " +
+                            s"origin=${wrappedMdl.getOrigin}, " +
                             s"refId=$refId, " +
                             s"callback=$mtdStr" +
                         s"]"
@@ -1648,7 +1845,7 @@ object NCDeployManager extends NCService {
                     throw new NCE(
                         s"Additional reference in @${clazz.getSimpleName} annotation is empty [" +
                             s"mdlId=$mdlId, " +
-                            s"origin=${mdl.getOrigin}" +
+                            s"origin=${wrappedMdl.getOrigin}" +
                         s"]"
                     )
 
@@ -1667,23 +1864,7 @@ object NCDeployManager extends NCService {
         // Process @NCModelAddPackages annotation.
         scanAdditionalClasses(
             CLS_MDL_PKGS_REF,
-            (a: NCModelAddPackage) =>
-                a.value().toIndexedSeq.flatMap(p => {
-                    //noinspection UnstableApiUsage
-                    val res = ClassPath.from(cl).getTopLevelClassesRecursive(p).asScala.map(_.load())
-
-                    // Check should be after classes loading attempt.
-                    if (cl.getDefinedPackage(p) == null)
-                        throw new NCE(
-                            s"Invalid additional references in @${CLS_MDL_PKGS_REF.getSimpleName} annotation [" +
-                                s"mdlId=$mdlId, " +
-                                s"origin=${mdl.getOrigin}, " +
-                                s"package=$p" +
-                            s"]"
-                        )
-
-                    res
-                })
+            (a: NCModelAddPackage) => a.value().toIndexedSeq.flatMap(p => getPackageClasses(wrappedMdl, p))
         )
 
         val unusedIntents = intentDecls.filter(i => !intents.exists(_._1.id == i.id))
@@ -1691,7 +1872,7 @@ object NCDeployManager extends NCService {
         if (unusedIntents.nonEmpty)
             logger.warn(s"Intents are unused (have no callback): [" +
                 s"mdlId=$mdlId, " +
-                s"origin=${mdl.getOrigin}, " +
+                s"origin=${wrappedMdl.getOrigin}, " +
                 s"intentIds=${unusedIntents.map(_.id).mkString("(", ", ", ")")}]"
             )
         
@@ -1699,6 +1880,32 @@ object NCDeployManager extends NCService {
     }
 
     /**
+      *
+      * @param mdl
+      * @param pack
+      * @return
+      */
+    @throws[NCE]
+    private def getPackageClasses(mdl: NCModel, pack: String): Set[Class[_]] = {
+        val cl = Thread.currentThread().getContextClassLoader
+
+        //noinspection UnstableApiUsage
+        val res = ClassPath.from(cl).getTopLevelClassesRecursive(pack).asScala.map(_.load())
+
+        // Check should be after classes loading attempt.
+        if (cl.getDefinedPackage(pack) == null)
+            throw new NCE(
+                s"Invalid additional references in @${CLS_MDL_PKGS_REF.getSimpleName} annotation [" +
+                    s"mdlId=${mdl.getId}, " +
+                    s"origin=${mdl.getOrigin}, " +
+                    s"package=$pack" +
+                    s"]"
+            )
+
+        res.toSet
+    }
+
+    /**
       * Scans given model for intent samples.
       *
       * @param mdl Model to scan.
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/readme.txt b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/readme.txt
new file mode 100644
index 0000000..32b1050
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy/readme.txt
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+
+Do not add new classes here. This package scanned by tests models.
\ No newline at end of file
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy1/NCElementAnnotationsSpec.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy1/NCElementAnnotationsSpec.scala
new file mode 100644
index 0000000..88ff99f
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy1/NCElementAnnotationsSpec.scala
@@ -0,0 +1,122 @@
+/*
+ * 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
+ *
+ *      https://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.probe.mgrs.deploy1
+
+import org.apache.nlpcraft.model.{NCElement, NCAddElement, NCAddElementClass, NCIntent, NCModelAdapter, NCModelAddClasses, NCModelAddPackage, NCResult}
+import org.apache.nlpcraft.{NCTestContext, NCTestElement, NCTestEnvironment}
+import org.junit.jupiter.api.Test
+
+import java.util
+
+@NCAddElement("""{ "id": "e5" }""")
+@NCAddElement("""{ "id": "e6",  "synonyms": ["e66"] }""")
+class NCElementAnnotations1 {
+    @NCAddElement("""{ "id": "e7" }""")
+    @NCAddElement("""{ "id": "e8" }""")
+    def x(): Unit = ()
+}
+
+class NCElementAnn1 extends NCElement {
+    override def getId: String = "e12"
+}
+/**
+  *
+  */
+@NCModelAddClasses(Array{classOf[NCElementAnnotations1]})
+@NCModelAddPackage(Array("org.apache.nlpcraft.probe.mgrs.deploy1.pack"))
+@NCAddElement("""{ "id": "e3" }""")
+@NCAddElement("""{ "id": "e4" }""")
+class NCElementAnnotationsSpecModel extends NCModelAdapter("nlpcraft.intents.idl.test", "IDL Test Model", "1.0") {
+    override def getElements: util.Set[NCElement] = Set(NCTestElement("e1"))
+
+    @NCIntent("intent=onE1 term={# == 'e1'}")
+    def onE1(): NCResult = NCResult.text("OK")
+
+    @NCAddElement("""{ "id": "e2" }""")
+    @NCIntent("intent=onE2 term={# == 'e2'}")
+    def onE2(): NCResult = NCResult.text("OK")
+
+    @NCIntent("intent=onE3 term={# == 'e3'}")
+    def onE3(): NCResult = NCResult.text("OK")
+
+    @NCIntent("intent=onE4 term={# == 'e4'}")
+    def onE4(): NCResult = NCResult.text("OK")
+
+    @NCIntent("intent=onE5 term={# == 'e5'}")
+    def onE5(): NCResult = NCResult.text("OK")
+
+    @NCIntent("intent=onE6 term={# == 'e6'}")
+    def onE6(): NCResult = NCResult.text("OK")
+
+    @NCIntent("intent=onE7 term={# == 'e7'}")
+    def onE7(): NCResult = NCResult.text("OK")
+
+    @NCIntent("intent=onE8 term={# == 'e8'}")
+    def onE8(): NCResult = NCResult.text("OK")
+
+    @NCIntent("intent=onE9 term={# == 'e9'}")
+    def onE9(): NCResult = NCResult.text("OK")
+
+    @NCIntent("intent=onE10 term={# == 'e10'}")
+    def onE10(): NCResult = NCResult.text("OK")
+
+    @NCIntent("intent=onE11 term={# == 'e11'}")
+    def onE11(): NCResult = NCResult.text("OK")
+
+    @NCAddElementClass(classOf[NCElementAnn1])
+    @NCIntent("intent=onE12 term={# == 'e12'}")
+    def onE12(): NCResult = NCResult.text("OK")
+}
+/**
+ *
+ */
+@NCTestEnvironment(model = classOf[NCElementAnnotationsSpecModel], startClient = true)
+class NCElementAnnotationsSpec extends NCTestContext {
+    /**
+     *
+     */
+    @Test
+    def test(): Unit = {
+        // Defined in model.
+        checkIntent("e1", "onE1")
+
+        // Added via annotation to model method.
+        checkIntent("e2", "onE2")
+
+        // Added via annotation to model class.
+        checkIntent("e3", "onE3")
+        checkIntent("e4", "onE4")
+
+        // Added via annotation to class and methods, where class added via NCModelAddClasses.
+        // Multiple annotation tested.
+        // Complex JSON tested.
+        checkIntent("e5", "onE5")
+        checkIntent("e66", "onE6")
+        checkIntent("e7", "onE7")
+        checkIntent("e8", "onE8")
+
+        // Added via annotation to class and method, where class added via NCModelAddPackage.
+        // Complex YAML tested.
+        checkIntent("e9", "onE9")
+        checkIntent("e101", "onE10")
+        checkIntent("e11", "onE11")
+
+        // Added via class annotation (second approach).
+        checkIntent("e12", "onE12")
+    }
+}
\ No newline at end of file
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy1/pack/NCElementAnnotations2.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy1/pack/NCElementAnnotations2.scala
new file mode 100644
index 0000000..a3e3d0d
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy1/pack/NCElementAnnotations2.scala
@@ -0,0 +1,34 @@
+/*
+ * 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
+ *
+ *      https://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.probe.mgrs.deploy1.pack
+
+import org.apache.nlpcraft.model.NCAddElement
+
+@NCAddElement("""{ "id": "e9" }""")
+@NCAddElement(
+    """---
+        id: "e10"
+        synonyms:
+          - "e100"
+          - "e101"
+    """
+)
+class NCElementAnnotations2 {
+    @NCAddElement("""{ "id": "e11" }""")
+    def x(): Unit = ()
+}
\ No newline at end of file
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy1/readme.txt b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy1/readme.txt
new file mode 100644
index 0000000..32b1050
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/deploy1/readme.txt
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+
+Do not add new classes here. This package scanned by tests models.
\ No newline at end of file