diff --git a/.gitignore b/.gitignore
index 3e73adc7da..9f21c44253 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,3 +52,6 @@ package-lock.json
/cypress*
.factorypath
+
+# Apache RAT check excludes file
+!.rat-excludes
\ No newline at end of file
diff --git a/.rat-excludes b/.rat-excludes
new file mode 100644
index 0000000000..6473bd705a
--- /dev/null
+++ b/.rat-excludes
@@ -0,0 +1,100 @@
+# .rat-excludes
+.rat-excludes
+# DISCLAIMER-WIP
+DISCLAIMER-WIP
+# apps-integration-tests/integration-tests-data-index-service/integration-tests-data-index-service-common/src/main/resources/approval.bpmn
+approval.bpmn
+# apps-integration-tests/integration-tests-data-index-service/integration-tests-data-index-service-quarkus/src/main/resources/META-INF/processSVG/approvals.svg
+approvals.svg
+# apps-integration-tests/integration-tests-data-index-service/integration-tests-data-index-service-quarkus/src/test/resources/approval.bpmn
+approval.bpmn
+# apps-integration-tests/integration-tests-jobs-service/integration-tests-jobs-service-common/src/main/resources/org/acme/processes/MultipleTimerInstancesBoundaryTimerEvent.bpmn
+MultipleTimerInstancesBoundaryTimerEvent.bpmn
+# apps-integration-tests/integration-tests-jobs-service/integration-tests-jobs-service-common/src/main/resources/org/acme/processes/MultipleTimerInstancesTimerEvent.bpmn
+MultipleTimerInstancesTimerEvent.bpmn
+# apps-integration-tests/integration-tests-jobs-service/integration-tests-jobs-service-common/src/main/resources/org/acme/travels/async.bpmn
+async.bpmn
+# apps-integration-tests/integration-tests-jobs-service/integration-tests-jobs-service-common/src/main/resources/org/acme/travels/timer-on-task.bpmn
+timer-on-task.bpmn
+# apps-integration-tests/integration-tests-jobs-service/integration-tests-jobs-service-common/src/main/resources/org/acme/travels/timers.bpmn
+timers.bpmn
+# apps-integration-tests/integration-tests-jobs-service/integration-tests-jobs-service-common/src/main/resources/org/acme/travels/timersCycle.bpmn
+timersCycle.bpmn
+# apps-integration-tests/integration-tests-trusty-service/integration-tests-trusty-service-common/src/main/resources/TrafficViolation.dmn
+TrafficViolation.dmn
+# data-audit/data-audit-common/src/main/resources/META-INF/data-audit-job-query.graphqls
+data-audit-job-query.graphqls
+# data-audit/data-audit-common/src/main/resources/META-INF/data-audit-process-query.graphqls
+data-audit-process-query.graphqls
+# data-audit/data-audit-common/src/main/resources/META-INF/data-audit-types.graphqls
+data-audit-types.graphqls
+# data-audit/data-audit-common/src/main/resources/META-INF/data-audit-usertask-query.graphqls
+data-audit-usertask-query.graphqls
+# data-audit/kogito-addons-data-audit-jpa/kogito-addons-data-audit-jpa-common/src/main/resources/META-INF/data-audit-orm.xml
+data-audit-orm.xml
+# data-audit/kogito-addons-data-audit-jpa/kogito-addons-data-audit-jpa-common/src/main/resources/META-INF/services/org.kie.kogito.app.audit.spi.DataAuditStore
+org.kie.kogito.app.audit.spi.DataAuditStore
+# data-audit/kogito-addons-data-audit-jpa/kogito-addons-data-audit-jpa-common/src/main/resources/META-INF/services/org.kie.kogito.app.audit.spi.GraphQLSchemaQueryProvider
+org.kie.kogito.app.audit.spi.GraphQLSchemaQueryProvider
+# data-audit/kogito-addons-data-audit-springboot/src/test/resources/application.properties
+application.properties
+# data-index/data-index-graphql/src/main/resources/basic.schema.graphqls
+basic.schema.graphqls
+# data-index/data-index-service/data-index-service-common/src/main/resources/domain.schema.graphqls
+domain.schema.graphqls
+# data-index/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-infinispan/integration-tests-process/src/main/resources/hello.bpmn
+hello.bpmn
+# data-index/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-infinispan/integration-tests-process/src/main/resources/META-INF/processSVG/hello.svg
+hello.svg
+# data-index/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-inmemory/integration-tests-process/src/main/resources/hello.bpmn
+hello.bpmn
+# data-index/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-inmemory/integration-tests-process/src/main/resources/META-INF/processSVG/hello.svg
+hello.svg
+# data-index/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-jpa/integration-tests-process/src/main/resources/hello.bpmn
+hello.bpmn
+# data-index/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-jpa/integration-tests-process/src/main/resources/META-INF/processSVG/hello.svg
+hello.svg
+# data-index/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-mongodb/integration-tests-process/src/main/resources/hello.bpmn
+hello.bpmn
+# data-index/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-mongodb/integration-tests-process/src/main/resources/META-INF/processSVG/hello.svg
+hello.svg
+# data-index/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-postgresql/integration-tests-process/src/main/resources/hello.bpmn
+hello.bpmn
+# data-index/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-postgresql/integration-tests-process/src/main/resources/META-INF/processSVG/hello.svg
+hello.svg
+# data-index/kogito-addons-quarkus-data-index-persistence/kogito-addons-quarkus-data-index-persistence-infinispan/integration-tests-process/src/main/resources/hello.bpmn
+hello.bpmn
+# data-index/kogito-addons-quarkus-data-index-persistence/kogito-addons-quarkus-data-index-persistence-jpa/integration-tests-process/src/main/resources/hello.bpmn
+hello.bpmn
+# data-index/kogito-addons-quarkus-data-index-persistence/kogito-addons-quarkus-data-index-persistence-jpa/integration-tests-process/src/main/resources/META-INF/processSVG/hello.svg
+hello.svg
+# data-index/kogito-addons-quarkus-data-index-persistence/kogito-addons-quarkus-data-index-persistence-mongodb/integration-tests-process/src/main/resources/hello.bpmn
+hello.bpmn
+# data-index/kogito-addons-quarkus-data-index-persistence/kogito-addons-quarkus-data-index-persistence-postgresql/integration-tests-process/src/main/resources/hello.bpmn
+hello.bpmn
+# explainability/explainability-service/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
+org.mockito.plugins.MockMaker
+# jitexecutor/jitexecutor-bpmn/src/test/resources/MultipleInvalidModel.bpmn2
+MultipleInvalidModel.bpmn2
+# jitexecutor/jitexecutor-bpmn/src/test/resources/MultipleProcess.bpmn2
+MultipleProcess.bpmn2
+# jitexecutor/jitexecutor-bpmn/src/test/resources/SingleInvalidModel.bpmn2
+SingleInvalidModel.bpmn2
+# jitexecutor/jitexecutor-bpmn/src/test/resources/SingleProcess.bpmn
+SingleProcess.bpmn
+# jitexecutor/jitexecutor-bpmn/src/test/resources/SingleProcess.bpmn2
+SingleProcess.bpmn2
+# jitexecutor/jitexecutor-bpmn/src/test/resources/SingleUnparsableModel.bpmn2
+SingleUnparsableModel.bpmn2
+# jitexecutor/jitexecutor-bpmn/src/test/resources/UnparsableModel.bpmn2
+UnparsableModel.bpmn2
+# jitexecutor/jitexecutor-bpmn/src/test/resources/ValidModel.bpmn
+ValidModel.bpmn
+# jitexecutor/jitexecutor-dmn/src/main/resources/META-INF/resources/bundle.js
+bundle.js
+# jobs-service/kogito-addons-jobs-service/kogito-addons-quarkus-jobs/src/test/resources/application.properties
+application.properties
+# jobs-service/kogito-addons-jobs-service/kogito-addons-quarkus-jobs-service-embedded/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource
+org.eclipse.microprofile.config.spi.ConfigSource
+# trusty/trusty-service/trusty-service-common/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
+org.mockito.plugins.MockMaker
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index 261eeb9e9f..43ec293049 100644
--- a/LICENSE
+++ b/LICENSE
@@ -199,3 +199,56 @@
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.
+
+-------------------------------------------------------------------------------
+for jitexecutor/jitexecutor-dmn/src/main/resources/META-INF/resources/bundle.js
+
+It is a combination of:
+
+Copyright (c) 2017 Jed Watson.
+Licensed under the MIT License (MIT)
+
+The MIT License (MIT)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+and
+
+(c) Sindre Sorhus
+Licensed under the MIT License (MIT)
+
+and
+
+Copyright (c) Meta Platforms, Inc. and affiliates.
+Licensed under the MIT License (MIT)
+
+and
+
+Copyright 2011 Gary Court. All rights reserved.
+License available: https://github.com/garycourt/uri-js/blob/4.4.0/LICENSE
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY GARY COURT "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of Gary Court.
diff --git a/NOTICE b/NOTICE
index 6a7d335aa3..a1c5656ff4 100644
--- a/NOTICE
+++ b/NOTICE
@@ -5,5 +5,5 @@ This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).
The Initial Developer of some parts of the framework, which are copied from, derived from, or
-inspired by KIE (Knowledge Is Everthing) group, is Red Hat, Inc (https://www.redhat.com/).
+inspired by KIE (Knowledge Is Everything) group, is Red Hat, Inc (https://www.redhat.com/).
Copyright Red Hat, Inc. and/or its affiliates.. All Rights Reserved.
diff --git a/apps-integration-tests/integration-tests-data-index-service/integration-tests-data-index-service-springboot/pom.xml b/apps-integration-tests/integration-tests-data-index-service/integration-tests-data-index-service-springboot/pom.xml
index afc37da461..954a439cb4 100644
--- a/apps-integration-tests/integration-tests-data-index-service/integration-tests-data-index-service-springboot/pom.xml
+++ b/apps-integration-tests/integration-tests-data-index-service/integration-tests-data-index-service-springboot/pom.xml
@@ -187,7 +187,14 @@
org.kie.kogito
kogito-maven-plugin
${project.version}
- true
+
+
+ compile
+
+ generateModel
+
+
+
org.apache.maven.plugins
diff --git a/apps-integration-tests/integration-tests-jobs-service/integration-tests-jobs-service-springboot/pom.xml b/apps-integration-tests/integration-tests-jobs-service/integration-tests-jobs-service-springboot/pom.xml
index c7c28d746d..9f3994afab 100644
--- a/apps-integration-tests/integration-tests-jobs-service/integration-tests-jobs-service-springboot/pom.xml
+++ b/apps-integration-tests/integration-tests-jobs-service/integration-tests-jobs-service-springboot/pom.xml
@@ -139,7 +139,14 @@
org.kie.kogito
kogito-maven-plugin
${project.version}
- true
+
+
+ compile
+
+ generateModel
+
+
+
diff --git a/apps-integration-tests/integration-tests-trusty-service/integration-tests-trusty-service-springboot/pom.xml b/apps-integration-tests/integration-tests-trusty-service/integration-tests-trusty-service-springboot/pom.xml
index 8ccdf7d69c..5219530993 100644
--- a/apps-integration-tests/integration-tests-trusty-service/integration-tests-trusty-service-springboot/pom.xml
+++ b/apps-integration-tests/integration-tests-trusty-service/integration-tests-trusty-service-springboot/pom.xml
@@ -112,7 +112,14 @@
org.kie.kogito
kogito-maven-plugin
${project.version}
- true
+
+
+ compile
+
+ generateModel
+
+
+
diff --git a/jitexecutor/jitexecutor-dmn/src/main/resources/META-INF/resources/bundle.js b/jitexecutor/jitexecutor-dmn/src/main/resources/META-INF/resources/bundle.js
index a13ad01d97..f3d2591433 100644
--- a/jitexecutor/jitexecutor-dmn/src/main/resources/META-INF/resources/bundle.js
+++ b/jitexecutor/jitexecutor-dmn/src/main/resources/META-INF/resources/bundle.js
@@ -1,21 +1,3 @@
-/*
- * 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.
- */
!function(e){var t={};function r(n){if(t[n])return t[n].exports;var a=t[n]={i:n,l:!1,exports:{}};return e[n].call(a.exports,a,a.exports,r),a.l=!0,a.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var a in e)r.d(n,a,function(t){return e[t]}.bind(null,a));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=474)}([function(e,t,r){"use strict";e.exports=r(236)},function(e,t,r){"use strict";r.r(t),r.d(t,"ADDITIONAL_PROPERTY_FLAG",(function(){return R})),r.d(t,"canExpand",(function(){return I})),r.d(t,"getDefaultRegistry",(function(){return $})),r.d(t,"getSchemaType",(function(){return M})),r.d(t,"getWidget",(function(){return L})),r.d(t,"hasWidget",(function(){return z})),r.d(t,"getDefaultFormState",(function(){return U})),r.d(t,"mergeDefaultsWithFormData",(function(){return V})),r.d(t,"getUiOptions",(function(){return B})),r.d(t,"getDisplayLabel",(function(){return W})),r.d(t,"isObject",(function(){return H})),r.d(t,"mergeObjects",(function(){return Q})),r.d(t,"asNumber",(function(){return K})),r.d(t,"orderProperties",(function(){return G})),r.d(t,"isConstant",(function(){return J})),r.d(t,"toConstant",(function(){return Y})),r.d(t,"isSelect",(function(){return X})),r.d(t,"isMultiSelect",(function(){return Z})),r.d(t,"isFilesArray",(function(){return ee})),r.d(t,"isFixedItems",(function(){return te})),r.d(t,"allowAdditionalItems",(function(){return re})),r.d(t,"optionsList",(function(){return ne})),r.d(t,"findSchemaDefinition",(function(){return ae})),r.d(t,"guessType",(function(){return oe})),r.d(t,"stubExistingAdditionalProperties",(function(){return ie})),r.d(t,"resolveSchema",(function(){return le})),r.d(t,"retrieveSchema",(function(){return se})),r.d(t,"mergeSchemas",(function(){return pe})),r.d(t,"deepEquals",(function(){return he})),r.d(t,"shouldRender",(function(){return ve})),r.d(t,"toIdSchema",(function(){return ye})),r.d(t,"toPathSchema",(function(){return ge})),r.d(t,"parseDateString",(function(){return be})),r.d(t,"toDateString",(function(){return Ee})),r.d(t,"utcToLocal",(function(){return we})),r.d(t,"localToUTC",(function(){return xe})),r.d(t,"pad",(function(){return Se})),r.d(t,"dataURItoBlob",(function(){return Oe})),r.d(t,"rangeSpec",(function(){return Pe})),r.d(t,"getMatchingOption",(function(){return ke})),r.d(t,"schemaRequiresTrueValue",(function(){return je}));var n=r(225),a=r.n(n),o=r(52),i=r.n(o),l=r(53),u=r.n(l),s=r(22),c=r(147),f=r.n(c),d=r(4),p=r(7),m=r.n(p),h=r(19),v=r(6),y=r(5),g=r(11),b=r(9),E=r.n(b),w=r(0),x=r.n(w),S=r(148),O=r(228),P=r.n(O),k=r(229),j=r.n(k),_=r(27),C=r(232),T=r.n(C),F=r(233),N=r.n(F);function D(e){var t=function(e,t){if("object"!==Object(h.a)(e)||null===e)return e;var r=e[a.a];if(void 0!==r){var n=r.call(e,t||"default");if("object"!==Object(h.a)(n))return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"===Object(h.a)(t)?t:String(t)}var R="__additional_property",A={boolean:{checkbox:"CheckboxWidget",radio:"RadioWidget",select:"SelectWidget",hidden:"HiddenWidget"},string:{text:"TextWidget",password:"PasswordWidget",email:"EmailWidget",hostname:"TextWidget",ipv4:"TextWidget",ipv6:"TextWidget",uri:"URLWidget","data-url":"FileWidget",radio:"RadioWidget",select:"SelectWidget",textarea:"TextareaWidget",hidden:"HiddenWidget",date:"DateWidget",datetime:"DateTimeWidget","date-time":"DateTimeWidget","alt-date":"AltDateWidget","alt-datetime":"AltDateTimeWidget",color:"ColorWidget",file:"FileWidget"},number:{text:"TextWidget",select:"SelectWidget",updown:"UpDownWidget",range:"RangeWidget",radio:"RadioWidget",hidden:"HiddenWidget"},integer:{text:"TextWidget",select:"SelectWidget",updown:"UpDownWidget",range:"RangeWidget",radio:"RadioWidget",hidden:"HiddenWidget"},array:{select:"SelectWidget",checkboxes:"CheckboxesWidget",files:"FileWidget",hidden:"HiddenWidget"}};function I(e,t,r){if(!e.additionalProperties)return!1;var n=B(t).expandable;return!1===n?n:void 0===e.maxProperties||E()(r).length2&&void 0!==arguments[2]?arguments[2]:{},n=M(e);function a(e){if(!e.MergedWidget){var t=e.defaultProps&&e.defaultProps.options||{};e.MergedWidget=function(r){var n=r.options,a=void 0===n?{}:n,o=Object(g.a)(r,["options"]);return x.a.createElement(e,Object(v.a)({options:Object(y.a)({},t,a)},o))}}return e.MergedWidget}if("function"==typeof t||S.isForwardRef(x.a.createElement(t))||S.isMemo(t))return a(t);if("string"!=typeof t)throw new Error("Unsupported widget definition: ".concat(Object(h.a)(t)));if(r.hasOwnProperty(t)){var o=r[t];return L(e,o,r)}if(!A.hasOwnProperty(n))throw new Error('No widget for type "'.concat(n,'"'));if(A[n].hasOwnProperty(t)){var i=r[A[n][t]];return L(e,i,r)}throw new Error('No widget "'.concat(t,'" for type "').concat(n,'"'))}function z(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};try{return L(e,t,r),!0}catch(e){if(e.message&&(e.message.startsWith("No widget")||e.message.startsWith("Unsupported widget")))return!1;throw e}}function q(e,t,r){var n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},a=arguments.length>4&&void 0!==arguments[4]&&arguments[4],o=H(e)?e:{},i=H(n)?n:{},l=t;if(H(l)&&H(o.default))l=Q(l,o.default);else if("default"in o)l=o.default;else{if("$ref"in o){var u=ae(o.$ref,r);return q(u,l,r,i,a)}if("dependencies"in o){var s=ce(o,r,i);return q(s,l,r,i,a)}te(o)?l=o.items.map((function(e,n){return q(e,m()(t)?t[n]:void 0,r,i,a)})):"oneOf"in o?o=o.oneOf[ke(void 0,o.oneOf,r)]:"anyOf"in o&&(o=o.anyOf[ke(void 0,o.anyOf,r)])}switch(void 0===l&&(l=o.default),M(o)){case"object":return E()(o.properties||{}).reduce((function(e,t){var n=q(o.properties[t],(l||{})[t],r,(i||{})[t],a);return(a||void 0!==n)&&(e[t]=n),e}),{});case"array":if(m()(l)&&(l=l.map((function(e,t){return q(o.items[t]||o.additionalItems||{},e,r)}))),m()(n)&&(l=n.map((function(e,t){return q(o.items,(l||{})[t],r,e)}))),o.minItems){if(Z(o,r))return l||[];var c=l?l.length:0;if(o.minItems>c){var f=l||[],d=m()(o.items)?o.additionalItems:o.items,p=j()(new Array(o.minItems-c),q(d,d.defaults,r));return f.concat(p)}}}return l}function U(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=arguments.length>3&&void 0!==arguments[3]&&arguments[3];if(!H(e))throw new Error("Invalid schema: "+e);var a=se(e,r,t),o=q(a,e.default,r,t,n);return void 0===t?o:H(t)||m()(t)?V(o,t):0===t||!1===t||""===t?t:t||o}function V(e,t){if(m()(t))return m()(e)||(e=[]),t.map((function(t,r){return e[r]?V(e[r],t):t}));if(H(t)){var r=Object(v.a)({},e);return E()(t).reduce((function(r,n){return r[n]=V(e?e[n]:{},t[n]),r}),r)}return t}function B(e){return E()(e).filter((function(e){return 0===e.indexOf("ui:")})).reduce((function(t,r){var n=e[r];return"ui:widget"===r&&H(n)?(console.warn("Setting options via ui:widget object is deprecated, use ui:options instead"),Object(y.a)({},t,n.options||{},{widget:n.component})):"ui:options"===r&&H(n)?Object(y.a)({},t,n):Object(y.a)({},t,Object(d.a)({},r.substring(3),n))}),{})}function W(e,t,r){var n=B(t).label,a=void 0===n||n;return"array"===e.type&&(a=Z(e,r)||ee(e,t,r)),"object"===e.type&&(a=!1),"boolean"!==e.type||t["ui:widget"]||(a=!1),t["ui:field"]&&(a=!1),a}function H(e){return!("undefined"!=typeof File&&e instanceof File)&&("object"===Object(h.a)(e)&&null!==e&&!m()(e))}function Q(e,t){var r=arguments.length>2&&void 0!==arguments[2]&&arguments[2],n=Object(v.a)({},e);return E()(t).reduce((function(n,a){var o=e?e[a]:{},i=t[a];return e&&e.hasOwnProperty(a)&&H(i)?n[a]=Q(o,i,r):r&&m()(o)&&m()(i)?n[a]=o.concat(i):n[a]=i,n}),n)}function K(e){if(""!==e){if(null===e)return null;if(/\.$/.test(e))return e;if(/\.0$/.test(e))return e;var t=Number(e),r="number"==typeof t&&!f()(t);return/\.\d*0$/.test(e)?e:r?t:e}}function G(e,t){if(!m()(t))return e;var r,n=function(e){return e.reduce((function(e,t){return e[t]=!0,e}),{})},a=n(e),o=t.filter((function(e){return"*"===e||a[e]})),i=n(o),l=e.filter((function(e){return!i[e]})),u=o.indexOf("*");if(-1===u){if(l.length)throw new Error("uiSchema order list does not contain ".concat((r=l).length>1?"properties '".concat(r.join("', '"),"'"):"property '".concat(r[0],"'")));return o}if(u!==o.lastIndexOf("*"))throw new Error("uiSchema order list contains more than one wildcard item");var c=Object(s.a)(o);return c.splice.apply(c,[u,1].concat(Object(s.a)(l))),c}function J(e){return m()(e.enum)&&1===e.enum.length||e.hasOwnProperty("const")}function Y(e){if(m()(e.enum)&&1===e.enum.length)return e.enum[0];if(e.hasOwnProperty("const"))return e.const;throw new Error("schema cannot be inferred as a constant")}function X(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=se(e,t),n=r.oneOf||r.anyOf;return!!m()(r.enum)||!!m()(n)&&n.every((function(e){return J(e)}))}function Z(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return!(!e.uniqueItems||!e.items)&&X(e.items,t)}function ee(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if("files"===t["ui:widget"])return!0;if(e.items){var n=se(e.items,r);return"string"===n.type&&"data-url"===n.format}return!1}function te(e){return m()(e.items)&&e.items.length>0&&e.items.every((function(e){return H(e)}))}function re(e){return!0===e.additionalItems&&console.warn("additionalItems=true is currently not supported"),H(e.additionalItems)}function ne(e){return e.enum?e.enum.map((function(t,r){return{label:e.enumNames&&e.enumNames[r]||String(t),value:t}})):(e.oneOf||e.anyOf).map((function(e,t){var r=Y(e);return{label:e.title||String(r),value:r}}))}function ae(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=e;if(!e.startsWith("#"))throw new Error("Could not find a definition for ".concat(r,"."));e=decodeURIComponent(e.substring(1));var n=N.a.get(t,e);if(void 0===n)throw new Error("Could not find a definition for ".concat(r,"."));return n.hasOwnProperty("$ref")?ae(n.$ref,t):n}var oe=function(e){return m()(e)?"array":"string"==typeof e?"string":null==e?"null":"boolean"==typeof e?"boolean":isNaN(e)?"object"===Object(h.a)(e)?"object":"string":"number"};function ie(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return e=Object(y.a)({},e,{properties:Object(y.a)({},e.properties)}),E()(r).forEach((function(n){var a;e.properties.hasOwnProperty(n)||(a=e.additionalProperties.hasOwnProperty("$ref")?se({$ref:e.additionalProperties.$ref},t,r):e.additionalProperties.hasOwnProperty("type")?Object(y.a)({},e.additionalProperties):{type:oe(r[n])},e.properties[n]=a,e.properties[n][R]=!0)})),e}function le(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(e.hasOwnProperty("$ref"))return ue(e,t,r);if(e.hasOwnProperty("dependencies")){var n=ce(e,t,r);return se(n,t,r)}return e.hasOwnProperty("allOf")?Object(y.a)({},e,{allOf:e.allOf.map((function(e){return se(e,t,r)}))}):e}function ue(e,t,r){var n=ae(e.$ref,t),a=(e.$ref,Object(g.a)(e,["$ref"]));return se(Object(y.a)({},n,a),t,r)}function se(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(!H(e))return{};var n=le(e,t,r);if("allOf"in e)try{n=P()(Object(y.a)({},n,{allOf:n.allOf}))}catch(e){console.warn("could not merge subschemas in allOf:\n"+e);var a=n,o=(a.allOf,Object(g.a)(a,["allOf"]));return o}var i=n.hasOwnProperty("additionalProperties")&&!1!==n.additionalProperties;return i?ie(n,t,r):n}function ce(e,t,r){var n=e.dependencies,a=void 0===n?{}:n,o=Object(g.a)(e,["dependencies"]);return"oneOf"in o?o=o.oneOf[ke(r,o.oneOf,t)]:"anyOf"in o&&(o=o.anyOf[ke(r,o.anyOf,t)]),function e(t,r,n,a){for(var o in t)if(void 0!==a[o]&&(!r.properties||o in r.properties)){var i=t[o],l=Object(g.a)(t,[o].map(D));return m()(i)?r=fe(r,i):H(i)&&(r=de(r,n,a,o,i)),e(l,r,n,a)}return r}(a,o,t,r)}function fe(e,t){if(!t)return e;var r=m()(e.required)?u()(new i.a([].concat(Object(s.a)(e.required),Object(s.a)(t)))):t;return Object(y.a)({},e,{required:r})}function de(e,t,r,n,a){var o=se(a,t,r),i=o.oneOf;if(e=pe(e,Object(g.a)(o,["oneOf"])),void 0===i)return e;if(!m()(i))throw new Error("invalid: it is some ".concat(Object(h.a)(i)," instead of an array"));var l=i.map((function(e){return e.hasOwnProperty("$ref")?ue(e,t,r):e}));return function(e,t,r,n,a){var o=a.filter((function(e){if(!e.properties)return!1;var t=e.properties[n];if(t){var a={type:"object",properties:Object(d.a)({},n,t)};return 0===Object(_.a)(r,a).errors.length}}));if(1!==o.length)return console.warn("ignoring oneOf in dependencies because there isn't exactly one subschema that is valid"),e;var i=o[0],l=i.properties,u=(l[n],Object(g.a)(l,[n].map(D))),s=Object(y.a)({},i,{properties:u});return pe(e,se(s,t,r))}(e,t,r,n,l)}function pe(e,t){var r=Object(v.a)({},e);return E()(t).reduce((function(r,n){var a=e?e[n]:{},o=t[n];return e&&e.hasOwnProperty(n)&&H(o)?r[n]=pe(a,o):e&&t&&("object"===M(e)||"object"===M(t))&&"required"===n&&m()(a)&&m()(o)?r[n]=T()(a,o):r[n]=o,r}),r)}function me(e){return"[object Arguments]"===Object.prototype.toString.call(e)}function he(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:[];if(e===t)return!0;if("function"==typeof e||"function"==typeof t)return!0;if("object"!==Object(h.a)(e)||"object"!==Object(h.a)(t))return!1;if(null===e||null===t)return!1;if(e instanceof Date&&t instanceof Date)return e.getTime()===t.getTime();if(e instanceof RegExp&&t instanceof RegExp)return e.source===t.source&&e.global===t.global&&e.multiline===t.multiline&&e.lastIndex===t.lastIndex&&e.ignoreCase===t.ignoreCase;if(me(e)||me(t)){if(!me(e)||!me(t))return!1;var a=Array.prototype.slice;return he(a.call(e),a.call(t),r,n)}if(e.constructor!==t.constructor)return!1;var o=E()(e),i=E()(t);if(0===o.length&&0===i.length)return!0;if(o.length!==i.length)return!1;for(var l,u=r.length;u--;)if(r[u]===e)return n[u]===t;r.push(e),n.push(t),o.sort(),i.sort();for(var s=o.length-1;s>=0;s--)if(o[s]!==i[s])return!1;for(var c=o.length-1;c>=0;c--)if(!he(e[l=o[c]],t[l],r,n))return!1;return r.pop(),n.pop(),!0}function ve(e,t,r){var n=e.props,a=e.state;return!he(n,t)||!he(a,r)}function ye(e,t,r){var n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},a=arguments.length>4&&void 0!==arguments[4]?arguments[4]:"root",o={$id:t||a};if("$ref"in e||"dependencies"in e||"allOf"in e){var i=se(e,r,n);return ye(i,t,r,n,a)}if("items"in e&&!e.items.$ref)return ye(e.items,t,r,n,a);if("object"!==e.type)return o;for(var l in e.properties||{}){var u=e.properties[l],s=o.$id+"_"+l;o[l]=ye(H(u)?u:{},s,r,(n||{})[l],a)}return o}function ge(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",r=arguments.length>2?arguments[2]:void 0,n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},a={$name:t.replace(/^\./,"")};if("$ref"in e||"dependencies"in e||"allOf"in e){var o=se(e,r,n);return ge(o,t,r,n)}if(e.hasOwnProperty("additionalProperties")&&(a.__rjsf_additionalProperties=!0),e.hasOwnProperty("items")&&m()(n))n.forEach((function(n,o){a[o]=ge(e.items,"".concat(t,".").concat(o),r,n)}));else if(e.hasOwnProperty("properties"))for(var i in e.properties)a[i]=ge(e.properties[i],"".concat(t,".").concat(i),r,(n||{})[i]);return a}function be(e){var t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];if(!e)return{year:-1,month:-1,day:-1,hour:t?-1:0,minute:t?-1:0,second:t?-1:0};var r=new Date(e);if(f()(r.getTime()))throw new Error("Unable to parse date "+e);return{year:r.getUTCFullYear(),month:r.getUTCMonth()+1,day:r.getUTCDate(),hour:t?r.getUTCHours():0,minute:t?r.getUTCMinutes():0,second:t?r.getUTCSeconds():0}}function Ee(e){var t=e.year,r=e.month,n=e.day,a=e.hour,o=void 0===a?0:a,i=e.minute,l=void 0===i?0:i,u=e.second,s=void 0===u?0:u,c=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],f=Date.UTC(t,r-1,n,o,l,s),d=new Date(f).toJSON();return c?d:d.slice(0,10)}function we(e){if(!e)return"";var t=new Date(e),r=Se(t.getFullYear(),4),n=Se(t.getMonth()+1,2),a=Se(t.getDate(),2),o=Se(t.getHours(),2),i=Se(t.getMinutes(),2),l=Se(t.getSeconds(),2),u=Se(t.getMilliseconds(),3);return"".concat(r,"-").concat(n,"-").concat(a,"T").concat(o,":").concat(i,":").concat(l,".").concat(u)}function xe(e){if(e)return new Date(e).toJSON()}function Se(e,t){for(var r=String(e);r.length onAck(event.getId(), job))
- .withNack(reason -> onNack(reason, job))));
+ .withNack(reason -> onNack(reason, job)), event));
} catch (Exception e) {
String msg = String.format("An unexpected error was produced while processing a Job status change for the job: %s", job);
LOGGER.error(msg, e);
@@ -82,6 +78,14 @@ public void jobStatusChange(JobDetails job) {
}
}
+ protected JobDataEvent buildEvent(JobDetails job) {
+ return JobDataEvent
+ .builder()
+ .source(url + RestApiConstants.JOBS_PATH)
+ .data(ScheduledJobAdapter.of(job))//this should support jobs crated with V1 and V2
+ .build();
+ }
+
protected CompletionStage onAck(String eventId, JobDetails job) {
LOGGER.debug("Job Status change emitted successfully, eventId: {}, jobDetails: {}", eventId, job);
return CompletableFuture.completedFuture(null);
@@ -93,7 +97,7 @@ protected CompletionStage onNack(Throwable reason, JobDetails job) {
return CompletableFuture.completedFuture(null);
}
- protected Message decorate(Message message) {
+ protected Message decorate(Message message, JobDataEvent event) {
return message;
}
}
diff --git a/jobs-service/jobs-service-common/src/test/java/org/kie/kogito/jobs/service/stream/AbstractJobStreamsTest.java b/jobs-service/jobs-service-common/src/test/java/org/kie/kogito/jobs/service/stream/AbstractJobStreamsTest.java
index f7ac6ed1ce..1f2e3fc33b 100644
--- a/jobs-service/jobs-service-common/src/test/java/org/kie/kogito/jobs/service/stream/AbstractJobStreamsTest.java
+++ b/jobs-service/jobs-service-common/src/test/java/org/kie/kogito/jobs/service/stream/AbstractJobStreamsTest.java
@@ -58,7 +58,7 @@ public abstract class AbstractJobStreamsTest {
protected static final String URL = "http://localhost:8180";
private static final String SERIALIZED_MESSAGE = "SERIALIZED_MESSAGE";
- private static final String JOB_ID = "JOB_ID";
+ protected static final String JOB_ID = "JOB_ID";
private static final String CORRELATION_ID = "CORRELATION_ID";
private static final JobStatus STATUS = JobStatus.SCHEDULED;
private static final ZonedDateTime LAST_UPDATE = ZonedDateTime.parse("2022-08-03T18:00:15.001+01:00");
@@ -170,7 +170,7 @@ private JobDetails mockJobDetails() {
}
- private void assertExpectedEvent(JobDataEvent event) {
+ protected void assertExpectedEvent(JobDataEvent event) {
assertThat(event.getId()).isNotNull();
assertThat(event.getType()).isEqualTo(JobDataEvent.JOB_EVENT_TYPE);
assertThat(event.getSource()).hasToString(URL + "/jobs");
diff --git a/jobs-service/jobs-service-messaging-http/src/main/java/org/kie/kogito/jobs/service/messaging/http/stream/HttpJobStreams.java b/jobs-service/jobs-service-messaging-http/src/main/java/org/kie/kogito/jobs/service/messaging/http/stream/HttpJobStreams.java
index 52bb50d35f..61a2efebaf 100644
--- a/jobs-service/jobs-service-messaging-http/src/main/java/org/kie/kogito/jobs/service/messaging/http/stream/HttpJobStreams.java
+++ b/jobs-service/jobs-service-messaging-http/src/main/java/org/kie/kogito/jobs/service/messaging/http/stream/HttpJobStreams.java
@@ -26,6 +26,7 @@
import org.eclipse.microprofile.reactive.messaging.Emitter;
import org.eclipse.microprofile.reactive.messaging.Message;
import org.eclipse.microprofile.reactive.messaging.OnOverflow;
+import org.kie.kogito.jobs.service.events.JobDataEvent;
import org.kie.kogito.jobs.service.model.JobDetails;
import org.kie.kogito.jobs.service.stream.AbstractJobStreams;
import org.slf4j.Logger;
@@ -45,6 +46,7 @@ public class HttpJobStreams extends AbstractJobStreams {
public static final String PUBLISH_EVENTS_CONFIG_KEY = "kogito.jobs-service.http.job-status-change-events";
public static final String JOB_STATUS_CHANGE_EVENTS_HTTP = "kogito-job-service-job-status-events-http";
+ public static final String PARTITION_KEY_EXTENSION = "partitionkey";
private static final Logger LOGGER = LoggerFactory.getLogger(HttpJobStreams.class);
@@ -70,7 +72,16 @@ public void jobStatusChange(JobDetails job) {
}
@Override
- protected Message decorate(Message message) {
+ protected JobDataEvent buildEvent(JobDetails job) {
+ JobDataEvent event = super.buildEvent(job);
+ // use the well-known extension https://github.com/cloudevents/spec/blob/main/cloudevents/extensions/partitioning.md
+ // to instruct potential http driven Brokers like, Knative Eventing Kafka Broker, to process accordingly.
+ event.addExtensionAttribute(PARTITION_KEY_EXTENSION, event.getData().getId());
+ return event;
+ }
+
+ @Override
+ protected Message decorate(Message message, JobDataEvent event) {
return message.addMetadata(OUTGOING_HTTP_METADATA.get());
}
}
diff --git a/jobs-service/jobs-service-messaging-http/src/test/java/org/kie/kogito/jobs/service/messaging/http/stream/HttpJobStreamsTest.java b/jobs-service/jobs-service-messaging-http/src/test/java/org/kie/kogito/jobs/service/messaging/http/stream/HttpJobStreamsTest.java
index 8449d88da6..7b951b5e39 100644
--- a/jobs-service/jobs-service-messaging-http/src/test/java/org/kie/kogito/jobs/service/messaging/http/stream/HttpJobStreamsTest.java
+++ b/jobs-service/jobs-service-messaging-http/src/test/java/org/kie/kogito/jobs/service/messaging/http/stream/HttpJobStreamsTest.java
@@ -21,6 +21,7 @@
import java.util.Optional;
import org.eclipse.microprofile.reactive.messaging.Message;
+import org.kie.kogito.jobs.service.events.JobDataEvent;
import org.kie.kogito.jobs.service.stream.AbstractJobStreamsTest;
import io.cloudevents.jackson.JsonFormat;
@@ -29,6 +30,7 @@
import jakarta.ws.rs.core.HttpHeaders;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.kie.kogito.jobs.service.messaging.http.stream.HttpJobStreams.PARTITION_KEY_EXTENSION;
class HttpJobStreamsTest extends AbstractJobStreamsTest {
@@ -44,4 +46,12 @@ protected void assertExpectedMetadata(Message message) {
assertThat(metadata.getHeaders()).hasSize(1);
assertThat(metadata.getHeaders().get(HttpHeaders.CONTENT_TYPE)).containsExactlyInAnyOrder(JsonFormat.CONTENT_TYPE);
}
+
+ @Override
+ protected void assertExpectedEvent(JobDataEvent event) {
+ super.assertExpectedEvent(event);
+ assertThat(event.getExtension(PARTITION_KEY_EXTENSION))
+ .isNotNull()
+ .isEqualTo(JOB_ID);
+ }
}
diff --git a/jobs-service/jobs-service-messaging-kafka/src/main/java/org/kie/kogito/jobs/service/messaging/kafka/stream/KafkaJobStreams.java b/jobs-service/jobs-service-messaging-kafka/src/main/java/org/kie/kogito/jobs/service/messaging/kafka/stream/KafkaJobStreams.java
index c9b149ede2..315d89dff4 100644
--- a/jobs-service/jobs-service-messaging-kafka/src/main/java/org/kie/kogito/jobs/service/messaging/kafka/stream/KafkaJobStreams.java
+++ b/jobs-service/jobs-service-messaging-kafka/src/main/java/org/kie/kogito/jobs/service/messaging/kafka/stream/KafkaJobStreams.java
@@ -23,7 +23,9 @@
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.reactive.messaging.Channel;
import org.eclipse.microprofile.reactive.messaging.Emitter;
+import org.eclipse.microprofile.reactive.messaging.Message;
import org.eclipse.microprofile.reactive.messaging.OnOverflow;
+import org.kie.kogito.jobs.service.events.JobDataEvent;
import org.kie.kogito.jobs.service.model.JobDetails;
import org.kie.kogito.jobs.service.stream.AbstractJobStreams;
import org.kie.kogito.jobs.service.stream.AvailableStreams;
@@ -32,6 +34,8 @@
import com.fasterxml.jackson.databind.ObjectMapper;
+import io.smallrye.reactive.messaging.kafka.api.OutgoingKafkaRecordMetadata;
+
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@@ -54,4 +58,10 @@ public void jobStatusChange(JobDetails job) {
LOGGER.debug("jobStatusChange call received, enabled: {}, job: {}", enabled, job);
super.jobStatusChange(job);
}
+
+ @Override
+ protected Message decorate(Message message, JobDataEvent event) {
+ // regular kafka partitioning.
+ return message.addMetadata(OutgoingKafkaRecordMetadata.builder().withKey(event.getData().getId()).build());
+ }
}
diff --git a/jobs-service/jobs-service-messaging-kafka/src/test/java/org/kie/kogito/jobs/service/messaging/kafka/stream/KafkaJobStreamsTest.java b/jobs-service/jobs-service-messaging-kafka/src/test/java/org/kie/kogito/jobs/service/messaging/kafka/stream/KafkaJobStreamsTest.java
index a0dbc6c95d..d5be8d1f5f 100644
--- a/jobs-service/jobs-service-messaging-kafka/src/test/java/org/kie/kogito/jobs/service/messaging/kafka/stream/KafkaJobStreamsTest.java
+++ b/jobs-service/jobs-service-messaging-kafka/src/test/java/org/kie/kogito/jobs/service/messaging/kafka/stream/KafkaJobStreamsTest.java
@@ -20,12 +20,24 @@
import java.util.Optional;
+import org.eclipse.microprofile.reactive.messaging.Message;
import org.kie.kogito.jobs.service.stream.AbstractJobStreamsTest;
+import io.smallrye.reactive.messaging.kafka.api.OutgoingKafkaRecordMetadata;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
class KafkaJobStreamsTest extends AbstractJobStreamsTest {
@Override
protected KafkaJobStreams createJobStreams() {
return new KafkaJobStreams(objectMapper, Optional.of(true), emitter, URL);
}
+
+ @Override
+ protected void assertExpectedMetadata(Message message) {
+ OutgoingKafkaRecordMetadata> metadata = message.getMetadata(OutgoingKafkaRecordMetadata.class).orElse(null);
+ assertThat(metadata).isNotNull();
+ assertThat(metadata.getKey()).isEqualTo(JOB_ID);
+ }
}