From da94450af5d1b9f7a6cebc08ba12bd24ceaf7a3d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Dec 2022 00:41:03 +0000 Subject: [PATCH 01/96] Bump commons-net from 3.6 to 3.9.0 in /source Bumps commons-net from 3.6 to 3.9.0. --- updated-dependencies: - dependency-name: commons-net:commons-net dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- source/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/pom.xml b/source/pom.xml index 0b7c68c3c6..599858162e 100644 --- a/source/pom.xml +++ b/source/pom.xml @@ -72,7 +72,7 @@ 1.0.16 1.3.3 2.7 - 3.6 + 3.9.0 3.12.0 4.4 0.8.5 From 25a6dcf6cf651c7054151d393420416082fef934 Mon Sep 17 00:00:00 2001 From: Benoit DUMONT Date: Thu, 8 Dec 2022 21:17:14 +0100 Subject: [PATCH 02/96] Fixed typo messages on error messages. Avoid double // on RunTestCase execution calls. --- .../impl/ExecutionQueueWorkerThread.java | 12 ++++++------ .../core/servlet/zzpublic/RunTestCaseV002.java | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/source/src/main/java/org/cerberus/core/engine/queuemanagement/impl/ExecutionQueueWorkerThread.java b/source/src/main/java/org/cerberus/core/engine/queuemanagement/impl/ExecutionQueueWorkerThread.java index 8b0e7ec473..e4062e4d3b 100644 --- a/source/src/main/java/org/cerberus/core/engine/queuemanagement/impl/ExecutionQueueWorkerThread.java +++ b/source/src/main/java/org/cerberus/core/engine/queuemanagement/impl/ExecutionQueueWorkerThread.java @@ -406,9 +406,9 @@ private void runParseAnswer(String answer, String cerberusUrl, String cerberusFu // Check answer format Matcher matcher = EXECUTION_ID_FROM_ANSWER_PATTERN.matcher(answer); if (!matcher.find()) { - LOG.error("Bad answer format (could not find 'RunID = '). URL Called: " + cerberusFullUrl); - LOG.error("Bad answer format (could not find 'RunID = '). Answer: " + answer); - throw new RunQueueProcessException("Error occured when calling the service to run the testcase. Service answer did not have the expected format (missing 'RunID = '). Probably due to bad cerberus_url value. URL Called: '" + cerberusUrl + "'."); + LOG.error("Bad answer format (could not find 'id = '). URL Called: " + cerberusFullUrl); + LOG.error("Bad answer format (could not find 'id = '). Answer: " + answer); + throw new RunQueueProcessException("Error occured when calling the service to run the testcase. Service answer did not have the expected format (missing 'id = '). Probably due to bad cerberus_url value. URL Called: '" + cerberusUrl + "'."); } // Extract the return code @@ -424,9 +424,9 @@ private void runParseAnswer(String answer, String cerberusUrl, String cerberusFu if (executionID == 0) { Matcher descriptionMatcher = RETURN_CODE_DESCRIPTION_FROM_ANSWER_PATTERN.matcher(answer); if (!descriptionMatcher.find()) { - LOG.error("Bad answer format (could not find 'ReturnCodeDescription = '). URL Called: " + cerberusFullUrl); - LOG.error("Bad answer format (could not find 'ReturnCodeDescription = '). Answer: " + answer); - throw new RunQueueProcessException("Error occured when calling the service to run the testcase. Service answer did not have the expected format (missing 'ReturnCodeDescription = '). Probably due to bad cerberus_url value. URL Called: '" + cerberusUrl + "'."); + LOG.error("Bad answer format (could not find 'controlMessage = '). URL Called: " + cerberusFullUrl); + LOG.error("Bad answer format (could not find 'controlMessage = '). Answer: " + answer); + throw new RunQueueProcessException("Error occured when calling the service to run the testcase. Service answer did not have the expected format (missing 'controlMessage = '). Probably due to bad cerberus_url value. URL Called: '" + cerberusUrl + "'."); } throw new RunQueueProcessException(descriptionMatcher.group(1)); } diff --git a/source/src/main/java/org/cerberus/core/servlet/zzpublic/RunTestCaseV002.java b/source/src/main/java/org/cerberus/core/servlet/zzpublic/RunTestCaseV002.java index cf4d1a4392..491441bc13 100644 --- a/source/src/main/java/org/cerberus/core/servlet/zzpublic/RunTestCaseV002.java +++ b/source/src/main/java/org/cerberus/core/servlet/zzpublic/RunTestCaseV002.java @@ -74,7 +74,7 @@ public class RunTestCaseV002 extends HttpServlet { private static final org.apache.logging.log4j.Logger LOG = org.apache.logging.log4j.LogManager.getLogger(RunTestCaseV002.class); - public static final String SERVLET_URL = "/RunTestCaseV002"; + public static final String SERVLET_URL = "RunTestCaseV002"; public static final String PARAMETER_TEST = "Test"; public static final String PARAMETER_TEST_CASE = "TestCase"; From b0acd6a275e557c9e1062a8db6ceeda1e0edc333 Mon Sep 17 00:00:00 2001 From: Benoit DUMONT Date: Tue, 13 Dec 2022 00:10:25 +0100 Subject: [PATCH 03/96] Fixed NPE when scrollTo on non existing element. Slight improvements on homepage (stats of tescases per application) --- .../service/appium/impl/AppiumService.java | 6 +++--- .../webdriver/impl/WebDriverService.java | 20 +++++++++++-------- source/src/main/webapp/css/pages/Homepage.css | 4 ++++ source/src/main/webapp/js/pages/Homepage.js | 20 ++++++++++++++++--- 4 files changed, 36 insertions(+), 14 deletions(-) diff --git a/source/src/main/java/org/cerberus/core/service/appium/impl/AppiumService.java b/source/src/main/java/org/cerberus/core/service/appium/impl/AppiumService.java index c8cea81a50..bf7fd2719b 100644 --- a/source/src/main/java/org/cerberus/core/service/appium/impl/AppiumService.java +++ b/source/src/main/java/org/cerberus/core/service/appium/impl/AppiumService.java @@ -201,11 +201,11 @@ private MessageEvent parseWebDriverException(WebDriverException exception) { * Get the {@link Coordinates} represented by the given {@link Identifier} * * @param identifier the {@link Identifier} to parse to get the - * {@link Coordinates} + * {@link Coordinates} * @return the {@link Coordinates} represented by the given * {@link Identifier} * @throws NoSuchElementException if no {@link Coordinates} can be found - * inside the given {@link Identifier} + * inside the given {@link Identifier} */ private Coordinates getCoordinates(final Identifier identifier) { if (identifier == null || !identifier.isSameIdentifier(Identifier.Identifiers.COORDINATE)) { @@ -410,8 +410,8 @@ private boolean scrollDown(AppiumDriver driver, By element, int numberOfScrollDo } private void scroll(AppiumDriver driver, int fromX, int fromY, int toX, int toY) { - TouchAction touchAction = new TouchAction(driver); + TouchAction touchAction = new TouchAction(driver); touchAction.longPress(PointOption.point(fromX, fromY)).moveTo(PointOption.point(toX, toY)).release().perform(); } diff --git a/source/src/main/java/org/cerberus/core/service/webdriver/impl/WebDriverService.java b/source/src/main/java/org/cerberus/core/service/webdriver/impl/WebDriverService.java index 080d9a6e7c..1b96b09799 100644 --- a/source/src/main/java/org/cerberus/core/service/webdriver/impl/WebDriverService.java +++ b/source/src/main/java/org/cerberus/core/service/webdriver/impl/WebDriverService.java @@ -213,6 +213,8 @@ public MessageEvent scrollTo(Session session, Identifier identifier, String text if (answer.isCodeEquals(MessageEventEnum.ACTION_SUCCESS_WAIT_ELEMENT.getCode())) { webElement = (WebElement) answer.getItem(); } + return answer.getResultMessage(); + } else { webElement = session.getDriver().findElement(By.xpath("//*[contains(text()," + text + ")]")); } @@ -609,7 +611,7 @@ public String getTitle(Session session) { * @param applicationUrl * @return current URL without HTTP://IP:PORT/CONTEXTROOT/ * @throws CerberusEventException Cannot find application host (from - * Database) inside current URL (from Selenium) + * Database) inside current URL (from Selenium) */ @Override public String getCurrentUrl(Session session, String applicationUrl) throws CerberusEventException { @@ -799,11 +801,13 @@ public MessageEvent doSeleniumActionClick(Session session, final Identifier iden } return answer.getResultMessage(); + } catch (NoSuchElementException exception) { message = new MessageEvent(MessageEventEnum.ACTION_FAILED_CLICK_NO_SUCH_ELEMENT); message.setDescription(message.getDescription().replace("%ELEMENT%", identifier.getIdentifier() + "=" + identifier.getLocator())); LOG.debug(exception.toString()); return message; + } catch (WebDriverException exception) { LOG.warn(exception.toString()); return parseWebDriverException(exception); @@ -1252,13 +1256,13 @@ public boolean focusBrowserWindow(Session session) { // Arbitrary String[] browsers = new String[]{ - "", - "Google Chrome", - "Mozilla Firefox", - "Opera", - "Safari", - "Internet Explorer", - "Microsoft Edge",}; + "", + "Google Chrome", + "Mozilla Firefox", + "Opera", + "Safari", + "Internet Explorer", + "Microsoft Edge",}; for (String browser : browsers) { HWND window; diff --git a/source/src/main/webapp/css/pages/Homepage.css b/source/src/main/webapp/css/pages/Homepage.css index 8804ffc2be..b54edc2483 100644 --- a/source/src/main/webapp/css/pages/Homepage.css +++ b/source/src/main/webapp/css/pages/Homepage.css @@ -45,4 +45,8 @@ padding-bottom: 10px; margin-top: 10px; text-transform: uppercase; +} + +.datatable-alignright{ + text-align: right; } \ No newline at end of file diff --git a/source/src/main/webapp/js/pages/Homepage.js b/source/src/main/webapp/js/pages/Homepage.js index c4f99ddf11..91f95e18d4 100644 --- a/source/src/main/webapp/js/pages/Homepage.js +++ b/source/src/main/webapp/js/pages/Homepage.js @@ -614,7 +614,14 @@ function aoColumnsFunc() { return "" + data + ""; } }, - {"data": "Total", "bSortable": true, "sName": "Total", "title": "Total", "sWidth": "10px"} + { + "data": "Total", + "bSortable": true, + "sWidth": "10px", + "sClass": "datatable-alignright", + "sName": "Total", + "title": "Total" + } ]; for (var s = 0; s < statusLen; s++) { @@ -623,9 +630,16 @@ function aoColumnsFunc() { "data": status[s].value, "bSortable": true, "sWidth": "10px", + "sClass": "datatable-alignright", "sName": status[s].value, - "title": status[s].value - }; + "title": status[s].value, + "mRender": function (data, type, oObj) { + if ((data) === 0) { + return ""; + }; + return data; + } + }; aoColumns.push(obj); } } From 9a70d0e30c897042c52e569fd571b05cd634d818 Mon Sep 17 00:00:00 2001 From: Benoit DUMONT Date: Tue, 27 Dec 2022 20:42:55 +0100 Subject: [PATCH 04/96] Secure file saving when testfolder and testid have / or \ characters. --- .../engine/execution/impl/RecorderService.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/source/src/main/java/org/cerberus/core/engine/execution/impl/RecorderService.java b/source/src/main/java/org/cerberus/core/engine/execution/impl/RecorderService.java index 80d481a0ed..36cf77daf1 100644 --- a/source/src/main/java/org/cerberus/core/engine/execution/impl/RecorderService.java +++ b/source/src/main/java/org/cerberus/core/engine/execution/impl/RecorderService.java @@ -1075,7 +1075,7 @@ private void recordFile(String path, String fileName, String content, HashMap Date: Tue, 27 Dec 2022 21:07:52 +0100 Subject: [PATCH 05/96] Manage correctly special characters on email notifications. --- .../email/impl/EmailGenerationService.java | 10 +++++----- .../service/notifications/email/impl/EmailService.java | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/source/src/main/java/org/cerberus/core/service/notifications/email/impl/EmailGenerationService.java b/source/src/main/java/org/cerberus/core/service/notifications/email/impl/EmailGenerationService.java index 6409ec19b0..f555769a35 100644 --- a/source/src/main/java/org/cerberus/core/service/notifications/email/impl/EmailGenerationService.java +++ b/source/src/main/java/org/cerberus/core/service/notifications/email/impl/EmailGenerationService.java @@ -19,6 +19,7 @@ */ package org.cerberus.core.service.notifications.email.impl; +import java.net.URLEncoder; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.cerberus.core.crud.entity.BatchInvariant; @@ -73,7 +74,6 @@ public class EmailGenerationService implements IEmailGenerationService { @Autowired private IEmailBodyGeneration emailBodyGeneration; - @Override public Email generateRevisionChangeEmail(String system, String country, String env, String build, String revision) throws Exception { Email email = new Email(); @@ -318,7 +318,7 @@ public Email generateNotifyStartTagExecution(Tag tag, String to) throws Exceptio StringBuilder urlreporttag = new StringBuilder(); urlreporttag.append(cerberusUrl); urlreporttag.append("/ReportingExecutionByTag.jsp?Tag="); - urlreporttag.append(tag.getTag()); + urlreporttag.append(URLEncoder.encode(tag.getTag(), "UTF-8")); body = body.replace("%TAG%", tag.getTag()); body = body.replace("%URLTAGREPORT%", urlreporttag.toString()); body = body.replace("%CAMPAIGN%", tag.getCampaign()); @@ -362,7 +362,7 @@ public Email generateNotifyEndTagExecution(Tag tag, String to) throws Exception StringBuilder urlreporttag = new StringBuilder(); urlreporttag.append(cerberusUrl); urlreporttag.append("/ReportingExecutionByTag.jsp?Tag="); - urlreporttag.append(tag); + urlreporttag.append(URLEncoder.encode(URLEncoder.encode(tag.getTag(), "UTF-8"), "UTF-8")); // Body replace. body = body.replace("%TAG%", tag.getTag()); @@ -689,9 +689,9 @@ public Email generateNotifyTestCaseChange(TestCase testCase, String to, String e StringBuilder urlTestCase = new StringBuilder(); urlTestCase.append(cerberusUrl); urlTestCase.append("/TestCaseScript.jsp?test="); - urlTestCase.append(testCase.getTest()); + urlTestCase.append(URLEncoder.encode(testCase.getTest(), "UTF-8")); urlTestCase.append("&testcase="); - urlTestCase.append(testCase.getTestcase()); + urlTestCase.append(URLEncoder.encode(testCase.getTestcase(), "UTF-8")); switch (eventReference) { case EventHook.EVENTREFERENCE_TESTCASE_CREATE: diff --git a/source/src/main/java/org/cerberus/core/service/notifications/email/impl/EmailService.java b/source/src/main/java/org/cerberus/core/service/notifications/email/impl/EmailService.java index 3f1dfca672..ff884e2cd3 100644 --- a/source/src/main/java/org/cerberus/core/service/notifications/email/impl/EmailService.java +++ b/source/src/main/java/org/cerberus/core/service/notifications/email/impl/EmailService.java @@ -19,6 +19,7 @@ */ package org.cerberus.core.service.notifications.email.impl; +import java.nio.charset.Charset; import org.cerberus.core.service.notifications.email.entity.Email; import org.apache.commons.mail.HtmlEmail; import org.cerberus.core.crud.service.ILogEventService; @@ -51,6 +52,7 @@ public void sendHtmlMail(Email cerberusEmail) throws Exception { email.setFrom(cerberusEmail.getFrom()); email.setSubject(cerberusEmail.getSubject()); email.setHtmlMsg(cerberusEmail.getBody()); + email.setCharset("UTF-8"); if (cerberusEmail.isSetTls()) { email = (HtmlEmail) email.setStartTLSEnabled(true); } From 33598dacdae9d7a262e5f8d1f1ea579c9a5c55b7 Mon Sep 17 00:00:00 2001 From: Benoit DUMONT Date: Wed, 28 Dec 2022 10:33:51 +0100 Subject: [PATCH 06/96] Decode value3 of actions. Fixed #2413 --- .../core/engine/gwt/impl/ActionService.java | 30 ++++++++++++++++--- .../appservice/impl/ServiceService.java | 5 ++-- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/source/src/main/java/org/cerberus/core/engine/gwt/impl/ActionService.java b/source/src/main/java/org/cerberus/core/engine/gwt/impl/ActionService.java index 7c0fccca3d..8007dbd0ed 100644 --- a/source/src/main/java/org/cerberus/core/engine/gwt/impl/ActionService.java +++ b/source/src/main/java/org/cerberus/core/engine/gwt/impl/ActionService.java @@ -187,8 +187,7 @@ public TestCaseStepActionExecution doAction(TestCaseStepActionExecution actionEx // When starting a new action, we reset the property list that was already calculated. execution.setRecursiveAlreadyCalculatedPropertiesList(new ArrayList<>()); - answerDecode = variableService.decodeStringCompletly(actionExecution.getValue1(), - execution, actionExecution, false); + answerDecode = variableService.decodeStringCompletly(actionExecution.getValue1(), execution, actionExecution, false); actionExecution.setValue1(answerDecode.getItem()); if (!(answerDecode.isCodeStringEquals("OK"))) { @@ -212,8 +211,7 @@ public TestCaseStepActionExecution doAction(TestCaseStepActionExecution actionEx // When starting a new action, we reset the property list that was already calculated. execution.setRecursiveAlreadyCalculatedPropertiesList(new ArrayList<>()); - answerDecode = variableService.decodeStringCompletly(actionExecution.getValue2(), - execution, actionExecution, false); + answerDecode = variableService.decodeStringCompletly(actionExecution.getValue2(), execution, actionExecution, false); actionExecution.setValue2(answerDecode.getItem()); if (!(answerDecode.isCodeStringEquals("OK"))) { @@ -232,6 +230,30 @@ public TestCaseStepActionExecution doAction(TestCaseStepActionExecution actionEx return actionExecution; } + try { + + // When starting a new action, we reset the property list that was already calculated. + execution.setRecursiveAlreadyCalculatedPropertiesList(new ArrayList<>()); + + answerDecode = variableService.decodeStringCompletly(actionExecution.getValue3(), execution, actionExecution, false); + actionExecution.setValue3(answerDecode.getItem()); + + if (!(answerDecode.isCodeStringEquals("OK"))) { + // If anything wrong with the decode --> we stop here with decode message in the action result. + actionExecution.setActionResultMessage(answerDecode.getResultMessage().resolveDescription("FIELD", "Action Value3")); + actionExecution.setExecutionResultMessage(new MessageGeneral(answerDecode.getResultMessage().getMessage())); + actionExecution.setStopExecution(answerDecode.getResultMessage().isStopTest()); + actionExecution.setEnd(new Date().getTime()); + LOG.debug("Action interupted due to decode 'Action Value3' Error."); + return actionExecution; + } + } catch (CerberusEventException cex) { + actionExecution.setActionResultMessage(cex.getMessageError()); + actionExecution.setExecutionResultMessage(new MessageGeneral(cex.getMessageError().getMessage())); + actionExecution.setEnd(new Date().getTime()); + return actionExecution; + } + /** * Timestamp starts after the decode. */ diff --git a/source/src/main/java/org/cerberus/core/service/appservice/impl/ServiceService.java b/source/src/main/java/org/cerberus/core/service/appservice/impl/ServiceService.java index 22b35765e8..5ddfd14675 100644 --- a/source/src/main/java/org/cerberus/core/service/appservice/impl/ServiceService.java +++ b/source/src/main/java/org/cerberus/core/service/appservice/impl/ServiceService.java @@ -467,6 +467,7 @@ public AnswerItem callService(String service, String targetNbEvents, String decodedFilterValue = appService.getKafkaFilterValue(); String decodedFilterHeaderPath = appService.getKafkaFilterHeaderPath(); String decodedFilterHeaderValue = appService.getKafkaFilterHeaderValue(); + try { answerDecode = variableService.decodeStringCompletly(decodedFilterPath, tCExecution, null, false); @@ -549,8 +550,8 @@ public AnswerItem callService(String service, String targetNbEvents, appService.setKafkaFilterValue(decodedFilterValue); String kafkaKey = kafkaService.getKafkaConsumerKey(decodedTopic, decodedServicePath); - AnswerItem resultSearch = kafkaService.searchEvent(tCExecution.getKafkaLatestOffset().get(kafkaKey), decodedTopic, decodedServicePath, - appService.getHeaderList(), appService.getContentList(), decodedFilterPath, decodedFilterValue, decodedFilterHeaderPath, decodedFilterHeaderValue, + AnswerItem resultSearch = kafkaService.searchEvent(tCExecution.getKafkaLatestOffset().get(kafkaKey), decodedTopic, decodedServicePath, + appService.getHeaderList(), appService.getContentList(), decodedFilterPath, decodedFilterValue, decodedFilterHeaderPath, decodedFilterHeaderValue, appService.isAvroEnable(), appService.getSchemaRegistryURL(), targetNbEventsInt, targetNbSecInt); if (!(resultSearch.isCodeStringEquals("OK"))) { From b077f6ea625d5e4ad5e92166a04499520b2b3980 Mon Sep 17 00:00:00 2001 From: Benoit DUMONT Date: Wed, 28 Dec 2022 17:51:39 +0100 Subject: [PATCH 07/96] Service heritage fix. Fixed #2425 --- .../crud/dao/impl/AppServiceContentDAO.java | 23 ++++++++++--------- .../core/crud/service/IAppServiceService.java | 4 ++-- .../crud/service/impl/AppServiceService.java | 19 +++++++++++++-- .../appservice/impl/ServiceService.java | 1 - 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/source/src/main/java/org/cerberus/core/crud/dao/impl/AppServiceContentDAO.java b/source/src/main/java/org/cerberus/core/crud/dao/impl/AppServiceContentDAO.java index 61ad002842..712d30bff4 100644 --- a/source/src/main/java/org/cerberus/core/crud/dao/impl/AppServiceContentDAO.java +++ b/source/src/main/java/org/cerberus/core/crud/dao/impl/AppServiceContentDAO.java @@ -58,7 +58,6 @@ @Repository public class AppServiceContentDAO implements IAppServiceContentDAO { - private final DatabaseSpring databaseSpring; private final IFactoryAppServiceContent factoryAppServiceContent; private static final Logger LOG = LogManager.getLogger(AppServiceContentDAO.class); @@ -79,7 +78,7 @@ public AnswerItem readByKey(String service, String key) { LOG.debug("SQL.param.key : {}", key); try (Connection connection = this.databaseSpring.connect(); - PreparedStatement preStat = connection.prepareStatement(query, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) { + PreparedStatement preStat = connection.prepareStatement(query, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) { preStat.setString(1, service); preStat.setString(2, key); @@ -158,10 +157,12 @@ public AnswerList readByVariousByCriteria(String service, boo // Debug message on SQL. LOG.debug("SQL : {}", query); + LOG.debug("SQL.param.service : {}", service); + LOG.debug("SQL.param.isActive : {}", isActive); try (Connection connection = this.databaseSpring.connect(); - PreparedStatement preStat = connection.prepareStatement(query.toString()); - Statement stm = connection.createStatement()) { + PreparedStatement preStat = connection.prepareStatement(query.toString()); + Statement stm = connection.createStatement()) { int i = 1; if (StringUtil.isNotEmpty(searchTerm)) { @@ -187,7 +188,7 @@ public AnswerList readByVariousByCriteria(String service, boo } try (ResultSet resultSet = preStat.executeQuery(); - ResultSet rowSet = stm.executeQuery("SELECT FOUND_ROWS()")) { + ResultSet rowSet = stm.executeQuery("SELECT FOUND_ROWS()")) { while (resultSet.next()) { objectList.add(this.loadFromResultSet(resultSet)); } @@ -230,7 +231,7 @@ public Answer create(AppServiceContent object) { LOG.debug("SQL : {}", query); try (Connection connection = this.databaseSpring.connect(); - PreparedStatement preStat = connection.prepareStatement(query.toString())) { + PreparedStatement preStat = connection.prepareStatement(query.toString())) { int i = 1; preStat.setString(i++, object.getService()); @@ -269,7 +270,7 @@ public Answer delete(AppServiceContent object) { LOG.debug("SQL.param.key : {}", object.getKey()); try (Connection connection = this.databaseSpring.connect(); - PreparedStatement preStat = connection.prepareStatement(query)) { + PreparedStatement preStat = connection.prepareStatement(query)) { int i = 1; preStat.setString(i++, object.getService()); preStat.setString(i, object.getKey()); @@ -296,7 +297,7 @@ public Answer update(String service, String key, AppServiceContent object) { LOG.debug("SQL.param.key : {}", object.getKey()); try (Connection connection = this.databaseSpring.connect(); - PreparedStatement preStat = connection.prepareStatement(query)) { + PreparedStatement preStat = connection.prepareStatement(query)) { int i = 1; preStat.setString(i++, object.getService()); preStat.setString(i++, object.getKey()); @@ -365,8 +366,8 @@ public AnswerList readDistinctValuesByCriteria(String system, String sea LOG.debug("SQL : {}", query); try (Connection connection = databaseSpring.connect(); - PreparedStatement preStat = connection.prepareStatement(query.toString()); - Statement stm = connection.createStatement()) { + PreparedStatement preStat = connection.prepareStatement(query.toString()); + Statement stm = connection.createStatement()) { int i = 1; if (StringUtil.isNotEmpty(system)) { @@ -388,7 +389,7 @@ public AnswerList readDistinctValuesByCriteria(String system, String sea preStat.setString(i++, individualColumnSearchValue); } try (ResultSet resultSet = preStat.executeQuery(); - ResultSet rowSet = stm.executeQuery("SELECT FOUND_ROWS()")) { + ResultSet rowSet = stm.executeQuery("SELECT FOUND_ROWS()")) { while (resultSet.next()) { distinctValues.add(resultSet.getString("distinctValues") == null ? "" : resultSet.getString("distinctValues")); diff --git a/source/src/main/java/org/cerberus/core/crud/service/IAppServiceService.java b/source/src/main/java/org/cerberus/core/crud/service/IAppServiceService.java index 4ba6b914d5..a34a248a7a 100644 --- a/source/src/main/java/org/cerberus/core/crud/service/IAppServiceService.java +++ b/source/src/main/java/org/cerberus/core/crud/service/IAppServiceService.java @@ -72,8 +72,8 @@ public interface IAppServiceService { /** * Get the {@link AppService} of the given key * - * @param key the key of the {@link AppService} to getY will load detail - * only with Active data on header and content. null wil load all data. + * @param key the key of the {@link AppService} to get Y will load detail + * only with Active data on header and content. null will load all data. * @return */ AnswerItem readByKeyWithDependency(String key); diff --git a/source/src/main/java/org/cerberus/core/crud/service/impl/AppServiceService.java b/source/src/main/java/org/cerberus/core/crud/service/impl/AppServiceService.java index 8b25ac7156..ef49a4790c 100644 --- a/source/src/main/java/org/cerberus/core/crud/service/impl/AppServiceService.java +++ b/source/src/main/java/org/cerberus/core/crud/service/impl/AppServiceService.java @@ -127,10 +127,25 @@ public AnswerItem readByKeyWithDependency(String key, boolean active try { if (appService != null) { - AnswerList content = appServiceContentService.readByVarious(key, activeDetail); + AnswerList content; + // Add first the inherited values. + if (StringUtil.isNotEmpty(appService.getParentContentService())) { + content = appServiceContentService.readByVarious(appService.getParentContentService(), activeDetail); + if (content != null) { + List contentList = content.getDataList(); + for (AppServiceContent appServiceContent : contentList) { + appServiceContent.setInherited(true); + } + appService.setContentList(content.getDataList()); + } + } + // Add then the normal values. + content = appServiceContentService.readByVarious(key, activeDetail); if (content != null) { - appService.setContentList(content.getDataList()); + appService.addContentList(content.getDataList()); } + + // Header List AnswerList header = appServiceHeaderService.readByVarious(key, activeDetail); if (header != null) { appService.setHeaderList(header.getDataList()); diff --git a/source/src/main/java/org/cerberus/core/service/appservice/impl/ServiceService.java b/source/src/main/java/org/cerberus/core/service/appservice/impl/ServiceService.java index 5ddfd14675..ed530910e0 100644 --- a/source/src/main/java/org/cerberus/core/service/appservice/impl/ServiceService.java +++ b/source/src/main/java/org/cerberus/core/service/appservice/impl/ServiceService.java @@ -106,7 +106,6 @@ public AnswerItem callService(String service, String targetNbEvents, // If Service information is defined, we get it from database. LOG.debug("Getting AppService from service : " + service); appService = appServiceService.convert(appServiceService.readByKeyWithDependency(service, true)); - } String servicePath; From b87a73065a448989ff9557afb7344390d2407d39 Mon Sep 17 00:00:00 2001 From: Benoit DUMONT Date: Wed, 28 Dec 2022 18:04:15 +0100 Subject: [PATCH 08/96] Added decode on Schema registry URL. Fixed #2426 --- .../appservice/impl/ServiceService.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/source/src/main/java/org/cerberus/core/service/appservice/impl/ServiceService.java b/source/src/main/java/org/cerberus/core/service/appservice/impl/ServiceService.java index ed530910e0..5ce20b07b7 100644 --- a/source/src/main/java/org/cerberus/core/service/appservice/impl/ServiceService.java +++ b/source/src/main/java/org/cerberus/core/service/appservice/impl/ServiceService.java @@ -449,6 +449,21 @@ public AnswerItem callService(String service, String targetNbEvents, return result; } + String decodedSchemaRegistryURL = appService.getSchemaRegistryURL(); + if (appService.isAvroEnable()) { + answerDecode = variableService.decodeStringCompletly(decodedSchemaRegistryURL, tCExecution, null, false); + decodedSchemaRegistryURL = answerDecode.getItem(); + if (!(answerDecode.isCodeStringEquals("OK"))) { + // If anything wrong with the decode --> we stop here with decode message in the action result. + String field = "Kafka Schema Registry URL"; + message = new MessageEvent(MessageEventEnum.ACTION_FAILED_CALLSERVICE) + .resolveDescription("DESCRIPTION", answerDecode.getResultMessage().resolveDescription("FIELD", field).getDescription()); + LOG.debug("Service Call interupted due to decode '" + field + "'."); + result.setResultMessage(message); + return result; + } + } + switch (appService.getMethod()) { case AppService.METHOD_KAFKAPRODUCE: @@ -456,7 +471,7 @@ public AnswerItem callService(String service, String targetNbEvents, * Call REST and store it into the execution. */ result = kafkaService.produceEvent(decodedTopic, decodedKey, decodedRequest, decodedServicePath, appService.getHeaderList(), appService.getContentList(), - token, appService.isAvroEnable(), appService.getSchemaRegistryURL(), appService.getAvroSchema(), timeOutMs); + token, appService.isAvroEnable(), decodedSchemaRegistryURL, appService.getAvroSchema(), timeOutMs); message = result.getResultMessage(); break; @@ -551,7 +566,7 @@ public AnswerItem callService(String service, String targetNbEvents, String kafkaKey = kafkaService.getKafkaConsumerKey(decodedTopic, decodedServicePath); AnswerItem resultSearch = kafkaService.searchEvent(tCExecution.getKafkaLatestOffset().get(kafkaKey), decodedTopic, decodedServicePath, appService.getHeaderList(), appService.getContentList(), decodedFilterPath, decodedFilterValue, decodedFilterHeaderPath, decodedFilterHeaderValue, - appService.isAvroEnable(), appService.getSchemaRegistryURL(), targetNbEventsInt, targetNbSecInt); + appService.isAvroEnable(), decodedSchemaRegistryURL, targetNbEventsInt, targetNbSecInt); if (!(resultSearch.isCodeStringEquals("OK"))) { message = new MessageEvent(MessageEventEnum.ACTION_FAILED_CALLSERVICE); From cbe4a39de71bb54c2c33341e0353ba5f7587a8dc Mon Sep 17 00:00:00 2001 From: Benoit DUMONT Date: Thu, 29 Dec 2022 00:30:02 +0100 Subject: [PATCH 09/96] Added Kafka AVRO support also on key. #2418 --- .../core/crud/dao/impl/AppServiceDAO.java | 17 +- .../cerberus/core/crud/entity/AppService.java | 3 +- .../core/crud/factory/IFactoryAppService.java | 5 +- .../crud/factory/impl/FactoryAppService.java | 5 +- .../core/engine/gwt/impl/ActionService.java | 8 +- .../appservice/impl/ServiceService.java | 6 +- .../core/service/ftp/impl/FtpService.java | 2 +- .../core/service/kafka/IKafkaService.java | 18 +- .../core/service/kafka/impl/KafkaService.java | 211 +++++++++++++----- .../core/service/rest/impl/RestService.java | 4 +- .../core/service/soap/impl/SoapService.java | 2 +- .../countryenvironment/CreateAppService.java | 5 +- .../countryenvironment/UpdateAppService.java | 6 +- source/src/main/resources/database.sql | 8 + .../include/transversalobject/AppService.html | 51 +++-- .../webapp/js/transversalobject/AppService.js | 72 ++++-- 16 files changed, 289 insertions(+), 134 deletions(-) diff --git a/source/src/main/java/org/cerberus/core/crud/dao/impl/AppServiceDAO.java b/source/src/main/java/org/cerberus/core/crud/dao/impl/AppServiceDAO.java index 4e63be9845..05e15a7fcd 100644 --- a/source/src/main/java/org/cerberus/core/crud/dao/impl/AppServiceDAO.java +++ b/source/src/main/java/org/cerberus/core/crud/dao/impl/AppServiceDAO.java @@ -433,9 +433,9 @@ public Answer create(AppService object) { MessageEvent msg; StringBuilder query = new StringBuilder() .append("INSERT INTO appservice (`Service`, `Group`, `Application`, `Type`, `Method`, `ServicePath`, `isFollowRedir`, `Operation`, `ServiceRequest`, ") - .append(" `isAvroEnable`, `SchemaRegistryUrl`, `AvroSchema`, `ParentContentService`, `KafkaTopic`, `KafkaKey`, ") + .append(" `isAvroEnable`, `SchemaRegistryUrl`, `AvroSchemaKey`, `AvroSchemaValue`, `ParentContentService`, `KafkaTopic`, `KafkaKey`, ") .append(" `KafkaFilterPath`, `KafkaFilterValue`, `KafkaFilterHeaderPath`, `KafkaFilterHeaderValue`, `AttachementURL`, `Description`, `FileName`, `UsrCreated`) ") - .append("VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); + .append("VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); LOG.debug(SQL_MESSAGE, query); @@ -458,7 +458,8 @@ public Answer create(AppService object) { preStat.setString(i++, object.getServiceRequest()); preStat.setBoolean(i++, object.isAvroEnable()); preStat.setString(i++, object.getSchemaRegistryURL()); - preStat.setString(i++, object.getAvroSchema()); + preStat.setString(i++, object.getAvroSchemaKey()); + preStat.setString(i++, object.getAvroSchemaValue()); if (StringUtil.isNotEmpty(object.getParentContentService())) { preStat.setString(i++, object.getParentContentService()); } else { @@ -497,7 +498,7 @@ public Answer update(String service, AppService object) { MessageEvent msg; StringBuilder query = new StringBuilder() .append("UPDATE appservice srv SET `Service` = ?, `Group` = ?, `ServicePath` = ?, `isFollowRedir` = ?, `Operation` = ?, ServiceRequest = ?, ") - .append("`isAvroEnable` = ?, `SchemaRegistryUrl` = ?, `AvroSchema` = ?, ParentContentService = ?, KafkaTopic = ?, KafkaKey = ?, ") + .append("`isAvroEnable` = ?, `SchemaRegistryUrl` = ?, `AvroSchemaKey` = ?, `AvroSchemaValue` = ?, ParentContentService = ?, KafkaTopic = ?, KafkaKey = ?, ") .append("KafkaFilterPath = ?, KafkaFilterValue = ?, KafkaFilterHeaderPath = ?, KafkaFilterHeaderValue = ?, AttachementURL = ?, ") .append("`Description` = ?, `Type` = ?, Method = ?, `UsrModif`= ?, `DateModif` = NOW(), `FileName` = ?"); if ((object.getApplication() != null) && (!object.getApplication().isEmpty())) { @@ -523,7 +524,8 @@ public Answer update(String service, AppService object) { preStat.setString(i++, object.getServiceRequest()); preStat.setBoolean(i++, object.isAvroEnable()); preStat.setString(i++, object.getSchemaRegistryURL()); - preStat.setString(i++, object.getAvroSchema()); + preStat.setString(i++, object.getAvroSchemaKey()); + preStat.setString(i++, object.getAvroSchemaValue()); if (StringUtil.isEmpty(object.getParentContentService())) { preStat.setString(i++, null); } else { @@ -646,10 +648,11 @@ public AppService loadFromResultSet(ResultSet rs) throws SQLException { boolean isFollowRedir = rs.getBoolean("srv.isFollowRedir"); boolean isAvroEnable = rs.getBoolean("srv.isAvroEnable"); String schemaRegistryURL = ParameterParserUtil.parseStringParam(rs.getString("srv.SchemaRegistryUrl"), ""); - String avroSchema = ParameterParserUtil.parseStringParam(rs.getString("srv.AvroSchema"), ""); + String avroSchemaKey = ParameterParserUtil.parseStringParam(rs.getString("srv.AvroSchemaKey"), ""); + String avroSchemaValue = ParameterParserUtil.parseStringParam(rs.getString("srv.AvroSchemaValue"), ""); String parentContentService = ParameterParserUtil.parseStringParam(rs.getString("srv.ParentContentService"), ""); return factoryAppService.create(service, type, method, application, group, serviceRequest, kafkaTopic, kafkaKey, kafkaFilterPath, kafkaFilterValue, kafkaFilterHeaderPath, kafkaFilterHeaderValue, - description, servicePath, isFollowRedir, attachementURL, operation, isAvroEnable, schemaRegistryURL, avroSchema, parentContentService, usrCreated, dateCreated, usrModif, dateModif, fileName); + description, servicePath, isFollowRedir, attachementURL, operation, isAvroEnable, schemaRegistryURL, avroSchemaKey, avroSchemaValue, parentContentService, usrCreated, dateCreated, usrModif, dateModif, fileName); } private static void deleteFolder(File folder, boolean deleteit) { diff --git a/source/src/main/java/org/cerberus/core/crud/entity/AppService.java b/source/src/main/java/org/cerberus/core/crud/entity/AppService.java index bdd83bfb26..77d5801d5b 100644 --- a/source/src/main/java/org/cerberus/core/crud/entity/AppService.java +++ b/source/src/main/java/org/cerberus/core/crud/entity/AppService.java @@ -58,7 +58,8 @@ public class AppService { private String kafkaFilterHeaderValue; private boolean isAvroEnable; private String schemaRegistryURL; - private String avroSchema; + private String avroSchemaValue; + private String avroSchemaKey; private String parentContentService; private String group; // Information in order to group the services in order to organise them private String description; diff --git a/source/src/main/java/org/cerberus/core/crud/factory/IFactoryAppService.java b/source/src/main/java/org/cerberus/core/crud/factory/IFactoryAppService.java index 106cb7c94d..60dfc6c2a8 100644 --- a/source/src/main/java/org/cerberus/core/crud/factory/IFactoryAppService.java +++ b/source/src/main/java/org/cerberus/core/crud/factory/IFactoryAppService.java @@ -49,7 +49,8 @@ public interface IFactoryAppService { * @param operation * @param isAvroEnable * @param schemaRegistryUrl - * @param avroSchema + * @param avroSchemaKey + * @param avroSchemaValue * @param parentContentService * @param usrCreated * @param dateCreated @@ -60,6 +61,6 @@ public interface IFactoryAppService { */ AppService create(String service, String type, String method, String application, String group, String serviceRequest, String kafkaTopic, String kafkaKey, String kafkaFilterPath, String kafkaFilterValue, String kafkaFilterHeaderPath, String kafkaFilterHeaderValue, - String description, String servicePath, boolean isFollowRedir, String attachementURL, String operation, boolean isAvroEnable, String schemaRegistryUrl, String avroSchema, String parentContentService, + String description, String servicePath, boolean isFollowRedir, String attachementURL, String operation, boolean isAvroEnable, String schemaRegistryUrl, String avroSchemaKey, String avroSchemaValue, String parentContentService, String usrCreated, Timestamp dateCreated, String usrModif, Timestamp dateModif, String fileName); } diff --git a/source/src/main/java/org/cerberus/core/crud/factory/impl/FactoryAppService.java b/source/src/main/java/org/cerberus/core/crud/factory/impl/FactoryAppService.java index 37a9f1fb66..a7e3603bb6 100644 --- a/source/src/main/java/org/cerberus/core/crud/factory/impl/FactoryAppService.java +++ b/source/src/main/java/org/cerberus/core/crud/factory/impl/FactoryAppService.java @@ -39,7 +39,7 @@ public class FactoryAppService implements IFactoryAppService { public AppService create(String service, String type, String method, String application, String group, String serviceRequest, String kafkaTopic, String kafkaKey, String kafkaFilterPath, String kafkaFilterValue, String kafkaFilterHeaderPath, String kafkaFilterHeaderValue, String description, - String servicePath, boolean isFollowRedir, String attachementURL, String operation, boolean isAvroEnable, String schemaRegistryUrl, String avroSchema, String parentContentService, + String servicePath, boolean isFollowRedir, String attachementURL, String operation, boolean isAvroEnable, String schemaRegistryUrl, String avroSchemaKey, String avroSchemaValue, String parentContentService, String usrCreated, Timestamp dateCreated, String usrModif, Timestamp dateModif, String fileName) { AppService s = new AppService(); s.setService(service); @@ -71,7 +71,8 @@ public AppService create(String service, String type, String method, String appl s.setKafkaFilterHeaderValue(kafkaFilterHeaderValue); s.setAvroEnable(isAvroEnable); s.setSchemaRegistryURL(schemaRegistryUrl); - s.setAvroSchema(avroSchema); + s.setAvroSchemaKey(avroSchemaKey); + s.setAvroSchemaValue(avroSchemaValue); s.setParentContentService(parentContentService); s.setRecordTraceFile(true); s.setFollowRedir(isFollowRedir); diff --git a/source/src/main/java/org/cerberus/core/engine/gwt/impl/ActionService.java b/source/src/main/java/org/cerberus/core/engine/gwt/impl/ActionService.java index 8007dbd0ed..ebc74f67f1 100644 --- a/source/src/main/java/org/cerberus/core/engine/gwt/impl/ActionService.java +++ b/source/src/main/java/org/cerberus/core/engine/gwt/impl/ActionService.java @@ -566,7 +566,7 @@ private MessageEvent doActionGetRobotFile(TestCaseExecution execution, TestCaseS } LOG.debug(contentJSON.toString(1)); - AppService appSrv = factoryAppService.create("", "", "", "", "", "", "", "", "", "", "", "", "", "", false, "", "", false, "", "", "", "", null, "", null, ""); + AppService appSrv = factoryAppService.create("", "", "", "", "", "", "", "", "", "", "", "", "", "", false, "", "", false, "", "", "", "", "", null, "", null, ""); JSONObject contentJSONnew = new JSONObject(); // We copy the header values for the service answered. @@ -1893,7 +1893,7 @@ private MessageEvent doActionSetNetworkTrafficContent(TestCaseExecution exe, Tes har = harService.enrichWithStats(har, exe.getCountryEnvironmentParameters().getDomain(), exe.getSystem(), exe.getNetworkTrafficIndexList()); - AppService appSrv = factoryAppService.create("", AppService.TYPE_REST, AppService.METHOD_HTTPGET, "", "", "", "", "", "", "", "", "", "", "", true, "", "", false, "", "", "", "", null, "", null, null); + AppService appSrv = factoryAppService.create("", AppService.TYPE_REST, AppService.METHOD_HTTPGET, "", "", "", "", "", "", "", "", "", "", "", true, "", "", false, "", "", "", "", "", null, "", null, null); appSrv.setResponseHTTPBody(har.toString()); appSrv.setResponseHTTPBodyContentType(AppService.RESPONSEHTTPBODYCONTENTTYPE_JSON); appSrv.setRecordTraceFile(false); @@ -1974,7 +1974,7 @@ private MessageEvent doActionSetConsoleContent(TestCaseExecution exe, TestCaseSt consoleStat = consolelogService.enrichWithStats(consoleLogs); consoleRecap.put("stat", consoleStat); - AppService appSrv = factoryAppService.create("", "", "", "", "", "", "", "", "", "", "", "", "", "", false, "", "", false, "", "", null, "", null, "", null, ""); + AppService appSrv = factoryAppService.create("", "", "", "", "", "", "", "", "", "", "", "", "", "", false, "", "", false, "", "", "", null, "", null, "", null, ""); appSrv.setResponseHTTPBody(consoleRecap.toString()); appSrv.setResponseHTTPBodyContentType(AppService.RESPONSEHTTPBODYCONTENTTYPE_JSON); appSrv.setRecordTraceFile(false); @@ -2007,7 +2007,7 @@ private MessageEvent doActionSetContent(TestCaseExecution exe, TestCaseStepActio */ LOG.debug("Setting static content."); - AppService appSrv = factoryAppService.create("", "", "", "", "", "", "", "", "", "", "", "", "", "", false, "", "", false, "", "", "", "", null, "", null, ""); + AppService appSrv = factoryAppService.create("", "", "", "", "", "", "", "", "", "", "", "", "", "", false, "", "", false, "", "", "", "", "", null, "", null, ""); appSrv.setResponseHTTPBody(textContent); appSrv.setResponseHTTPBodyContentType(appServiceService.guessContentType(appSrv, AppService.RESPONSEHTTPBODYCONTENTTYPE_JSON)); appSrv.setRecordTraceFile(false); diff --git a/source/src/main/java/org/cerberus/core/service/appservice/impl/ServiceService.java b/source/src/main/java/org/cerberus/core/service/appservice/impl/ServiceService.java index 5ce20b07b7..17b554fbcc 100644 --- a/source/src/main/java/org/cerberus/core/service/appservice/impl/ServiceService.java +++ b/source/src/main/java/org/cerberus/core/service/appservice/impl/ServiceService.java @@ -99,7 +99,7 @@ public AnswerItem callService(String service, String targetNbEvents, if (StringUtil.isEmpty(service)) { LOG.debug("Creating AppService from parameters."); appService = factoryAppService.create("null", AppService.TYPE_SOAP, "", "", "", request, "", "", "", "", "", "", "Automatically created Service from datalib.", - servicePathParam, true, "", operation, false, "", "", null, null, null, null, null, null); + servicePathParam, true, "", operation, false, "", "", "", null, null, null, null, null, null); service = "null"; } else { @@ -471,7 +471,7 @@ public AnswerItem callService(String service, String targetNbEvents, * Call REST and store it into the execution. */ result = kafkaService.produceEvent(decodedTopic, decodedKey, decodedRequest, decodedServicePath, appService.getHeaderList(), appService.getContentList(), - token, appService.isAvroEnable(), decodedSchemaRegistryURL, appService.getAvroSchema(), timeOutMs); + token, appService.isAvroEnable(), decodedSchemaRegistryURL, appService.getAvroSchemaKey(), appService.getAvroSchemaValue(), timeOutMs); message = result.getResultMessage(); break; @@ -566,7 +566,7 @@ public AnswerItem callService(String service, String targetNbEvents, String kafkaKey = kafkaService.getKafkaConsumerKey(decodedTopic, decodedServicePath); AnswerItem resultSearch = kafkaService.searchEvent(tCExecution.getKafkaLatestOffset().get(kafkaKey), decodedTopic, decodedServicePath, appService.getHeaderList(), appService.getContentList(), decodedFilterPath, decodedFilterValue, decodedFilterHeaderPath, decodedFilterHeaderValue, - appService.isAvroEnable(), decodedSchemaRegistryURL, targetNbEventsInt, targetNbSecInt); + appService.isAvroEnable(), decodedSchemaRegistryURL, StringUtil.isNotEmpty(appService.getAvroSchemaKey()), StringUtil.isNotEmpty(appService.getAvroSchemaValue()), targetNbEventsInt, targetNbSecInt); if (!(resultSearch.isCodeStringEquals("OK"))) { message = new MessageEvent(MessageEventEnum.ACTION_FAILED_CALLSERVICE); diff --git a/source/src/main/java/org/cerberus/core/service/ftp/impl/FtpService.java b/source/src/main/java/org/cerberus/core/service/ftp/impl/FtpService.java index 79d9263586..3e4325f2b6 100644 --- a/source/src/main/java/org/cerberus/core/service/ftp/impl/FtpService.java +++ b/source/src/main/java/org/cerberus/core/service/ftp/impl/FtpService.java @@ -165,7 +165,7 @@ public AnswerItem callFTP(String chain, String system, String conten FTPClient ftp = new FTPClient(); AppService myResponse = factoryAppService.create(service, AppService.TYPE_FTP, - method, "", "", content, "", "", "", "", "", "", "", informations.get("path"), true, "", "", false, "", "", "", "", null, "", null, filePath); + method, "", "", content, "", "", "", "", "", "", "", informations.get("path"), true, "", "", false, "", "", "", "", "", null, "", null, filePath); try { if (proxyService.useProxy(StringUtil.getURLFromString(informations.get("host"), "", "", "ftp://"), system)) { diff --git a/source/src/main/java/org/cerberus/core/service/kafka/IKafkaService.java b/source/src/main/java/org/cerberus/core/service/kafka/IKafkaService.java index e2679f5e6b..2c4b3f9988 100644 --- a/source/src/main/java/org/cerberus/core/service/kafka/IKafkaService.java +++ b/source/src/main/java/org/cerberus/core/service/kafka/IKafkaService.java @@ -57,20 +57,22 @@ public interface IKafkaService { * @param token * @param isAvroEnable * @param schemaRegistryURL - * @param avroSchema + * @param avroSchemaKey + * @param avroSchemaValue * @param timeoutMs * @return */ public AnswerItem produceEvent(String topic, String key, String eventMessage, - String bootstrapServers, List serviceHeader, List serviceContent, String token, boolean isAvroEnable, String schemaRegistryURL, String avroSchema, int timeoutMs); + String bootstrapServers, List serviceHeader, List serviceContent, String token, boolean isAvroEnable, String schemaRegistryURL, String avroSchemaKey, String avroSchemaValue, int timeoutMs); /** + * Get the last offset of every partition. * * @param topic * @param bootstrapServers * @param serviceHeader * @param timeoutMs - * @return + * @return a map that contain the last offset of every partition. * @throws InterruptedException * @throws ExecutionException */ @@ -90,16 +92,20 @@ public AnswerItem> seekEvent(String topic, String boot * @param targetNbEventsInt * @param filterHeaderValue * @param avroEnable + * @param avroEnableKey + * @param avroEnableValue * @param schemaRegistryURL * @param targetNbSecInt * @return */ public AnswerItem searchEvent(Map mapOffsetPosition, String topic, String bootstrapServers, - List serviceHeader, List serviceContent, String filterPath, String filterValue, String filterHeaderPath, String filterHeaderValue, - boolean avroEnable, String schemaRegistryURL, int targetNbEventsInt, int targetNbSecInt); + List serviceHeader, List serviceContent, String filterPath, String filterValue, String filterHeaderPath, String filterHeaderValue, + boolean avroEnable, String schemaRegistryURL, boolean avroEnableKey, boolean avroEnableValue, int targetNbEventsInt, int targetNbSecInt); /** - * Get the latest Offset of all partitions. + * Get the latest Offset of all partitions. This is triggered at the + * beginning of the execution only when at least a SEARCH KAFKA service is + * called. * * @param mainExecutionTestCaseStepList * @param tCExecution diff --git a/source/src/main/java/org/cerberus/core/service/kafka/impl/KafkaService.java b/source/src/main/java/org/cerberus/core/service/kafka/impl/KafkaService.java index e7a0b9bc9f..dee9c7141c 100644 --- a/source/src/main/java/org/cerberus/core/service/kafka/impl/KafkaService.java +++ b/source/src/main/java/org/cerberus/core/service/kafka/impl/KafkaService.java @@ -195,11 +195,11 @@ private Object readFrom(String jsonString, ParsedSchema parsedSchema) { @Override public AnswerItem produceEvent(String topic, String key, String eventMessage, String bootstrapServers, - List serviceHeader, List serviceContent, String token, boolean activateAvro, String schemaRegistryURL, String avroSchema, int timeoutMs) { + List serviceHeader, List serviceContent, String token, boolean activateAvro, String schemaRegistryURL, String avroSchemaKey, String avroSchemaValue, int timeoutMs) { MessageEvent message = new MessageEvent(MessageEventEnum.ACTION_FAILED_CALLSERVICE_PRODUCEKAFKA); AnswerItem result = new AnswerItem<>(); - AppService serviceREST = factoryAppService.create("", AppService.TYPE_KAFKA, AppService.METHOD_KAFKAPRODUCE, "", "", "", "", "", "", "", "", "", "", "", true, "", "", false, "", "", null, + AppService serviceREST = factoryAppService.create("", AppService.TYPE_KAFKA, AppService.METHOD_KAFKAPRODUCE, "", "", "", "", "", "", "", "", "", "", "", true, "", "", false, "", "", "", null, "", null, "", null, null); // If token is defined, we add 'cerberus-token' on the http header. @@ -214,8 +214,16 @@ public AnswerItem produceEvent(String topic, String key, String even serviceContent.add(factoryAppServiceContent.create(null, ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer", true, 0, "", "", null, "", null)); serviceContent.add(factoryAppServiceContent.create(null, ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer", true, 0, "", "", null, "", null)); } else { - serviceContent.add(factoryAppServiceContent.create(null, ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer", true, 0, "", "", null, "", null)); - serviceContent.add(factoryAppServiceContent.create(null, ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "io.confluent.kafka.serializers.KafkaAvroSerializer", true, 0, "", "", null, "", null)); + if (StringUtil.isNotEmpty(avroSchemaKey)) { + serviceContent.add(factoryAppServiceContent.create(null, ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer", true, 0, "", "", null, "", null)); + } else { + serviceContent.add(factoryAppServiceContent.create(null, ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "io.confluent.kafka.serializers.KafkaAvroSerializer", true, 0, "", "", null, "", null)); + } + if (StringUtil.isNotEmpty(avroSchemaValue)) { + serviceContent.add(factoryAppServiceContent.create(null, ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer", true, 0, "", "", null, "", null)); + } else { + serviceContent.add(factoryAppServiceContent.create(null, ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "io.confluent.kafka.serializers.KafkaAvroSerializer", true, 0, "", "", null, "", null)); + } // props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class); serviceContent.add(factoryAppServiceContent.create(null, "schema.registry.url", schemaRegistryURL, true, 0, "", "", null, "", null)); } @@ -287,12 +295,28 @@ public AnswerItem produceEvent(String topic, String key, String even // LOG.debug("userSchema"); // LOG.debug(userSchema); Schema.Parser parser = new Schema.Parser(); - Schema schema = parser.parse(avroSchema); + Schema schemaValue; + schemaValue = parser.parse(avroSchemaValue); + Schema schemaKey; + schemaKey = parser.parse(avroSchemaKey); + // ParsedSchema toto ; + ProducerRecord record; + if (StringUtil.isNotEmpty(avroSchemaKey)) { + if (StringUtil.isNotEmpty(avroSchemaValue)) { + record = new ProducerRecord<>(topic, jsonToAvro(key, schemaKey), jsonToAvro(eventMessage, schemaValue)); + } else { + record = new ProducerRecord<>(topic, jsonToAvro(key, schemaKey), eventMessage); + } + } else { + if (StringUtil.isNotEmpty(avroSchemaValue)) { + record = new ProducerRecord<>(topic, key, jsonToAvro(eventMessage, schemaValue)); + } else { + record = new ProducerRecord<>(topic, key, eventMessage); + } + } // GenericRecord eventMessageGeneric = new GenericData.Record(schema); - ProducerRecord record = new ProducerRecord<>(topic, key, jsonToAvro(eventMessage, schema)); - for (AppServiceHeader object : serviceHeader) { if (object.isActive()) { record.headers().add(new RecordHeader(object.getKey(), object.getValue().getBytes())); @@ -429,7 +453,7 @@ public AnswerItem> seekEvent(String topic, String boot @Override public AnswerItem searchEvent(Map mapOffsetPosition, String topic, String bootstrapServers, List serviceHeader, List serviceContent, String filterPath, String filterValue, String filterHeaderPath, String filterHeaderValue, - boolean activateAvro, String schemaRegistryURL, int targetNbEventsInt, int targetNbSecInt) { + boolean activateAvro, String schemaRegistryURL, boolean avroEnableKey, boolean avroEnableValue, int targetNbEventsInt, int targetNbSecInt) { MessageEvent message = new MessageEvent(MessageEventEnum.ACTION_FAILED_CALLSERVICE_SEARCHKAFKA); AnswerItem result = new AnswerItem<>(); @@ -565,65 +589,140 @@ public AnswerItem searchEvent(Map mapOffsetPositio } else { - // AVRO VERSION - @SuppressWarnings("unchecked") - ConsumerRecords recordsAvro = consumer.poll(Duration.ofSeconds(pollDurationSec)); - LOG.debug("End Poll."); - if (Instant.now().toEpochMilli() > timeoutTime) { - LOG.debug("Timed out searching for record"); - consumer.wakeup(); //exit - } - //Now for each record in the batch of records we got from Kafka - for (ConsumerRecord record : recordsAvro) { - try { - LOG.debug("New record " + record.topic() + " " + record.partition() + " " + record.offset()); - LOG.debug(" " + record.key() + " | " + record.value()); + if (avroEnableKey) { + // AVRO KEY+VALUE VERSION + @SuppressWarnings("unchecked") + ConsumerRecords recordsAvro = consumer.poll(Duration.ofSeconds(pollDurationSec)); + LOG.debug("End Poll."); + if (Instant.now().toEpochMilli() > timeoutTime) { + LOG.debug("Timed out searching for record"); + consumer.wakeup(); //exit + } + //Now for each record in the batch of records we got from Kafka + for (ConsumerRecord record : recordsAvro) { + try { + LOG.debug("New record " + record.topic() + " " + record.partition() + " " + record.offset()); + LOG.debug(" " + record.key() + " | " + record.value()); + + // Parsing header. + JSONObject headerJSON = new JSONObject(); + for (Header header : record.headers()) { + String headerKey = header.key(); + String headerValue = new String(header.value()); + headerJSON.put(headerKey, headerValue); + } - // Parsing header. - JSONObject headerJSON = new JSONObject(); - for (Header header : record.headers()) { - String headerKey = header.key(); - String headerValue = new String(header.value()); - headerJSON.put(headerKey, headerValue); - } + boolean recordError = false; - boolean recordError = false; + // Parsing message. + JSONObject recordJSON = new JSONObject(); + try { + recordJSON = new JSONObject(record.value().toString()); + } catch (JSONException ex) { + LOG.error(ex, ex); + recordError = true; + } - // Parsing message. - JSONObject recordJSON = new JSONObject(); - try { - recordJSON = new JSONObject(record.value().toString()); - } catch (JSONException ex) { + // Parsing message. + JSONObject keyJSON = new JSONObject(); + try { + keyJSON = new JSONObject(record.key().toString()); + } catch (JSONException ex) { + LOG.error(ex, ex); + recordError = true; + } + + // Complete event with headers. + JSONObject messageJSON = new JSONObject(); + messageJSON.put("key", keyJSON); + messageJSON.put("value", recordJSON); + messageJSON.put("offset", record.offset()); + messageJSON.put("partition", record.partition()); + messageJSON.put("header", headerJSON); + + nbEvents++; + + boolean match = isRecordMatch(record.value().toString(), filterPath, filterValue, messageJSON.toString(), filterHeaderPath, filterHeaderValue); + + if (match) { + resultJSON.put(messageJSON); + nbFound++; + if (nbFound >= targetNbEventsInt) { + consume = false; //exit the consume loop + consumer.wakeup(); //takes effect on the next poll loop so need to break. + break; //if we've found a match, stop looping through the current record batch + } + } + + } catch (Exception ex) { + //Catch any exceptions thrown from message processing/testing as they should have already been reported/dealt with + //but we don't want to trigger the catch block for Kafka consumption LOG.error(ex, ex); - recordError = true; } + } - // Complete event with headers. - JSONObject messageJSON = new JSONObject(); - messageJSON.put("key", record.key()); - messageJSON.put("value", recordJSON); - messageJSON.put("offset", record.offset()); - messageJSON.put("partition", record.partition()); - messageJSON.put("header", headerJSON); + } else { - nbEvents++; + // AVRO VALUE VERSION + @SuppressWarnings("unchecked") + ConsumerRecords recordsAvro = consumer.poll(Duration.ofSeconds(pollDurationSec)); + LOG.debug("End Poll."); + if (Instant.now().toEpochMilli() > timeoutTime) { + LOG.debug("Timed out searching for record"); + consumer.wakeup(); //exit + } + //Now for each record in the batch of records we got from Kafka + for (ConsumerRecord record : recordsAvro) { + try { + LOG.debug("New record " + record.topic() + " " + record.partition() + " " + record.offset()); + LOG.debug(" " + record.key() + " | " + record.value()); + + // Parsing header. + JSONObject headerJSON = new JSONObject(); + for (Header header : record.headers()) { + String headerKey = header.key(); + String headerValue = new String(header.value()); + headerJSON.put(headerKey, headerValue); + } - boolean match = isRecordMatch(record.value().toString(), filterPath, filterValue, messageJSON.toString(), filterHeaderPath, filterHeaderValue); + boolean recordError = false; - if (match) { - resultJSON.put(messageJSON); - nbFound++; - if (nbFound >= targetNbEventsInt) { - consume = false; //exit the consume loop - consumer.wakeup(); //takes effect on the next poll loop so need to break. - break; //if we've found a match, stop looping through the current record batch + // Parsing message. + JSONObject recordJSON = new JSONObject(); + try { + recordJSON = new JSONObject(record.value().toString()); + } catch (JSONException ex) { + LOG.error(ex, ex); + recordError = true; } - } - } catch (Exception ex) { - //Catch any exceptions thrown from message processing/testing as they should have already been reported/dealt with - //but we don't want to trigger the catch block for Kafka consumption - LOG.error(ex, ex); + // Complete event with headers. + JSONObject messageJSON = new JSONObject(); + messageJSON.put("key", record.key()); + messageJSON.put("value", recordJSON); + messageJSON.put("offset", record.offset()); + messageJSON.put("partition", record.partition()); + messageJSON.put("header", headerJSON); + + nbEvents++; + + boolean match = isRecordMatch(record.value().toString(), filterPath, filterValue, messageJSON.toString(), filterHeaderPath, filterHeaderValue); + + if (match) { + resultJSON.put(messageJSON); + nbFound++; + if (nbFound >= targetNbEventsInt) { + consume = false; //exit the consume loop + consumer.wakeup(); //takes effect on the next poll loop so need to break. + break; //if we've found a match, stop looping through the current record batch + } + } + + } catch (Exception ex) { + //Catch any exceptions thrown from message processing/testing as they should have already been reported/dealt with + //but we don't want to trigger the catch block for Kafka consumption + LOG.error(ex, ex); + } } } diff --git a/source/src/main/java/org/cerberus/core/service/rest/impl/RestService.java b/source/src/main/java/org/cerberus/core/service/rest/impl/RestService.java index 6761749852..5f4df945d8 100644 --- a/source/src/main/java/org/cerberus/core/service/rest/impl/RestService.java +++ b/source/src/main/java/org/cerberus/core/service/rest/impl/RestService.java @@ -143,7 +143,7 @@ private AppService executeHTTPCall(CloseableHttpClient httpclient, HttpRequestBa // Create a custom response handler ResponseHandler responseHandler = (final HttpResponse response) -> { AppService myResponse = factoryAppService.create("", AppService.TYPE_REST, - AppService.METHOD_HTTPGET, "", "", "", "", "", "", "", "", "", "", "", true, "", "", false, "", "", null, "", null, "", null, null); + AppService.METHOD_HTTPGET, "", "", "", "", "", "", "", "", "", "", "", true, "", "", false, "", "", "", null, "", null, "", null, null); int responseCode = response.getStatusLine().getStatusCode(); myResponse.setResponseHTTPCode(responseCode); myResponse.setResponseHTTPVersion(response.getProtocolVersion().toString()); @@ -172,7 +172,7 @@ public AnswerItem callREST(String servicePath, String requestString, List headerList, List contentList, String token, int timeOutMs, String system, boolean isFollowRedir, TestCaseExecution tcexecution) { AnswerItem result = new AnswerItem<>(); - AppService serviceREST = factoryAppService.create("", AppService.TYPE_REST, method, "", "", "", "", "", "", "", "", "", "", "", true, "", "", false, "", "", null, + AppService serviceREST = factoryAppService.create("", AppService.TYPE_REST, method, "", "", "", "", "", "", "", "", "", "", "", true, "", "", false, "", "", "", null, "", null, "", null, null); serviceREST.setProxy(false); serviceREST.setProxyHost(null); diff --git a/source/src/main/java/org/cerberus/core/service/soap/impl/SoapService.java b/source/src/main/java/org/cerberus/core/service/soap/impl/SoapService.java index 01cc50f72b..083c67882f 100644 --- a/source/src/main/java/org/cerberus/core/service/soap/impl/SoapService.java +++ b/source/src/main/java/org/cerberus/core/service/soap/impl/SoapService.java @@ -208,7 +208,7 @@ public AnswerItem callSOAP(String envelope, String servicePath, Stri String unescapedEnvelope = StringEscapeUtils.unescapeXml(envelope); boolean is12SoapVersion = SOAP_1_2_NAMESPACE_PATTERN.matcher(unescapedEnvelope).matches(); - AppService serviceSOAP = factoryAppService.create("", AppService.TYPE_SOAP, null, "", "", envelope, "", "", "", "", "", "", "", servicePath, true, "", soapOperation, false, "", "", null, "", null, "", null, null); + AppService serviceSOAP = factoryAppService.create("", AppService.TYPE_SOAP, null, "", "", envelope, "", "", "", "", "", "", "", servicePath, true, "", soapOperation, false, "", "", "", null, "", null, "", null, null); serviceSOAP.setTimeoutms(timeOutMs); ByteArrayOutputStream out = null; MessageEvent message = null; diff --git a/source/src/main/java/org/cerberus/core/servlet/crud/countryenvironment/CreateAppService.java b/source/src/main/java/org/cerberus/core/servlet/crud/countryenvironment/CreateAppService.java index f7c0b0657d..1546e6219d 100644 --- a/source/src/main/java/org/cerberus/core/servlet/crud/countryenvironment/CreateAppService.java +++ b/source/src/main/java/org/cerberus/core/servlet/crud/countryenvironment/CreateAppService.java @@ -140,7 +140,8 @@ final void processRequest(final HttpServletRequest request, final HttpServletRes String kafkaTopic = ParameterParserUtil.parseStringParamAndDecode(fileData.get("kafkaTopic"), "", charset); boolean isAvroEnable = ParameterParserUtil.parseBooleanParamAndDecode(fileData.get("isAvroEnable"), true, charset); String schemaRegistryUrl = ParameterParserUtil.parseStringParamAndDecode(fileData.get("schemaRegistryUrl"), null, charset); - String avroSchema = ParameterParserUtil.parseStringParamAndDecode(fileData.get("avroSchema"), null, charset); + String avroSchemaKey = ParameterParserUtil.parseStringParamAndDecode(fileData.get("avroSchemaKey"), null, charset); + String avroSchemaValue = ParameterParserUtil.parseStringParamAndDecode(fileData.get("avroSchemaValue"), null, charset); String parentContentService = ParameterParserUtil.parseStringParamAndDecode(fileData.get("parentContentService"), "", charset); String kafkaKey = ParameterParserUtil.parseStringParamAndDecode(fileData.get("kafkaKey"), "", charset); String kafkaFilterPath = ParameterParserUtil.parseStringParamAndDecode(fileData.get("kafkaFilterPath"), "", charset); @@ -178,7 +179,7 @@ final void processRequest(final HttpServletRequest request, final HttpServletRes appServiceHeaderFactory = appContext.getBean(IFactoryAppServiceHeader.class); LOG.debug(request.getUserPrincipal().getName()); AppService appService = appServiceFactory.create(service, type, method, application, group, serviceRequest, kafkaTopic, kafkaKey, kafkaFilterPath, kafkaFilterValue, kafkaFilterHeaderPath, kafkaFilterHeaderValue, description, servicePath, - isFollowRedir, attachementurl, operation, isAvroEnable, schemaRegistryUrl, avroSchema, parentContentService, request.getUserPrincipal().getName(), null, null, null, fileName); + isFollowRedir, attachementurl, operation, isAvroEnable, schemaRegistryUrl, avroSchemaKey, avroSchemaValue, parentContentService, request.getUserPrincipal().getName(), null, null, null, fileName); ans = appServiceService.create(appService); finalAnswer = AnswerUtil.agregateAnswer(finalAnswer, ans); diff --git a/source/src/main/java/org/cerberus/core/servlet/crud/countryenvironment/UpdateAppService.java b/source/src/main/java/org/cerberus/core/servlet/crud/countryenvironment/UpdateAppService.java index d5f83be569..f9a81d6964 100644 --- a/source/src/main/java/org/cerberus/core/servlet/crud/countryenvironment/UpdateAppService.java +++ b/source/src/main/java/org/cerberus/core/servlet/crud/countryenvironment/UpdateAppService.java @@ -140,7 +140,8 @@ final void processRequest(final HttpServletRequest request, final HttpServletRes String kafkaTopic = ParameterParserUtil.parseStringParamAndDecode(fileData.get("kafkaTopic"), "", charset); boolean isAvroEnable = ParameterParserUtil.parseBooleanParam(fileData.get("isAvroEnable"), false); String schemaRegistryUrl = ParameterParserUtil.parseStringParamAndDecode(fileData.get("schemaRegistryUrl"), null, charset); - String avrSchema = ParameterParserUtil.parseStringParamAndDecode(fileData.get("avrSchema"), null, charset); + String avrSchemaKey = ParameterParserUtil.parseStringParamAndDecode(fileData.get("avrSchemaKey"), null, charset); + String avrSchemaValue = ParameterParserUtil.parseStringParamAndDecode(fileData.get("avrSchemaValue"), null, charset); String parentContentService = ParameterParserUtil.parseStringParamAndDecode(fileData.get("parentContentService"), "", charset); String kafkaKey = ParameterParserUtil.parseStringParamAndDecode(fileData.get("kafkaKey"), "", charset); String kafkaFilterPath = ParameterParserUtil.parseStringParamAndDecode(fileData.get("kafkaFilterPath"), "", charset); @@ -216,7 +217,8 @@ final void processRequest(final HttpServletRequest request, final HttpServletRes appService.setFollowRedir(isFollowRedir); appService.setAvroEnable(isAvroEnable); appService.setSchemaRegistryURL(schemaRegistryUrl); - appService.setAvroSchema(avrSchema); + appService.setAvroSchemaKey(avrSchemaKey); + appService.setAvroSchemaValue(avrSchemaValue); appService.setParentContentService(parentContentService); ans = appServiceService.update(originalService, appService); diff --git a/source/src/main/resources/database.sql b/source/src/main/resources/database.sql index 74fc6f0cf5..6e78c2e225 100644 --- a/source/src/main/resources/database.sql +++ b/source/src/main/resources/database.sql @@ -6181,3 +6181,11 @@ INSERT INTO `parameter` (`system`, `param`, `value`, `description`) -- 1742 ALTER TABLE `appservice` ADD COLUMN `AvroSchema` MEDIUMTEXT NULL DEFAULT NULL AFTER `SchemaRegistryUrl`; + +-- 1743 +ALTER TABLE `appservice` + ADD COLUMN `AvroSchemaKey` MEDIUMTEXT NULL DEFAULT NULL AFTER `SchemaRegistryUrl`; + +ALTER TABLE `appservice` + CHANGE COLUMN `AvroSchema` `AvroSchemaValue` MEDIUMTEXT NULL DEFAULT NULL ; + diff --git a/source/src/main/webapp/include/transversalobject/AppService.html b/source/src/main/webapp/include/transversalobject/AppService.html index f430d3c2e8..abbecab36a 100644 --- a/source/src/main/webapp/include/transversalobject/AppService.html +++ b/source/src/main/webapp/include/transversalobject/AppService.html @@ -6,15 +6,14 @@ + +
@@ -251,33 +256,33 @@

Drag and drop Files
- +
-

+
- +
-
- + +
- +
-
+
- +
-
+ - - + + diff --git a/source/src/main/webapp/js/transversalobject/AppService.js b/source/src/main/webapp/js/transversalobject/AppService.js index f9f7d7da9a..7b8fde3342 100644 --- a/source/src/main/webapp/js/transversalobject/AppService.js +++ b/source/src/main/webapp/js/transversalobject/AppService.js @@ -191,12 +191,15 @@ function prepareAppServiceModal() { if ($("#editSoapLibraryModal #type").val() == "KAFKA") { console.info($(this).val()); if ($(this).val() == "SEARCH") { - $("#editSoapLibraryModal #avrSchemaDiv").hide(); + $("#editSoapLibraryModal #avrSchemaKeyDiv").hide(); + $("#editSoapLibraryModal #avrSchemaValueDiv").hide(); } else { - $("#editSoapLibraryModal #avrSchemaDiv").show(); + $("#editSoapLibraryModal #avrSchemaKeyDiv").show(); + $("#editSoapLibraryModal #avrSchemaValueDiv").show(); } } else { - $("#editSoapLibraryModal #avrSchemaDiv").show(); + $("#editSoapLibraryModal #avrSchemaKeyDiv").show(); + $("#editSoapLibraryModal #avrSchemaValueDiv").show(); } }); @@ -243,7 +246,8 @@ function confirmAppServiceModalHandler(mode, page) { //Add envelope, not in the form var editorRequest = ace.edit($("#editSoapLibraryModal #srvRequest")[0]); - var editorSchema = ace.edit($("#editSoapLibraryModal #avrSchema")[0]); + var editorSchemaKey = ace.edit($("#editSoapLibraryModal #avrSchemaKey")[0]); + var editorSchemaValue = ace.edit($("#editSoapLibraryModal #avrSchemaValue")[0]); // Getting Data from Content TAB var table1 = $("#contentTableBody tr"); @@ -268,7 +272,8 @@ function confirmAppServiceModalHandler(mode, page) { formData.append("contentList", JSON.stringify(table_content)); formData.append("headerList", JSON.stringify(table_header)); formData.append("srvRequest", encodeURIComponent(editorRequest.getSession().getDocument().getValue())); - formData.append("avrSchema", encodeURIComponent(editorSchema.getSession().getDocument().getValue())); + formData.append("avrSchemaKey", encodeURIComponent(editorSchemaKey.getSession().getDocument().getValue())); + formData.append("avrSchemaValue", encodeURIComponent(editorSchemaValue.getSession().getDocument().getValue())); if (file.prop("files").length != 0) { formData.append("file", file.prop("files")[0]); @@ -345,7 +350,8 @@ function refreshDisplayOnTypeChange(newValue) { $("#editSoapLibraryModal #kafkaFilter").hide(); $("label[name='avroField']").hide(); $("#editSoapLibraryModal #avro").hide(); - $("#editSoapLibraryModal #avrSchemaDiv").hide(); + $("#editSoapLibraryModal #avrSchemaKeyDiv").hide(); + $("#editSoapLibraryModal #avrSchemaValueDiv").hide(); $("label[name='isFollowRedirField']").parent().hide(); $('#editSoapLibraryModal #tab3Text').text("Request Detail"); } else if (newValue === "FTP") { @@ -371,7 +377,8 @@ function refreshDisplayOnTypeChange(newValue) { $("#editSoapLibraryModal #kafkaFilter").hide(); $("label[name='avroField']").hide(); $("#editSoapLibraryModal #avro").hide(); - $("#editSoapLibraryModal #avrSchemaDiv").hide(); + $("#editSoapLibraryModal #avrSchemaKeyDiv").hide(); + $("#editSoapLibraryModal #avrSchemaValueDiv").hide(); $("label[name='isFollowRedirField']").parent().hide(); $('#editSoapLibraryModal #tab3Text').text("Request Detail"); } else if (newValue === "KAFKA") { @@ -398,9 +405,11 @@ function refreshDisplayOnTypeChange(newValue) { $("label[name='avroField']").show(); $("#editSoapLibraryModal #avro").show(); if ($("#editSoapLibraryModal #method").val() === "SEARCH") { - $("#editSoapLibraryModal #avrSchemaDiv").hide(); + $("#editSoapLibraryModal #avrSchemaKeyDiv").hide(); + $("#editSoapLibraryModal #avrSchemaValueDiv").hide(); } else { - $("#editSoapLibraryModal #avrSchemaDiv").show(); + $("#editSoapLibraryModal #avrSchemaKeyDiv").show(); + $("#editSoapLibraryModal #avrSchemaValueDiv").show(); } $("label[name='isFollowRedirField']").parent().hide(); $('#editSoapLibraryModal #tab3Text').text("KAFKA Props"); @@ -427,7 +436,8 @@ function refreshDisplayOnTypeChange(newValue) { $("#editSoapLibraryModal #kafkaFilter").hide(); $("label[name='avroField']").hide(); $("#editSoapLibraryModal #avro").hide(); - $("#editSoapLibraryModal #avrSchemaDiv").hide(); + $("#editSoapLibraryModal #avrSchemaKeyDiv").hide(); + $("#editSoapLibraryModal #avrSchemaValueDiv").hide(); $("label[name='isFollowRedirField']").parent().show(); $('#editSoapLibraryModal #tab3Text').text("Request Detail"); } @@ -506,6 +516,8 @@ function feedAppServiceModal(serviceName, modalId, mode) { serviceObj1.isFollowRedir = true; serviceObj1.isAvroEnable = false; serviceObj1.schemaRegistryURL = ""; + serviceObj1.avroSchemaKey = ""; + serviceObj1.avroSchemaValue = ""; serviceObj1.parentContentService = ""; feedAppServiceModalData(serviceObj1, modalId, mode, hasPermissions); @@ -531,7 +543,8 @@ function feedAppServiceModalData(service, modalId, mode, hasPermissionsUpdate) { //Destroy the previous Ace object. ace.edit($("#editSoapLibraryModal #srvRequest")[0]).destroy(); - ace.edit($("#editSoapLibraryModal #avrSchema")[0]).destroy(); + ace.edit($("#editSoapLibraryModal #avrSchemaKey")[0]).destroy(); + ace.edit($("#editSoapLibraryModal #avrSchemaValue")[0]).destroy(); // Data Feed. if (mode === "EDIT") { @@ -569,7 +582,8 @@ function feedAppServiceModalData(service, modalId, mode, hasPermissionsUpdate) { formEdit.find("#isFollowRedir").prop("checked", true); formEdit.find("#attachementurl").prop("value", ""); formEdit.find("#srvRequest").text(""); - formEdit.find("#avrSchema").text(""); + formEdit.find("#avrSchemaKey").text(""); + formEdit.find("#avrSchemaValue").text(""); formEdit.find("#isAvroEnable").prop("checked", false); formEdit.find("#schemaRegistryUrl").prop("value", ""); formEdit.find("#parentContentService").prop("value", ""); @@ -594,7 +608,8 @@ function feedAppServiceModalData(service, modalId, mode, hasPermissionsUpdate) { formEdit.find("#parentContentService").val(service.parentContentService); formEdit.find("#attachementurl").prop("value", service.attachementURL); formEdit.find("#srvRequest").text(service.serviceRequest); - formEdit.find("#avrSchema").text(service.avroSchema); + formEdit.find("#avrSchemaKey").text(service.avroSchemaKey); + formEdit.find("#avrSchemaValue").text(service.avroSchemaValue); formEdit.find("#group").prop("value", service.group); formEdit.find("#operation").prop("value", service.operation); formEdit.find("#description").prop("value", service.description); @@ -623,10 +638,16 @@ function feedAppServiceModalData(service, modalId, mode, hasPermissionsUpdate) { editor.setOptions({ maxLines: Infinity }); - var editorSchema = ace.edit($("#editSoapLibraryModal #avrSchema")[0]); - editorSchema.setTheme("ace/theme/chrome"); - editorSchema.getSession().setMode(defineAceMode(editor.getSession().getDocument().getValue())); - editorSchema.setOptions({ + var editorSchemaKey = ace.edit($("#editSoapLibraryModal #avrSchemaKey")[0]); + editorSchemaKey.setTheme("ace/theme/chrome"); + editorSchemaKey.getSession().setMode(defineAceMode(editor.getSession().getDocument().getValue())); + editorSchemaKey.setOptions({ + maxLines: Infinity + }); + var editorSchemaValue = ace.edit($("#editSoapLibraryModal #avrSchemaValue")[0]); + editorSchemaValue.setTheme("ace/theme/chrome"); + editorSchemaValue.getSession().setMode(defineAceMode(editor.getSession().getDocument().getValue())); + editorSchemaValue.setOptions({ maxLines: Infinity }); @@ -637,9 +658,14 @@ function feedAppServiceModalData(service, modalId, mode, hasPermissionsUpdate) { editor.getSession().setMode(defineAceMode(editor.getSession().getDocument().getValue())); } }); - $($("#editSoapLibraryModal #avrSchema").get(0)).keyup(function () { - if (editorSchema.getSession().getMode().$id === "ace/mode/text") { - editorSchema.getSession().setMode(defineAceMode(editorSchema.getSession().getDocument().getValue())); + $($("#editSoapLibraryModal #avrSchemaKey").get(0)).keyup(function () { + if (editorSchemaKey.getSession().getMode().$id === "ace/mode/text") { + editorSchemaKey.getSession().setMode(defineAceMode(editorSchemaKey.getSession().getDocument().getValue())); + } + }); + $($("#editSoapLibraryModal #avrSchemaValue").get(0)).keyup(function () { + if (editorSchemaValue.getSession().getMode().$id === "ace/mode/text") { + editorSchemaValue.getSession().setMode(defineAceMode(editorSchemaValue.getSession().getDocument().getValue())); } }); @@ -665,7 +691,8 @@ function feedAppServiceModalData(service, modalId, mode, hasPermissionsUpdate) { formEdit.find("#group").prop("readonly", true); formEdit.find("#attachementurl").prop("readonly", true); formEdit.find("#srvRequest").prop("readonly", "readonly"); - formEdit.find("#avrSchema").prop("readonly", "readonly"); + formEdit.find("#avrSchemaKey").prop("readonly", "readonly"); + formEdit.find("#avrSchemaValue").prop("readonly", "readonly"); formEdit.find("#description").prop("readonly", "readonly"); formEdit.find("#isFollowRedir").prop("readonly", "readonly"); formEdit.find("#kafkaTopic").prop("readonly", "readonly"); @@ -688,7 +715,8 @@ function feedAppServiceModalData(service, modalId, mode, hasPermissionsUpdate) { formEdit.find("#group").prop("readonly", false); formEdit.find("#attachementurl").prop("readonly", false); formEdit.find("#srvRequest").removeProp("readonly"); - formEdit.find("#avrSchema").removeProp("readonly"); + formEdit.find("#avrSchemaKey").removeProp("readonly"); + formEdit.find("#avrSchemaValue").removeProp("readonly"); formEdit.find("#description").removeProp("disabled"); formEdit.find("#isFollowRedir").prop("readonly", false); formEdit.find("#kafkaTopic").removeProp("disabled"); From 84620c2ef9b8c65605ed4ed27ee9d11430f383fa Mon Sep 17 00:00:00 2001 From: Benoit DUMONT Date: Fri, 30 Dec 2022 17:25:11 +0100 Subject: [PATCH 10/96] Added Kafka AVRO support also on key. fixed #2418 Timeout overwrite also work for callService Action. --- .../core/crud/dao/impl/AppServiceDAO.java | 14 +- .../cerberus/core/crud/entity/AppService.java | 25 +- .../core/crud/factory/IFactoryAppService.java | 4 +- .../crud/factory/impl/FactoryAppService.java | 6 +- .../DocumentationDatabaseService.java | 26 ++ .../engine/execution/IRobotServerService.java | 9 + .../execution/impl/RobotServerService.java | 43 ++-- .../core/engine/gwt/impl/ActionService.java | 16 +- .../service/appservice/IServiceService.java | 3 +- .../appservice/impl/ServiceService.java | 16 +- .../service/datalib/impl/DataLibService.java | 2 +- .../core/service/ftp/impl/FtpService.java | 2 +- .../core/service/kafka/IKafkaService.java | 5 +- .../core/service/kafka/impl/KafkaService.java | 235 ++++++++++++------ .../core/service/rest/impl/RestService.java | 4 +- .../core/service/soap/impl/SoapService.java | 2 +- .../countryenvironment/CreateAppService.java | 8 +- .../countryenvironment/UpdateAppService.java | 4 + source/src/main/resources/database.sql | 6 +- .../dependencies/Ace-1.2.6/worker-json.js | 1 + source/src/main/webapp/images/logo-AVRO.png | Bin 0 -> 74934 bytes .../include/transversalobject/AppService.html | 72 +++--- .../main/webapp/js/pages/AppServiceList.js | 80 +++++- .../webapp/js/transversalobject/AppService.js | 160 +++++++++--- 24 files changed, 550 insertions(+), 193 deletions(-) create mode 100644 source/src/main/webapp/dependencies/Ace-1.2.6/worker-json.js create mode 100644 source/src/main/webapp/images/logo-AVRO.png diff --git a/source/src/main/java/org/cerberus/core/crud/dao/impl/AppServiceDAO.java b/source/src/main/java/org/cerberus/core/crud/dao/impl/AppServiceDAO.java index 05e15a7fcd..17056bab2c 100644 --- a/source/src/main/java/org/cerberus/core/crud/dao/impl/AppServiceDAO.java +++ b/source/src/main/java/org/cerberus/core/crud/dao/impl/AppServiceDAO.java @@ -433,9 +433,9 @@ public Answer create(AppService object) { MessageEvent msg; StringBuilder query = new StringBuilder() .append("INSERT INTO appservice (`Service`, `Group`, `Application`, `Type`, `Method`, `ServicePath`, `isFollowRedir`, `Operation`, `ServiceRequest`, ") - .append(" `isAvroEnable`, `SchemaRegistryUrl`, `AvroSchemaKey`, `AvroSchemaValue`, `ParentContentService`, `KafkaTopic`, `KafkaKey`, ") + .append(" `isAvroEnable`, `SchemaRegistryUrl`, `isAvroEnableKey`, `AvroSchemaKey`, `isAvroEnableValue`, `AvroSchemaValue`, `ParentContentService`, `KafkaTopic`, `KafkaKey`, ") .append(" `KafkaFilterPath`, `KafkaFilterValue`, `KafkaFilterHeaderPath`, `KafkaFilterHeaderValue`, `AttachementURL`, `Description`, `FileName`, `UsrCreated`) ") - .append("VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); + .append("VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); LOG.debug(SQL_MESSAGE, query); @@ -458,7 +458,9 @@ public Answer create(AppService object) { preStat.setString(i++, object.getServiceRequest()); preStat.setBoolean(i++, object.isAvroEnable()); preStat.setString(i++, object.getSchemaRegistryURL()); + preStat.setBoolean(i++, object.isAvroEnableKey()); preStat.setString(i++, object.getAvroSchemaKey()); + preStat.setBoolean(i++, object.isAvroEnableValue()); preStat.setString(i++, object.getAvroSchemaValue()); if (StringUtil.isNotEmpty(object.getParentContentService())) { preStat.setString(i++, object.getParentContentService()); @@ -498,7 +500,7 @@ public Answer update(String service, AppService object) { MessageEvent msg; StringBuilder query = new StringBuilder() .append("UPDATE appservice srv SET `Service` = ?, `Group` = ?, `ServicePath` = ?, `isFollowRedir` = ?, `Operation` = ?, ServiceRequest = ?, ") - .append("`isAvroEnable` = ?, `SchemaRegistryUrl` = ?, `AvroSchemaKey` = ?, `AvroSchemaValue` = ?, ParentContentService = ?, KafkaTopic = ?, KafkaKey = ?, ") + .append("`isAvroEnable` = ?, `SchemaRegistryUrl` = ?, `isAvroEnableKey` = ?, `AvroSchemaKey` = ?, `isAvroEnableValue` = ?, `AvroSchemaValue` = ?, ParentContentService = ?, KafkaTopic = ?, KafkaKey = ?, ") .append("KafkaFilterPath = ?, KafkaFilterValue = ?, KafkaFilterHeaderPath = ?, KafkaFilterHeaderValue = ?, AttachementURL = ?, ") .append("`Description` = ?, `Type` = ?, Method = ?, `UsrModif`= ?, `DateModif` = NOW(), `FileName` = ?"); if ((object.getApplication() != null) && (!object.getApplication().isEmpty())) { @@ -524,7 +526,9 @@ public Answer update(String service, AppService object) { preStat.setString(i++, object.getServiceRequest()); preStat.setBoolean(i++, object.isAvroEnable()); preStat.setString(i++, object.getSchemaRegistryURL()); + preStat.setBoolean(i++, object.isAvroEnableKey()); preStat.setString(i++, object.getAvroSchemaKey()); + preStat.setBoolean(i++, object.isAvroEnableValue()); preStat.setString(i++, object.getAvroSchemaValue()); if (StringUtil.isEmpty(object.getParentContentService())) { preStat.setString(i++, null); @@ -648,11 +652,13 @@ public AppService loadFromResultSet(ResultSet rs) throws SQLException { boolean isFollowRedir = rs.getBoolean("srv.isFollowRedir"); boolean isAvroEnable = rs.getBoolean("srv.isAvroEnable"); String schemaRegistryURL = ParameterParserUtil.parseStringParam(rs.getString("srv.SchemaRegistryUrl"), ""); + boolean isAvroEnableKey = rs.getBoolean("srv.isAvroEnableKey"); String avroSchemaKey = ParameterParserUtil.parseStringParam(rs.getString("srv.AvroSchemaKey"), ""); + boolean isAvroEnableValue = rs.getBoolean("srv.isAvroEnableValue"); String avroSchemaValue = ParameterParserUtil.parseStringParam(rs.getString("srv.AvroSchemaValue"), ""); String parentContentService = ParameterParserUtil.parseStringParam(rs.getString("srv.ParentContentService"), ""); return factoryAppService.create(service, type, method, application, group, serviceRequest, kafkaTopic, kafkaKey, kafkaFilterPath, kafkaFilterValue, kafkaFilterHeaderPath, kafkaFilterHeaderValue, - description, servicePath, isFollowRedir, attachementURL, operation, isAvroEnable, schemaRegistryURL, avroSchemaKey, avroSchemaValue, parentContentService, usrCreated, dateCreated, usrModif, dateModif, fileName); + description, servicePath, isFollowRedir, attachementURL, operation, isAvroEnable, schemaRegistryURL, isAvroEnableKey, avroSchemaKey, isAvroEnableValue, avroSchemaValue, parentContentService, usrCreated, dateCreated, usrModif, dateModif, fileName); } private static void deleteFolder(File folder, boolean deleteit) { diff --git a/source/src/main/java/org/cerberus/core/crud/entity/AppService.java b/source/src/main/java/org/cerberus/core/crud/entity/AppService.java index 77d5801d5b..677b86cba8 100644 --- a/source/src/main/java/org/cerberus/core/crud/entity/AppService.java +++ b/source/src/main/java/org/cerberus/core/crud/entity/AppService.java @@ -58,8 +58,10 @@ public class AppService { private String kafkaFilterHeaderValue; private boolean isAvroEnable; private String schemaRegistryURL; - private String avroSchemaValue; + private boolean isAvroEnableKey; private String avroSchemaKey; + private boolean isAvroEnableValue; + private String avroSchemaValue; private String parentContentService; private String group; // Information in order to group the services in order to organise them private String description; @@ -138,7 +140,7 @@ public void addContentList(AppServiceContent object) { this.contentList.add(object); } - public void addContentList(List object) { + public void addContentList(List object) { this.contentList.addAll(object); } @@ -273,12 +275,19 @@ public JSONObject toJSONOnKAFKAExecution() { if (!StringUtil.isEmpty(this.getServiceRequest())) { try { JSONObject reqBody = new JSONObject(this.getServiceRequest()); - jsonMyRequest.put("KAFKA-Request", reqBody); + jsonMyRequest.put("KAFKA-Value", reqBody); + } catch (JSONException e) { + jsonMyRequest.put("KAFKA-Value", this.getServiceRequest()); + } + } + if (!StringUtil.isEmpty(this.getKafkaKey())) { + try { + JSONObject keyBody = new JSONObject(this.getKafkaKey()); + jsonMyRequest.put("KAFKA-Key", keyBody); } catch (JSONException e) { - jsonMyRequest.put("KAFKA-Request", this.getServiceRequest()); + jsonMyRequest.put("KAFKA-Key", this.getKafkaKey()); } } - jsonMyRequest.put("KAFKA-Key", this.getKafkaKey()); if (!(this.getKafkaWaitNbEvent() == 0)) { jsonMyRequest.put("WaitNbEvents", this.getKafkaWaitNbEvent()); } @@ -289,7 +298,11 @@ public JSONObject toJSONOnKAFKAExecution() { JSONObject jsonFilters = new JSONObject(); jsonFilters.put("Path", this.getKafkaFilterPath()); jsonFilters.put("Value", this.getKafkaFilterValue()); - jsonMyRequest.put("KAFKA-SearchFilter", jsonFilters); + jsonMyRequest.put("KAFKA-SearchFilterValue", jsonFilters); + JSONObject jsonFiltersHeader = new JSONObject(); + jsonFiltersHeader.put("Path", this.getKafkaFilterHeaderPath()); + jsonFiltersHeader.put("Value", this.getKafkaFilterHeaderValue()); + jsonMyRequest.put("KAFKA-SearchFilter", jsonFiltersHeader); } jsonMain.put("Request", jsonMyRequest); diff --git a/source/src/main/java/org/cerberus/core/crud/factory/IFactoryAppService.java b/source/src/main/java/org/cerberus/core/crud/factory/IFactoryAppService.java index 60dfc6c2a8..56baf1b23d 100644 --- a/source/src/main/java/org/cerberus/core/crud/factory/IFactoryAppService.java +++ b/source/src/main/java/org/cerberus/core/crud/factory/IFactoryAppService.java @@ -49,7 +49,9 @@ public interface IFactoryAppService { * @param operation * @param isAvroEnable * @param schemaRegistryUrl + * @param isAvroEnableKey * @param avroSchemaKey + * @param isAvroEnableValue * @param avroSchemaValue * @param parentContentService * @param usrCreated @@ -61,6 +63,6 @@ public interface IFactoryAppService { */ AppService create(String service, String type, String method, String application, String group, String serviceRequest, String kafkaTopic, String kafkaKey, String kafkaFilterPath, String kafkaFilterValue, String kafkaFilterHeaderPath, String kafkaFilterHeaderValue, - String description, String servicePath, boolean isFollowRedir, String attachementURL, String operation, boolean isAvroEnable, String schemaRegistryUrl, String avroSchemaKey, String avroSchemaValue, String parentContentService, + String description, String servicePath, boolean isFollowRedir, String attachementURL, String operation, boolean isAvroEnable, String schemaRegistryUrl, boolean isAvroEnableKey, String avroSchemaKey, boolean isAvroEnableValue, String avroSchemaValue, String parentContentService, String usrCreated, Timestamp dateCreated, String usrModif, Timestamp dateModif, String fileName); } diff --git a/source/src/main/java/org/cerberus/core/crud/factory/impl/FactoryAppService.java b/source/src/main/java/org/cerberus/core/crud/factory/impl/FactoryAppService.java index a7e3603bb6..e5b031adf0 100644 --- a/source/src/main/java/org/cerberus/core/crud/factory/impl/FactoryAppService.java +++ b/source/src/main/java/org/cerberus/core/crud/factory/impl/FactoryAppService.java @@ -39,7 +39,9 @@ public class FactoryAppService implements IFactoryAppService { public AppService create(String service, String type, String method, String application, String group, String serviceRequest, String kafkaTopic, String kafkaKey, String kafkaFilterPath, String kafkaFilterValue, String kafkaFilterHeaderPath, String kafkaFilterHeaderValue, String description, - String servicePath, boolean isFollowRedir, String attachementURL, String operation, boolean isAvroEnable, String schemaRegistryUrl, String avroSchemaKey, String avroSchemaValue, String parentContentService, + String servicePath, boolean isFollowRedir, String attachementURL, String operation, + boolean isAvroEnable, String schemaRegistryUrl, boolean isAvroEnableKey, String avroSchemaKey, boolean isAvroEnableValue, String avroSchemaValue, + String parentContentService, String usrCreated, Timestamp dateCreated, String usrModif, Timestamp dateModif, String fileName) { AppService s = new AppService(); s.setService(service); @@ -71,7 +73,9 @@ public AppService create(String service, String type, String method, String appl s.setKafkaFilterHeaderValue(kafkaFilterHeaderValue); s.setAvroEnable(isAvroEnable); s.setSchemaRegistryURL(schemaRegistryUrl); + s.setAvroEnableKey(isAvroEnableKey); s.setAvroSchemaKey(avroSchemaKey); + s.setAvroEnableValue(isAvroEnableValue); s.setAvroSchemaValue(avroSchemaValue); s.setParentContentService(parentContentService); s.setRecordTraceFile(true); diff --git a/source/src/main/java/org/cerberus/core/database/DocumentationDatabaseService.java b/source/src/main/java/org/cerberus/core/database/DocumentationDatabaseService.java index edc65c58ec..be2cea8f73 100644 --- a/source/src/main/java/org/cerberus/core/database/DocumentationDatabaseService.java +++ b/source/src/main/java/org/cerberus/core/database/DocumentationDatabaseService.java @@ -157,6 +157,32 @@ public ArrayList getSqlDocumentation() { b.append(",('appservice','type','','en','Type','','_service_library')"); b.append(",('appservice','type','','ru','Тип','','_service_library')"); b.append(",('appservice','type','','fr','Type','','_librairie_de_services')"); + b.append(",('appservice','kafkaTopic','','en','Kafka Topic','','_service_library')"); + b.append(",('appservice','kafkaTopic','','fr','Topic Kafka','','_librairie_de_services')"); + b.append(",('appservice','kafkaKey','','en','Kafka Key','','_service_library')"); + b.append(",('appservice','kafkaKey','','fr','Key Kafka','','_librairie_de_services')"); + b.append(",('appservice','kafkaFilterPath','','en','Kafka Filter JSON Path (value)','','_service_library')"); + b.append(",('appservice','kafkaFilterPath','','fr','JSON Path du Filtre Kafka (value)','','_librairie_de_services')"); + b.append(",('appservice','kafkaFilterValue','','en','Kafka Filter Value (value)','','_service_library')"); + b.append(",('appservice','kafkaFilterValue','','fr','Valeur du Filtre Kafka (value)','','_librairie_de_services')"); + b.append(",('appservice','kafkaFilterHeaderPath','','en','Kafka Filter JSON Path (complete)','','_service_library')"); + b.append(",('appservice','kafkaFilterHeaderPath','','fr','JSON Path du Filtre Kafka (complet)','','_librairie_de_services')"); + b.append(",('appservice','kafkaFilterHeaderValue','','en','Kafka Filter Value (complete)','','_service_library')"); + b.append(",('appservice','kafkaFilterHeaderValue','','fr','Valeur du Filtre Kafka (complet)','','_librairie_de_services')"); + b.append(",('appservice','isAvroEnable','','en','Avro Enabled','','_service_library')"); + b.append(",('appservice','isAvroEnable','','fr','Avro Actif','','_librairie_de_services')"); + b.append(",('appservice','schemaRegistryURL','','en','Schema Registry URL','','_service_library')"); + b.append(",('appservice','schemaRegistryURL','','fr','URL du Schema Registry','','_librairie_de_services')"); + b.append(",('appservice','isAvroEnableKey','','en','Avro Key Enabled','','_service_library')"); + b.append(",('appservice','isAvroEnableKey','','fr','Avro Key Actif','','_librairie_de_services')"); + b.append(",('appservice','isAvroEnableValue','','en','Avro Value Enabled','','_service_library')"); + b.append(",('appservice','isAvroEnableValue','','fr','Avro Value Actif','','_librairie_de_services')"); + b.append(",('appservice','avroSchemaKey','','en','Key Avro Schema','','_service_library')"); + b.append(",('appservice','avroSchemaKey','','fr','Schema Avro de la Key','','_librairie_de_services')"); + b.append(",('appservice','avroSchemaValue','','en','Value Avro Schema','','_service_library')"); + b.append(",('appservice','avroSchemaValue','','fr','Schema Avro de la Value','','_librairie_de_services')"); + b.append(",('appservice','parentContentService','','en','Parent Service (for Kafka Props)','','_service_library')"); + b.append(",('appservice','parentContentService','','fr','Service Père (pour les Kafka Props)','','_librairie_de_services')"); b.append(",('appservicecontent','Sort','','en','Sort','','_service_library')"); b.append(",('appservicecontent','Sort','','ru','Сортировать','','_service_library')"); b.append(",('appservicecontent','Sort','','fr','Tri','','_librairie_de_services')"); diff --git a/source/src/main/java/org/cerberus/core/engine/execution/IRobotServerService.java b/source/src/main/java/org/cerberus/core/engine/execution/IRobotServerService.java index 96fbe109e5..7fbb3bbf35 100644 --- a/source/src/main/java/org/cerberus/core/engine/execution/IRobotServerService.java +++ b/source/src/main/java/org/cerberus/core/engine/execution/IRobotServerService.java @@ -63,6 +63,15 @@ public interface IRobotServerService { */ public HashMap getMapFromOptions(JSONArray options); + /** + * + * @param options JSONArray of options. + * @param option string value of the option. Ex : + * RobotServerService.OPTIONS_TIMEOUT_SYNTAX + * @return int value of the option requested. O if not exist. + */ + public int getFromOptions(JSONArray options, String option); + /** * * @param session diff --git a/source/src/main/java/org/cerberus/core/engine/execution/impl/RobotServerService.java b/source/src/main/java/org/cerberus/core/engine/execution/impl/RobotServerService.java index 7bbd8fa293..e6f2eb422a 100644 --- a/source/src/main/java/org/cerberus/core/engine/execution/impl/RobotServerService.java +++ b/source/src/main/java/org/cerberus/core/engine/execution/impl/RobotServerService.java @@ -214,7 +214,7 @@ public void startServer(TestCaseExecution tCExecution) throws CerberusException // typeDelay parameters session.setCerberus_sikuli_typeDelay(cerberus_sikuli_typeDelay); session.setCerberus_sikuli_typeDelay_default(cerberus_sikuli_typeDelay); - + // auto scroll parameters session.setCerberus_selenium_autoscroll(cerberus_selenium_autoscroll); session.setCerberus_selenium_autoscroll_vertical_offset(cerberus_selenium_autoscroll_vertical_offset); @@ -277,8 +277,8 @@ public void startServer(TestCaseExecution tCExecution) throws CerberusException // SetUp Proxy String hubUrl = StringUtil.cleanHostURL(RobotServerService.getBaseUrl(StringUtil.formatURLCredential( - tCExecution.getSession().getHostUser(), - tCExecution.getSession().getHostPassword(), session.getHost()), + tCExecution.getSession().getHostUser(), + tCExecution.getSession().getHostPassword(), session.getHost()), session.getPort())) + "/wd/hub"; LOG.debug("Hub URL :{}", hubUrl); URL url = new URL(hubUrl); @@ -315,7 +315,7 @@ public Request authenticate(Route route, Response response) throws IOException { .build(); } }; -*/ + */ } factory.builder().proxy(myproxy); } else { @@ -1047,11 +1047,11 @@ public boolean stopServer(TestCaseExecution tce) { case TestCaseExecution.ROBOTPROVIDER_BROWSERSTACK: case TestCaseExecution.ROBOTPROVIDER_NONE: try { - tce.addFileList(recorderService.recordSeleniumLog(tce)); - } catch (Exception ex) { - LOG.error("Exception Getting Selenium Logs {}", tce.getId(), ex); - } - break; + tce.addFileList(recorderService.recordSeleniumLog(tce)); + } catch (Exception ex) { + LOG.error("Exception Getting Selenium Logs {}", tce.getId(), ex); + } + break; default: } @@ -1060,11 +1060,11 @@ public boolean stopServer(TestCaseExecution tce) { case TestCaseExecution.ROBOTPROVIDER_BROWSERSTACK: case TestCaseExecution.ROBOTPROVIDER_NONE: try { - tce.addFileList(recorderService.recordConsoleLog(tce)); - } catch (Exception ex) { - LOG.error("Exception Getting Console Logs " + tce.getId(), ex); - } - break; + tce.addFileList(recorderService.recordConsoleLog(tce)); + } catch (Exception ex) { + LOG.error("Exception Getting Console Logs " + tce.getId(), ex); + } + break; default: } @@ -1225,6 +1225,21 @@ public HashMap getMapFromOptions(JSONArray options) { return result; } + @Override + public int getFromOptions(JSONArray options, String option) { + int timeout = 0; + HashMap result = new HashMap<>(); + result = this.getMapFromOptions(options); + try { + if (result.containsKey(option)) { + timeout = Integer.parseInt(result.get(option)); + } + } catch (Exception e) { + LOG.error("Cannot convert option '{}' to integer from options {}", option, options); + } + return timeout; + } + @Override public void setOptionsTimeout(Session session, Integer timeout) { if (session != null) { diff --git a/source/src/main/java/org/cerberus/core/engine/gwt/impl/ActionService.java b/source/src/main/java/org/cerberus/core/engine/gwt/impl/ActionService.java index ebc74f67f1..bd56f3461d 100644 --- a/source/src/main/java/org/cerberus/core/engine/gwt/impl/ActionService.java +++ b/source/src/main/java/org/cerberus/core/engine/gwt/impl/ActionService.java @@ -566,7 +566,7 @@ private MessageEvent doActionGetRobotFile(TestCaseExecution execution, TestCaseS } LOG.debug(contentJSON.toString(1)); - AppService appSrv = factoryAppService.create("", "", "", "", "", "", "", "", "", "", "", "", "", "", false, "", "", false, "", "", "", "", "", null, "", null, ""); + AppService appSrv = factoryAppService.create("", "", "", "", "", "", "", "", "", "", "", "", "", "", false, "", "", false, "", false, "", false, "", "", "", null, "", null, ""); JSONObject contentJSONnew = new JSONObject(); // We copy the header values for the service answered. @@ -1689,13 +1689,13 @@ public MessageEvent doActionDragAndDrop(TestCaseExecution tCExecution, String va } } - private MessageEvent doActionCallService(TestCaseStepActionExecution testCaseStepActionExecution, String value1, String value2, String value3) { + private MessageEvent doActionCallService(TestCaseStepActionExecution action, String value1, String value2, String value3) { MessageEvent message = new MessageEvent(MessageEventEnum.ACTION_FAILED_CALLSERVICE); - TestCaseExecution tCExecution = testCaseStepActionExecution.getTestCaseStepExecution().gettCExecution(); + TestCaseExecution tCExecution = action.getTestCaseStepExecution().gettCExecution(); AnswerItem lastServiceCalledAnswer; - lastServiceCalledAnswer = serviceService.callService(value1, value2, value3, null, null, null, null, tCExecution); + lastServiceCalledAnswer = serviceService.callService(value1, value2, value3, null, null, null, null, tCExecution, robotServerService.getFromOptions(action.getOptions(), RobotServerService.OPTIONS_TIMEOUT_SYNTAX)); message = lastServiceCalledAnswer.getResultMessage(); if (lastServiceCalledAnswer.getItem() != null) { @@ -1707,7 +1707,7 @@ private MessageEvent doActionCallService(TestCaseStepActionExecution testCaseSte /** * Record the Request and Response in file system. */ - testCaseStepActionExecution.addFileList(recorderService.recordServiceCall(tCExecution, testCaseStepActionExecution, 0, null, lastServiceCalled)); + action.addFileList(recorderService.recordServiceCall(tCExecution, action, 0, null, lastServiceCalled)); } return message; @@ -1893,7 +1893,7 @@ private MessageEvent doActionSetNetworkTrafficContent(TestCaseExecution exe, Tes har = harService.enrichWithStats(har, exe.getCountryEnvironmentParameters().getDomain(), exe.getSystem(), exe.getNetworkTrafficIndexList()); - AppService appSrv = factoryAppService.create("", AppService.TYPE_REST, AppService.METHOD_HTTPGET, "", "", "", "", "", "", "", "", "", "", "", true, "", "", false, "", "", "", "", "", null, "", null, null); + AppService appSrv = factoryAppService.create("", AppService.TYPE_REST, AppService.METHOD_HTTPGET, "", "", "", "", "", "", "", "", "", "", "", true, "", "", false, "", false, "", false, "", "", "", null, "", null, null); appSrv.setResponseHTTPBody(har.toString()); appSrv.setResponseHTTPBodyContentType(AppService.RESPONSEHTTPBODYCONTENTTYPE_JSON); appSrv.setRecordTraceFile(false); @@ -1974,7 +1974,7 @@ private MessageEvent doActionSetConsoleContent(TestCaseExecution exe, TestCaseSt consoleStat = consolelogService.enrichWithStats(consoleLogs); consoleRecap.put("stat", consoleStat); - AppService appSrv = factoryAppService.create("", "", "", "", "", "", "", "", "", "", "", "", "", "", false, "", "", false, "", "", "", null, "", null, "", null, ""); + AppService appSrv = factoryAppService.create("", "", "", "", "", "", "", "", "", "", "", "", "", "", false, "", "", false, "", false, "", false, "", null, "", null, "", null, ""); appSrv.setResponseHTTPBody(consoleRecap.toString()); appSrv.setResponseHTTPBodyContentType(AppService.RESPONSEHTTPBODYCONTENTTYPE_JSON); appSrv.setRecordTraceFile(false); @@ -2007,7 +2007,7 @@ private MessageEvent doActionSetContent(TestCaseExecution exe, TestCaseStepActio */ LOG.debug("Setting static content."); - AppService appSrv = factoryAppService.create("", "", "", "", "", "", "", "", "", "", "", "", "", "", false, "", "", false, "", "", "", "", "", null, "", null, ""); + AppService appSrv = factoryAppService.create("", "", "", "", "", "", "", "", "", "", "", "", "", "", false, "", "", false, "", false, "", false, "", "", "", null, "", null, ""); appSrv.setResponseHTTPBody(textContent); appSrv.setResponseHTTPBodyContentType(appServiceService.guessContentType(appSrv, AppService.RESPONSEHTTPBODYCONTENTTYPE_JSON)); appSrv.setRecordTraceFile(false); diff --git a/source/src/main/java/org/cerberus/core/service/appservice/IServiceService.java b/source/src/main/java/org/cerberus/core/service/appservice/IServiceService.java index 4610753b9b..4031431a03 100644 --- a/source/src/main/java/org/cerberus/core/service/appservice/IServiceService.java +++ b/source/src/main/java/org/cerberus/core/service/appservice/IServiceService.java @@ -44,8 +44,9 @@ public interface IServiceService { * @param servicePath * @param operation * @param tCExecution + * @param timeoutMs * @return */ - AnswerItem callService(String service, String targetNbEvents, String targetNbSec, String database, String request, String servicePath, String operation, TestCaseExecution tCExecution); + AnswerItem callService(String service, String targetNbEvents, String targetNbSec, String database, String request, String servicePath, String operation, TestCaseExecution tCExecution, int timeoutMs); } diff --git a/source/src/main/java/org/cerberus/core/service/appservice/impl/ServiceService.java b/source/src/main/java/org/cerberus/core/service/appservice/impl/ServiceService.java index 17b554fbcc..9114b0190e 100644 --- a/source/src/main/java/org/cerberus/core/service/appservice/impl/ServiceService.java +++ b/source/src/main/java/org/cerberus/core/service/appservice/impl/ServiceService.java @@ -79,7 +79,7 @@ public class ServiceService implements IServiceService { private ICountryEnvironmentDatabaseService countryEnvironmentDatabaseService; @Override - public AnswerItem callService(String service, String targetNbEvents, String targetNbSec, String database, String request, String servicePathParam, String operation, TestCaseExecution tCExecution) { + public AnswerItem callService(String service, String targetNbEvents, String targetNbSec, String database, String request, String servicePathParam, String operation, TestCaseExecution tCExecution, int timeoutMs) { MessageEvent message = new MessageEvent(MessageEventEnum.ACTION_FAILED_CALLSERVICE); String decodedRequest; String decodedServicePath = null; @@ -99,7 +99,7 @@ public AnswerItem callService(String service, String targetNbEvents, if (StringUtil.isEmpty(service)) { LOG.debug("Creating AppService from parameters."); appService = factoryAppService.create("null", AppService.TYPE_SOAP, "", "", "", request, "", "", "", "", "", "", "Automatically created Service from datalib.", - servicePathParam, true, "", operation, false, "", "", "", null, null, null, null, null, null); + servicePathParam, true, "", operation, false, "", false, "", false, "", null, null, null, null, null, null); service = "null"; } else { @@ -331,7 +331,9 @@ public AnswerItem callService(String service, String targetNbEvents, token = String.valueOf(tCExecution.getId()); } // Get from parameter the call timeout to be used. - int timeOutMs = parameterService.getParameterIntegerByKey("cerberus_callservice_timeoutms", system, 60000); + if (timeoutMs == 0) { + timeoutMs = parameterService.getParameterIntegerByKey("cerberus_callservice_timeoutms", system, 60000); + } // The rest of the data will be prepared depending on the TYPE and METHOD used. switch (appService.getType()) { case AppService.TYPE_SOAP: @@ -382,7 +384,7 @@ public AnswerItem callService(String service, String targetNbEvents, * Call SOAP and store it into the execution. */ result = soapService.callSOAP(decodedRequest, decodedServicePath, decodedOperation, decodedAttachement, - appService.getHeaderList(), token, timeOutMs, system); + appService.getHeaderList(), token, timeoutMs, system); LOG.debug("SOAP Called done."); LOG.debug("Result message." + result.getResultMessage()); @@ -406,7 +408,7 @@ public AnswerItem callService(String service, String targetNbEvents, * Call REST and store it into the execution. */ result = restService.callREST(decodedServicePath, decodedRequest, appService.getMethod(), - appService.getHeaderList(), appService.getContentList(), token, timeOutMs, system, appService.isFollowRedir(), tCExecution); + appService.getHeaderList(), appService.getContentList(), token, timeoutMs, system, appService.isFollowRedir(), tCExecution); message = result.getResultMessage(); break; @@ -471,7 +473,7 @@ public AnswerItem callService(String service, String targetNbEvents, * Call REST and store it into the execution. */ result = kafkaService.produceEvent(decodedTopic, decodedKey, decodedRequest, decodedServicePath, appService.getHeaderList(), appService.getContentList(), - token, appService.isAvroEnable(), decodedSchemaRegistryURL, appService.getAvroSchemaKey(), appService.getAvroSchemaValue(), timeOutMs); + token, appService.isAvroEnable(), decodedSchemaRegistryURL, appService.isAvroEnableKey(), appService.getAvroSchemaKey(), appService.isAvroEnableValue(), appService.getAvroSchemaValue(), timeoutMs); message = result.getResultMessage(); break; @@ -566,7 +568,7 @@ public AnswerItem callService(String service, String targetNbEvents, String kafkaKey = kafkaService.getKafkaConsumerKey(decodedTopic, decodedServicePath); AnswerItem resultSearch = kafkaService.searchEvent(tCExecution.getKafkaLatestOffset().get(kafkaKey), decodedTopic, decodedServicePath, appService.getHeaderList(), appService.getContentList(), decodedFilterPath, decodedFilterValue, decodedFilterHeaderPath, decodedFilterHeaderValue, - appService.isAvroEnable(), decodedSchemaRegistryURL, StringUtil.isNotEmpty(appService.getAvroSchemaKey()), StringUtil.isNotEmpty(appService.getAvroSchemaValue()), targetNbEventsInt, targetNbSecInt); + appService.isAvroEnable(), decodedSchemaRegistryURL, appService.isAvroEnableKey(), appService.isAvroEnableValue(), targetNbEventsInt, targetNbSecInt); if (!(resultSearch.isCodeStringEquals("OK"))) { message = new MessageEvent(MessageEventEnum.ACTION_FAILED_CALLSERVICE); diff --git a/source/src/main/java/org/cerberus/core/service/datalib/impl/DataLibService.java b/source/src/main/java/org/cerberus/core/service/datalib/impl/DataLibService.java index f110f5456c..1f4d6d5b61 100644 --- a/source/src/main/java/org/cerberus/core/service/datalib/impl/DataLibService.java +++ b/source/src/main/java/org/cerberus/core/service/datalib/impl/DataLibService.java @@ -833,7 +833,7 @@ private AnswerList> getDataObjectList(TestDataLib lib, H columnsToHide = getListOfSecrets(lib.getTestDataLibID()); // Service Call is made here. - AnswerItem ai = serviceService.callService(lib.getService(), null, null, lib.getDatabaseUrl(), lib.getEnvelope(), lib.getServicePath(), lib.getMethod(), execution); + AnswerItem ai = serviceService.callService(lib.getService(), null, null, lib.getDatabaseUrl(), lib.getEnvelope(), lib.getServicePath(), lib.getMethod(), execution, 0); msg = ai.getResultMessage(); diff --git a/source/src/main/java/org/cerberus/core/service/ftp/impl/FtpService.java b/source/src/main/java/org/cerberus/core/service/ftp/impl/FtpService.java index 3e4325f2b6..d5e75cbc7d 100644 --- a/source/src/main/java/org/cerberus/core/service/ftp/impl/FtpService.java +++ b/source/src/main/java/org/cerberus/core/service/ftp/impl/FtpService.java @@ -165,7 +165,7 @@ public AnswerItem callFTP(String chain, String system, String conten FTPClient ftp = new FTPClient(); AppService myResponse = factoryAppService.create(service, AppService.TYPE_FTP, - method, "", "", content, "", "", "", "", "", "", "", informations.get("path"), true, "", "", false, "", "", "", "", "", null, "", null, filePath); + method, "", "", content, "", "", "", "", "", "", "", informations.get("path"), true, "", "", false, "", false, "", false, "", "", "", null, "", null, filePath); try { if (proxyService.useProxy(StringUtil.getURLFromString(informations.get("host"), "", "", "ftp://"), system)) { diff --git a/source/src/main/java/org/cerberus/core/service/kafka/IKafkaService.java b/source/src/main/java/org/cerberus/core/service/kafka/IKafkaService.java index 2c4b3f9988..0dd249ddbc 100644 --- a/source/src/main/java/org/cerberus/core/service/kafka/IKafkaService.java +++ b/source/src/main/java/org/cerberus/core/service/kafka/IKafkaService.java @@ -57,13 +57,16 @@ public interface IKafkaService { * @param token * @param isAvroEnable * @param schemaRegistryURL + * @param isAvroEnableKey * @param avroSchemaKey + * @param isAvroEnableValue * @param avroSchemaValue * @param timeoutMs * @return */ public AnswerItem produceEvent(String topic, String key, String eventMessage, - String bootstrapServers, List serviceHeader, List serviceContent, String token, boolean isAvroEnable, String schemaRegistryURL, String avroSchemaKey, String avroSchemaValue, int timeoutMs); + String bootstrapServers, List serviceHeader, List serviceContent, String token, + boolean isAvroEnable, String schemaRegistryURL, boolean isAvroEnableKey, String avroSchemaKey, boolean isAvroEnableValue, String avroSchemaValue, int timeoutMs); /** * Get the last offset of every partition. diff --git a/source/src/main/java/org/cerberus/core/service/kafka/impl/KafkaService.java b/source/src/main/java/org/cerberus/core/service/kafka/impl/KafkaService.java index dee9c7141c..e815f13643 100644 --- a/source/src/main/java/org/cerberus/core/service/kafka/impl/KafkaService.java +++ b/source/src/main/java/org/cerberus/core/service/kafka/impl/KafkaService.java @@ -195,11 +195,12 @@ private Object readFrom(String jsonString, ParsedSchema parsedSchema) { @Override public AnswerItem produceEvent(String topic, String key, String eventMessage, String bootstrapServers, - List serviceHeader, List serviceContent, String token, boolean activateAvro, String schemaRegistryURL, String avroSchemaKey, String avroSchemaValue, int timeoutMs) { + List serviceHeader, List serviceContent, String token, boolean activateAvro, String schemaRegistryURL, + boolean isAvroEnableKey, String avroSchemaKey, boolean isAvroEnableValue, String avroSchemaValue, int timeoutMs) { MessageEvent message = new MessageEvent(MessageEventEnum.ACTION_FAILED_CALLSERVICE_PRODUCEKAFKA); AnswerItem result = new AnswerItem<>(); - AppService serviceREST = factoryAppService.create("", AppService.TYPE_KAFKA, AppService.METHOD_KAFKAPRODUCE, "", "", "", "", "", "", "", "", "", "", "", true, "", "", false, "", "", "", null, + AppService serviceREST = factoryAppService.create("", AppService.TYPE_KAFKA, AppService.METHOD_KAFKAPRODUCE, "", "", "", "", "", "", "", "", "", "", "", true, "", "", false, "", false, "", false, "", null, "", null, "", null, null); // If token is defined, we add 'cerberus-token' on the http header. @@ -214,15 +215,15 @@ public AnswerItem produceEvent(String topic, String key, String even serviceContent.add(factoryAppServiceContent.create(null, ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer", true, 0, "", "", null, "", null)); serviceContent.add(factoryAppServiceContent.create(null, ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer", true, 0, "", "", null, "", null)); } else { - if (StringUtil.isNotEmpty(avroSchemaKey)) { - serviceContent.add(factoryAppServiceContent.create(null, ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer", true, 0, "", "", null, "", null)); - } else { + if (isAvroEnableKey) { serviceContent.add(factoryAppServiceContent.create(null, ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "io.confluent.kafka.serializers.KafkaAvroSerializer", true, 0, "", "", null, "", null)); - } - if (StringUtil.isNotEmpty(avroSchemaValue)) { - serviceContent.add(factoryAppServiceContent.create(null, ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer", true, 0, "", "", null, "", null)); } else { + serviceContent.add(factoryAppServiceContent.create(null, ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer", true, 0, "", "", null, "", null)); + } + if (isAvroEnableValue) { serviceContent.add(factoryAppServiceContent.create(null, ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "io.confluent.kafka.serializers.KafkaAvroSerializer", true, 0, "", "", null, "", null)); + } else { + serviceContent.add(factoryAppServiceContent.create(null, ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer", true, 0, "", "", null, "", null)); } // props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class); serviceContent.add(factoryAppServiceContent.create(null, "schema.registry.url", schemaRegistryURL, true, 0, "", "", null, "", null)); @@ -230,6 +231,7 @@ public AnswerItem produceEvent(String topic, String key, String even // Setting timeout although does not seem to work fine as result on aiven is always 60000 ms. serviceContent.add(factoryAppServiceContent.create(null, ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, String.valueOf(timeoutMs), true, 0, "", "", null, "", null)); serviceContent.add(factoryAppServiceContent.create(null, ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG, String.valueOf(timeoutMs), true, 0, "", "", null, "", null)); + serviceContent.add(factoryAppServiceContent.create(null, ProducerConfig.MAX_BLOCK_MS_CONFIG, String.valueOf(timeoutMs), true, 0, "", "", null, "", null)); for (AppServiceContent object : serviceContent) { if (object.isActive()) { @@ -296,20 +298,22 @@ public AnswerItem produceEvent(String topic, String key, String even // LOG.debug(userSchema); Schema.Parser parser = new Schema.Parser(); Schema schemaValue; - schemaValue = parser.parse(avroSchemaValue); Schema schemaKey; - schemaKey = parser.parse(avroSchemaKey); // ParsedSchema toto ; ProducerRecord record; - if (StringUtil.isNotEmpty(avroSchemaKey)) { - if (StringUtil.isNotEmpty(avroSchemaValue)) { + if (isAvroEnableKey) { + if (isAvroEnableValue) { + schemaKey = parser.parse(avroSchemaKey); + schemaValue = parser.parse(avroSchemaValue); record = new ProducerRecord<>(topic, jsonToAvro(key, schemaKey), jsonToAvro(eventMessage, schemaValue)); } else { + schemaKey = parser.parse(avroSchemaKey); record = new ProducerRecord<>(topic, jsonToAvro(key, schemaKey), eventMessage); } } else { - if (StringUtil.isNotEmpty(avroSchemaValue)) { + if (isAvroEnableValue) { + schemaValue = parser.parse(avroSchemaValue); record = new ProducerRecord<>(topic, key, jsonToAvro(eventMessage, schemaValue)); } else { record = new ProducerRecord<>(topic, key, eventMessage); @@ -471,12 +475,22 @@ public AnswerItem searchEvent(Map mapOffsetPositio serviceContent.add(factoryAppServiceContent.create(null, ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers, true, 0, "", "", null, "", null)); serviceContent.add(factoryAppServiceContent.create(null, ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false", true, 0, "", "", null, "", null)); serviceContent.add(factoryAppServiceContent.create(null, ConsumerConfig.MAX_POLL_RECORDS_CONFIG, "10", true, 0, "", "", null, "", null)); + if (!activateAvro) { serviceContent.add(factoryAppServiceContent.create(null, ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer", true, 0, "", "", null, "", null)); serviceContent.add(factoryAppServiceContent.create(null, ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer", true, 0, "", "", null, "", null)); } else { - serviceContent.add(factoryAppServiceContent.create(null, ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer", true, 0, "", "", null, "", null)); - serviceContent.add(factoryAppServiceContent.create(null, ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "io.confluent.kafka.serializers.KafkaAvroDeserializer", true, 0, "", "", null, "", null)); + if (avroEnableKey) { + serviceContent.add(factoryAppServiceContent.create(null, ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "io.confluent.kafka.serializers.KafkaAvroDeserializer", true, 0, "", "", null, "", null)); + } else { + serviceContent.add(factoryAppServiceContent.create(null, ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer", true, 0, "", "", null, "", null)); + } + if (avroEnableValue) { + serviceContent.add(factoryAppServiceContent.create(null, ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "io.confluent.kafka.serializers.KafkaAvroDeserializer", true, 0, "", "", null, "", null)); + } else { + serviceContent.add(factoryAppServiceContent.create(null, ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer", true, 0, "", "", null, "", null)); + } +// props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class); serviceContent.add(factoryAppServiceContent.create(null, "schema.registry.url", schemaRegistryURL, true, 0, "", "", null, "", null)); } @@ -590,75 +604,153 @@ public AnswerItem searchEvent(Map mapOffsetPositio } else { if (avroEnableKey) { - // AVRO KEY+VALUE VERSION - @SuppressWarnings("unchecked") - ConsumerRecords recordsAvro = consumer.poll(Duration.ofSeconds(pollDurationSec)); - LOG.debug("End Poll."); - if (Instant.now().toEpochMilli() > timeoutTime) { - LOG.debug("Timed out searching for record"); - consumer.wakeup(); //exit - } - //Now for each record in the batch of records we got from Kafka - for (ConsumerRecord record : recordsAvro) { - try { - LOG.debug("New record " + record.topic() + " " + record.partition() + " " + record.offset()); - LOG.debug(" " + record.key() + " | " + record.value()); - // Parsing header. - JSONObject headerJSON = new JSONObject(); - for (Header header : record.headers()) { - String headerKey = header.key(); - String headerValue = new String(header.value()); - headerJSON.put(headerKey, headerValue); - } + if (avroEnableValue) { - boolean recordError = false; - - // Parsing message. - JSONObject recordJSON = new JSONObject(); + // AVRO KEY+VALUE VERSION + @SuppressWarnings("unchecked") + ConsumerRecords recordsAvro = consumer.poll(Duration.ofSeconds(pollDurationSec)); + LOG.debug("End Poll."); + if (Instant.now().toEpochMilli() > timeoutTime) { + LOG.debug("Timed out searching for record"); + consumer.wakeup(); //exit + } + //Now for each record in the batch of records we got from Kafka + for (ConsumerRecord record : recordsAvro) { try { - recordJSON = new JSONObject(record.value().toString()); - } catch (JSONException ex) { + LOG.debug("New record " + record.topic() + " " + record.partition() + " " + record.offset()); + LOG.debug(" " + record.key() + " | " + record.value()); + + // Parsing header. + JSONObject headerJSON = new JSONObject(); + for (Header header : record.headers()) { + String headerKey = header.key(); + String headerValue = new String(header.value()); + headerJSON.put(headerKey, headerValue); + } + + boolean recordError = false; + + // Parsing message. + JSONObject recordJSON = new JSONObject(); + try { + recordJSON = new JSONObject(record.value().toString()); + } catch (JSONException ex) { + LOG.error(ex, ex); + recordError = true; + } + + // Parsing message. + JSONObject keyJSON = new JSONObject(); + try { + keyJSON = new JSONObject(record.key().toString()); + } catch (JSONException ex) { + LOG.error(ex, ex); + recordError = true; + } + + // Complete event with headers. + JSONObject messageJSON = new JSONObject(); + messageJSON.put("key", keyJSON); + messageJSON.put("value", recordJSON); + messageJSON.put("offset", record.offset()); + messageJSON.put("partition", record.partition()); + messageJSON.put("header", headerJSON); + + nbEvents++; + + boolean match = isRecordMatch(record.value().toString(), filterPath, filterValue, messageJSON.toString(), filterHeaderPath, filterHeaderValue); + + if (match) { + resultJSON.put(messageJSON); + nbFound++; + if (nbFound >= targetNbEventsInt) { + consume = false; //exit the consume loop + consumer.wakeup(); //takes effect on the next poll loop so need to break. + break; //if we've found a match, stop looping through the current record batch + } + } + + } catch (Exception ex) { + //Catch any exceptions thrown from message processing/testing as they should have already been reported/dealt with + //but we don't want to trigger the catch block for Kafka consumption LOG.error(ex, ex); - recordError = true; } + } - // Parsing message. - JSONObject keyJSON = new JSONObject(); + } else { + + // AVRO KEY VERSION + @SuppressWarnings("unchecked") + ConsumerRecords recordsAvro = consumer.poll(Duration.ofSeconds(pollDurationSec)); + LOG.debug("End Poll."); + if (Instant.now().toEpochMilli() > timeoutTime) { + LOG.debug("Timed out searching for record"); + consumer.wakeup(); //exit + } + //Now for each record in the batch of records we got from Kafka + for (ConsumerRecord record : recordsAvro) { try { - keyJSON = new JSONObject(record.key().toString()); - } catch (JSONException ex) { - LOG.error(ex, ex); - recordError = true; - } - - // Complete event with headers. - JSONObject messageJSON = new JSONObject(); - messageJSON.put("key", keyJSON); - messageJSON.put("value", recordJSON); - messageJSON.put("offset", record.offset()); - messageJSON.put("partition", record.partition()); - messageJSON.put("header", headerJSON); + LOG.debug("New record " + record.topic() + " " + record.partition() + " " + record.offset()); + LOG.debug(" " + record.key() + " | " + record.value()); + + // Parsing header. + JSONObject headerJSON = new JSONObject(); + for (Header header : record.headers()) { + String headerKey = header.key(); + String headerValue = new String(header.value()); + headerJSON.put(headerKey, headerValue); + } - nbEvents++; + boolean recordError = false; - boolean match = isRecordMatch(record.value().toString(), filterPath, filterValue, messageJSON.toString(), filterHeaderPath, filterHeaderValue); + // Parsing message. + JSONObject recordJSON = new JSONObject(); + try { + recordJSON = new JSONObject(record.value()); + } catch (JSONException ex) { + LOG.error(ex, ex); + recordError = true; + } - if (match) { - resultJSON.put(messageJSON); - nbFound++; - if (nbFound >= targetNbEventsInt) { - consume = false; //exit the consume loop - consumer.wakeup(); //takes effect on the next poll loop so need to break. - break; //if we've found a match, stop looping through the current record batch + // Parsing message. + JSONObject keyJSON = new JSONObject(); + try { + keyJSON = new JSONObject(record.key().toString()); + } catch (JSONException ex) { + LOG.error(ex, ex); + recordError = true; } - } - } catch (Exception ex) { - //Catch any exceptions thrown from message processing/testing as they should have already been reported/dealt with - //but we don't want to trigger the catch block for Kafka consumption - LOG.error(ex, ex); + // Complete event with headers. + JSONObject messageJSON = new JSONObject(); + messageJSON.put("key", keyJSON); + messageJSON.put("value", record.value()); + messageJSON.put("offset", record.offset()); + messageJSON.put("partition", record.partition()); + messageJSON.put("header", headerJSON); + + nbEvents++; + + boolean match = isRecordMatch(record.value(), filterPath, filterValue, messageJSON.toString(), filterHeaderPath, filterHeaderValue); + + if (match) { + resultJSON.put(messageJSON); + nbFound++; + if (nbFound >= targetNbEventsInt) { + consume = false; //exit the consume loop + consumer.wakeup(); //takes effect on the next poll loop so need to break. + break; //if we've found a match, stop looping through the current record batch + } + } + + } catch (Exception ex) { + //Catch any exceptions thrown from message processing/testing as they should have already been reported/dealt with + //but we don't want to trigger the catch block for Kafka consumption + LOG.error(ex, ex); + } } + } } else { @@ -724,6 +816,7 @@ public AnswerItem searchEvent(Map mapOffsetPositio LOG.error(ex, ex); } } + } } diff --git a/source/src/main/java/org/cerberus/core/service/rest/impl/RestService.java b/source/src/main/java/org/cerberus/core/service/rest/impl/RestService.java index 5f4df945d8..0c151a674c 100644 --- a/source/src/main/java/org/cerberus/core/service/rest/impl/RestService.java +++ b/source/src/main/java/org/cerberus/core/service/rest/impl/RestService.java @@ -143,7 +143,7 @@ private AppService executeHTTPCall(CloseableHttpClient httpclient, HttpRequestBa // Create a custom response handler ResponseHandler responseHandler = (final HttpResponse response) -> { AppService myResponse = factoryAppService.create("", AppService.TYPE_REST, - AppService.METHOD_HTTPGET, "", "", "", "", "", "", "", "", "", "", "", true, "", "", false, "", "", "", null, "", null, "", null, null); + AppService.METHOD_HTTPGET, "", "", "", "", "", "", "", "", "", "", "", true, "", "", false, "", false, "", false, "", null, "", null, "", null, null); int responseCode = response.getStatusLine().getStatusCode(); myResponse.setResponseHTTPCode(responseCode); myResponse.setResponseHTTPVersion(response.getProtocolVersion().toString()); @@ -172,7 +172,7 @@ public AnswerItem callREST(String servicePath, String requestString, List headerList, List contentList, String token, int timeOutMs, String system, boolean isFollowRedir, TestCaseExecution tcexecution) { AnswerItem result = new AnswerItem<>(); - AppService serviceREST = factoryAppService.create("", AppService.TYPE_REST, method, "", "", "", "", "", "", "", "", "", "", "", true, "", "", false, "", "", "", null, + AppService serviceREST = factoryAppService.create("", AppService.TYPE_REST, method, "", "", "", "", "", "", "", "", "", "", "", true, "", "", false, "", false, "", false, "", null, "", null, "", null, null); serviceREST.setProxy(false); serviceREST.setProxyHost(null); diff --git a/source/src/main/java/org/cerberus/core/service/soap/impl/SoapService.java b/source/src/main/java/org/cerberus/core/service/soap/impl/SoapService.java index 083c67882f..5f668d4dd3 100644 --- a/source/src/main/java/org/cerberus/core/service/soap/impl/SoapService.java +++ b/source/src/main/java/org/cerberus/core/service/soap/impl/SoapService.java @@ -208,7 +208,7 @@ public AnswerItem callSOAP(String envelope, String servicePath, Stri String unescapedEnvelope = StringEscapeUtils.unescapeXml(envelope); boolean is12SoapVersion = SOAP_1_2_NAMESPACE_PATTERN.matcher(unescapedEnvelope).matches(); - AppService serviceSOAP = factoryAppService.create("", AppService.TYPE_SOAP, null, "", "", envelope, "", "", "", "", "", "", "", servicePath, true, "", soapOperation, false, "", "", "", null, "", null, "", null, null); + AppService serviceSOAP = factoryAppService.create("", AppService.TYPE_SOAP, null, "", "", envelope, "", "", "", "", "", "", "", servicePath, true, "", soapOperation, false, "", false, "", false, "", null, "", null, "", null, null); serviceSOAP.setTimeoutms(timeOutMs); ByteArrayOutputStream out = null; MessageEvent message = null; diff --git a/source/src/main/java/org/cerberus/core/servlet/crud/countryenvironment/CreateAppService.java b/source/src/main/java/org/cerberus/core/servlet/crud/countryenvironment/CreateAppService.java index 1546e6219d..1c2e4f0612 100644 --- a/source/src/main/java/org/cerberus/core/servlet/crud/countryenvironment/CreateAppService.java +++ b/source/src/main/java/org/cerberus/core/servlet/crud/countryenvironment/CreateAppService.java @@ -140,8 +140,10 @@ final void processRequest(final HttpServletRequest request, final HttpServletRes String kafkaTopic = ParameterParserUtil.parseStringParamAndDecode(fileData.get("kafkaTopic"), "", charset); boolean isAvroEnable = ParameterParserUtil.parseBooleanParamAndDecode(fileData.get("isAvroEnable"), true, charset); String schemaRegistryUrl = ParameterParserUtil.parseStringParamAndDecode(fileData.get("schemaRegistryUrl"), null, charset); - String avroSchemaKey = ParameterParserUtil.parseStringParamAndDecode(fileData.get("avroSchemaKey"), null, charset); - String avroSchemaValue = ParameterParserUtil.parseStringParamAndDecode(fileData.get("avroSchemaValue"), null, charset); + boolean isAvroEnableKey = ParameterParserUtil.parseBooleanParamAndDecode(fileData.get("isAvroEnableKey"), true, charset); + String avroSchemaKey = ParameterParserUtil.parseStringParamAndDecode(fileData.get("avrSchemaKey"), null, charset); + boolean isAvroEnableValue = ParameterParserUtil.parseBooleanParamAndDecode(fileData.get("isAvroEnableValue"), true, charset); + String avroSchemaValue = ParameterParserUtil.parseStringParamAndDecode(fileData.get("avrSchemaValue"), null, charset); String parentContentService = ParameterParserUtil.parseStringParamAndDecode(fileData.get("parentContentService"), "", charset); String kafkaKey = ParameterParserUtil.parseStringParamAndDecode(fileData.get("kafkaKey"), "", charset); String kafkaFilterPath = ParameterParserUtil.parseStringParamAndDecode(fileData.get("kafkaFilterPath"), "", charset); @@ -179,7 +181,7 @@ final void processRequest(final HttpServletRequest request, final HttpServletRes appServiceHeaderFactory = appContext.getBean(IFactoryAppServiceHeader.class); LOG.debug(request.getUserPrincipal().getName()); AppService appService = appServiceFactory.create(service, type, method, application, group, serviceRequest, kafkaTopic, kafkaKey, kafkaFilterPath, kafkaFilterValue, kafkaFilterHeaderPath, kafkaFilterHeaderValue, description, servicePath, - isFollowRedir, attachementurl, operation, isAvroEnable, schemaRegistryUrl, avroSchemaKey, avroSchemaValue, parentContentService, request.getUserPrincipal().getName(), null, null, null, fileName); + isFollowRedir, attachementurl, operation, isAvroEnable, schemaRegistryUrl, isAvroEnableKey, avroSchemaKey, isAvroEnableValue, avroSchemaValue, parentContentService, request.getUserPrincipal().getName(), null, null, null, fileName); ans = appServiceService.create(appService); finalAnswer = AnswerUtil.agregateAnswer(finalAnswer, ans); diff --git a/source/src/main/java/org/cerberus/core/servlet/crud/countryenvironment/UpdateAppService.java b/source/src/main/java/org/cerberus/core/servlet/crud/countryenvironment/UpdateAppService.java index f9a81d6964..f3836d8e8c 100644 --- a/source/src/main/java/org/cerberus/core/servlet/crud/countryenvironment/UpdateAppService.java +++ b/source/src/main/java/org/cerberus/core/servlet/crud/countryenvironment/UpdateAppService.java @@ -140,7 +140,9 @@ final void processRequest(final HttpServletRequest request, final HttpServletRes String kafkaTopic = ParameterParserUtil.parseStringParamAndDecode(fileData.get("kafkaTopic"), "", charset); boolean isAvroEnable = ParameterParserUtil.parseBooleanParam(fileData.get("isAvroEnable"), false); String schemaRegistryUrl = ParameterParserUtil.parseStringParamAndDecode(fileData.get("schemaRegistryUrl"), null, charset); + boolean isAvroEnableKey = ParameterParserUtil.parseBooleanParam(fileData.get("isAvroEnableKey"), false); String avrSchemaKey = ParameterParserUtil.parseStringParamAndDecode(fileData.get("avrSchemaKey"), null, charset); + boolean isAvroEnableValue = ParameterParserUtil.parseBooleanParam(fileData.get("isAvroEnableValue"), false); String avrSchemaValue = ParameterParserUtil.parseStringParamAndDecode(fileData.get("avrSchemaValue"), null, charset); String parentContentService = ParameterParserUtil.parseStringParamAndDecode(fileData.get("parentContentService"), "", charset); String kafkaKey = ParameterParserUtil.parseStringParamAndDecode(fileData.get("kafkaKey"), "", charset); @@ -216,6 +218,8 @@ final void processRequest(final HttpServletRequest request, final HttpServletRes appService.setKafkaFilterHeaderValue(kafkaFilterHeaderValue); appService.setFollowRedir(isFollowRedir); appService.setAvroEnable(isAvroEnable); + appService.setAvroEnableKey(isAvroEnableKey); + appService.setAvroEnableValue(isAvroEnableValue); appService.setSchemaRegistryURL(schemaRegistryUrl); appService.setAvroSchemaKey(avrSchemaKey); appService.setAvroSchemaValue(avrSchemaValue); diff --git a/source/src/main/resources/database.sql b/source/src/main/resources/database.sql index 6e78c2e225..319012ee28 100644 --- a/source/src/main/resources/database.sql +++ b/source/src/main/resources/database.sql @@ -6182,10 +6182,12 @@ INSERT INTO `parameter` (`system`, `param`, `value`, `description`) ALTER TABLE `appservice` ADD COLUMN `AvroSchema` MEDIUMTEXT NULL DEFAULT NULL AFTER `SchemaRegistryUrl`; --- 1743 +-- 1743-1745 ALTER TABLE `appservice` ADD COLUMN `AvroSchemaKey` MEDIUMTEXT NULL DEFAULT NULL AFTER `SchemaRegistryUrl`; - ALTER TABLE `appservice` CHANGE COLUMN `AvroSchema` `AvroSchemaValue` MEDIUMTEXT NULL DEFAULT NULL ; +ALTER TABLE `appservice` + ADD COLUMN `IsAvroEnableKey` BOOLEAN DEFAULT 0 AFTER `SchemaRegistryUrl`, + ADD COLUMN `IsAvroEnableValue` BOOLEAN DEFAULT 0 AFTER `AvroSchemaKey`; diff --git a/source/src/main/webapp/dependencies/Ace-1.2.6/worker-json.js b/source/src/main/webapp/dependencies/Ace-1.2.6/worker-json.js new file mode 100644 index 0000000000..a443035ec3 --- /dev/null +++ b/source/src/main/webapp/dependencies/Ace-1.2.6/worker-json.js @@ -0,0 +1 @@ +"no use strict";(function(e){function t(e,t){var n=e,r="";while(n){var i=t[n];if(typeof i=="string")return i+r;if(i)return i.location.replace(/\/*$/,"/")+(r||i.main||i.name);if(i===!1)return"";var s=n.lastIndexOf("/");if(s===-1)break;r=n.substr(s)+r,n=n.slice(0,s)}return e}if(typeof e.window!="undefined"&&e.document)return;if(e.require&&e.define)return;e.console||(e.console=function(){var e=Array.prototype.slice.call(arguments,0);postMessage({type:"log",data:e})},e.console.error=e.console.warn=e.console.log=e.console.trace=e.console),e.window=e,e.ace=e,e.onerror=function(e,t,n,r,i){postMessage({type:"error",data:{message:e,data:i.data,file:t,line:n,col:r,stack:i.stack}})},e.normalizeModule=function(t,n){if(n.indexOf("!")!==-1){var r=n.split("!");return e.normalizeModule(t,r[0])+"!"+e.normalizeModule(t,r[1])}if(n.charAt(0)=="."){var i=t.split("/").slice(0,-1).join("/");n=(i?i+"/":"")+n;while(n.indexOf(".")!==-1&&s!=n){var s=n;n=n.replace(/^\.\//,"").replace(/\/\.\//,"/").replace(/[^\/]+\/\.\.\//,"")}}return n},e.require=function(r,i){i||(i=r,r=null);if(!i.charAt)throw new Error("worker.js require() accepts only (parentId, id) as arguments");i=e.normalizeModule(r,i);var s=e.require.modules[i];if(s)return s.initialized||(s.initialized=!0,s.exports=s.factory().exports),s.exports;if(!e.require.tlns)return console.log("unable to load "+i);var o=t(i,e.require.tlns);return o.slice(-3)!=".js"&&(o+=".js"),e.require.id=i,e.require.modules[i]={},importScripts(o),e.require(r,i)},e.require.modules={},e.require.tlns={},e.define=function(t,n,r){arguments.length==2?(r=n,typeof t!="string"&&(n=t,t=e.require.id)):arguments.length==1&&(r=t,n=[],t=e.require.id);if(typeof r!="function"){e.require.modules[t]={exports:r,initialized:!0};return}n.length||(n=["require","exports","module"]);var i=function(n){return e.require(t,n)};e.require.modules[t]={exports:{},factory:function(){var e=this,t=r.apply(this,n.map(function(t){switch(t){case"require":return i;case"exports":return e.exports;case"module":return e;default:return i(t)}}));return t&&(e.exports=t),e}}},e.define.amd={},require.tlns={},e.initBaseUrls=function(t){for(var n in t)require.tlns[n]=t[n]},e.initSender=function(){var n=e.require("ace/lib/event_emitter").EventEmitter,r=e.require("ace/lib/oop"),i=function(){};return function(){r.implement(this,n),this.callback=function(e,t){postMessage({type:"call",id:t,data:e})},this.emit=function(e,t){postMessage({type:"event",name:e,data:t})}}.call(i.prototype),new i};var n=e.main=null,r=e.sender=null;e.onmessage=function(t){var i=t.data;if(i.event&&r)r._signal(i.event,i.data);else if(i.command)if(n[i.command])n[i.command].apply(n,i.args);else{if(!e[i.command])throw new Error("Unknown command:"+i.command);e[i.command].apply(e,i.args)}else if(i.init){e.initBaseUrls(i.tlns),require("ace/lib/es5-shim"),r=e.sender=e.initSender();var s=require(i.module)[i.classname];n=e.main=new s(r)}}})(this),define("ace/lib/oop",["require","exports","module"],function(e,t,n){"use strict";t.inherits=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})},t.mixin=function(e,t){for(var n in t)e[n]=t[n];return e},t.implement=function(e,n){t.mixin(e,n)}}),define("ace/range",["require","exports","module"],function(e,t,n){"use strict";var r=function(e,t){return e.row-t.row||e.column-t.column},i=function(e,t,n,r){this.start={row:e,column:t},this.end={row:n,column:r}};(function(){this.isEqual=function(e){return this.start.row===e.start.row&&this.end.row===e.end.row&&this.start.column===e.start.column&&this.end.column===e.end.column},this.toString=function(){return"Range: ["+this.start.row+"/"+this.start.column+"] -> ["+this.end.row+"/"+this.end.column+"]"},this.contains=function(e,t){return this.compare(e,t)==0},this.compareRange=function(e){var t,n=e.end,r=e.start;return t=this.compare(n.row,n.column),t==1?(t=this.compare(r.row,r.column),t==1?2:t==0?1:0):t==-1?-2:(t=this.compare(r.row,r.column),t==-1?-1:t==1?42:0)},this.comparePoint=function(e){return this.compare(e.row,e.column)},this.containsRange=function(e){return this.comparePoint(e.start)==0&&this.comparePoint(e.end)==0},this.intersects=function(e){var t=this.compareRange(e);return t==-1||t==0||t==1},this.isEnd=function(e,t){return this.end.row==e&&this.end.column==t},this.isStart=function(e,t){return this.start.row==e&&this.start.column==t},this.setStart=function(e,t){typeof e=="object"?(this.start.column=e.column,this.start.row=e.row):(this.start.row=e,this.start.column=t)},this.setEnd=function(e,t){typeof e=="object"?(this.end.column=e.column,this.end.row=e.row):(this.end.row=e,this.end.column=t)},this.inside=function(e,t){return this.compare(e,t)==0?this.isEnd(e,t)||this.isStart(e,t)?!1:!0:!1},this.insideStart=function(e,t){return this.compare(e,t)==0?this.isEnd(e,t)?!1:!0:!1},this.insideEnd=function(e,t){return this.compare(e,t)==0?this.isStart(e,t)?!1:!0:!1},this.compare=function(e,t){return!this.isMultiLine()&&e===this.start.row?tthis.end.column?1:0:ethis.end.row?1:this.start.row===e?t>=this.start.column?0:-1:this.end.row===e?t<=this.end.column?0:1:0},this.compareStart=function(e,t){return this.start.row==e&&this.start.column==t?-1:this.compare(e,t)},this.compareEnd=function(e,t){return this.end.row==e&&this.end.column==t?1:this.compare(e,t)},this.compareInside=function(e,t){return this.end.row==e&&this.end.column==t?1:this.start.row==e&&this.start.column==t?-1:this.compare(e,t)},this.clipRows=function(e,t){if(this.end.row>t)var n={row:t+1,column:0};else if(this.end.rowt)var r={row:t+1,column:0};else if(this.start.row=0&&t.row=0&&t.column<=e[t.row].length}function s(e,t){t.action!="insert"&&t.action!="remove"&&r(t,"delta.action must be 'insert' or 'remove'"),t.lines instanceof Array||r(t,"delta.lines must be an Array"),(!t.start||!t.end)&&r(t,"delta.start/end must be an present");var n=t.start;i(e,t.start)||r(t,"delta.start must be contained in document");var s=t.end;t.action=="remove"&&!i(e,s)&&r(t,"delta.end must contained in document for 'remove' actions");var o=s.row-n.row,u=s.column-(o==0?n.column:0);(o!=t.lines.length-1||t.lines[o].length!=u)&&r(t,"delta.range must match delta lines")}t.applyDelta=function(e,t,n){var r=t.start.row,i=t.start.column,s=e[r]||"";switch(t.action){case"insert":var o=t.lines;if(o.length===1)e[r]=s.substring(0,i)+t.lines[0]+s.substring(i);else{var u=[r,1].concat(t.lines);e.splice.apply(e,u),e[r]=s.substring(0,i)+e[r],e[r+t.lines.length-1]+=s.substring(i)}break;case"remove":var a=t.end.column,f=t.end.row;r===f?e[r]=s.substring(0,i)+s.substring(a):e.splice(r,f-r+1,s.substring(0,i)+e[f].substring(a))}}}),define("ace/lib/event_emitter",["require","exports","module"],function(e,t,n){"use strict";var r={},i=function(){this.propagationStopped=!0},s=function(){this.defaultPrevented=!0};r._emit=r._dispatchEvent=function(e,t){this._eventRegistry||(this._eventRegistry={}),this._defaultHandlers||(this._defaultHandlers={});var n=this._eventRegistry[e]||[],r=this._defaultHandlers[e];if(!n.length&&!r)return;if(typeof t!="object"||!t)t={};t.type||(t.type=e),t.stopPropagation||(t.stopPropagation=i),t.preventDefault||(t.preventDefault=s),n=n.slice();for(var o=0;othis.row)return;var n=t(e,{row:this.row,column:this.column},this.$insertRight);this.setPosition(n.row,n.column,!0)},this.setPosition=function(e,t,n){var r;n?r={row:e,column:t}:r=this.$clipPositionToDocument(e,t);if(this.row==r.row&&this.column==r.column)return;var i={row:this.row,column:this.column};this.row=r.row,this.column=r.column,this._signal("change",{old:i,value:r})},this.detach=function(){this.document.removeEventListener("change",this.$onChange)},this.attach=function(e){this.document=e||this.document,this.document.on("change",this.$onChange)},this.$clipPositionToDocument=function(e,t){var n={};return e>=this.document.getLength()?(n.row=Math.max(0,this.document.getLength()-1),n.column=this.document.getLine(n.row).length):e<0?(n.row=0,n.column=0):(n.row=e,n.column=Math.min(this.document.getLine(n.row).length,Math.max(0,t))),t<0&&(n.column=0),n}}).call(s.prototype)}),define("ace/document",["require","exports","module","ace/lib/oop","ace/apply_delta","ace/lib/event_emitter","ace/range","ace/anchor"],function(e,t,n){"use strict";var r=e("./lib/oop"),i=e("./apply_delta").applyDelta,s=e("./lib/event_emitter").EventEmitter,o=e("./range").Range,u=e("./anchor").Anchor,a=function(e){this.$lines=[""],e.length===0?this.$lines=[""]:Array.isArray(e)?this.insertMergedLines({row:0,column:0},e):this.insert({row:0,column:0},e)};(function(){r.implement(this,s),this.setValue=function(e){var t=this.getLength()-1;this.remove(new o(0,0,t,this.getLine(t).length)),this.insert({row:0,column:0},e)},this.getValue=function(){return this.getAllLines().join(this.getNewLineCharacter())},this.createAnchor=function(e,t){return new u(this,e,t)},"aaa".split(/a/).length===0?this.$split=function(e){return e.replace(/\r\n|\r/g,"\n").split("\n")}:this.$split=function(e){return e.split(/\r\n|\r|\n/)},this.$detectNewLine=function(e){var t=e.match(/^.*?(\r\n|\r|\n)/m);this.$autoNewLine=t?t[1]:"\n",this._signal("changeNewLineMode")},this.getNewLineCharacter=function(){switch(this.$newLineMode){case"windows":return"\r\n";case"unix":return"\n";default:return this.$autoNewLine||"\n"}},this.$autoNewLine="",this.$newLineMode="auto",this.setNewLineMode=function(e){if(this.$newLineMode===e)return;this.$newLineMode=e,this._signal("changeNewLineMode")},this.getNewLineMode=function(){return this.$newLineMode},this.isNewLine=function(e){return e=="\r\n"||e=="\r"||e=="\n"},this.getLine=function(e){return this.$lines[e]||""},this.getLines=function(e,t){return this.$lines.slice(e,t+1)},this.getAllLines=function(){return this.getLines(0,this.getLength())},this.getLength=function(){return this.$lines.length},this.getTextRange=function(e){return this.getLinesForRange(e).join(this.getNewLineCharacter())},this.getLinesForRange=function(e){var t;if(e.start.row===e.end.row)t=[this.getLine(e.start.row).substring(e.start.column,e.end.column)];else{t=this.getLines(e.start.row,e.end.row),t[0]=(t[0]||"").substring(e.start.column);var n=t.length-1;e.end.row-e.start.row==n&&(t[n]=t[n].substring(0,e.end.column))}return t},this.insertLines=function(e,t){return console.warn("Use of document.insertLines is deprecated. Use the insertFullLines method instead."),this.insertFullLines(e,t)},this.removeLines=function(e,t){return console.warn("Use of document.removeLines is deprecated. Use the removeFullLines method instead."),this.removeFullLines(e,t)},this.insertNewLine=function(e){return console.warn("Use of document.insertNewLine is deprecated. Use insertMergedLines(position, ['', '']) instead."),this.insertMergedLines(e,["",""])},this.insert=function(e,t){return this.getLength()<=1&&this.$detectNewLine(t),this.insertMergedLines(e,this.$split(t))},this.insertInLine=function(e,t){var n=this.clippedPos(e.row,e.column),r=this.pos(e.row,e.column+t.length);return this.applyDelta({start:n,end:r,action:"insert",lines:[t]},!0),this.clonePos(r)},this.clippedPos=function(e,t){var n=this.getLength();e===undefined?e=n:e<0?e=0:e>=n&&(e=n-1,t=undefined);var r=this.getLine(e);return t==undefined&&(t=r.length),t=Math.min(Math.max(t,0),r.length),{row:e,column:t}},this.clonePos=function(e){return{row:e.row,column:e.column}},this.pos=function(e,t){return{row:e,column:t}},this.$clipPosition=function(e){var t=this.getLength();return e.row>=t?(e.row=Math.max(0,t-1),e.column=this.getLine(t-1).length):(e.row=Math.max(0,e.row),e.column=Math.min(Math.max(e.column,0),this.getLine(e.row).length)),e},this.insertFullLines=function(e,t){e=Math.min(Math.max(e,0),this.getLength());var n=0;e0,r=t=0&&this.applyDelta({start:this.pos(e,this.getLine(e).length),end:this.pos(e+1,0),action:"remove",lines:["",""]})},this.replace=function(e,t){e instanceof o||(e=o.fromPoints(e.start,e.end));if(t.length===0&&e.isEmpty())return e.start;if(t==this.getTextRange(e))return e.end;this.remove(e);var n;return t?n=this.insert(e.start,t):n=e.start,n},this.applyDeltas=function(e){for(var t=0;t=0;t--)this.revertDelta(e[t])},this.applyDelta=function(e,t){var n=e.action=="insert";if(n?e.lines.length<=1&&!e.lines[0]:!o.comparePoints(e.start,e.end))return;n&&e.lines.length>2e4&&this.$splitAndapplyLargeDelta(e,2e4),i(this.$lines,e,t),this._signal("change",e)},this.$splitAndapplyLargeDelta=function(e,t){var n=e.lines,r=n.length,i=e.start.row,s=e.start.column,o=0,u=0;do{o=u,u+=t-1;var a=n.slice(o,u);if(u>r){e.lines=a,e.start.row=i+o,e.start.column=s;break}a.push(""),this.applyDelta({start:this.pos(i+o,s),end:this.pos(i+u,s=0),action:e.action,lines:a},!0)}while(!0)},this.revertDelta=function(e){this.applyDelta({start:this.clonePos(e.start),end:this.clonePos(e.end),action:e.action=="insert"?"remove":"insert",lines:e.lines.slice()})},this.indexToPosition=function(e,t){var n=this.$lines||this.getAllLines(),r=this.getNewLineCharacter().length;for(var i=t||0,s=n.length;i0){t&1&&(n+=e);if(t>>=1)e+=e}return n};var r=/^\s\s*/,i=/\s\s*$/;t.stringTrimLeft=function(e){return e.replace(r,"")},t.stringTrimRight=function(e){return e.replace(i,"")},t.copyObject=function(e){var t={};for(var n in e)t[n]=e[n];return t},t.copyArray=function(e){var t=[];for(var n=0,r=e.length;n="0"&&i<="9")t+=i,a();if(i==="."){t+=".";while(a()&&i>="0"&&i<="9")t+=i}if(i==="e"||i==="E"){t+=i,a();if(i==="-"||i==="+")t+=i,a();while(i>="0"&&i<="9")t+=i,a()}e=+t;if(!isNaN(e))return e;u("Bad number")},l=function(){var e,t,n="",r;if(i==='"')while(a()){if(i==='"')return a(),n;if(i==="\\"){a();if(i==="u"){r=0;for(t=0;t<4;t+=1){e=parseInt(a(),16);if(!isFinite(e))break;r=r*16+e}n+=String.fromCharCode(r)}else{if(typeof s[i]!="string")break;n+=s[i]}}else n+=i}u("Bad string")},c=function(){while(i&&i<=" ")a()},h=function(){switch(i){case"t":return a("t"),a("r"),a("u"),a("e"),!0;case"f":return a("f"),a("a"),a("l"),a("s"),a("e"),!1;case"n":return a("n"),a("u"),a("l"),a("l"),null}u("Unexpected '"+i+"'")},p,d=function(){var e=[];if(i==="["){a("["),c();if(i==="]")return a("]"),e;while(i){e.push(p()),c();if(i==="]")return a("]"),e;a(","),c()}}u("Bad array")},v=function(){var e,t={};if(i==="{"){a("{"),c();if(i==="}")return a("}"),t;while(i){e=l(),c(),a(":"),Object.hasOwnProperty.call(t,e)&&u('Duplicate key "'+e+'"'),t[e]=p(),c();if(i==="}")return a("}"),t;a(","),c()}}u("Bad object")};return p=function(){c();switch(i){case"{":return v();case"[":return d();case'"':return l();case"-":return f();default:return i>="0"&&i<="9"?f():h()}},function(e,t){var n;return o=e,r=0,i=" ",n=p(),c(),i&&u("Syntax error"),typeof t=="function"?function s(e,n){var r,i,o=e[n];if(o&&typeof o=="object")for(r in o)Object.hasOwnProperty.call(o,r)&&(i=s(o,r),i!==undefined?o[r]=i:delete o[r]);return t.call(e,n,o)}({"":n},""):n}}),define("ace/mode/json_worker",["require","exports","module","ace/lib/oop","ace/worker/mirror","ace/mode/json/json_parse"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("../worker/mirror").Mirror,s=e("./json/json_parse"),o=t.JsonWorker=function(e){i.call(this,e),this.setTimeout(200)};r.inherits(o,i),function(){this.onUpdate=function(){var e=this.doc.getValue(),t=[];try{e&&s(e)}catch(n){var r=this.doc.indexToPosition(n.at-1);t.push({row:r.row,column:r.column,text:n.message,type:"error"})}this.sender.emit("annotate",t)}}.call(o.prototype)}),define("ace/lib/es5-shim",["require","exports","module"],function(e,t,n){function r(){}function w(e){try{return Object.defineProperty(e,"sentinel",{}),"sentinel"in e}catch(t){}}function H(e){return e=+e,e!==e?e=0:e!==0&&e!==1/0&&e!==-1/0&&(e=(e>0||-1)*Math.floor(Math.abs(e))),e}function B(e){var t=typeof e;return e===null||t==="undefined"||t==="boolean"||t==="number"||t==="string"}function j(e){var t,n,r;if(B(e))return e;n=e.valueOf;if(typeof n=="function"){t=n.call(e);if(B(t))return t}r=e.toString;if(typeof r=="function"){t=r.call(e);if(B(t))return t}throw new TypeError}Function.prototype.bind||(Function.prototype.bind=function(t){var n=this;if(typeof n!="function")throw new TypeError("Function.prototype.bind called on incompatible "+n);var i=u.call(arguments,1),s=function(){if(this instanceof s){var e=n.apply(this,i.concat(u.call(arguments)));return Object(e)===e?e:this}return n.apply(t,i.concat(u.call(arguments)))};return n.prototype&&(r.prototype=n.prototype,s.prototype=new r,r.prototype=null),s});var i=Function.prototype.call,s=Array.prototype,o=Object.prototype,u=s.slice,a=i.bind(o.toString),f=i.bind(o.hasOwnProperty),l,c,h,p,d;if(d=f(o,"__defineGetter__"))l=i.bind(o.__defineGetter__),c=i.bind(o.__defineSetter__),h=i.bind(o.__lookupGetter__),p=i.bind(o.__lookupSetter__);if([1,2].splice(0).length!=2)if(!function(){function e(e){var t=new Array(e+2);return t[0]=t[1]=0,t}var t=[],n;t.splice.apply(t,e(20)),t.splice.apply(t,e(26)),n=t.length,t.splice(5,0,"XXX"),n+1==t.length;if(n+1==t.length)return!0}())Array.prototype.splice=function(e,t){var n=this.length;e>0?e>n&&(e=n):e==void 0?e=0:e<0&&(e=Math.max(n+e,0)),e+ta)for(h=l;h--;)this[f+h]=this[a+h];if(s&&e===c)this.length=c,this.push.apply(this,i);else{this.length=c+s;for(h=0;h>>0;if(a(t)!="[object Function]")throw new TypeError;while(++s>>0,s=Array(i),o=arguments[1];if(a(t)!="[object Function]")throw new TypeError(t+" is not a function");for(var u=0;u>>0,s=[],o,u=arguments[1];if(a(t)!="[object Function]")throw new TypeError(t+" is not a function");for(var f=0;f>>0,s=arguments[1];if(a(t)!="[object Function]")throw new TypeError(t+" is not a function");for(var o=0;o>>0,s=arguments[1];if(a(t)!="[object Function]")throw new TypeError(t+" is not a function");for(var o=0;o>>0;if(a(t)!="[object Function]")throw new TypeError(t+" is not a function");if(!i&&arguments.length==1)throw new TypeError("reduce of empty array with no initial value");var s=0,o;if(arguments.length>=2)o=arguments[1];else do{if(s in r){o=r[s++];break}if(++s>=i)throw new TypeError("reduce of empty array with no initial value")}while(!0);for(;s>>0;if(a(t)!="[object Function]")throw new TypeError(t+" is not a function");if(!i&&arguments.length==1)throw new TypeError("reduceRight of empty array with no initial value");var s,o=i-1;if(arguments.length>=2)s=arguments[1];else do{if(o in r){s=r[o--];break}if(--o<0)throw new TypeError("reduceRight of empty array with no initial value")}while(!0);do o in this&&(s=t.call(void 0,s,r[o],o,n));while(o--);return s});if(!Array.prototype.indexOf||[0,1].indexOf(1,2)!=-1)Array.prototype.indexOf=function(t){var n=g&&a(this)=="[object String]"?this.split(""):F(this),r=n.length>>>0;if(!r)return-1;var i=0;arguments.length>1&&(i=H(arguments[1])),i=i>=0?i:Math.max(0,r+i);for(;i>>0;if(!r)return-1;var i=r-1;arguments.length>1&&(i=Math.min(i,H(arguments[1]))),i=i>=0?i:r-Math.abs(i);for(;i>=0;i--)if(i in n&&t===n[i])return i;return-1};Object.getPrototypeOf||(Object.getPrototypeOf=function(t){return t.__proto__||(t.constructor?t.constructor.prototype:o)});if(!Object.getOwnPropertyDescriptor){var y="Object.getOwnPropertyDescriptor called on a non-object: ";Object.getOwnPropertyDescriptor=function(t,n){if(typeof t!="object"&&typeof t!="function"||t===null)throw new TypeError(y+t);if(!f(t,n))return;var r,i,s;r={enumerable:!0,configurable:!0};if(d){var u=t.__proto__;t.__proto__=o;var i=h(t,n),s=p(t,n);t.__proto__=u;if(i||s)return i&&(r.get=i),s&&(r.set=s),r}return r.value=t[n],r}}Object.getOwnPropertyNames||(Object.getOwnPropertyNames=function(t){return Object.keys(t)});if(!Object.create){var b;Object.prototype.__proto__===null?b=function(){return{__proto__:null}}:b=function(){var e={};for(var t in e)e[t]=null;return e.constructor=e.hasOwnProperty=e.propertyIsEnumerable=e.isPrototypeOf=e.toLocaleString=e.toString=e.valueOf=e.__proto__=null,e},Object.create=function(t,n){var r;if(t===null)r=b();else{if(typeof t!="object")throw new TypeError("typeof prototype["+typeof t+"] != 'object'");var i=function(){};i.prototype=t,r=new i,r.__proto__=t}return n!==void 0&&Object.defineProperties(r,n),r}}if(Object.defineProperty){var E=w({}),S=typeof document=="undefined"||w(document.createElement("div"));if(!E||!S)var x=Object.defineProperty}if(!Object.defineProperty||x){var T="Property description must be an object: ",N="Object.defineProperty called on non-object: ",C="getters & setters can not be defined on this javascript engine";Object.defineProperty=function(t,n,r){if(typeof t!="object"&&typeof t!="function"||t===null)throw new TypeError(N+t);if(typeof r!="object"&&typeof r!="function"||r===null)throw new TypeError(T+r);if(x)try{return x.call(Object,t,n,r)}catch(i){}if(f(r,"value"))if(d&&(h(t,n)||p(t,n))){var s=t.__proto__;t.__proto__=o,delete t[n],t[n]=r.value,t.__proto__=s}else t[n]=r.value;else{if(!d)throw new TypeError(C);f(r,"get")&&l(t,n,r.get),f(r,"set")&&c(t,n,r.set)}return t}}Object.defineProperties||(Object.defineProperties=function(t,n){for(var r in n)f(n,r)&&Object.defineProperty(t,r,n[r]);return t}),Object.seal||(Object.seal=function(t){return t}),Object.freeze||(Object.freeze=function(t){return t});try{Object.freeze(function(){})}catch(k){Object.freeze=function(t){return function(n){return typeof n=="function"?n:t(n)}}(Object.freeze)}Object.preventExtensions||(Object.preventExtensions=function(t){return t}),Object.isSealed||(Object.isSealed=function(t){return!1}),Object.isFrozen||(Object.isFrozen=function(t){return!1}),Object.isExtensible||(Object.isExtensible=function(t){if(Object(t)===t)throw new TypeError;var n="";while(f(t,n))n+="?";t[n]=!0;var r=f(t,n);return delete t[n],r});if(!Object.keys){var L=!0,A=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],O=A.length;for(var M in{toString:null})L=!1;Object.keys=function I(e){if(typeof e!="object"&&typeof e!="function"||e===null)throw new TypeError("Object.keys called on a non-object");var I=[];for(var t in e)f(e,t)&&I.push(t);if(L)for(var n=0,r=O;nsr_nx!QIeS0*+0Q0YO+_9L`#Cle5)z)m8yO8GB-CmoB;<5Vbl{Wp z*1;6u7pjex+z!8OE1d%XhVryXKcq0gg_Q3?(ve)8r~ zo*q3pxo(UVN^z?pC#(DT?`!FNhW?A#SM;K=xe>0YB3_$pzvGVGOuu7yRLf5<`=1g; zl{M(%MtgbqV-z*l^%^()t@(IMN`knSW=Rxhs=vK?$`;K|-2W8#XrTC2cBV#s+m>n< z6Co%0?9shQz?hFF2hYE&@5q+!r;aQPrFTATqvC`EPq!SMWQm&#nBUMX<-Wwb7tW4p zwyyEMz2=a5xI?}qzxB>x!k*IQoo*_WdAP^JnJa(C-&5l za&#kaw}#S5B_E6t!d6uV#eVaMroz3nV*k4BNs{o*obm7im4> z`X)6{ zlY`t7mse`e3Qs)y(o4X~me@zC|M2p;|oVUi;rA%R#EvgYexEN3{*hU(YKqFT!N zQ{AA`abP`4*gX&rd3G?o=?Qy6?TDUE=DYD$xp$~GswMK$pfCoQ3U5;1mM*F1*skXGv)|OtGUfw~W>Ypam)Dtsp zkogc_Q&SV;7YujUb(1B#ybh1%bh~02c~3fCO>d6a;j|LN_Byu&-PX>hm-^};f^%iF zN@e~I=Dh;v)1HD3_*yUIGYrbarhB;73#{Lb zL^2z}89jk@3cmxGpEFYTGBJc$2!A)o221W?@=yCLc9W9&h}NW<(Viw?Xg_D51EtKp z2z(5jGAsE(#2yTJ%kwOpTFnVuc4@_InoIBU+gtCpNZk;7gt%2tIiONH2NjELZI?>B@Vw5-t%uQCjCr z)~CHq>%KM{{D@nSKO>H`7B#9UZ6ObT=@ptIhcd|8K%*(j9S8g;ept+;_m+{g3Oa_L zg*Z0Y>iV8MDR|3UUEMubrz^5!LHEj8P0+i@vx8P)t?llfgTG~~3%u_}2lz$(-AgF3 zABO*7{zkEXV(KqZsElnGWcg5*(^-}O7d&Hp*y|2gfU6?z$&7Gx8(q$^p{O%3-KUB7 zw^c1epv?djmt}L$%8C1x6esv4{{{FC9N=+Gdv1x4l&Zpu@}xn$-kxG>C2$zpSD9lmJrR9>G9D)+`gZ_LchSP^uZhi z@9pgiCL>R)z&DqK=Z^R)w)pLxRU9kzH!eZe1330PTTHskw5&LiDp+}>3=+3jiQ}1S zzGn;2pwqtkrUJ)@F(A&{K;#X+_ch_&!53F0_!St=hmqN}_~-0PEEbbCcDG+_DI1f9 zuz7f(S@+Ns)#BY|E2Y;UVg$VLtnZjq7iM^GbTP@ow2Ltn-~MTXZbBKL-r!=RBdbtZ zbL&!eS>+j@nOl&5wu64_5P3jvuMumJOwp=}r_CIQy>%0QukCXy( zNw}63>9%6J#}>Tz-mYdWVdW->5L`Pwl1;BvXObr_@DKJJT$m=NF!7ppuzgH`VL-v{jF2;&x=ZWGJLqo;d( zTB|YKxb6{JA=9mLs(w*`us4>hzKCmf*Y~b@m$%cE>zqS{H;O|r`LiSKVOv0$$xV- zwekXC@SiT)yv_+7()e!K|3#&Ijmc^u7@f|P^*k`&w5Z~gCP z|Bmvw-qb_5%uDJ|w86S_0w+qb))GyQzx$FAn5nu}S0UtDgvpQGM@!Q3Y{sg%Xy4Ud z7GZ6{H2peG@|G&wdmBk8(kHB%Ju2<{Z-93S68LN?w28(3dFV~WN=b0sM~V!pBw>=v zAeteYwtZ=2X>1;i_IMkUZ_DT+1MoY55;9_hqbirY-jCjvq z`$V!A;RLWhIXc@oxlEx({<(CijePv8K7~+b`QLWQ$wTVKJvXr6rX3ocm9Fu8$&&`~ zu;{~9y3y26rIo|ujo!tCmbt%2d; za@2fjl`XxYcuKC3u%vZcE?6%UIwF3q;;!kNaOjvdD_R21(mUujI}%j`?((9 zTc_~dOn&(SOpZgi*AYi4@^(WIjNFd8U^f$jQ-stt*RUZlm ztZqgwbgiKKuUFk2+#Mf(9&t~wv_Jc>ee&7qUKGqG^=T4?f#LYaOdOv>la4LIa#X-x z*&O?9RwHm%LrqH40nya1vbDEsW&70K?fXSRB+z=2v+<62`}3U;5;j-VFesClV0Eu#W*|qhwy5mL<|Bf1xok{wVSKw4f<+u61@tIS9L40;}c2lk%_E zAelRQ3~TDqz;!Psr%2C6VAN^d$)C_7V4Wxb=7(snRoIGCN`0DNz z|3#@G^T9E`>p(82_E#r+h1&^(B4E)p^$_7Y<5CIEeF7@&%sF`-BxqDlPY80FXCZgv z86qw(E0^D~>54!W(6N^c)X`uUuit?^3^dKaudjM9du2^}XN#8fI{wcU&XNvi00Yy- z(cabTlYROFZ!E>A%eHdB`|ja)+-oO`sLh3ohDk1E%i+xOF}4|RY9X=VB4DLOXO=|A86ZCY?QCR&b; z44E3`CxMISoz2;v{V|`u?$SPx*n#nK#5C=kNyF(?lauDAZ_6Re zNW^$9YNC)Jxn@JZ`j=!?#0Z1^`lHx_VH5i zW%ztSi)W6P>4g-$GawK2bk~&Uqr>ia@Nc&#kCXlC#I9?MwxdcblwuE_{|v&-q&DzO zF#Y2}X8H$#UiIu>0~T>(PXCnDSSN9!AjyTEo+RI(8J1x+*J;n2j?oI=Lb-M?=~}gR zBEBa(0o~p%gvX-=#<_q%xyYUzc9V82;rO8L^zKnEl>q1c zL8+noUzuVCPt%~lgJ31PWFYJb-rPT4{rra;t!sO-IBIX93&7@S+xJvh}cBkILM2}H?>OvSdYjPg7lx$ zsuiAyL` znMJzvS-u6ppZt(yEFy1t$DM&VjPm+^AZzZ8|gwb}yC4SW4G|EfC8_73#$F2*eFKUKZA9L>X%`%;u&*W^t$9lZZNQ}5QEv-YC}Bhc@>+rf z0%GIkH)^>zQwYZehI9o(`}rMB&dzBLq0ufX$n`ZgvR4hh$6Whv(3)68AaHj9AXria zT#n@DN3BBEWsieB$DENq((g5YJrjrCqMS5>KV{^`$!y#->=%>uaJDrzzHV)?+{Ekh zq$SOWMVGv7CAoB{)j_0J6nbmNU>NQFlNxFz^lhAU56d12-51VhYRTGj#x!*I7y?BS zi>oT3YZ0kyGJW?#WRyyPHHzbtXJ3eICmDiI0YlSv4;W@>mKja+>u8!ryD{xy`8?_7 zfZTN>fXJZi#tvU(wANo*Zl261p5+lH%Zu&jpjkMOZKs)#)Fqan65i9gYJ?xOfj1wO z!JPFv}b=Q5J zOFjUP$Sbbp5rp{Zov6gMtF8you3BCzA}v$M_;sd5YvBz{n~x0Gen&u(0NHDC9AC-} z(S{?{YpuW05hBdbmoo~+8i8d=1U(R2hxASP@NWRS(`;!q9-&%gu1V95^HA@1`O-lk*}^!N9$Xe zwQ>lIWWyo15D6Ob2%m+kWMxXy%Vc#1BUm~>xJFfVqYZi@*;d2$vd!l5SC9$4Ytw8?l=pHP`eAqP>S9pfbhQ4gXXh0o}-gxad)G59)*uW%v*55aDw*F{9mJJpEG{oR?Nzm6l%?>|Sw z`yq#1lIIwqDfeY-9zd$B8>uz&FU_NC*TTP+z2L3nQ08CKw*dB?nsT{;hueA-S=63i z$5|k_EM&v4>?~LA9Or1{OA3ipKvDb$Ke_hfLaJmg8Y6{`7h=LmNaiiw(%$#s=Ejrq zI#o{dmZ_M)77n8U2tAqpn5#xs-H-Fae>HWM(K1r_+KIga*#0F`K?OI`l9&8p_d=f2 zja=InO#M*{Lz@2T6T!nDCh*Iu3%#Lji(W2(d)355KMb8A7l|57<@p{%K&DYt^q4*X zek02y^I;PzfAJpMuWS{J+T}|~bvjr8Idk!A-Oz6f1>(&RzqPx#FXYM=gr{gql6c^*Pteps$V9@QEpJ{d7003w1npcx& zL_CY>Yt<&})ZT-KLQbMthpQr+ovi@iSi{{WhE~Df2E8xr7Dp+;o1IeFkOj-5Qe7NS z+1cCK(qae`BK&)JIgkKxN*<%e5|j-}`SEd@5-*mg7`CTIBe3l17#;8(l#T7H7W$7b zD}+A8v9Y7B;we$|nzmrH~uVAB|9Ta_~!Bo0e8khN&=<{<0#r8_eoQgG;vXT zrkLLT-rPJ{(OKyTh=FoJ`46;W%Sg49lbE=j`6o_UU6N==+xYEjNazn3FzC^xbuDiOps^&69Wq{O<|8eJm)` z6u0-x)tZy)ecw-09=VXQoG1-l%z;$|l}QXRfc2W7QMb);4TjjqC7>qrXF}@nsf3+t zE7~`eHo7aFmTCcY-+TuVgtW@7IIYp(`h3gDn4;)csuTi z?$g(D*j6Me4o>+RqAXxL>3spLit~dfjvRRDM#R*A4us7@qEw0DY z=4T}QFU@bTKJF^X97f3v+EvbHPSB;<$}8)MajAa9sQrYvh&NzhLFvE0jz@};X0B4*2v2^7P-9SLDjsQKR?CFs|w z$$1`DBR^-MD?S|>8~$d^#*dJ;(PXA*IJ}kR>zN$eP=K9>9MLz%GFe0KMBL2S9vd#U z>Hp`0(Xzc;y}L=CMp%XlWyg^_f%uKf>+{B!P$wRJyBF=0%!KAcPqVHTjDAS~WKD4} z0NA3G?_N=>MJ5VBTql~qO110l?|s6U!bW1vF81 z@cRfv{4r`ijr23c?}*tp`=Gfo-nWO=D76e^lW&6P5m4Y6ABukiruMnh@ayQjbo4#7 zRta+LtDMH4#q!}6Na!o5ItXAFI5!sCljQ)Uvg2+CRWhZPd!6rH1cV5)<@96ElE^NT zqDiYpMO>GhaDWhpTRXPBye)j{tD9C8QFc(WXz=BDc&G!~diP7>oD-b*Ymai`zsTr^ z$@>FN>78p@{MRPx=iQASQ_^000Dd`n0Eq17&sEseV{m%+Z+S=a-PP>ChM}8FuM1)G zLP;nu_ZX{(%Zd0)PxRLZI1lgqdNlFIsmEadBH!fgZscAcHVh-*E1Tjos|ZT1!9R|NhV>HFbkUyN+GstGFjOZW{gbP-D)efjv3kQ z6uc3QBl%hWxBZ!@zYV68n+RHC56rHm0;)u;f}i-N&fn@8pIkIrL%olyM@rvPggQWQzV_aS18^?l%7)Fh9@Wsj63oa6Z0#2Q!#e7v z`pxVt^SLGjAh2(~CmcreI*%>g1s#eVbsmJB2V8e^w!X@yI6UD;*$tZwU5`x=r5Ec! zuyjZ3&j5d%F0|Y3%Bk^+p3b1`z$QXlQr`7U_uu@xgExvPvc%d!?{ zLo8m->-hG9@Ml&^?H4#f)PZ)Fd@yD(wlct9^qS$$=4N(%5#sl;n7%FnM+RN!uFx%0 z458nzA+V;<4>wD`NY)FtyohWJt@#$xF9BT!;1YEmKPFZAtO~<>s~uh-aRMrQO24_2&`Z}@NIkAs%9JS97^73-#aLxHK zTijfb{$v%!w!H7-4()(#dqhAfxXpZkFR!kcn}18#*T~eiq@cZiWFPE_ZBM^B3x9Je zv_vxb)Dr)9C&?*@zFo8)KkNbG_+A`*JO%(PKmsul28FZ2Y6?<`2<+mZ)s8zqbh|m5 zVabVzoM`o_#C=+f4Ciuv#Rh{gP^xs_J9IhyYCQEQVSjV^S!p&cx3QnD)`E<-lOxix z?gi9=E&A@%mYNiC2 zH;-WVriW+6;*?Wp769yxqC1w3%9MvWef$$8eZN>SEClw%v?n$uYaTQ>+Q(QrLcerlhmeNZEXO&~1@nvN#r8k-_c0hW#^gX@u!3 zmfXYVl43ZY#Czka7UVCNzugu8$Tj^yjlll3S#fSxktS26H)}OK8^@N$*(QiR+PKx+ zgat<~fg>LUfaV+VD4z;HHV#j1*!1Y|)h};uaI|))M?C{VQ~m=745Ze{-RTkv4h{;H zqviq5kD`wDE~5X#IB_!t)Os89y`gtLUF=aG{NT@$=ydauQV6K8x*{+!Cv41POcjOo zo3idXE;x~02xXIDJvIM~jlcaGbV%LNw55l`4wNe~@7y2dJd&g6ull8Z>TGXs0zWq+ z_!B=!J9!J4k{NO|A5UPi_KZ-c z{KwbLSzMS^-@y!?AG-*UhnOvP%NB%;%Kia;$f6hHd_bT^K{tecK0@l7X!szk6aZ5 z@VZG1Yq;qzN&~8BL`D-}we_kSyXvm@&H?T{aBfRe^#<=0xos30nC{L|Xj8>%=wv{+ z4cIfHB6-!w@UTHxFEN^e7RdV8rrv*^dUU~(%hP+f6_b8G3Zx1p^FIV5-NY?ZRG{-1VM-&0p@)ukoF%5AQPW+OFTDZCJ#fc&e&@b#zn+a4)6!MB5Op3e@6I;DsmDI*HzCL<{&(9VB<~3WN?36O+IKz_-#g0dhga() zU4Sh;U0>_fIrWK-KmPFxASgoJoXg7YnIYoz?f##ZBsj-_Do#e?wZ}tgNAGUXKVfRi zn5yc!ny$%ZubnE~zNyr|y`iXP2$&3GPXF@MZYIcJR<;ZZkq=7gtPM5BAz%I+RLAEgr7lV1CJTv2?vsZhFg$tt%EqXk6~=#myIQGTz(Og5}|o-CD`%3X};eH{cc4iXSRfTj`UXrz(64^Uho;}|G)ZdZf42 z^81pptrc_QM6LIx*}|;`b_bX0*-6mLVIY-#g}QXO**rCP+z4m>Pj|IM6@ZSb3E3> z1oSj$X2#gfj?*n$xw6AT&jU07A`~OEs3E|Z%*NptbA^p~odY?~mxqGFWcI$y2VRo& zkHhI7R7yZ0*nXIrNS3_X*^f8IBW}v--eeET-v3noO#m1pf>DMqPYQ29fPlUm<|h|( z_l9$q`m9-;M@IOHFH$v^owl7rwux;S(oEe@LauW$0AkKcWKJz*m+%CKG}*)2el*Iy zM>ZCboXbC}kE$S<(FXXQKOQ}Gz@$diSI9Pp>QI9~5yFI?AB?gT`0tqIDO&*wx(F2Q zoOqvop>nvC%EvLH?1vdJH}w6{1wb_tA&{ZGh)Jd?|AJ3C74wv=VB)Io-B_2&HQNj9 z3SDoCotGq+dVVbSaLX{K2hL54Q*PTWm$ldVJe6>P5Jn9lPaRZq`o4488cp)|*}xy6 z4Y0vXBCA_;-1)2X-9)Gz4q43cg02YHrnm6k67ed!51IKuNkX+B7H<7Foon#W})!a6+R{NL!{S8hiLl1WEeJI|B9;>n-g|pXvMDMeKPke-Vm(yX#K zuqf6lUYRs7gC;d@wx$(BHN~sS<`j{oiMMZeXJ-VsuyU!G4+qA_u#Hx}s06Hi86wAz z2wVTI5}PAzf!ZgI%1IR*ciEMgd+AGec{%6wQQqPp^LM{&bkw_Ycsl+&Rw1I~x9me3 zX1QH;=PC}Bx;H}F#$+=2_OChYy@$FjN4v@qux;X4SjO8q!X|Lc4Mq7Mli7q!&3rPl zr9;@EH*-5{upzmUgZE+NHKKAhkEUu8Phs-o2+HIl8{v2l{`>?4c;b#{ma#_G*0tXE z+{LoJ<7&z25n~eRk}#Eq*G?P`ev-N&B1LK`*# z50PcEuisO;(1JXX7f4)XXz!`u!^0K^zfR(dsF3rFiLUnTsZG!LwcqgawqU}`CVJ)t zL`{SEZg^$xOr&oUt0BevO(j+8;EZ&$)RN=WQ}^|kwtdUJ`zD^~tKYl!*^}OyEh5Er zjMiwH6`U(;(Y6lT?3Dc>;axiO8C3(|Q6ngU;6vbJJ%O&H6Bg=t2h)W24L?1Cqr=12 z&9VynV5F{13DhH+!jGv)cP^2S34o|y@+_E=G*$*-Cez3qDw3?cL3Y`9m_mtnL%i?c z<;M9yEDWj8faM(QKkF2s&MAJXIe%F&fpIjB^}=wfjLA)7?F)QF_Trk@yL%bBYB$bU zjc-^|>R;SYbXwA4XX=R~yRwqY6s~O=U(UPVPrZ=G%eej9b9Qr!?@9VISD^yuU8p{d z&7%T#AvEXI+;U=_sOIG7gTsRnUrXl1eVyeyb^Q;Pw<8m1yh~_c^4lS_tE#ax1j5lP z;syq(A?M%OQ8CC6<#3fC*1hoG74Hp$!_of3roJIlQUXeIa(=BNt_G3sDRz_y-yshe zMw3$zo5<+LRHG-w?28VDCj2`z%H`r&hX(Z$k&APA!nEA zDbtm+%lMN`)sHDmGKV)Wa){2$Lod&T9mZL2BxUX&W7R@Up*O2!*c^k-%V#>WHo>EN zZO!v3O(8v;VafRVdBa2rr8m~OO;A3@s;bEUf~v(63a8PeR<55GOc}K;)S}w{g@jfn z;P5*RQ-Vm+A=Mn1jZNty^2=&VMSP69?sU>uab&$tol1d1gs*%@#e$ap25&Ji) z3TrR~6uAd&%BK1&-r`*?ptpcJO9(+Aj3HOyR1(M4 z<%AeDl0tN0L^c3J3U*Krq&O76v~4oFgcy+r$$(=X&hU4Y`onw5`K(%OB|pN-};$)JcgK$sz};*cXqyv>@oN zNllZ&cAkunS*okfLZj}r5JlxmA1d>KDJd6^VL%crv2uwz#<{)Lkovs&HDb*;qAxU^Eny^7&lilpt6xNF?H?_-= zorbr3)O9W0{$s5%E}PBt%~})JFZcs}UTe(H*4o@99W-C==AQUyx~e<4cQg!;i>=XCf-T*|)x_ z_i=39?~|gOyYNUECB3kr)4~QASk1w++xp}M|R-MuN4MC8W87@<(i(^>fb7L6Z8Kv>Ro3!>0ERZ>N1}Zvv z!AwRmKHC3L#882xM3@CVWZoJUSZX2EJn@Q+6Xw`Xs98Swen5|qut-T@_s?Smor zwXazKx!^DIA94Yp(Xdw@^7^`CQA}9LC3vWeH2DL?$4=Y|tx1wief`052%^@*bu9RG zbjv2!t@zKefhk%NUjAQf*=CN2bN|&wM=fJ729_?GFL=E9rm+Q&?i54Ei0Oz&2K zEZSyXRB=t&v0Tv9*Kapp4z|26xmpfV@meNx!e~VPk*q67FT#gGI^KlV4es>?6@&u^ zRC9F@Gno{tD1Z=~xB{p|9pp1%rN`w#QFhI&?Z z!Z{2daCqUiQEZLJ6=uAKNZu)hH|4u~evhR|-oJ;wy6U&*Qe3`NE>vMq1FuqVv~3p29F56j&WYrL z6<2#~M{R9HMFQ-hOl0-^Z5Rzn6p$p!&ZrHS{P`4hV`%8UWjs$#zi~m%j=I4XJ=dWa zT9sdGR@$G-4^62+gW~H|LJmK?@lfVO*S>Y5&CU{$E%-*k#Ur1zfZJ(Z8S*_a$&_Vr zLimo)7en@8%bfBN*|DS4hrmCllhky)(|XPTpsw zn`T6MEeE_eEl)dW$b1YQq*TI;t4Ky<7O)mU%=*rF{+^d1ntzwKDcYej8+7Z$(Qj6< zgDZPr*`A1u2L{693}uK&^DA-()qgD|NgNC;1`sAM9>x;90*p#awbzD z%EmWi=*GSfDIzqUq>K^Cf)VJJ%$mHcu)X{`MXT}Awk%wO@U8mWed9?R7F~P~Q?(Y? zDvzMKA78d(mCY`sKUvO1#?ctb!^Wt7Ryv|Mq6{HDli@{8z>OaQ^a2`~re?DhBXY+a z3*zK6?a(!9E~!F%f?&Qb{pYI47! z+>@`&N$1a&^?s|ZE09p46+)7JE_$@R+oPGT)38_Q@WQv%n_oLrV}ggEgAKB7mBA+9 zFwO=K3ip%?toHTnp~8`mCI1?hdyi1i3ZBm8`}bM|^?D&c3&pj~@J=NhyrP4?{M}nY zcGj{b&;TGWC)ja1q32BQ45SyFj#~-p`t2NFc+*{eqk4VTFK$*|s}y_T^e3+=@BgFa z@D&RB5YN2b%(jW0#DC#)ZpP3avoSwLntFP?Qw@zBqXPmiS==`JIUZd1MKk`E(d}lf0iqSn4u`1v* z{YRK8@M@zXtS6KMp%?+GtuI=`8sA#yqPBOsIjAynPVy9ZIdui%b{y@4cxh_BZP=yYmG^iY;FiWEB z4s0QJd~%+_kd;8aph--$=-F9(%%jyw@CkFj@%BTMIJB~5f6{2WNyG$HL#K$zp{myD z;dP!c>oK)3;4k9%=#cpuF2Axe>KNFBXhgaA`uKO-k$Qq^6Ci-L8(0Z7Uu@srmt4Om zY3^pEhs$C>I3nzI@}6;hN7P-w6y)vAux3r|fOJTbk7`-2HKz5s7%{nJc*WYUC)>M{ zFRoOs6BYUO1kh_$ZO)|j+xtj3U2>3a3oC~YK*GUZ>#~Fm{?n!*ZF_H3N-KF8>I6GTRc8Z^+=>0YBZ#{lRkfVLYa($j#?e(4< z$=Tofq{`y5veDxpC-!giBI;}~#cU?5`k4zTO;|qqpPq&4V_E^2h9v8tp(8tbosw)zrLf zP4BMlksUE6TQn>rkY`wQaL=MEY?1!0+yUTjNT?l=eufLuP!$k~XGA0W_x;*t5!-~x zdHaFG#YPO<@rzw#4TUc1EU9egQD-Io=-nrgfFwB^)5rTl>vH1+ayrZ&lrteV_`25C zZd!BP_gr1O;7QVi32!rwJxk0u43m_s*YB3T+`XlI=_BcO6*1-zM#ir!!=_dAh`9h% zdmtk$;!~oX)D}{D-%{B)Ja8O5WcUE%4<|3T_`oil+++ElK|qaGX1)|c4))SoZ2yt{ zJ*ECamuJBHW;oy2Aq$HqXBDf`r^^GqXUrT0=9CX_86{HA;2{2~_R!h4t+IXv^b&=f z4Jj!e90U8Z9db;0u>DnX0*sIE)uDOHyYwYhEMSy~@->)mB730C3TR=nY+)ABF%H7Y zSHwI@Hf{9gB5UAszUiP8axZj>H_?3h-HHlzX-pw0ddrWleJc{CHIB*(e(u1ho#&&oLDk1sUqVjRVDAC;Xy!l-+w&!huF6C?3yi-6bM`h$a>%S; zlgrv+EV|T(G}8X)j}1siP%RTn-3Ui~X?#w=_GD1f?ml9%MqKQ;4^2`V63 zDCrlzW{?;gVeZzmC@~F;Q@;H0nF}0<&)gV6p#b_c^Fbu^%%!pPdNM|J`PyxsT0=jb zeGGs1oXS64qKwA6%R`F|ta-#u3{bxXG;!WlP-oNZ_l@zyW|!kqYFF>Rw>oTRZwA1K zcW&`li?~q?iw*W3tv{1_z3y3wNiNbVc2IZNGg|UcBBWZ@kK!cu#^7FB9HTwb-Z5P? z&`@M2E}qb_S1MOf!I|ct%y`-u-(lTZj&knNnPI0A@H#0H4~X;p_Ob!_4c7aMM&}7Q z4%$U5G=SnpDIAKIW;ZFo4(I{*66EPc&#v)Q`9!GY@-*$Mo|3LCseer61#1v;T--It z9F&-s31F`%O7ITSL(a?$6?feH67x@h)RyKURxTR9TDz>QC){vw2%9zgAq(?cEC+j$ z=!c}vnEy)CBZS(1o;yYhD6d<^>E&2%@xERT*ZB5 zx+X?Su6041=_l~m=SuIjGtk&MAT*v%Cq!9~Tdx$R;S1EZzpjRqIpZIqQK?_K_nz(| z6f303-v$_~KL$TE>hQPPbUyYW&v%#n3C?y%c;+E;f?F+Vp4H`Ba^|69wKJ(wtCZuu zI_>ZRZlZoqMdAaBpuGC#UU2@E6Tnw7vX*}T4yKfa+UBHFV^`UW|i4lsD zS<^PwKQ|*A{0lVEIdc$8Z!PXee?ZqF4Mp@3BAOZ86I@;c3_N}b=tE~)moVFkuZN&T ztD?}#UbTg=8j&8|jfr+35*%;cLzrmoM~`IvE^%PV^+sY;lH3y)=Jw z^7qQ1HgDr7QB412j1&|CUxciC7)o(mZj9(e`AXPH!3E^wXTDD^_Vu2B`X&_XqfQg^ z=6RLfcQ}(n?fbo2EcV}D?6jj*#^j=Gq7NKG1)1xDu`>#v*8icekn?$KRwS>B#}+Hn zfGDSg^To#@bO6^qn9(wSk+|J32YA?AJb}KE0DGMfT?ePLEbkzujU%3VQ!VkS#UCE- zoCk)0c9G$#4NZmhnoctn(or}C@77O6a!Ng_&cGsJo?L`qxT)%7hM9v zYy`Hv5Fk2CZ3(7Fl$Jq_wC=+vOej;xrV|_7aYcax>+pO-!oO($<32Z*76M4d zNI|JqBkj$0D)myyZZfQD#g?iG=#IOlW<}j^0g9@chj*q@c>aAQ;{Ox4`w6X5g5!L5 zmb?U(qEr zSFm?KfK;=-njq4vx|>6=4}VaPM;-;m9hrF17(be8DArsXmffiNB17_)e&cBS`rs$t zS~al9$CNeRoAzB$yh&JE z;Bqzd)`*k>t%!JnK|D5 z>HAtnUV#S=Z$kfuXafEi)_!SS_iZObXSt5|{8}1;L|pFalW>6SJ{&r-{!NPG3{?3& zMjX>|lTMWp(1T@Y4y*;clwIuOdw*SNRc)*Cr43l?G&=8V^*w*BCH{1?^!P!C7EnQ6 zn|7lHw7H9egD5`yCNe~8S&&`6oE2ZFK9vwxe<|z}CFjuzg1-j;WuEC`w&x3x)A6H@ zaF$tM&Iiy+RlA3cp%EbeqvOw;%NpfhTi4358r zQ+oHjgutrq?wb*ARP0r;sQu}w#*0Kk5TrC4Ju~XSibODZ)zMD1+eDe&V<~;1iiluB)B(J%ea~7#lsnM&eB}xX5YSLPA{Gad(_Rk(7T?6KkFJ8HgOnQ&a8Kc8={rII4{D_>NDXpDGu)R5>`SB>( z@{+`^W#kM_XG!_V=Ljb$>jGx-nSHs7)|XFQGgPQrjuyhH0|kU9l7AVVtHUUE%g^vl zOq1{4DwGJ%FjiwHDw~m%J^;Pi+f@{izay+7jC&}XbOgB52Nlv~2#U4|^h?3kDoVO| zJ^j;QSyXnZOa3`yl6d5!wc~)&mC@h}<{-jtLc#zCX`zPWhA|_6{CUQ|Ne?l?_K%Rf zTa3mweQ&Q4F!M#$ob?}LfaSwGD@~(^UTppq99gYQfR^rGOGhx)|Fm?l2z!Jv0hR#hYbO?n}6s; z%)M&r>JAxYZfDL1A`=QA;w2DkJNQExc20@lIoNAK1FmE8syaK~nN(cE3hS}D=cu7X z>ai${7q>O9vvfV!4(>jjka^y;I6_4J4_Rj!7iGJ}YdWP%8V3Od1Vp+K5QCJKR9d>b zJ0wI(x&($snxQ+SySriN80tKO?|%0_=X~*lAN)CWKkHuW`d>?2LPDXIjz6PdR@dA1 zd8@Cu>m2b~uT0&M^aL)J^#A6Fd8nOb&Eb=XEfP6klVOn|4;q??fpW zo`k(ebTKH5t_I)JiMF+u9jG`uKPfiN6a%yQD?`b8+E4Jg4@_qi#%aAww|9s_M+?Mh zyv%|`?*EBm9Bk0xp+;P0K4pClWSj;%IM3jy3etX~g#QJp^Ol4gaO?cV;`!b5od@Sw zi>bJb-q}y?-SX(`&~q=+Q*6f)H>G;#^z3$R*BZeC8pX6i_*g7rp4Z2n83Nm(^~>Bt zrA({2FcfsYvVbL@{4%1CCPg!@^H)P@1P_1aHLgFe3$}w2GC>vB8<0l#wl0#iu>?`7 z!b&A#_rZ1Al8(XU+Shm69Z&_3<_CzDmaZHnr)~D|#bJ5WFE|0ny*MC3nKy1!Y*(tA78?vX+<#^;7 z6uEP5!eM?LIIJS zN#W>VA1G=f176c4gq4&%PPR`lVXl9N0Jff@&HGdJN^)*J#&renl2lRKUcXW||oXWvA zfK)(CBk96Napt9Qu{L5sx=#NP*b1eOCPHQ+(rQL=@Py>aC?t4uB7Q~!5h+m1TOm!U z-A`Td*SHEUT7JmA8wN#-0)$XjUEmch@@9}z@_M}Rd`J8`XmRObW^l20N5f3ZemJzb z5!=Fx{pK|MG3B}He1H(}XmrZUFCi)64NNG=A7USTSiU|?m=ao@tbo5BEno4t7-TDF)V1YENF#k)gihl~crK()@WKX$75Z^Jhd%%Qe zY8X@Sq?)pS`6M|;k~q@nwzQD<2I zSlGvbGt8WA)e62Ctf3mPcjvXMyO!2;;qGRuBlT@D{)c>BM6&8Tl5WWC<~)4ytS=0r zYw6;7tfxmKAMs5<^ktab4>2ZzXiOT$(3+4;83lV#cZi2Pua2yd)uiuTG{6*_yV;7A z!W=_j7X9I2FsB{v%M+BcsyMB-pbh#CamJ<&;8x5KVj$CaT*I9BQBK3wvCPz6; zed!zI21?U#%9Z%QJHALSIcLm9r-B_4K=boqA1q?sYeqjT%^n2GX$fo`N63QOhK1QVUO!}GhbM*JyO=IZHy$(e$ z@~viGlDnG^3A}xXjsPBq#)88#&%3RC5bMRz3Z(dmpjy0q`Vf~M8y2$_M8npnbPXb5l5K??v<4cXAEJI_y5hqSp;7+z1FOK ziI3znep~Cxsy1tYR|T(d*xt%*?VaC0ImCOkzt$g6^}W2h5uom?TGQ|0f-FFPhL-SU zn2(V?qk`W2&2~z%tvO?`5L9T;GH;2&Y|&+GBTG|0UC# zAC70}57=%hKbS-szbV*lKI(RFR^40!L9a-yjl4X-N-olDI+N6ez-`H+>8E6Do~LH? z@4(|T&}QwPqmw(?GSUBN13HS&Zx_1m5~Wt6kd`!5X2fQxMmC1SrDxt4OIHM>6bw-% zKC~#R4K7ex0)7r_6PpfPDWHmRt$4tcDk|B}0 zgxKPMgcI_s6A0MNo6Kh(w5QB~;OOHR)~!>rN4BM$A)#Cj{GB_gLp?!V%GLtHv^1SI z#UDq>aYN*CVc`_|3xx1BG|TCWTPM0)|t z!~Qe|c;jP8gYPH|#$FIRIQgYgeT+__Q!^gbqO4m=C*lk$vd5%D?O2|8Qvg0Tml_kK5SU>a@|8c&Yd{did)BEl2}s6SFm3p~A2 zm_k(i@k7N;wF%I!9rquadLY=de5v4fFt= zv|H2{^*lNzYJJVYB?CZZB87d6!H48n*D60sPlp6QqcuP$oNo4;%+i@lv{6#t24$8U z=w*QR+;20VYlj}V-L{Vihwi%Um#8=)1dk#()FVnxvlHy^-73s8h__2~B{A#>Ak^7h zyBjN#HN$l`?|hmjY2i6W`YE2+XX^uI0iiNPhNP|LXr2LjUfAyKX{MA<)^ym=Lgch= z12+Ph>^?3oFu@jcn}OuwLniJFU^V9-T6?#c#w!H8JcIK2(_(uK3q5YC! zWn}M;Y#)UV#gIOsU~abC!tdBZNI~qy=%_yjKq8j{3~paG@e4o$gUm8xldP zwYVWIaA4Kh+4_vXxsB{hlHg5K2aa`x>unNFZ2L=GkD{Arkh^QFw)H<&9O0i$ng%>1 z_Z5}zYT>{bX=p|Z(3_jVsb;Q4*z^*+E9vA!pTOD6ar$0Fv?VqbHu4Vowel7;lHZFf zDWG^BR!Rh|SDw>Rb|SJ#p(vHkHa6x02~MVv&hwYnq82-iepQV(9%fcdLH>vXfRk&- zepfr!^D=fjzbCTo^#4bTl1%ObuXyYJF9NsT3h7EY1MgC5{gu_;TU@d4tFu7!fPud@ z2sZ;>-4yHnNE=P^b~15{TjY2`={OA^p`p%r1YtmNjy^f`iEQ5q&T>2ry50T>Lsz(6 z!oLI;HFHYEh3znZGnlM+aou58yM^5eR+T@Mnm%GDLX*0R;3Jm zLnFu@qlSC5wGTR*#Y!CS+z2`dr}fJwaa{@eb4tirjr0_ypJ(&x}zpr zP7`WhB6Wm^PI>amz5LS7lQ}0DX}@$4rur96ji1|^f*vof4=4g4C3yY-YneHz^3wUa z?civAsL_yuv!Zfb$fY5;ys$oZt%u{d`R~J`CzW*}CG)k_iq&8UM(#+Gnki>PZEWU5 zUH|6=B3;$fI$&(JIW-U?0o~HX2rO2=fp9)Kwg|w};ObZ0LaGWq_64znU_yt4twwn4 zQpor2@-~MfolFNF{DpYQBAbwW4KnTE%W7=fxbuzFXKM@-X?@vB_ZG6x+jl=f&8S7u zI|BeURzPHBj?%?iW@8R_2p<2sD%Sn>(d{%{&7K%hInd&wS<1TQ#Ta#}&Pk%_{o`S0 zBB$4|d;>!OiIN-O5k+J>Sp3@tI4a85^zR*f4e_*4JBzQVWWBANr*2guEvf$$Wkq&5 zIfeQi&Eum=+7D11ygYZWAj=zZK2{z!@2`E!4=!t*%UwS$o~!udA){W7wWu`LR1M5E zH24A;lbSgjN=V+^Oe!vX9_HX5!ZH8FD}IkHR?oa2jj=Cw9hiTRnB@^r1}CMA{k@Vd zAY{($3fg*;mG+D&;4(%+-$^wNccA3DDT23ZR5{+V>Uh1_ntZkg@qY*|8oRF-spfX9 zgs9R@=)QV1KhHp9SLUX*=tMpOeCkS@jPl`VA_0fzPh2bc4U$Aj|4lf4P9a*eM%|9u z;{^mSsIjQ1%7%DCXg50kvEY_a8Ca@gg!OtayksVUP7AUedh$fr<`ODZK%g+o7-h!X z3rFz-MRwg@co=f|@r*fAA7nXD3%Iv8h3@BlfvLr}GAe4YXHr7S#X*)5OLY^}+dD<3Y z|CMRs3*I#C?6B2xsm0VwbikdGSqn`HbozP5sCSy>#WoM=-5z#1c)ad_JvA$HTrBon zgiZ+A?a$z&=G#HR`;K!%**;cMG~lJ)eB0G7gl&i6-wJVbt$aqCVpHVF8w&0H>JzJR zR=~q%<+`EQq^#3$`H19-s5=|dguyadRh8Xn8U)({u4${mR>TPuAEWu_J6Pc40N~04 z_*KnJ@hCoJk@QCG3E1z^>8N+P1A{8dK+A69eaFh#BFgO?ej`eys}|ga!OS~Z-5v{a z4?^Dab*vzwSTJqCL5W;W7#q`l26kHWkcx#GbcNqd!m~6k-B2!|+gO}1O{Ws7oTWTm z5AJn*!He91e31$J2JefL;-35(2nuM1ICPmF)l1EZjwaFfS$<(m+mloeG0ti24YrfV z*`=v+UGLRAZxk6Sl=ofGMp#_WkP$eu6wHJG3-wg-!f?$sDmV6Mej)tH6IDM4q8_`_yFhdCxqd7c28yuAUaY5vpY z#{$fS|G>%GYu&McS~R^k_DZ-WCh61Yx5=A7xPl^{e$5X&7ZwL4GEK0iP`e4#Dt2KG0m8y%O2#>1eJNO1=Emx_Mou zdE(>OFyh?bH3IO}>b1wtE4dOdhu32M4F5sPf))JA}26z(wQ(kE$i))Bo?e*plk%AeWP|8Ky9G9 zq&ug9yj<1_PAT}RJyF^UvN8ELmNmnH`wu}(SoSXg0DQ9|cgC~S9ahvZHcuCa{!pg@ zHwlIyKQ#bJ_5Ry@J&Qz*n-UA&6Q;8RD00)g@{h* zTpuZ>{_8T-WlJO{P53Y!c!O*9Dmm1Jq7eL@o&;KTAs@srAZYAH6r-UR(lwBK1I-tjK2iYys;1Ns9%xYtD_ zw1Fl~I)mZl{`Li}e|$X+Sc&9&xN=3zV$?R?PNT%}Gw;SPUcWZ`u#=Q%51v_-Q(9+U z@=Wi`)bA%)7>G`gT26=OGvuP(0AWw;)emr3brl!SI=IpI2A8ifJ&Dmhi(B|)E^E5B z5XiQF5IsO+ejAp;j6B8eT>d%hnQqxz>ts9FF5C`ld-zL2Q@}x9a?V}P;jNS$%B>1u z-*l~6=tMd&`-kGoqjIQo2nC@l{u$@cbcb zDT+GbLoQaER^bdv5bcCC3izg}ModY4ksr*p=1Z2yw_?sYI~ITkQ|GKR`~H6w9zH>y zN#LdmfSfCN;$W{YoEv+2C!?}t0R)=AMTgv5G0ln)trVxD&NWSxbghJ-(p;1QDYRk^ zY~97VQcvJAj_nJ?P?3A^L&MWyw7`(p+3BJqp7xLb*$|!ar$^!Zr$hLsFBLD`UK}^| z2@O|VRW_B~%FFWcs_VDLz#~Xv5{tCI24qtoWBNLQKC)QzG!T6v2>bEUYKyP{Kx+`& zR~*Kt#VCA6C73y}k8^-?PleQ|w0l*LLBl``)-&1g! zY2NFiHRfVp@O+pi_STJtAAkUmE!77t#EpmVpI_QN5h8J>i3B8`iKg%7sn5x0fdEed z1Z-I=P>ap?GQ3Yi(iv_w2IM~9L@V1aI?aA2n~QTujN$4bzb?o#KiuIJdpbWc5>+;o z;Mqj1iP0}s@Xt6>h*Mh$Cs!2*quN7z5Ey{atqGUqR39G#%gXo)mSBtx4(lYU7` zN{1NAoBw7)kj$b|MTN2CAcqtw2I@EcUD!=t=_uFL5UZP5MOb`EQk=JsV!;a|R^JkhoXm){p**@CD;jqrArNOC#Otpqy+(AG+H8K@C|AY^T8NAr3< zqfX!38g7F-l{XbEv;PW&-qLqNqC%Aek~&`=ozy<&=-il;Wn{q-%YcHelEuooQ(Qr7 zEen08YJ_gfjWTv$6-6KE0c4x?!1_*I2WZ5fzu6*Mlq`#^5D;rWcAT!PtQ`JBO=N{o zjD0lsukbI!eU1?r;m}U(U*N2i)KGRK36i*GTS+Shc$ipuDM}LCZ&Fg&<ngoOL?qt&zjy4+Cp6PfQI10gm%0vhoT9S3d1PVUr%6CTitjq^vMx;tm+xlc2hEbtB^kXqZxs{C%IO+c$Ddu z)oLa$@_WIdmy$7F($!IbOc4QEQSY``I{E!$yqtT+Dm*$(YLEODXirU$e6wkDeII&6 zk90Sz$;8_H0^P5;{`l?ij)m7IgS&sRlb5EHUKw87+^OQ z=2MQG&ASz5GN5-!Y;_38ya*Wr@5tegSD&5pPUnCGTcE^qZ0%5ckf2}lKK}OfyDg2Ofl9mA=4VX$EYic3OydlWGgYH`UA2#_6&u?9zvkK>a} zlae1$E?_J}D_!X&BKEO)!4U)F@mrK+r^41T)Lz^=3Ix;7d+)@i?{}J{L#LX&yoTj? z%bf2OyzsvU_Ir#a(3`yi5E3S2b7%0f3wF?R;@?KvJhqF64XYkJoD2MnOJ`x}(7t$> zRJ-iMvHeU(>902GjkCPsE z(K&moV$n`jJ?h3|=lL`e{=Nedt9&b^kj^=C=Ko)u0)@TI{xQy)`ea9ST`^YYxRq%( zNvWw^sfYMGTt^qgLU9Gx7T|W6h44F8A@xyB;{cVQJNw;0W5f-biU)>saG{972L)LX z&D!I*hGreTSIyhwA=Ct)`CZCSg?7og$0FXD-!bTS1J3hP$WY)F*a>$q;zoJ#2zUGe zpSuq81V=qP9D@E6GWtF+ZQQ>#X+_+ks<%$waZ5f1RAw{ilO^#TM8Boa!!PZpCJS-4 z$Lav?><`{K*)1{V`cD$^Gtn>OXDS+Lw^Q&NH`ROezIR;Uos%2^%CMmTv!pgJobiOA zYdL^!IcNJ6)-9;QUyrS;BcsMeDqjJdsaRoA{Gmy?FM|fj3~3__vNXjR5lKKF5w)bE zO;}Uaiw@BVt|Z1#BMaSZw|T-6deq=gp}v(fJPg<}gs=KgSmOB|Ty(`Xn~3!O?}Kn6 zPI*A}6-o-%lZ+yx$Is-&qfE?db6<4*CX2nT>Aa4GqmcXHIOAK?Vd-RHP0^=5Vdo_K z1)(Yo61v=Vcf)Ri-UfLEZ=}ei=%bl{L=e*q07D8ZM<|IYtR1g*kemVa9$R$}OH2vC zZh^;7LQrVM*V_>L4|d6@(ie^Tqkm(v+8OxW0XOs7FP1AHXl)wrW);3(VDls8k{+}O zb)jfPkJrnXK|&gH+i|mUJOK=VInw0AMXF+3&_#G3Z0~KZWj5cN0?r9~`Xd3Ebc|?4 zkBeW5zNDCvQ41-q#Bb6YEY_`voEs;cU88t6k*^DA50UDLr-Qzdb>ogXJuk;&sy}LE zA&kaNb8xc}kGCtgi-dY^!_`8#BGyA>#|#rvR^1LfQypNUc<_a2Ia7Uy)L0jRv}N9-33YM>T45 zcLc~XnK+j>_eUq}cQng>!g3W39zD0x{bxUd643v5!U;fLE02E0#;&U8P{A(GO>0SO zsb;^6DIO_OCJd)znRWq(j{qO*Tz`ufx` z&qG=tl}&`M4BJD&>cE<5fAWuoN}nSf5nS0`(<`{6G)ABD&l8%oD;#UcyWydE>0y`9b&C&eh45eEP!JeNi+F`*PO}h#jfW zsM1%w5u!vkpe1-gFgTsAI}ra4bMx73DeJgMUJ2!Yp=qmw%x1?^ipT#@M?2N})bi%^ z5dRx=X!kQ+xOO8ytucPRS)kmxl3;%*cN;p2AdCaww%C^m@Ur_1V@bb#J9etI1r8Dee^{g`F8Fl2ywe;f> z4jr9EpFvd(($KtAfdpMY<_aIB|7EK^|Dnxp-3!xpt0ThSX9BK|eBH(*Sv_h~$N|y- zbTf6|vBHZ{-tNs=SdP3l~Q2Q|2U5XP8$?LH)5*gpD9+72aPt!Mr~UWd4- zEDURL9Q1Jw3gH!Bkeb@r%XTN|D?BV=NJIV%eL*#pWbQ`)zCPFzWoBHO$0*CZUn-X^ z9zH=8khYIh(-k_fV#=_GmJOk|h%27^K9IOxp5}j@E9kJ@lT2}I7+iI=6JKRcq%Q5q zHa@0Zz`VG%seX%5@yJ=tM^_;2A5@#)v=3NPLeT0S>lOxX;!rEG#jDu18%X;3O_Jx6 zmEq7f;xuj@vZ31|$|WvIRoK@>(RoLxeb7nWytG68ZpcA*`&4Qewfl9>3;4Q#St4Pd zU>f!vI1$s4@-$OvJ0(37G0X1sDDoid`t~-uxA_r}`ZtX5C?%A4msyfNg?>$A)5AUl zW_-=0CCBA_9+-oqdSPVMYCmqHgBPDnrw-gsD_*B*J6uQ5-=@Q-Les+LgB&H6=INd& zBA*CDE{}YSe~d0*GD&sP8ZeAGM;K@nk%yiej^s!C9lE`H=;x_nT77r(3mw!rUMk;# zH+FKH2Q^e`w#SBNMR@T`l!01CgsI!>Y_w}1-~@@bbBk(#dbIn8>UdKZzej1B=y@VQ zfp?(w>^5mcu^Wzj<4;6keTXpfA{joJyy(0WV7hh~qOB>K(0*xEs`3}- zOtu4#WQk85FQ}Wu$Fs+4!IA{8%V)OICrZzeXBzlvSwH{Lta5tmV8oM-?| z8q$Ef!LaO>Ys61UN=|%vh!ez)PuIVDNL|x>>lkFgzI`lgGkY*$uaC60=-f77+qEUs zB1!SiUCU3#wo_4{vz=iI>_Lzjf1#{yHO#RQd~o1r zgv0{mDnddv(cor1K#7WX-td|T_Ax?T_EsACOdbJMc<5t|zT{x=&xyDkGxRFPNkNi< zy=rJt-CQy4m8_mhQyLyK)a7iEH(O~J)aJT?Hln_ik&F`^2YiX@wES7&lCDfBeKvYD z(&(WOY1kwax}u%uz<`N<#F79VYod_REa*d=RjUD!I-@c?QdIm!E$7f8EbwKJTaZ)Y z>w7HtZB1S1$U)MIp>Wc{-zc)cRCnR>I(b~p;Wm5M-XC}2&!1d?Rydflk7!j$1os7fvc@3rpFf`>ZgZ`ofUs??a;(qKoVFU5%47|s1=@E~U3`|zn z1Dh``1d=YdkXV2+D6PgYnZ`2hSY63gF-`Kc$tBvSrtzEJTsNxM*?k3IlO!Ezz;n*` zmSApn0LZ|j<}az%B25i zsS*piQCY<`Z=S(XtM_hBcEDid8%~ZLA&ZtjgIJMNJJsv-!%)8$%*RMOx&&(^0c46g z1e?0JT(FO0S3_!vC_ZV-E1seXx^v5^Y-4_^Iw}{q&>QVMe{POxc5s@hEMSgP0-C=$ z4~X~HZ>dYAXc+b+o0;p&lAZPWa9)GLKb54cK5mFy<35S$WM{^Rc=xi41{!U&EJjRb ze|^J9xE2;qHOI7Q(#jro({r$s**NaI z91e-%L`3(EGBqg?B7@%-jW6RB0Blh8$EtVPYlXF}I$`iZB?&uCpxII&ut6(2m=-U+ zS6xavxc9A8Cv!OAc=XES{9b2QHAZQ4S;bJABV_j5H6E5ijqN(tm6S_gr$OTj=Z0kgv>-q*h1~W`Y%K~~agRdOh#X5}c zVWT}mTF$E@K&Oi$zzXSe1I5UWO~`p%VDcm9J}{9Q)*U07CHA&85<4q7JnbdN-qVx| z&AhEa`mlG_f}~KsSm}s!f;{|q(bo1We9{5vBO3MLOJOi<*0&H63J<%0f4UA{?|vp* zn3%gfQJ4Kf@*ay%*dHWMP~sKdvD@@RjYS%-TB^3Yw{3=*jW_LXziOj1?Os;2-bp_u z8G5b`uhR`g(5(|+sP?$zECp3rwvY4M^m?;@nQb$<_je!+pJ)6Dp zoiR#OcZrCwOZa^3dA6T0JQyl&d?YWnD*>ULkN<-fzxgHpXK(LX-of{!AZFXoK552o zVhiTXG>Pd?0Qvc9Ae|ND6U%jF`B_$7+-%LnbN}c&VA$7YBP9;#=kT_^; zYm8?FfSR52Su~UO^B!QJ%@jabkZ%{mB;c#WpjS?mnAx2lEZ1qr+-iC%XD`JOVELlg z0f4$aNU=t};wAK9_4I;GmgNTXm7$&#kBtbPW7K=gHZumu;_`sI8OczR%Gv7jp_qD~ ztGa-8T{!ife$)rF4G=4DSEmzGQh@RZ&Q$e-6sYqcB-++ zhIeA2T3F9m4&BbMHs6}7$9)}?q6fYfC8)O)%j-khOgp=1o)=^Eb!bB9GJt02?maN! ziEJ6kFZ?gWja8$h$mo~K%z^ruvzHj=H8M-p7%#a7Q+i`@TgG6@dn@Y5Y4lLG=S#2a z5{O`imjyq{!_^}yDNZKj$)A*6F;D`wEc|+KN2jwq1e1qv>zP6#Wsb$qC0k^XQnFi7 z6v+d&Orpw~)g`5~4!DM5d>ZK7#HusvNNhBln=*qX_;h|$k6u5uPG@V|+&l^W$qS!X zDJXkMn$-7i0|uj-N|$Y@E$2|4TyofHEz2E5&@f~h1|T2xcE{&h!@y3Pn6TClW`wl1y+zHJ zr7&6$-~i;0We%@OuED$PMsPoM6rXgCm7}Vc>Ip4X5}wC!^YNh5_C5Ic6npP=r4oJl z3vjtCcb$aDpzk1tHX+#dGsim zXRSSx^2{ad!$6ux=F!1!Y@q4Q3E^6MO%boezi(8iJXwj7iXj+7i6k*}OZmsB_iEua z?MGe7D^$707*k94sn*YEKV|h2As;E^2Z+Z%NZG$suq5C&F9|*l4;4Zd%F0Y?KM5zR z?vV;h>j7NT2kr?dE{>b+qx5pWT#iOj64BbdpP#aI(b~EU6F-1HC$3BU@R$d2E%NDn z#=P_Y%mcL-|G~cX@nr(7j&#_*!b_0X`PT61kn(+2+CYjGzzW?@K^KRC@7ICA!)0Ux z@p4PoU?l$}0@4U9wVb0)XCl6o-GD2#ws*QHs)Drbp>16eP=qvKQ`*DW^6Ex`NmcuT zN0NpTH2>qa`4!mw%@iwW(Ax`_JxTFG>mBc4F@VW4>9UBz1H6aDd&;rE;Ur2jm!B!1 z60IaIzrXACNp1o8q&bG}B5$|0!D5;{^>tlGxgSrC1=!|ZFuUv zY7NU1K|fAzexwDVIt#YFP$>1eIp zLyXz;KH{HKt3>K719+zZI)FqFEHGG-B%?~ zC7c@B5)K#ZbX27fyN=u+?jg*O@L&`wji0R;w2AENq=n(20$~BmYJ!(_g71hO9s3 zyTLB)tp&zE7n}O?Ff;~0KT}kSZGq3tVF3y%7`0t6S_yGL8ilR?7P3?YR#B*Uc}W8U zUV*>9)m=1zg#jK=`Vl;8AO5?8g}CaU;rjXpEGeA6QPEZR3{T>pr8UQ=cb9@6Tx{NA z389zM4C6RYgq~0$eJchQPzp(BtgQeb>L{l+)2V`eIg#8)zIQ{7UUe~IOiyGooifeL zeWwITRQrOJs+DyT(0qx#R9N-KC^+BmArI8#=3e{WAh4AcJvMXzNzQmn zD7H7iBc;RgV`HTs{HYHtiIsjv=06!0`>@t0F05zj;p96wxMXW(!h#6z{UamL2#$r$ z|FLVQff?tRZ>lp!yaNPFr`H!1fNP8MZ&fku4e31)wwsO5{TaDkGdZRm*n9E(RE4+q zMGC_KSZmISd4){&*nSbse^M?$^TPB(<*MpC{0Ao=e51SDN#${mK7~*LngKIYN;-IO zfs3wEop$kWb63o5CbKP{|5PSkz7#Y3?pFp5&vNq1c-gED2}cwqjaGzIk^4pWhh39&2 zOxkj&7zx=5x)OP~rOByo9i47xBGul^CTQDRWXG$U@2pqsKph8GuWVzGKOf@2Pd6U z*}%{F)A$wl>S@gx^3l+jbT|oma868J4&4V`+rxb!pMJlT2$9tze}(cuFY9%kVzkJV zSXKeAq#sFmI&)N1xfWc+Q2EqKV2#@$OyP=uxkzvB|DnTScCE9$9_a_I58=ar1;ht! zQcX_UKq}O5ME0J80~rob1WsA#A@CRzT$TXEvu@k70$Xw0*J_aqfjj8fH>6{n>r=QaMSEjDChlzL5s6en1p+Lpp8=$#Uny+(TtQ~nwRGb&koX}zc$NZ(k z+?eSv`C1df$_HOGEB%&3=`sVphv;!&x+@N{A7?W57(AUgXbQD`u`E2MP7_iiDVKV9 zVPRJShr`s=k^h!{@7Yvy&jh!-e3FO0#7Fs>)3ayZiu&YG=*;fT{((xsrDeqD+$Nw_ zntCFSG{^2AkCO3S43Rj|I2V!&7nWb;cJL3RlkRXWu&1{*0z}kili1z1}(<3^m$gwyZ?t2Z{F(7klCm(TWLr6{^J_p@R>@q> z7HnT4oct~mSR16(&U;~I5g>2$Q%RJ3$Wa;20uysEJgo1lD`Gfn0|ZH=B?V}vduE#f z?vob(@#VMJBXzEXoO=rJ%rm4|o{H^{n3UktjPl=TM*;YnH1tol5`RyF%72;Bw136u z?{wL2w7;bNBX;L+o^0S!n75+M(~1L71lRPYpMX-x0Ge5eg)P2-n=B4pi*Wg^CaJz2 zo2xj%H}0_Ve!?68CLHq6jXsTM#{7=%_J2LlWg3VEG>ER|my6!bO;=OS=;+0^V*OaB z7+#+r?j8*&k69$}f&#Y}&#~a=Y@pn%PZ3**OTSoL1DoGXa4Lw?syN^|M~mV>*Lt!Z z550Gh&6JD1$4;J!bb$7vwS*uip5zUo5&awBLiK1Pi6e$Oso{;SeJBRZ8`2IqC-v9) z^3em?O5DhNdX>PLE*!b)0JJ3fkdLO}l%#Ft>cm4LNYD{(5knG<6$E@|YItfenx9-q zxd9IBf7T^p+7y8dY*y?2Zdc@u3~;4MYm0CZ;^$fMn-&2X3$E`s#AqT{DLFOHGH!j6 znqaP@u`vT>?((uG)tV|mHtwr0_E&D(5;m0=5nV}JU+aZ~nt+kCXZugTp542OfabN| z4-fkilSQb{75e2aYpHmELuR?on3aP16z$wT8rtZ&xS$P-WsFMx8f1DOEwA7MPs zwnK!PaJH}rQ;FSFb14U~edL6M1{yC17;P%~9md5x{+?>%Pw&Xro6uK&Bd^~ZL;KsZ znM-c&g{#423Sv^SnR!DF^(_^w+SL@23bWebp!sSRw&0(SZOTnOf1@zIN{o^*!Y>nw zKtS+Dcqb{YjK-PNXAsRw;iRN8?~Tp&covnQ&XU`g+NW+jJu?Kmd*<}xS?VHfMTkvK zJ~ z%9j>O*%qU|IVCe@(D~IMx$vU;Dxn&$d#*KG2fF=7-nm}=pT>12a#Cg%Xl9w(*qmX) z49xftxmcTyVJzXuEU&JLMZ_V*PDhhdf#wn)Q$b=2Y14zV0dwd8Z&8rIyt^XGd6Sb2 zof;=k?q{m!+z}z2gdA}`Whmi3Yw3c-uFa4@jDm!+UR=~q*KV=)=JS>7GY1~8xV{ad zE^BeLJ~Ob6y8I|OT>)B*EK2yIDv+RXu~da<{UW1S>(7yj-b#Vqjeuy987#@3)6=a5 z#*A2qNW|H1hEekye^Aw*-Qg)wWSteQ?fBIzmnLfrcO|~H(JmqLPH_%wj`#d%=Aa7B zlR5;{i8!ie*c~bjnW?M6s)3P6Qo;b@vOPkO#1|6oHUO8tUUeLgh9W#mMEiwDE-I=<;Sy53?w;34#YGXlldIm1c9N*Hs7d4wI6hs zpZ&Nj4&I*9s3T^+qBgI>PESFABAm!gn4rI`3ogY&NGQV-V-L2|+e0=PLw7vL;_~f$ zgtYn$KcclovN-Gd4r)zaiHI)6gHRF5U%gYhw>)SuWqomX$MaFeOtrMNaN^Y-BY z*^%fs^0HO`xVSiT5!#*V?;9ipC!lXOyf;ZywSx)U(F>^~E;^zF0++J&SBnUDJnTLg z-*O!*cq&OtO9-1OF5lb};;Rx%y16`Zzss3Iao08X4K9&kN=mcupaJ3hZm}mz27K+v z;kAy;N6xAHFeH4MlkRkCge@0PWvICPOiu{=*(S1*gF{7yQ;mScx-|h28%PgQlM@kT zxI~TQNTgo^{4vZHHS-}|#|U!a(*ec~NA@1M`?}cT?*EJ6|efQ>ug!eS=fEPEc&Xa-Rb1K*@P?IY8~o#b!~kxHtj0wSDV8Dq?_Q+5zp)dc)p%;A0c?(` zipjm{j*Y2O+AXL^j%#iq0*Pq5k(oZx*AHBBuKXZ z=`#Gi18HPQ`%RboJ9GpS{pifGe0wqztA+h7!iPhW@$uo215O8Bp9nMTfwEYiAS4PJ zii|g+`WheHuIFEKQRHnk+xvzh1;d`y3YWpY2#nvn7fE$E+_8XqAf#hE5k1a*}M4$V% z+T``&B+EPen>WC#*;8mTTB1-4v{FEr5j*8zaeWpOs)0;>ec`8y;!fVYdArJR_{tHS z&qf=G6_ADYwu9Q9qaIT|s*13>hLAWofm^Q~QTcL_GCS6h*>hMSaf(R}a@;*Ka**P3 zT;ZjkOE^hV-UPhNvAS%c{MYMhE;Chx#$@0{ihLC07?Q+j_4`Zn&K8LzJG7a2rXE;-g!CZ{o$jf&40ux3JVa`Za1XIX;eB z4W+)-N5G~spTOF%{dOyB;Mq+DAs<3qY6pEy}`tOH>OQ8}bf zOM!F9I)DDB37HDUo##EqYXSxGV$sBR*VM@c2K4R%f-TM{Vugj8Ztfhq++D!eNzRl( z5iJwJh&ZFz6&dVQNGT_Rpe)%R#?>ioeeh$32IS72BJ*@Eir0WESt=bJfl9N2UA1+@ zp3|mHK^^1NS^_(Gpua6^#)3uh@Jt&?-ETsdR4g5Ta3JFG%fcuUR?%Px`}c;qTAm+?(v(Oh_h&smuwj8E)bkb9-BOg@15Bq z9$05X9X3b!Y?E`+TVDQgzwVs7ECu)^FFdRA#GaADn5S>3!V4rRiw~KR`$F=jX`}~n zHVotz2~J!&v{9FmUt5?Qmv?iDM#*t&FTWXO`(e5$SxBJ;{&Cp|xmWF9o$sPGXHgU; zCPkg+SVXf9-^KefXOT;&=9Uisypr$EGsJRpe*ZiVuqY}Mb$LTRY$CSUyNG^ru?$6- z((t~Uu38Zl7bWtc#ag8wKH^uEMQGeoh~dY-osvsErq_$M{WyU|@0y7=uDAx26M9^) z#ypt9-K|tUeE8s)UI$uZ8xLkP>?F5lb7?x9+%xXH@5?4^B38kW`dv*(g3(c(@iiQ2 z#~6Y7#mr+a>PJHTLtm&>&CiySN2NXm410d^9uqRs!t83ybETXxdGm|=&Ns$FF5#1? zK+@;|sQr2T9fSDBUZB6D)?=AxABdIVGD4encgn6R+ z=fPTP+g1pc_NCK7zia0p7Q6BLwvIv`Dg6yi5ZQF}#BmS5=cl$S?JSY3FSctj+T)RG z@S8Dgny8meW7}}3cZp0D0~BO{$Ktb@Zz>@xX)kMZDu&sKgC_|~S>tnzm~>oS+Y5bK zg&4IC^bDUgl`d2faz_Ys5&T9AM@jA@IKP`bVX%Cb4*um?K2m|=Jrw0y%c=ccu7j3s zLqv7_=O%Li(@Ca$hw-o3pQ$hG(|}{tN&`3zj>U+)vn26eWxsx(JUq6k@DO-{?BR{HM~Whpkt&@Yzv0 z+%a(PzRbSA(SPZrfXC~OE_t66=~3qEm`%wf2%9$sJ~LIX5`+YWVB=Q>8i~p$`a0vX z4i2Y>spWI*iCea5cqby>jbAm75-7i^O>iZ*PzYlzo#1l2cqcR_%5^}khX#1Rm~0x{ zA_sqIF+U$@Vf<49=4P?wPyXONeDlXediZrCl~B_PF?`- zJ6D9o(oE!a3f(%vjf@yHebBLISPyAf?wER07BnWvI&B*$|-u*ggrV@N8~e*Yz-16Ewrma-hR*)-MQ1>ozA7dAcHlYyLe zOAtz>J2cxkpL=MnG*X1oo^({;Jn(}cYSm|+(x1kpu4p|+=9ry$Szcb}Kgx`1zmTm+ z2E_G~d@%${5srld_qe_7C*0%(=x$1Qs6NboGHD}#F*qGse3@v$>f|-?;f)zFFnb&R zDQJ0U(#Q*;P?9RMP~~8qtl(V zsKi5K;wTY?=b7msT1eLawlww3y#rG6h_9Uo;y(HjRmlY_MxRZKNMIe};ulYjIR zR$4%oyU0F3)~=m}MHg0{l4CUay+Q-YWyE9HIKjepc4 zG^)V;)H0TYMo`~|mr$e` zMAh?LEqHp)0RC)8<3%?`qK^cU-}<7=NW)Umef+%)jxU1>ZSKWgU0pj4nqvw-B^D#r zR0HDJJ-3x2(%p)}qzzpTpIhmc6S*e80Et`{!ov*~SEdH1mMDhCKR7$1MG#^7j4w#l z&P(R2{nH7J>mR(<@%h6h695iGrxw-oQ+4eLj>Tz;TaA9M3md47;3bP3)Uc(t1~S zk1zXbTTR&Ll$X*~RQxb>bGv^Mgp~M$r4324oWEG_#1bhYTN3@>S)+qvglj zHau(c$~+o=fofNsq8`g`|BLfN>(LisIDL7T^sSVSM9|R)eX6lr(@Bwj^~+5s3VdZ` zP3#~>M6%zezJE+s(Vsc&v7o~H-W3>|g9qG<^&YiH*=2bz8@dxwl^K_#JK>!juzu1{O z6o913(iKI@dm`;Qd7wDEB>byR$L+E;L#IQ6X-i`0Cy>IImr_^5-@Y+S2cmopC3&s!jK4%ns&miC0l7Bnk^g|~o7PIqRGk1PQRr`+@Kc=X zrp3}?odoIIm6m*m{QluX7zF_aUxNHhcs#BL-Vb2OLb-3 zzccOQQBk{hH(LIcXW%M%R&Y%81grn*8x~>8s}J$RYQS4w?X`TfWdE!R;S97#RR3)%3ls-Y)9&c@k7fNs zsCM@HU1S!|QC2nk-qD)wzM{S_eLx~V_0YfYC8`!Vx{uL8WhF<=Kv2`8FV7H!S-o4! zSCUAGt{n0S?scKJ^+Do&`dUdoE<~NnmA4_jL#%stAf-;jyf%n` zUZBPMNO0Js_I7tS8bz@$E_KC}|4|Y|Iz9 zinV9(TNOmT{gPgnGS|0R6So3MrsduHBKXVwT}ii2M3DxF;*Zq4Ay_}VhT32tYvPf& z52Agltgvr1a?%0RkMM_@qnmGPsoX`bkb{bL71&Pt+K*8uuGMaTp4d@X+}Q9+vK#g> zZ}lh61>&3k(4^&v%p*D4^+^df@hc-wboqt7S}=;cuvfu@;$$$joZfvi}64#@&;E*QG+f#Y2C0Q6TbJZ#_^zqu{n1!$KL6`Bt z^}A)ijh_83ZHy|oDD~LUw*z%|8c;%k>vp(CB1-EScIZB{fE{N;#B7&QmnoO9f_+4^71UMw)nob_qciizo zKLIvsxlA1BSDYZKeIovaFhHT!nD%kXom#lN+&`Bx3KP~c*l|boHhJ>=VS8JM@1^O- zwr75Wm&P}k(wnpMJKLXahOK=RzEfYMERlH2EB#1b4MeU$(^R*cUtFwP`^8P?o`!A` zKGMvClH%1g&SluRW?evV`6d4sF#209zK_5sI;ToPRlN3qW?o5qe~5<-N3acw0@;ss~ZFoUhopP;Dq94RVD zBO$vGiQoR#C`B%JF!U=y*rQmw-AzGz1PR&J3fFpEi{sBQhsTnY>yO_YD{?H`s#&~$ z)m{8_6J9E43+`w~Ikx=v0kMF-uK;4jlK5a1y!T=$Cw@Xl76nn5WwH9RGvY#$gpH|X zx6i=#C;maQ20cLv8__Y*N9Fx26(&m6`rhgNiLGgxg^RaB@uBQ!$7_R5U0=r!VHa>_ zBjfz@x9Ka)Cet<~6K6kP`nx1*ZsUshZ~jmA$%=({TDS3aB8AtFmP1@-ub8t{?+>Bv8HF@$fGI&FR{Z4Uj9DAf`)Mu zP0Zo1uZ(L?NcbPW|JTnZA4yEiYuMvoY<^gc$7InW-$F__utE535lWqkxRdcm^Nz_W z>)FWFD;FR44`1y#@AA_bq+yVkQE(SUj({zrA1@QWc$5U z{f|{Kr51-yU%zKlepFh}PyA42>3z`Z4e;&At-C`W+=ZM7KZSD~vFPx7Dc5v3e7^rp z=q-Ex@!jy{>k?-!YAOlyx=PQBh*n^?m}$aL*Je4QFklLsEqmT(ORb zy11E9;nS$jjXHm==V~T8G^Fs-WWk!PX2Pgc`ZWCj+b_CyaXYMbJ@cdz%O`-W5Aka4 zZ>tcwBzZDU@AKUA&W?DUrx-^6hH$%?qc1Bmg-1t&e#mS$-NoDqa$N}+x)^%5mpBmzMZJ*c7;|&4HM-ZpURYM>$G~Z~F4%@gkUis-Q zL-9GVv|uFT1h;}>|>(f zJ;X`gqkRiP)T_N(Ilwifd?CcR6~sKGRliJd{Q9X*><@4qHTWLwJVn%9HF%62+HCk> z`*xyO#eD-je_zLLLXUD+W(2?ISZfB{bbr93_pi}VEcg#F7xc~jPp9WOwa`%W+ABOs z(atc=uPo~lemrGy98~CPi4AxRY?sjHg=WkW&r8sf{1jgD`0;m~@D=hJpzNw(1okt8 zoX3ZC1S+rYI|;5fJv>{mSih7< zKs&!bqK3}QX;1KH%Sr7>#C03up}?4_E+nvS2!VCH$6|;Y#Doe;cl!_zNcoF7ueOVe zdrTc;6EUk25>nqZtZGed?3^rIM*5-Ppp=JI)ck=8bVaQu7!itXMfv7D-H)FcsoU+L zc{AX|lB$pO(TTK`m)cW!;H9G%y9a|%We8~%0gYwomm*<%28V$Bbj;DW4}J&c0n1t6 zv6PG4TeuYSU9b0NeOIx7c9%o`C`$KZsvBy{Nk zbwBqe9S4EP!?OdXr=8-NRY&fufD!mpS;ZVTFG;)I5Xt5E3>KRLBRc-MNES&y}PzuJhfd;EiKN9Sr3wJ7=`oUhuA zB%)a)tA1K)Yr&rNM0qTj1`7+_1icfO0}(fk+OjC+s%tj(j2<|M``;boGJ!j|)H-Z^ zHL2p|sA&PC_;rVvdY|F5VvF`eH zrg|}a#D1+AyV0@sg5iSc^{UZVwclmc+_Zv3S`Ov*^U+J`u2_qEXJu(H7%#{w#ZyMX zNh07ox=FFLGBvKUm^asEH>!ZqQ2X=6)2PobIVVjq=VHXwA0t%Xo20U-m!17ovKbcy zDt^5D@5{imGio(+6e!?;e)V?$2%chBm!Nw!mccn*rFY-}FcGWpHQnBC9IU?|qRyh+ z3c^~%+AcB_OZ&BHQ=d;jnMUF}biPyZ#dKLJc;WZaM(-`GfeoyuCLmv8?`nm(Gg}NWG}=ocYkp(Dh^{ ze(Ea*iY7a>?}-f$+OgS^Rm1!7T+dL5q_sOubWmeF2GXM{Gu4#D-9)LqayfzX_RBKD zrbe{;$vOf-AN{eXQh4H&@Ha&>SLZj!ai~R#FiSu0bZ7qvhv~O5x1?gHr|nR#;xFUK zl9z}uc)KVo-jD^}gr*D!?1a|wpN#+GAN3C-p1-s7jL}zw)rt##VIOrW0yVuuX}9Xd zpsHx9q&HKtT+h!YLZNYlx;>2ntdrJyTqj>v_~r|*T2s^uApKX1(OcN^yJuRp?>^}B$ZExt7A|K0!dOdJRlFkiWJrisj-!(lWV28Q_j5muiiL6!{2h%dQp^sTD!PLF) zb&v){A*Yx8o!k>ST~1t((uL|rLvQ=|Gz4ZjA~}-dyto7i0H5Z9O+1q&e~BL{M5$Y0 zf7<7C>C(4jZ3~7~Lt3`gQki(IIkiJ5DfxQbiFDoNz*%Ul@4hSl;%8kz?HSNQTix^A zw)63wy0UR7QaMrwJ{$tzjDhr$hHx4yr!Oka!=)0>$x_U1!3Tb9CVcBlBCq>r%3_(6 zTV@C;K&11JIZdtt8<6J=UPqA6fryPapaok3(Z`jp77%@_x8XN9taZ2Rep$ZCc1+1S z-lLx{T!G>eO5<+hL1?pZV_X#)$*y=bS9XPMC;>p|Dh<-LsJ%#;| zyQ2R?28}(xSazl|q@$4Enx3#vFXuDh0^ZM~+M9{1%1>{33X_*~($((fI?6mVpDq-3 zue=P@30x%uhgT%jUEPdVh>g6E7M6Lk)n(#4U(va9)u)$^P2$CDc$q>ti5wh1R67+? z5ltZCnq%+rjE0vC^X7+?_mk{D>d&&QN90`K7@==Hz4BS|mw(tb5z?rk}7owjR9ob7m!X$s5hFQ7}qTO9M&1(Cg2aO;e*Of1!m`LSSo@DLqj^$8Y z?w8Ek&UMMB;q*GDeI>oVB0DB$ZOZ>$2LB<6fs6x8v)8Vj0k{{Pk;IQ}J5u2HSzBa~ z16SGVYT0r!(;+Yt`*U~4pmxQ#%3vebSq=MLABcYfEzyHj?wc3_hK``h;b=@Kx>}Q& z78UpZoz4N?ovwN<<8vjEP4r7Fzbj`c($;~dkg z;R6TnmbZ*i0gH8OKA(73-CDb|bU&Q(%{4j>$$1f`%xP+^m*n<$tB%-_dT(WRx+;=s z+nB>C#qkNspek~wqyQ6BMZ}j%*O^2_GNT{c@oBId891TacR0*T-Y~UoDEvm_yu^Xe z?-#LH9)aSw+KGtgnbNDAyT6l2{l?oJjYt;;M~8z~2p_j&*bt2?HNrC@Sdcmx{53Uc z&<-m~`TrJ_5LC_V}lU=!T*Bs@8WZ|D87}g4l|mkkdjM*Qe#G)iqBtGmks;6 z3R!0mtBjX9bB+gQ{|+8TCNzYHmqAvyO^k(M?UP+whQ6Pes2D(D9rAS=;oRaxi2klT zA@)Y~Mc&fo8#l&{oImd?Gm`hf!M;8lL&|9^iG&y}(A&;7>b|f>F%{A&ogEdEN7=2D zt8t=Q($8x>-qM8-j0#|}#)HNmaX;mF4J^0X!X@X6uiB&ec^b;@ zIEze|M$~L5w>-GhSLD_5fYbdA9WUFfs)aQdAz=>#smmQC;Q&`OxI!wK40gYA(U-|O z%Q@dOxGaZt3sRTOmLAItrK%;*rGX;md?wUGYGdBp2Gz6o)hTWqP>FuFYuzAkMQa~z z5n_K`7g{t9PTgz-DW{~re`m$L)3jAH7i1{?dpM$YUx>~TJ=PeDsEuE108FH>Gu*j~ zKijcZ;^}TQOuV(`^9g@dYHkuYB1nyE`LVGO)YH~YQeK0y|FIEq-fK`d|GE-FKcnY{ zaq8?9qF%*E!X*475T$}FB@HGb^T#5aolLEk#%pNX5dA%3K&}Jd_al#=pcbssOB%q9 z3gdW;5%E{C?m)#he_+agH4C!7gqKCtK)=ZSjNKTY_^&9!i$EG5BCyT96c6ihwr2G+ zH%1txH6Bb2kg!phvVYx^aa*&3`H7Old^P3SuG?Fh%&B+kzZ~q)UI|i=a#3m!J z7;duC(}dnH+*qpEp(Ufmc$CBh0G0(e4#ROG3=*p9RopD%Ypc5#_)06zQp+V zMa*794b6mNyn^MQh5Z=JJ11|(noP@(JSy8~T>O$={i=#TkyZJw@)Pp2^*s*cakC@) zPLsxh0oWGcA(UJg{3cf)_giEm}`%DUX#b$>00Vl{yw$5&$ zk)5!eSB%tJqxpDUM>@G29P9^1f*f`te!u)cE;}GH;YwlU^)0ors#dd(?wM8fQnFs4Dtrcp)hw& zk+0pG&DTpu5VYHtpXG#3@8%U2*W}i-Uae07Vz?%0<$|@rd6tIr)~VB+3tMuxAc{*x z|6WAW6REMkw-?x!(ynxkW|FtqkIm%qu8x!ssx?RtDUlil`}_VA;WaGiy&sQ0F5dJW z{zSq{rf-^N&j~ZIM^nY8$GD4RmO`!gj`hk>M9Gb1l#G!_MP>3kMQKDv!2V~Ug%{jO z17VY5{>s}R9xGR|2++ORFg$?#ZUXOKMN#qR`>n^KqQVTU?4#?J5!H|75xJ$^`k=D> z*4@4ll>Xw9{W1M@-u*%N-2^gS(%#-fo8?m*wYI^bt!>-M1AgQ`;O!9d0NR}xW>7y9 z!No?vKkEw*6&d%b7lQk6A7kuq(k zM%xtLI+ic1|D&28e|p42YY3R%;IWfE?fm7X*=dRI3Q6>7sNM%&;%!VCER z+73A>QWv^cJ1`p+O(5_hqh|e{8GHJR|D|l}_3PE)xW^*hH)2b*{ffjtRV^Oj4mAt< zG}`#c735ko!+|D>~dBvi)jlemwhT zF|j9oA5`Z2AP&kD^DAOQb|5uOYsTFd6bmjTHua7RK>4D!GKhM2_j&QIl_LGw#Sq+( zVPbT^E@l$q-y3;G0DHDU57o+5gQ~JupgH@Fn%WgV*CJ5E2UM>X5T)reY4x;hzwbAI z+5foO((addQ;5gr_yNGzCUcO0^^UV)`eQ93oFh%uRQH{8OvEfO`@1GWJFTt!1>p*AKn_6K`Su zJHfwxO=AtvyVq&@mEz^w7{9ss1Pp6`cC-*z8jB(@a@0b zlT*ewcH%gzK2OWCFI}8Ru*)>b(*)e^KndtUKhGfx9D+mO8gD;OS zb5$!h3;zMr$Q52oza?+W9T!yzh#=3E30z5dBw!%b%)dk_#~CiPB=`8U@2;}>?#A4v z*&pFEL{a-;O{Si^7S_QEtu&M~GnoXfP|~1enIN}*>DuKZ(%^o?VYP>hu!Pb1g392j zLPUO7cXdzzY+fyQ`J{LPPIlHMG?W~<#AOuy#kaoG0L>cH84`n_uy+WWJ}H78i6vuj zZ?36>9I1HCCx{Eq0iq8ktQh>`cv_lJj*uv-2sr!LDf zA?3nu^{8Dcl!u*EPprs-H=(}u?f&&(F)NFlI_DhH=jYY-tnn6;(H(5HdD&~zbfCua z4l__q=pQxP2MT)c)x-1V@abGoR?uh;@W*g~D=E-fpdk_$E{{6@%9E3m#-zj%CHVTA zL4}}ML5h(}lQsCDNO*?c!dyt9zr_U>INlA_YlIiqmxp`iPl{C5pPJyuZX@clWS+*s z(z9Lz#ji|Xb|C7e7_0D*##!;H;d672(`_McirJu|u_kjrg;h#x(uER^A%eP`TUYYY z6?!#$;nl|-Ee+tsL24t`Ltk4A#XVzu#N0Q{HJ^8FR8XDMC)B{z`x48Gf3n6@4ziTf zdO8PMAm`3)glnojLocEW1iA@eyur6*3i2CjwJ8C$9=_!9jrIIr4^+vI9h_~Hi1pFr zjc1FcwmL1FF6%cnA>5$j?h};K*P*D~HaGp-YX-kv{*G~NvnMUCag|M8UXu&q*Uvf8 z7n`HIV|#SHeET&_Wtb3>)vu;fH!yxPahVRLRW_M&;JE&n;qRq0F+ed%w%Quue7N>L zK0HztplDllS@?$i%P|IYWcvV$KH1Tx2H6jmTzf;gDb(;K=4$M8eu2#!r5c(4`w9_54rWknZs<$tYQTXA1g zOqhOqQ-9p|I;T}83>31I9jMr6=%QTvZC(QfHw!$KRVA9KRSe{Bx3$p2kHbPYvc7*` zbcu~HD`!n6KD8jYn=-jg10SF8yY6TVXdBkn`%@Y(9N0Qdbadw% zCX14M|H=ikqcB&@z(=$Q45W_s^H!fE1YHqRoBN$*wsc6g2*NND7k&_?`Vqp|XFwuL zSJ8#uyj=MCEr{!0#QnJ47gsxDG@JTT2N$jV3n)Q*#92E8?P|Uq)z7Se(GOR5V^@7o z*rX9<=rVe(h8$WDX!Ep6O}ytmu!jwO@$(lr2l7My*F|>=?)e~br+AUgua60xF%Tv5 z3g(Sb;gDC}27(liaMRwd_mQxi$hU^}L9PX&FTk-Z=h2hew7-Tx5RS^FFJ2@h?9;Q0 z0xN~)H1s3{r@g%!aTGD>+!TI}w{1O5kA~dlQpXZ-Jn+mE4z7We+lMYV2Ns%RfsovW z7Nv@Ej0}wq>9oSb=|WYt<6cHF6@?zqEGtCB?`obg7CfoJRo9=tw+HB2N;FUoB}&|{ zE+_>S53Y3Hz#&Z9eiPylwzy!c7?weNCBaXeo26yBg{f;IIqa2v@&&rT&eUuOI~y+| z6;ex9d*nUKZzzErF`B^#aMfd7FA8|&nhGS7JpV*dIo$Cl)uYlOxq!uL#!a_!NkyQo zN5KxR!WK4`jjsTUJEmeo*vdtZ-{XaIcm}qTAa}fAVER*z2(ut&W(8OvOeBx0|CY1@ z0zsoHt3<3XMBi-;=h9n{2CoHyd;`r!>g)KeC)0XW{%<=Ji~V{2V~Q->$om1px)MHi z9LdXTwXzw$#z2-d zI4d9xq6|m#hP?xOT*F$}C(3P>wFL;(*1i<|;>3WMV9VT%S^@4S zV;qmIjklie&#?g8mi#;e?ew_N5d$$x)EYOmo=tGtT#YK|pUqn9upMw^ z*a)h{ESHQ7?W9<;Y8kFu;|@*Tu1RQ+EsrV-RmTOcT2Qr|u%RtT6=1AK5URA|A4r4s zfj+z$ME#+=FJruNM-nAv7txHSz@F&ZWq`Hjy?V5&B?wumj1Tv8)c3&T7?k7uE>4INP0ei!+{K6f@%VvU!AxY`BJyMMC-CbNQu(3<5)3y4 z>x=|mrC&d9=KmX+IQ5LiXuYFNXGcz2R|(WQeLzRJ6yB*K9|etvW0y*BUXI6kW4W%f zzf03W(ZJYx{gLRN9)Rg1?zPZikOVsSuNnytY|TVFTIPA{P)D{p2IY}Ezp_R2biwHX z=m%V=`p6I`zl(}VRE$g?*bzO=9@txA{q2|~kro?L-AX9lQo7#dPQu0JNMWCCav;&8 zV*d@fA9xW_peP~L&q`cZ1aS%um6=B!O_~5n6o(DfA0^g8(#To_N)x`~F6@maj@>H! zt-ADYqluUCL*94B7tr@#5bA=nO*YuecPb2Y1~5EI0#n9AWPs10yzCd9fZSu|_ZTo`?P~g?K(*4d#XzJ4) zF|32WM6gn>c=gvE@x2;hA}BpIzMU!aDE46Eu3wa{UfVf|wMs~;(a_N$MdIqX_#V#) zDS4I&x1V@&3U&mAcPjENYYsI%eM@=Y;*iW5zWTTr@v!Zoei!M$*W^4u4>~oczKUQQ z4IuXrO<(9)^85j}AR!zVe_1INnb3_1t4hmK{+JUu3V+O?3VKA=K)zf`+EXb<}l`U!ehhb%^E=TWuJ%^@~OgV z1XrvKD3H=@kc(%*5dM$jU5NT+0O#z%ek`t5j#sQPQRtyDmh!C9UB&dd2MaXsG#=LK z3y#MSJ;M8e*(TectX*3Y%rxk$L@^Q9>Xmz-e{-94tpN07=;kmrR#oTn>VXdTx!sTh z5P%QR6Gj2-NNS@oiVB65Jd^+XY>dgwF|N+tzh-0Fv$i3jmiG!bXNB<@E!Tg$%`Wc? z`+~Wpz@Y$orJldjp#Q5z&DNx0P?~ z-eX%EYY>B3?DuyF@pW}PW+@KI_4YkUTvsok7;dc@{WH%Akfe@j|1Fdi%8ML~GhILN zOs@i$nw|{jrUTFwk?D~9IsDp>NE(Mgj)rolF?&{-8=UST9$j>6njwr+Pxomlf4Dv3 z(?i%YOu$SD-0iu<P

D#^+IFpq#H^;`qV0*P%{CrorT#HopEt9KsTj|B3YwRELojb zG0e&kthOBf2q~YLI(^qN@jZ$BEKHl)l>_vKC*0sXTkSpYj!t>uF#qRg8b_EJsEn2E zR}XKz>GFRGi@N@k{7jpt<$qt6W7FwXiLa7NYdRQZ84P*0=;0N8B}HV3Lm=)Tc=QAH zxYPy8A*@A|-H{TPcO1|Xg% zy}#A6ZssZ6!=c%_9h;$h7iIPWXO-+5rg+HCkYpWwVJKy`ThXp8Q)=Bo!V=&xPKW)P z>pt62rFn%x@{|lC@QV%{TEA=?NUDy1r;h#2;_Rmb-*S@V<>Qa!k6-W{WMWY(Y7Em= zL3c|lAV;K65+ZnNVof*5#C&-lqzv}33vMj5pH$TZE-3B-alvue9hzv?}${ ziMDQ9SB1HzKCA#9AzoA8KCGJ)0=4ys*sEXAh(P*i0uJ$lmoF?WH(e zyh6KP(zL1>RyM|vk*J9$+_rn$ZkIF>rK40@zQ>_s3v00JZ=ok@<3}oSZj?uQF07Ow z^P=L+UE+~A7a?o)biQZ)%rCK>WznNaT{q-~Au=HbuwL|=*|3!I<4YC6r8dU836)+> z1S?u#M&%ibH-94r2S_%q9R0iB*I%YE01kSTZFhcb(s{ThRu;7r8#1S_@)mXVYZkwm z%k}KM)tjUN4$iDlh=o{X25sr_4BDAmz?TiA#8r+ZF*;nwi{fLD+|7UHN7SP>rG3Sg z55T<=YDc{`ps$5N$%Px5&Qa5D{!_71&;kuC@J#>qZMly__pp1iXKQt{Dg8%`SoYFz zEtQ{d!SX!`N>0g%5HfzG&=S7npG#^(Pwb2d7tr6S8VEjHBmYDixP&wAtb*CEP z8B_}=q9h`Wh{IK;GPMze3{af=x#GeIz{vuP$T@*r6i=JFR57hwarFe2D7Lv@I9A60 z7SX5^{_2>8w)f|}G8?)^4A0ouMKgcR&QG5gO`PuWkC`ve3f^!7n8zL22GZEsj^|qW ztXtw`$GvTo1(44ee;vN_SudLcy-jz?E}_H=?5x?V%Rap<5)tsHAK{5i@4?SkN}`+T z{)0(6elG?j#&B5w>L2yvzX$GEQIX3Qe;tYI{VAwHmO3W+_f$AF!3062x;M`>q0mMYdLwt_%S62LeCu#- zx(W;GrrMI+=;T>}Abdb{c^fM5O)b#-xY><)wfIiQe7}r6G@lwl?fE(rAS6}kB>?{0 z<}nXO@^;MCZ$O>Mje!G11Ok*`=}606JTT zGPOKg;#oW_pw-(S$3#Ci=BFF94zLjo5<#C;UiHP$Cxt-Fs5@Xz<4(y4(Tu%9fEHO( z_@AF)PR-N;@7)rt1;Mo^x@xgrH}y4ztF}!53;-cIwefl@7?n_B6aJ4)OfLewZbUd* zSp64RS}Gxcnp2vg9LIyC*yZ969$k1E#!C|cCPUsD6UyvP2hGMd!JoiDY146%B12Z% zIP^ofJgr*w`lcM{O_G1TcVp6Tb>)Wd$LERNtqSp=$V>{o#9FB`?OM7;9seR;YKnE~ zO#CL3WVISHgv8~u)8fHh-XfpQ<7YFu2q&@z^5O6Tvifq#>W%~Uh$HW?HM;nlSxi=N zE7_MR6&bE;lEDAU#qb$nY^`)Q7Ws+-P2-?zv*DTJYztCwGeAX~b6IGCzwzPkeEvbt zjmjkUnJdsoY7j@0p@t^5AJVwA`QSdQ{cK~Q_7|64B1Cn966*)&x&4rs>W{h;KJ`Te z*ki&X87I^!X3$g%6c^@eg@25&bj5~d43@MZ^=jedGaieGCQoU>V>J$ZMsN{H${kT? zJd5!XZ$f9h&?BBrF_1QM#zllDa79 zMo%!L_VU`FuRU<%Q=jw-q^bP8q+^ZTVFvmdZ;2EJ_y`txIbUogt!jP3e}dd|*}2kD zoGzoAVy;xD5l#*!2O3j5Sb++#O@VHiZ*bK;lK(?w{_ht6Hy|eKu#^*CBM%M-f8_G! zYo^Z8ss$3D*f{*capCL#sWo4EUgIGZz;zt7=*$_J7*F$+oj!mKs!ixubbzxh!9|djA|`1Mk6Qm_J*pYd2Gh2 z2!4Ov^Jf{KEZDAKR~&@N)4iZ_2hZ2Hih!X$GIN_(s&M`!y&unSuSX^_ec*paGKNWJ8-OhnO&n&r_r14L@pX zynSWurAsI59c>${e}X@5-iz&YsJZYTgHrV@CLB)T8XJP*OlKz5NrkX+kQatYK^gV` zs?~}{&n^OE+I_q`ydV6T$EhOz2{5W&l3$<0eLAr-rk0nic!B)j&U7H3se)^o<%+^} z0yp4LWZeEd>6;_&*ULQ+pKRH)yLfKbho%cD@*1)alEH_ z51mqlP;JspwG~+lNyOXSr2Z<_`E&7k6fUbKceyK}TG2*a>YEBD`{jlHYF=!ZUl~h$ zWy2f=Y)pbx0L5V%6t3Z+t8|4pa05la2fH0XVvV7}L+1+wLc>F~cr26G^C=#$!I40v zZ<9ZpDe=$TEgN*-;+%?tOIwh{c4bg;$JAr>CVcQiTRDD{FM^_qF}g#n3R9|%r(yq~ zlQ|+cg-?#wnkd3%D`9;NqxSU}v%bE575LU)mUIF}Ec*LQL##({MwF9z-j{%GpUOl3 znX*hhnj2bR^J`1jn;RdkQaxJ5BpS4PxaX7R98=8BC>yx~2bEAZZe+k0o8yTDzepL`?KFsP2#1O>%PrU)AtfIs-udIexDO92=^qnT zaj@F3Q6+*s65-Sp(TV@9Or9{5WPTvO{APD@iYZowznuq+aAI*0$iBdiw3D=*it0Uk z;wVh=6YdS-+5%=b)r$EQviLzCk(xG_6y8k`{22=IfetQeYXhWC9zb4b(k3-OOCa9H zZz$@%%7Kr=WE2CC9|S~HXo@FiKG(U+u)e5Z&oBZ(&5)wIx5AR@DhDwdviocw+Tx}w zZRo}E$}Ronns-VFe0s`Fj=jBK_{-N9gR5CCW1#w&e60Yq%Fg!z$^My?lu0q^Jlkjmlt-Y=C&|^i z0RIe8rI7$2(WIb!TJ)V-3?q;REhE7050ccCCX3Bb*h^J{)iWxas#NIFY+?75qdu;y z2zxomp;QT%62p*ryvjay2aF@?5S#be+3KN4HlqrBy>66y<*?nJEofYs^1!Si=ag)u z+~p50Lj#&iU%&|WZ#&7TlL}nEo{eIRxAFRt%pre*QVFcCnb3^tgS4~Al9RW$ow3N2 z3o@?*FuFe4eE45u4b3h!{L0`%NGB5kM(P>;q`IX{k?D28 znzA%aSe~ugK9~A)k&@IYLM;uKBQ=-}VJ9=P?PGPeL-3tV85cr!8p8PbxO>JlF zJOC7)*!;CV)QYW1(A{qc%^zWz!2bB0`;i33Mc&bJ+Eb)|mS*;ctP=IqgggCvOj+Qu zY*GgN6a{N9M96_e3x@@C$C%0|rl|wGnx-tG7OxCkq{=GU#b3^h56YI^Ev-!=dks%O z26H>h3IqSUvw{)SzYwe_y-G0=5=>~F^8fmozbt4kRQ!8rh}o%=J|BpvYo=qhZ3(&= z4jNi&lfFaWbbUv5YK=59Rg0wj?EKLjK5FMws2_iD_$v2ELo9cWB&3u4d!4pWl3BX;z!_r%+k`Z+g74zZGG(&XJ!(&5rj*_I+5Ek$T-DS~LDXa_oft0$3cQC4Blm z0g13eo6KO(O|iXevr;!Uv`*N+Dat>8-0F%TR=*Fb5uS62r;!7ej%T1XnccD9O6apD z7y76sdFxBB-i8K{Zr9@e8;KS}q#ss7;&MSj8+(hs_|1y}${;jgv^V9q;Yy2`^4A`a ziis$YbeWpgH0x3mWE4%fdJfIN4RCj*&wOL6Rq9zbq5MAtmdpi^#!`egys23X_6bhK z?=)yf|5!Z$3Pv*^LD~R_XatNAoPWEpEN>*XAn7Bo>XNs0F2v*`7<2YUZ$dkD=;+<} zp?S~UL?5S`zfEtJ|K!6@TL_(qscRwf?)u3Sox$ym1e@`68)JNs2Mqxn(=K1Q>D(4~ zR{4cEKSrv1D1jyx*r&S$P(ghmInO7ggJj7Ei`flnB-5q8)t178uW#w}|0@N^hMxad1|Y-2;*-S`H=lZ#xxRFkY`AE-Hy>U%Xoc zKLjZ~vHrgTC(6vv9nWa|G%+XTO4mV9isSt~fbjW^p_rK0*SAr;i)E5OKvrort+nc= z&XWDYXH&?`qc(1yQcTQpg+TF!8P1xL&%Xd{Ub!TD)S=XJ5EZ)LJsAa$W_tfNoRfDQ zMM!7&WDPw1#C5%Mi@s7@imVA9vk77TuiXPyi1f`-()vAUEDWbS1w$ zQJ1Yu)8{8H;MS9J*~i;+Ca$al<7N#*QN6sNNU83_f#~lt_yh~?4mm3}D+M!J@THE$ z-8*ggwu}i67l%$~2}lnX|4*m;Jy z&`|aEZ`}U4%lG#Ka$Tfp4&EOZA_V&)7e6pU8B_)r{>RUuTS|kK)f)0c5-$V=Zn(fM zibAi&;cq1EHXhauPhISn&z7gy|1uQ^s3eS?5pU~+?5lD#8R3^QAD} z{~=B#fao!W`L|a_Hkn39XXk}(m)D?H6lTb}4f4yp{>O!t;{!Um& zhi_&`(i6imy)YDmwyd6Awi$?Nl{us(Xwnd&C>52wy>$jp4w>de(6RpnS=DtceS5Ck zYvZLqWNq>_!R8Ug7dcd~!=3wRxr8?4vawAznY9*T?C;C5+%R;viu|>j?$7 zeOA~gC~|;YHnNgCgzRuApL`@iQkG|cv9Gw|zarRBrZ2dO_`$)%tf?%#Y9qq?NdU z39&j0&&{Ba@_}440Z2?ZhpDGi6AM8+7@lB zTaqjCA;E+*O3$9aSZBt}U*13k2I<G(#g~4wdT$CZ6gtj#pux-mLlwUu=_c4e2$n_+96R7i&f&stZ z%3h2wf+9Ia>gY@*K*DX%`tp^UGlHTJ(O-y*Ua!L9MnUkw(`rTWDRTt&%eOe(obl+$ z8A!)(cXUDWNp>Fx$;0STo+=|+*1?i8hwZnhvPAtH^0q;yKBq@<)Y z2$Iqzeb$XW?|aU5eINfTd$aF7*IaXq@tgRvmn4#KvaMjx-RviacZSUCbBfQHko&$O zTf6DZuVOL^DFmzFUxtD2PFsk^_?U{^7k1eQQ~-oPtQ!78cnt34HOs;~|C5|aFZ7E= zRq(VJJ+;>kI|(B}^Y&6mXs2rXVmg)T=KE?uf^CIgN(zqVYsXmYu-ohSVDIWx74_^M zM*EB8qg1FEHt(W--FwG((V(!7QGWl0+KLWc&W~Ck%vDzN80>m5SwVr!5v^HdFx$_Y zXH6g+(`%Y2iq9lMz5y2i$vq0-?=Kmt8h6||>HwIiAW(l0$=){wV>>HHjSInykLose zvA78rFdRU z!|Z?HU+Y7DZC=g3UXFMgjFi9k?s2r=^o1ljx%|O83&L+gB_EF4;ES9hSg@V;SG#-Y z5*Z8MSJla)id3TzlH4PME<5R}|0e#Sy9kS8YTM%on`B38-dt}~ z)jh&F6s@p7al*Ly$tx;$pomhi>~6l6YL@i&uM}RdI>hozf`-FQ_;mxAoDJhSXBJ*@ z+&HpP?=qz)Cu)UjK8k*RfBSj+t}Ib{&tuCDm#+p9FUR(Ynkfo5tR^aej7M$lgxuOv z7?D^*HYzV5dCv{%n8h*x=;qJ%MU)pQ+`N}3tgNc&sqk%+@iAT-FeWN}cfUsqvpT%? z2;>(U1y|!ED`)ut?jfQSQ93B5cAYe6RB`s4OMEK~hh!{LmY+3Di&!;D&KS2)@&|sM z7a}`5OWB+2rszT3aU-t)2jPq!b(RSJNF)40rzbU-oZ+Nm%Qz?;!@^?)(0F5TqYE-P z{YzJHr-yU<3vTQ1e-U8o3_@x+w76mNk-s~AO_%SAbmZ07e^xtoMK>(nzh+(VSpLcv%0sVjnbsK1|t2Tx0P zBh9xukk>)G6yJ?nHp@6>Tz`G2LXPd>WXmpA$-z6mT|4EjY% zzpGV%Gn8g{uh=Xp6{K{KGMZF}wE6Z#bbK)?Rb12>6F6+FN;#IK#P_%Pr4W@52C1+) z+o`m>-@Z?KrUUbmAqwdr$FJ6)TvdX`d|kVUrp)kSa2ScO`pJQ7RnIV3I1QCgk9YgL zmeIqJT?^cfys#HLzldnQtvaa-7yXdS`&A4qo+a}AXh1nIKOhZmf*Hn_6AJn|B9+J+ zH6sr)4baYV@!UAHT+@;E5jZvoo@mz2MzW)z+=-A%Yo~OC6*h3Ve`^!1vFSOMG^I=Z zJ*l1?{;JVEd#Tz?VW%8e)nVvV;FT&;APH^}9PTump+%Xo5ZSfC3sAnB%tTdVQtmw1``p0 zV1$sID|mo?qHphXEMx8;_Xn-~ZjEslhtN(7lJmIZhmw*4e(KVRIxE9NTw#h9^o$_b z2gXtK+E4#H2D+#!_>DnOVU^a9>t`o^hrFmNrSo5)p_SoRRU`0SeTg5PKzP`>R$$yU zoI6z<7HxnrYBOLBA3Ep?7!ap=JYO*>Fn>w|zi1jpL%H^X2&&|(Hq#14NPBXxY5cQ| z8eW-1@n=r|Qi}(yp<$(EgY*)-v^s%OzG@|oUe~82)OZoB3YqsU5}gfRrhs{z48a40 zy)f1aG%&d*+E|z4d1Ny`Z!M14j5oxXC6D0gb7g zBme{B@^ZmN42u2`I}Kram0d^(Bj>FRprT&=Kw&?B1piJ0PKF&*5v*3HmWW8xe$J^# zYDSUeAB7dgJmv;z(TX#PcL#pJD4s~f+9!&a7l$o4pD$a>^X$J2BKSyXLh?eY7)dG0 zM2^EEvF0O}S@%LU@Zda0N6Bp6eWdo9jyQfe``o-{cbo>c_=A-e$f2Scz70*HOCF!p znwY(?UyS+ErEWQow|p&nFqEC_fAgfp5xT`A*OwlJcKa+$RI|@huBX3vKft-Q6#PQc z#WJ&@J)=eT6>`}-qypqMxZXg#R%Z?Si#JsBYp=lJ??#<_*%E9Nrpa| z6B|Z_>NNhlGC!Z2aae=>OvFQWaqVeqtoVHo6j)A<=plj!Ll{e8s|wnjL!|v-4?Z~n zRTSSd`gALY)JVjORJQo5K|_c>O{rh!8PO(@JNt`>zP*rl#5e1!)14*yS6{+38&tUP z@kNDsCttXp<{xXutZQ_$NcDX*1n?oz+XOc6!AR0;+b7TP1XI}pSNL+)P&(rxz4BDxvf+7X~{HSoXA5PloBvm<{O{)>gDc%KYryPv3&GnIPseBoY< z)rXMmDJHFLF*Mu{9#4j-#kAKj!pAE!%RHOdZ47w)$Xa=+k9@n}`8kjfk_5gLlAu|C z_oARMD`YG9jE=w_P4%ZD7yN%B4Iks(!N>E8Cv&g@IwD?xSaWOaz zyS&W&k!OECmM+(cg3pNZ(5U(9eNFtCIIdB)rCcYmS9N{$+NpXGF@_m|V5_tA$L@YXaJb%3zE=*E22~v+dKKd}XLm*%CrZT9P`WGMM7K9@*xs6*M zIaDrNUl-cGz!3PRu*<3QUGz^j11Snocy2WC=UqtpBNul_htF`5Zj1SPUN^?{r>I%~ zg{i<7RLluh<4*I(A@cE~xugGb6Zlv7aHq$o+8{}dRnN_Gw^9oKVEqhe=k((1F{AXr!2}hu~g%=a(sC6+=p&QXlZ`iyf8o*q}e0qiSZo zqBSc|Ro^~F5fXj%GeE+h_Y2UhT86g&#!w9Du#%=!aMXIpkSFj*`6c5JCF=RV(6k*;!uotlCX)@6tHFnW*sUMc=>*1;p@|Pr z7RTfldL`&cFa^i@J^=p>q@g(`{WEU0&4Md{0mWyK$*5Kj>!v1qNQnd9m#kvrojnbw z>Ff7MAifno1Vt-K{I=zP@SY_J^*!y~PqkFswDef+=;UVdJ~1#NCi?hNRx+(G@tGmD zXd`XQt{&`d*nRZoHyCM8SCumgU&|)rxsdHeajg^D3?>wmFHkuRr>npql)Jfp7%H5! z{j<7UkSQNJJ0$vcQN9E0OUsE|!m_qZW_dz2a3|>ul|$kYMsB-JIXw9EzmhNPPC*@r z>VYU-ou{E~ArP=AwC@zV=gYTUBKeo%U4pr{)J!<1q&O15ryP+ZHWF%_6_5VPL7wX)MfKMF%Nk}J8v75;c+qT3WnYX1!@`}e2m#&Wt0!T(J7~kp+~vw6HZv-x z^*f<4hY=h`n7Jg6R$@{!kgpw;RlXGeA!M#6Z&mg$B*&8K>SMnZg`+McK~x)vvwZjq zOWBQHAznNX2Pug@V37vu#}m+HDJ>=blZ2DF7a&P^IXTLn?~U`i6^di?P}4UHW%`O7 z{&H&x|C0R$*j{_J0JBz2-H%H<8jA8{sJQy2@BVGWxKgyX-<1#)NMuZFO7bZwo1A@6 z+DdkZ@71m$_kAvGDiIFeJdWac_h#hGyjHNw;NnNCGc$#8ubvxb>(*tF1)~4wTah@D z;#K`NP-xwzfPTjV2DN0Z;%^nIEOJbhV_6x{AJnBlk<~OPxvbcN_UBZMIBJ?KxbBL%wyJ=2h#~T)xnU(*Z`Zdw8UdoAkftOtjhY z?X4Z6w_|JI{=JcR>h82Yhz{dF;QG+NUornf8iB(y9;QW;Ut&*-n>QkbecpMvaRObX z?bx}~Z{vLoRKHBlHUFtae~t&DzQ{b2wt7QR2xPnn^cr9?elUc#gwpl`nq}VkWEQTg zJD)W{=vb`Yd=Jj#X8p6%enqn2zqv4c%csOkxH~Z(cy7rX+eKreSwB8W`+4q~R`JY^ zwNdQ}>$|?sRF}=DAhIX~b09X0HLlx!R}fV1j`;T7`xRW6k2Z%@=dL=6>#VUZ(9I9+`i%_lBTbN$=d~ zL;cY6Fz4`ZaTJ|_U!H-C^k(0a z)q8FgRzaBgRxl&C{NC>}(^i(eRr77^pk#Kh5-z0yJsZkO z|EFiibjc$a{obt`+C_AU`53|JwE1BK(g0w5UL90dabT|3X!xWToN=g2x(>FOj*N}( zwJkC@$AOa+;YW&lCH?uBLe!)C>z0V_T9{kEidC^Wp22nwXSGOtinaMI0ks9egXxAU zZhOZ6{g3j7d#mL%$#)u1D=cRKR^s&wB#@q#CV(B{9rbiQH_z*T)V|RrJi+h4x;2s; zxR+s`g~E}~Cm`kmb;lo)?xG_dRfA@3*k5WXKSo-Gy;Ygoc!zmW7{uUqp)|BJ;RZn^ zbIPb*+warmzRxDg;~;Q|!9ul-(2Se+eg|)a6ZMy0@=f;vsId#1;mKWZIT0SqxPP>#C7LyX5FagqD1+=7MFd8r+@nLP~cg25n$NbNwtBKBu_XE8gcSuikL^3w4k!qEf|Ntq zgilGTDyQm9jv?cYYu3IEPdajDx;F~A+}xprrh$`RS4+bjg;Rx#N8e+e$o&S9*C_9@ z^P&on8x#h*Iz0X9A2$SGk-p7`_wN%9^Ev$>f%y*-Mcw-oA0YA&+}=Gt3!b3(l-u`X zoOu@CyS2t1I*2Fkv)-)##NYsRD5h=CgvWi64TRr+Dn)+YHi~(3X`I@*g|8#q$e@@J zVnl6A^&iaRT$$qdkB4nA_jhWL=R4Wl%wNvi1RvsIc2zIzYM#{{VbS=%9@fGdqdgNE z?ma{Xj`grMU+;9JrNY5&{_>8J;la6jw@!WKJ}QWs7eRTA&AWPGa9qk7IaCI?lm;{T zR!2(FN5{V+#>bxJha|+I+M|KJ_o6QsTLrIdtNzW=U>60^cqX!02(aZjFY72KxTfs~ zn*PAJ!$h6#owkNGZ#84=ZT{V!fDN;@2j_w6TkHrz(~LhYYBZKql=5*^(7 zDJWrRfe$=d83S+lA)Tt(=wYZ9@#1G9-~r6XyK%hKT8GV^*Y|z>_E~A3xU&s?C$)=E)N%PdMeF-2{{8o0X0c}Ndgs?ipQpmYb*I+>R6Y`i3OuI->)Xj|yT2H< zLq&8bU!5@KaT0$>!I-W20_4&Uld}75$h(G&76zMg)#Z|J+B&(OaHgBM zVF%nTig;MrkhxhLT9w3@O+DpAmYo!W%CMisVBjDrmfMq>2%+7_(xtT%E*$ZdfNWvX zk5JavElqkGkr|+Mm+~yLRqhf%`{u2Jg72K)`_GN!&8@?UZX^Nf)E>P{7Jce~RWa;= zV}38kUrPZF)z18GRU@P!_PFJc8|*rd*PtNk0kYqvju=gDz~&6B@g4rdKE2%`(PiAg zMkmZd9%8@V1^JDzgf;)mKUqMbDKJcZLfFdo?0jCEy-~1qv2HX8N=IWiZqmnWF13fn zW-8x%=8p+y8$$2cNx!UA4-3)2RY$NrI@>cWkSyM!l9r!p^y)O4Q818eb%z}Zjs5Oy zlgMxdSev`ND)`sQ8PI32o(dY*RpZt=A8z0}P;0pVr3=RsFu_RJU4<_VpW~a%&OH(J zKU}T9OqHVK6&1o3{l=d2)|g(?sQ`>IH^Z9fU%jI+gnh$mQ5m;K-b*wd0E2L@y(SUy zld;XsmsZ~1zXRj4XR%D4AfA2-4%?w$Wf$cyg(ra<<(T`o@6t|cgp1`g1TRwU`Nn=| z#rCVPbnC=yF|lrXC8ge6bX|%~`XF@Qg*5|fm`y0OVkRAT zd5KO$hk|>E;y)g~t;pQOk?r`(OBs~0T?t2qCI3f6H@Q4!X?k7~7bhWN${6kMWJ>Tc zrPEZwUK5kCvKI&sNgB<7H=;9ZXl`OtYR?*QeZfqSgKgpqo5jl*E%GEP35s(!-51 z;bnVl^~QkFB_jOuu_m_1st_5k-rOL``6~+$Usv=IkZ?Hn6am%=GB**|itl?Uc{B3| z&wfY#BfG{PxZ$Z6c8~L=-mO$4-vnJf`N$iF$jZkklSq8yK->)Y1Fk8rVn>Z#VG;l{ z9|lBE$ATfv8@|(m$I}Wcb4z$ZTSF)BiP(H5EQN57?huXMuGg;lP$>`_zG#yOLn|aavWoI_7*8X1Q@;ammkB3pK;IH z{}|UEdVCA@6>01yT*m;%0$3yj)-Z_Sgu)yP%vQJ+mdeGzEGdWUE|3|xR)zt&{WFy% zQR~}}$w(cS5}pa9z>{pR-n7PajH(ZlOO{hE*@rGLT0H!z~yehREbvm})30180{ zN}NAxZeYAR@P_5EsC+1>U5y%+=oqfALSL5JO3CK@&ank;GN=!j{BRFHsLB+IwQc%4tvYEO&z3V*Q^SOy#*_8_oy0ZJ-H+ktx9 zz!MTlnbmt`Lw%Ln599n`Ub>9r<*N` z{8pLh$Of~Mld)K31n~cyw2`$evY?X}It=*sc8m7h@JpRL({M~8>PSAp7j+TXd(?~u zKS<=9Tn3=naDqfjQ{J^9Z_#q-w3dUxUomAuWC4cvk2uGll8513%j|>U|1d0r9~c zmd4}o@gw>2{>-+CFS**19`k`%fq5kaG+~UBh#o`cDF<7iD+x2}L^|r^@!^NfshfYh zOq@I#D=+bUdh3&0IP>XYkx=K{j6oG-3$FkgDtz4lKJS!2z-DHr1YopD{aU8^U~rbX z{{5eo<4pqsw)+YWvWPZf+WRN`!fAmSK&i+Fs6fkEv_&r3!e+>G+uP9y`RcIl_d7ng z!o2g%oq2`UGL`p%?K#gG#7|P@DjhsDe-U3e!dZWuwiejtYI<)#GRRx>-b4_z)aEpt z3+-*qo;1zqcdp3s=+1x(uo&;Vsp6${sZ!^iDof7ILg)(dg)o788P%gOL(-oDA$K9@ zZwlIj#8{ZZK~hx~)(Cve|3NGSS4facL0_Ah|NTFQ*h4q3K=h~4t?SOS4}Det*XPl? zRTp3^lISifdepYwGnft&t5@-E4vZ zD^Tp^$DD3=YjKn=s`)id)21~F74cN7OULsd8)gX{BahHDx za7#CKNd(lUlSBgHLA0`twN*#pR03$gdMDv_Wu_f^r4$4(^)2tlw~B6HDy1W)g#N-o zVqohdD0na3E$tnO<@C_0Z9;P@386kAG+iI{rkF{7+N4~g}j8A zL@itK+Vb4e(9m!=p`D16^Szpf;As7&Yk60uyl2A6Jveomvn9X5uj0CrxuxEur!IYv zdAsAi_}2vN6m5i1uRW%N50j;Meu-coBp{-Y5-usgE_`iCA)|=gS5YwIaTXD``#bFB zdgR_-qGD88*B%=6UiUsR5omD2gTFam_4c?mp$BY$?NuOu@cB&j5=l&xyp8iIsB&b{Y;C(GU!diQRBssR~mc|w3sxgW;Ch# z&IFODjSSHs#EZ11tp2dK2cCO{s&jX@Lj`gxc;MUbf3lVF2{ArSR%YIrgrmd7|6E=P z)>LLYSA#3-qh|Y}CU=$T@Dxx4GwxWs;Yt>d10gkOPO6-alPmLHJ|AU?#faXLL0T5r z^+E<<*BCH2JtNT(538tgJy^3}CYR&Q-uzOGOTifl=QlyWuHOW72KRZhYfaGJ@~n)% zt|8olH7;Es&^=ic%cehBVy`MM*Mhhj5`@oj7@KtAqyXv?2SGrl7?Eccpd3=XIoKWybdh*yH0<8|_Q z{ClLB@~+%#Vzl>1hI`)ZdTKNpA@3cU=}AKw=$F%;u)jmO&^N$-I9UK1T<9{YL6Q_crZ3MG(YhZ87Y!aPVu4;sqBHC;+!LJM_ z;`eMZH13X4`nMepzWmWhE)mxsZ#E}28fX77qNK&;)>2*?&3`k$>RN7R2AB4|M%f9# zn%qL`ErU4+=8b(AwcpQ3n}5LBc=8-yLW&;9K5uV3l@v=^ZPUW#Ht+plJO;A3f5JuGB3No=0a-K3P~18BNhh+DJ(j4 zG06PrpBe$tCjQnr$Ia`LuTuv0*5Hs>uhrI}7Bz@Y#KT{5gXM^6U4bQJsUR#|R+Q7+ zqYWOUmd9_LEhdNsvA4Iz$JJ;D?;im33rcp!!JEKCJQ~H-sCozwBf|gU-YKVJ$)`jt zq+YV0p8eITr z^VW+Jt~+H|G68>;G$eR0C38t*CcN8+N${8bo6^GwjF5*T-N)GKxeM8UbLdx}oq@^A zahOnJ5y68KgZHz6eMtf`1R_Jt@11$dYD_-c;64R4CDNUK>;O6c9O?BMEaI>%@loV> z3vL{Dx%$30e~vQJyZzIjp`KsW>gx+8iX7`VAaUx4mKiDFc^a5pej9v0XaTK$LJzol zphE!hEHiu-1qAxqp@)XFus688MsvpIRbFN-U@q^@#Xe!Cyilh2{Kc?j;u?QUy|;ot%p?Ra*wslOqdFLDQ41e1V#hgTX4p3U^OKSB<^qBr z<1YOQ0+ygRZEFZe@zEEOFw*swH2qWMY~s;e!D{Qw83(d^ z(PHWu97)P}< z0xe+-JuII=;d};4!x0I>!PnmRQNKfDZrIte-<2n)$$`$h=(r{&Ko>aZ0W){`FAJb! z5-_Dqd&;t=W1l=`Z6yUq=)aMPuizeZa^4 zper2t+71M-QDyE!A)yL$M|o6h5TNn=NfJl7K3KL}o5YgvxITTN_j_BB2k zN3Ocd)c+5R|MMM1iDdaII#i$0+3dB8HF)Bdc;hYd2tA{pK8*ul2IBG&YXkGAzGe0HdC+;rjM!*AYWhrozJt>Rq{k=hyp zEWv63SZc3+Z>{x11#Ru|WD>I=sZ7w{sHz@81u?G}$uS&k_=1_ogWkD5#+eUtdqO|jP|xI>K1o1O2U&BEYNXDogUUN$!0eiyrFlUPxCH+Y7gdeWjT8`>FJP&=4g>#I<|q}hF?$z)GsJ#D*oz&`O@p7; z33h|Zgf{j7?o9p+m36^IR z0ynQQ#P?@YqQ_)P(*!0MaSmHuvFae$n0mN` zKc}ht8Xsg7YytP;1SnT(BlK|1_xtsN;H( z%FHvnaw+;!nwWfv9+tnmphKKBFY1%_w;@xAy8t_mDJl~>`PN}l+JB32WY=pAl>5_q zTBH;$g-9GI-{WSR&MU&n*v2h%`?M_d5?Xd>sRW2RUhY*yJnC8j;(u`FHEt|yA`h9w z+LAV5RKW;ov0P^1h!!l>NDHyEW`%FPeVM!h`vN-`CBJ`f$qi~=5w_&Pj$8Q;+ScI= z*g{lqDiQ7&%bZW=I3-SlO-b>F?Q|z(az`Do!iPlGzVjr)-CT2C3&CJP3d1bw8m4Mf zNz6noU{SJY-CJ?cXGcGq&OWpKj{QFiZnvH&>p(;2FAIg(exIDFtUM4DM*+al1_Vja zA+u-5>SffXj5V*wtB8destFYx8^0}6f(AL$qsM)E)52p5=7g>1{im0)QE4S^!GZwO zd$)AeaTH9L9-93arSXie>-2-^OZQws+-Hcb&F z|AfllWy3!>keaGld*S!!3md1z4L+tk4ny2ghnYen9oYKStD3e&pS5p_=$$DwJ_tr} zd%4H&I2G)mqj-ESYaS8*DEAE@04C@5&L|@4q{~9WFmwG^XEj9GH8;QqJ19!mpt|x= z{d_lk%OYkkDTV9WZ`%62{AIhTv5c66|4Bptiwy(hrNGA^39rcpmbZdeJ^6^d(G2#~ z*5+rOJ#r}|y#j}sm_BkFJFu~6^@CA8osw@i<0ltXzU$+6?p;X^hoSf6apb4s{J!ho zj?}y#?u+Oi2oT{0jqFI=K~tIW2#0qaHWt-D`>(le5)gl|Xz^LhC*Wll=lT;VJyiJ* zk#l|vrZy}wB8R}#f0NKW^xIOfr{^bHxa9o*;|~lY!L(-p!Eb(O2c-={QJ}LX=LuJ( z71)YfqOlR0NM@yK=hq@W1b8}L-*QR4o0B20Z(FF>*WSejh!w*&O0yabt9s+ z%U=-`vXWru>j5ZrBfFf0bOp~d|Fq@%-DZ!#pc;Aq4y=LNx|%sby%=mOl_*km!~$Ky zAW?y%Y#9A1~*MW3wt6LoQY!V!<4-S*Z!wLbuva# z1ao+@Gdw)`#P)$UCkfizi@)BKT71Zm?(Z!n&uhbe3|Al^Br>dkN61B3X4K+@? z!Wxp4ay((wRHXSjIO z$|$Q|NDtGbSy15AL!qGZDoFVVIA!u!x=>xRjE^R2nybB4t7v|U4L^v#e}|2Qrg<3y zK7WvE@@-}k{icGn!yjdFx8r>{p>T%R&;qo*M@@F#1Wcvr_iSN)*R@pt{}z-7v8`}+pV*ZEjEI+V8oT$c7C4{+1;jww0&o*-n3L)Suz z%Z#;LvZddU!u3Zo1Yl`sxi|@J*w~D)R-M-DpdEMl3^(BMmQSqyjQr;!knuJ&S<)V= z>{7o0ky(V3YEvl$VUF3GT(4U+^roAdL0(!5JSCXmDT%@d@kOQVG)9vmX(f9!XG4g@ z9}EQpXvl{^0;&4vU&apDjbKNuaFWY<9WO8AIZh*w*N3?`iR@fA16vU>Weaa!B5NY> z=Eft<>-ZGprytp6)F_u9gE@Ho7I#}?6i@PU*Gx&aZR<5$s|JdwehO~~7yZ8!ouyMm z`8dikAY*>5-dSfY*Dx0fc`XhYR{ud_%z^?KIi1yBbCLgzXs?!rp4B*(7}!01@Pn#c zU5OKLE=oXaLa}<^q#mo)5e($9GPbrger{%Jl?V`9B`(FuTmWuKxsI|{K#S$*`SY39 zqfq%TBa@z@o`)BI?QkAX*`S1zgOFZ|9)j&b#!KysC(&*d8|minJ1aw z3vK@~b+&|`k1a2kV4HsuW&^FNuND8D=TF(L?|6+9!~J$uV}Y|TPX7Q?8u}_C<+g{W z=~TSft%Rk9=Y#DeKp5Krrv5ua=>-3|N)QBr{S9)JtoOxouh5++8f6V0jI>f$5c;(D zF|``i>3Pbydk%7@XaTxmTCxKi3W!YOE^JAbE0;hMw~^(xOVK|^9Op#_Ud9(KD{Wu9$wGtM``S_wmb`QF<2Ix&L~cDZH1 z0-F5W!nQ>%f>D`(t_F&X%d*VVf5-=ukE$Y#!i|f1(Utt~){k6NF4mQ|oB@UyDYVfT9w?{u}{sn2^wTPLmw8s-*va3;Qd z0PB@sqfOHqwp18o-yQjf+&Rhyt1$mRtRXVmuphgsm2%)>sU^2LM8r;v3ML_M{4OAH zzJ$35a_i>;!j#bQDi@v@$EQX@f1HFSy1ib&o{mg(JPmdlmO(j7fhmY^(`EZH_qDxH z6l-M{Dj4}n<-2sx>Mx6larrbP(@j>_G3s{lmq#_)WKNx5WBYrsZVx^(cwiAEzx0)6 z#^GCi6&7R+Qwy${`rm~xe=a^OLrF}i`w#3(J=}^$m4RKhWybw^X=#@OQsFBJco6|1 zKF`%&JLW4U>WS<94TYoGtu-$-ePsm1Wp=2`#A=>~9UZ{|d)c@@U5ye1k)Di55;+fwzx}e3xuMg(c5yFLmBH1rK%t)0G z3BhT&WtLRIy~OclTVSYeEO3OFxP9B`yA5tS3)5s?`8W*M}6JSYNql()VO`Y6567-<9?{kLd!XEq6 zDW3|j3PuOE%=8I3(g)u#mvgCU(AD zMWC*hc4=T~rz(RbRe9O=szD3iwk&1tbJv1@npghN=}09?Elz7jCQ%iIW=v0)7mFvZ zJUulIY1ulG>~h#6RS^`E_nGfFKQkqq8F@Wnown&F6hw(X=7Yb~Wicu@!$W;AKOHyK zrG^eWU!%8r+$`vRGW3I1J`aEWBdonlJqx+Om@OoAT&wRZ3{yItd$9Er(cw?$!g>71 zf95$+cyp`z3)p9{wsF|q*D5J$5Z#^?5*yt$>#TXsDPx3h=H zi4m}&AKdP4tjay!P5sX>jxb@RbWoC?CKhiv(d@K&eG%A7Ibm=q11Qs zap91|;?(ZVpq;1%m=0BVN#Nl9&ML1_uZcXP5ZtqANy+W`p2|CI7K=e-T{Gx5n6fr* zb$J5meN}}_3G8Jbk?cYT(cw9a=fzkxwu()yE*imUX=D0Lbx~Ao84^C2$ftrGa}9T1 zr0pM@dp8JA=%TRWem@SDiqeSjru?~M_^Vc#{-X$o$@e8J)Pc#g*Xr^m>YXBiv)qcbBuB^!TNQ*AU`v+sU*HwZ0 zn`~Bd2K3eVZh#XEu0F$S*b#UTE^>B%H32)c#yKIAbwx7dMzyDW7c-I z7Zbh@+wODwWiRbGR`ABq%EWLV>+atD9PZgrcDP@e`XxFss_A%Y>?x6o=hZXVQ^hat z3q6aUy;#oFqNIfGfak~elk&_jLXGDLR@}rDc<;6jjVznjs_~NTOXpG>Ir1?pRFjNW zKl@9e1$vYwOtq^0xnbt7_Qh_Ea7;xpdE`za6csB`x{QG!5R!%U%Et4zhx(mq*D{X_ zI*O6eSW4A#mvzcqo+h7(&WpKA@c5is^43VK2qiY;ghX9RR(w+BNbA%`WnU2BJZ49B zYDTBwTwBIpd@rdp^|EoBVYFOWSQx80K^Hx42<9;}Y{Ows-!mb|BsQq_IK&L$?fl`j zrEnMuq9<`MX`hw}7Kh?#lS={dww>eb8G1lB%%`?UB?!*s*W~)#?{IU$iM(06qrzVH zp<9f`(>>9Xo_!2r_uy>S@4-JxOEIocKG!hgWxPO7X+=`=84#5d%2&h@U1dk zNu0bj`BN)3diQi-95Y_0rr!hJ7ZoJpH(yPv?=M7=kR{{1LVb5 zM-=5~1#kCO(?0t>IK;{||KsT%AJfAD`z1ouf;rr&)@|szQzsZLK;0-HWIR z@L3_u(6*b<=9?*wy3ix=9{BiJhU7EF^M-YUrAT4A>)uIUp3T~iuh*)FUyTpJlJ6ak ziDMC)H`Vdgw2K9(<)3lD_f22E)vJgQb-HvbQrSk?3v{fKE9D5;PL>U^rRKmvwEdaS z6l<50#=S5hR*h%(&SrQ}$yOZ+(8%lICZ7WOI#L-}%kWQ(BS z)g-P#@`z*HUq8rwm#!y?f7sOjdrh!-YVgCb@D~epeK}A#VUOd^G8JEwh?sZJC~(tD z_;NNOGibvOT7u04!u7Vc6NOW<96ijz%dXfil$W7)XGLa*MesOF=C>3CrzH#(U*~L` zeEIT4YbvTihole|@nCt3Hu2-ceo{hHwf93p|88NTeKa}b zZSz;tv{wm}`{KDX!gW@wZ>W7;Ki(EmV<{6uFB3OP-#uLmoe9>>*n*hT3VG}>9n(4z zXN__b-v_@p*v!x|{YdK{fms5wsX@Q*@0S|Jf_Fw*JP*Tbz?n4Cp2#ggXN=s6IN;~- zHiMdUaKEWP!p@M|P!WSV(&xeGgV#oxtv4U`O+#V+<>seoFg+5Yp{fA#rv!-(B+r*z9LR=nG3RTO1FJOtZh~8snIwdZJO%8pEhZKNfp|q z^YeXx=0_AqHE<#y_ky$TYG=$VsvVCslF9D15p{S8($0xs(d>W`w+q8)oEIUzu8t0h zFyTvOPf_VppmX6)&{r%hov$H$4|jZ!+sxs~1tkue7`bcC)Tar# zp-Y2UGPGnFsfAkwt<-tnthDXHnb&UWQI*AC%YS!QMUQEsrA%7!v^1D0kJj7j)Qb0O zD%*m;MLH&pXp*^~MX@6)X`BpHm@$<%J;bI}HdXl25=4A!jeIVQR?nr%|Co}T6|vDs zV@z=2#0bg^)3nxiobsq27XP>u^F&f!3ox10D>YXRxC&5nf%4I6UMko`iH zrS_BQ8DnR6l;1q)5-kq870Fq9>nH_TGSB0J&5J8>;)7bAmnQn@5*H#x79n!kub1k( zI^SjRCs}nN(JUbzpHWc|gKIO8+ON`ywqK}oX>^k9b37%}>e@2q#M5XdthdCNW_)(? zmM5iGHlw99+%nVvMu(eG(2r-8aIXU2ccg-^unhC}bGE4$ZtxW3M12I=0PoWY9e*zq zGOMiLR)UeTnKwqTNeY|NO%*v(Ie*i`RT2khVl4sEGB$&(Wn~OFF6>2;mvL~9AgGC3 z_u2$qVwZb+WpQ|5xfQ3O;HO=}b4r5mQUr^5j&M8q!I#)t>S2fWSwpfR#xjEz;p6n* z_1`*GK4DHQR?X$Vb0bgb{Zy(OKU-GKey9U=l|++L$40QczyDciXz-Z5=EsyF*AFXE z?_Y|%s?NVgZ}pI-zo0vRBt8>kIjPAEUYV+mjm>yebG<|BN{MfPZFzneS-r5Idi%-z zAomj)=!cjRb20tWYpd{aPIzvhd`hXPbiiZEcckjunkG{Z$9(IEQzEE~&*OC?pM4f}p4|OY&L%cE&&UA%VCJy6aQ$(C zp3G!iAIq)T>4N5FVVKW%9o4q_0G&y^aXGy&%YL-2>1mH6M9ImolqIchguai5%ux8Q zh5BvvLw%%t3dPJTQJ7N5H66pmhx=1w@mzRPJ5oB5l*!W%a0&v7+J0)bR4dT^SofO4#BA^78fz$S4dm>y$_8d}yQk$}5)Nbh z7W?}vhJ~eHvrye}_hSuMtK7JNHIcVs1xuyjvg^D6JyqN+E2huXmr|Nn8Y;NiMb2%A zT1hEB-}|}0EYFv=x+7>%VP5Qk@)A$xlr#WJ^eZnXi?LU`1Z{>e>mPx)AFIrB^EZeK z)#f|CM5z>6ifE@zQ3jfKU!v||Nz+5Ugs`2s(CKW7e0tGclCFreKG;O9^ohdK~TQ#KV-{PMIr;A?D z&Kq(JW}F|+p|S^H!<~P4|9cI^GZpWCfXk0ozz;Lwuo;9a=6 zCUInT4|i&A5+C;Tf!XWVlh?cYNKcKCR;mKi-lspbT>CIMIM`0$U$cBo*!oL8yp6vs z-{Mo0x1m}f4wrW0G)51R=he4|LK}=&KeQ$|k9fpTSO<;;M;ZR=As! zfbPN!7sT!qQMtQEX%_ayNu^P0#ACNO{(HiC*O1uAAzqQ8+unmh)ZbJ?UG`KoW(10e%+gA%uZyNSdSz-xhUXe-f(ZOH+mi+s1hI1QroPXY~^+)62 zF*0617V;~wu0}Vp_Dm^fOa4Ob&i%gkF~yp%giQRMOSsha$zxx9iDtJR=>>xCg5S0? zbX(aN-P8}Hh0m2QP~a$Bt`~V`vi^~-`-dqLwt`?4(^~N8Z@cr*?g7d;yTSfW*r*10^ zXFJ{E|HKkEu@>prm+C5G(hy5pifZZbNU0@LRK{LQQ9D&iEorZ|LlrG*8AfZTwsAx3 zS}_`mCZUO~G_}+kOX>Va^ghr1a6i2t-uIm6Ip_R-zxSLEXLXFqb;{7`$Kmrv<=5U; z7*~gzHi>GT;#;>|ajA>*gzO#x)dR9s@Vt*ouQsR~4UQ_T{#}x&jF?NG*XPCgrkH}= z5sX{h{#frZRNWA!vBV9^8YrDa0(^<>^%vp8znmEfHPZXYTxGy)t2fy_{F`UwF%fE2 zWtECwGYwm6qlcb4&4g1?#PmB})ErNh%_2Yv>`1yOgXZ8T8gE@^uNE##>G=+zj&9R( z!x6gWB%(+tcfg%tTaJKy?Y`Vg694225^W}PKF~T$42-Yzb?BibOVuoSyrm#pac$Kp zzN@|Frh+4inDGAHi`Q4?8+Rln#su;hCKiy}+6CR-jFDlQ`=4ab`oGFSl8A}DYDm;K z+efX|{5$A2xHd)hpIn0+5ZNa z&Hl)^hi=k-u*F#9}G}LERx}bJK^yT_2I~oIE`<_>Ewil@Yn<`_X74c89h$U0$KEIr_ZU)(u zN6Vc{_xWwd>(e&xF28+YLmMbQY!s*v>y%iKtHeL|0Jj)2lRDAS-wsFJ>N2hphp}Xe zR{UZ*-i*(jeM+Ff)7&sFJ(BnHY6K~|=fU+GME}+p=Xu8DcyH(-gw%?7EXx(fgDFjg z1MFrg;89SdOr_Cg6P1ydj06T`;Cgnkq9BDb3dluhOU|Mdai>|V-GO^7@cT|=F{KMOO!!KJZDCMVfwXV_L{vZ0jl1|-#powZFY)&*^TXA=I&Cz+1!ts#4=TozOf?2Hqw>*&@#TB^8*-z(KqMoiJLodcU< z#gvBi%0XZ-;S)hw$_W>DZ&NiY)v-$l3O34ykGF;Z#_t+Si`2XX+)H`-C~} zbRM;Q@;723w0LoAy!>Uwk~>m0{S>By=gh>=)6=t!i$SPh5R_+$?cMjf^74WG(qsCrN3-iz|VVmkFXYZeb!$!2VFxW|ehnx@yGLszV=lZ$QIXbs)T&=l zM46HI^CK|GAn;T;F7&begK zIh&8u{F-m1vNK-{rA=`~I6s$M>+%&pcpN1(f`oVJF~Nzy%PreX_ERI>(!oMS2J)Ai zHZQE?xd5s6sz}FH^`Q=5d8PX&bq#3YE~3ic+9suWP9%0UpcWTnD-sDzM~|t-AhVuH z>PSCyl`E(>W00Kn1U3xr^|UxVFot~cGm1Hhm$m>BY40k78nLI3~& literal 0 HcmV?d00001 diff --git a/source/src/main/webapp/include/transversalobject/AppService.html b/source/src/main/webapp/include/transversalobject/AppService.html index abbecab36a..d4588a0b9a 100644 --- a/source/src/main/webapp/include/transversalobject/AppService.html +++ b/source/src/main/webapp/include/transversalobject/AppService.html @@ -6,7 +6,7 @@

+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
- +
@@ -100,7 +122,7 @@