From 732bc71770f13c3e612a1317c5cef004ebf6819b Mon Sep 17 00:00:00 2001 From: praveek Date: Fri, 11 Feb 2022 15:16:03 -0800 Subject: [PATCH 001/476] Move rules engine from internal feature branch --- .../rulesengine/ComparisonExpression.java | 68 +++++ .../rulesengine/ConditionEvaluator.java | 266 ++++++++++++++++++ .../marketing/mobile/rulesengine/Context.java | 26 ++ .../mobile/rulesengine/DelimiterPair.java | 63 +++++ .../mobile/rulesengine/Evaluable.java | 24 ++ .../mobile/rulesengine/Evaluating.java | 18 ++ .../mobile/rulesengine/FunctionBlock.java | 16 ++ .../marketing/mobile/rulesengine/Log.java | 67 +++++ .../mobile/rulesengine/LogLevel.java | 27 ++ .../marketing/mobile/rulesengine/Logging.java | 17 ++ .../mobile/rulesengine/LogicalExpression.java | 66 +++++ .../mobile/rulesengine/MustacheToken.java | 79 ++++++ .../marketing/mobile/rulesengine/Operand.java | 32 +++ .../mobile/rulesengine/OperandFunction.java | 27 ++ .../mobile/rulesengine/OperandLiteral.java | 25 ++ .../rulesengine/OperandMustacheToken.java | 75 +++++ .../marketing/mobile/rulesengine/Rule.java | 19 ++ .../mobile/rulesengine/RulesEngine.java | 48 ++++ .../mobile/rulesengine/RulesResult.java | 49 ++++ .../marketing/mobile/rulesengine/Segment.java | 29 ++ .../mobile/rulesengine/SegmentText.java | 28 ++ .../mobile/rulesengine/SegmentToken.java | 41 +++ .../mobile/rulesengine/Template.java | 37 +++ .../mobile/rulesengine/TemplateParser.java | 106 +++++++ .../mobile/rulesengine/TokenFinder.java | 16 ++ .../mobile/rulesengine/Transformer.java | 34 +++ .../mobile/rulesengine/TransformerBlock.java | 16 ++ .../mobile/rulesengine/Transforming.java | 16 ++ .../mobile/rulesengine/UnaryExpression.java | 38 +++ 29 files changed, 1373 insertions(+) create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ComparisonExpression.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ConditionEvaluator.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Context.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/DelimiterPair.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Evaluable.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Evaluating.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/FunctionBlock.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Log.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/LogLevel.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Logging.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/LogicalExpression.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/MustacheToken.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Operand.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/OperandFunction.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/OperandLiteral.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/OperandMustacheToken.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Rule.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/RulesEngine.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/RulesResult.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Segment.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/SegmentText.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/SegmentToken.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Template.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/TemplateParser.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/TokenFinder.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Transformer.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/TransformerBlock.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Transforming.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/UnaryExpression.java diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ComparisonExpression.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ComparisonExpression.java new file mode 100644 index 000000000..0e986b5bd --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ComparisonExpression.java @@ -0,0 +1,68 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; + +/** + * {@link ComparisonExpression} allows for comparision of two operands and evaluates to a True or False. + * Comparison operators include LessThan, LessThanOrEqual to, Equal, NotEqual, + * GreaterThan, GreaterThanOrEqual to, Contains and notContains. + */ +public class ComparisonExpression implements Evaluable { + private final Operand lhs; + private final Operand rhs; + private final String operationName; + + /** + * Initializer. + * Constructs the {@link ComparisonExpression} object with operands and operation name. + * + * @param lhs an {@link Operand} + * @param operationName A {@code String} value defining the operation. + * @param rhs an {@link Operand} + */ + public ComparisonExpression(final Operand lhs, final String operationName, final Operand rhs) { + this.lhs = lhs; + this.operationName = operationName; + this.rhs = rhs; + } + + /** + * Call this method to evaluate this {@link ComparisonExpression}. + * + * This method always returns a valid non null {@link RulesResult} object. + * Returns RulesResult failure if the operation name or either of the operands are null. + * + * @param context The context containing details for token swapping and transforming the operand if required. + * A non null value of {@link Context} is expected to be passed to this method. + * @return A {@link RulesResult} object representing a evaluated rule + */ + public RulesResult evaluate(final Context context) { + if (operationName == null) { + return new RulesResult(RulesResult.FailureType.MISSING_OPERATOR, "Operator is null, Comparison returned false"); + } + + if (lhs == null || rhs == null) { + return new RulesResult(RulesResult.FailureType.INVALID_OPERAND, "Operand is null, Comparison returned false."); + } + + A resolvedLhs = lhs.resolve(context); + B resolvedRhs = rhs.resolve(context); + + if (resolvedLhs == null || resolvedRhs == null) { + return new RulesResult(RulesResult.FailureType.INVALID_OPERAND, String.format("Comparison %s %s %s returned false", + resolvedLhs, operationName, resolvedRhs)); + } + + return context.evaluator.evaluate(resolvedLhs, operationName, resolvedRhs); + } + +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ConditionEvaluator.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ConditionEvaluator.java new file mode 100644 index 000000000..347399d68 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ConditionEvaluator.java @@ -0,0 +1,266 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + + +package com.adobe.marketing.mobile.rulesengine; +import java.util.regex.Pattern; + + +public class ConditionEvaluator implements Evaluating { + private final Option option; + private static final String OPERATOR_EQUALS = "equals"; + private static final String OPERATOR_NOT_EQUALS = "notEquals"; + private static final String OPERATOR_GREATER_THAN = "greaterThan"; + private static final String OPERATOR_GREATER_THAN_OR_EQUALS = "greaterEqual"; + private static final String OPERATOR_LESS_THAN = "lessThan"; + private static final String OPERATOR_LESS_THAN_OR_EQUALS = "lessEqual"; + private static final String OPERATOR_CONTAINS = "contains"; + private static final String OPERATOR_NOT_CONTAINS = "notContains"; + private static final String OPERATOR_STARTS_WITH = "startsWith"; + private static final String OPERATOR_ENDS_WITH = "endsWith"; + private static final String OPERATOR_EXISTS = "exists"; + private static final String OPERATOR_NOT_EXISTS = "notExist"; + + public enum Option { + DEFAULT, // For case sensitive string operations + CASE_INSENSITIVE // For case insensitive string operations + } + + public ConditionEvaluator(final Option option) { + this.option = option; + } + + /** + * Runs operation on the operands. + * + * This method always returns a valid non null {@link RulesResult} object. + * {@link RulesResult#SUCCESS} is returned if the operation on the operands evaluates to true. + * + * @param lhs A resolved {@link Operand} + * @param operation A {@link String} representing the operation to be performed on the operands + * @param rhs A resolved {@code Operand} + */ + @Override + public RulesResult evaluate(final A lhs, final String operation, final B rhs) { + boolean evaluationResult; + + switch (operation) { + case OPERATOR_EQUALS: + evaluationResult = this.checkEqual(lhs, rhs); + break; + + case OPERATOR_NOT_EQUALS: + evaluationResult = this.notEqual(lhs, rhs); + break; + + case OPERATOR_STARTS_WITH: + evaluationResult = this.startsWith(lhs, rhs); + break; + + case OPERATOR_ENDS_WITH: + evaluationResult = this.endsWith(lhs, rhs); + break; + + case OPERATOR_GREATER_THAN: + evaluationResult = this.greaterThan(lhs, rhs); + break; + + case OPERATOR_GREATER_THAN_OR_EQUALS: + evaluationResult = this.greaterThanEquals(lhs, rhs); + break; + + case OPERATOR_LESS_THAN: + evaluationResult = this.lesserThan(lhs, rhs); + break; + + case OPERATOR_LESS_THAN_OR_EQUALS: + evaluationResult = this.lesserThanOrEqual(lhs, rhs); + break; + + case OPERATOR_CONTAINS: + evaluationResult = this.contains(lhs, rhs); + break; + + case OPERATOR_NOT_CONTAINS: + evaluationResult = this.notContains(lhs, rhs); + break; + + default: + return new RulesResult(RulesResult.FailureType.MISSING_OPERATOR, String.format("Operator is invalid \"%s\"", + operation)); + } + + return evaluationResult ? RulesResult.SUCCESS : new RulesResult(RulesResult.FailureType.CONDITION_FAILED, + String.format("Condition not matched for operation \"%s\"", operation)); + + } + + @Override + public RulesResult evaluate(final String operation, final A lhs) { + boolean evaluationResult = false; + + switch (operation) { + case OPERATOR_EXISTS: + evaluationResult = this.exists(lhs); + break; + + case OPERATOR_NOT_EXISTS: + evaluationResult = this.notExists(lhs); + break; + + default: + return new RulesResult(RulesResult.FailureType.MISSING_OPERATOR, String.format("Operator is invalid \"%s\"", + operation)); + } + + return evaluationResult ? RulesResult.SUCCESS : new RulesResult(RulesResult.FailureType.CONDITION_FAILED, + String.format("Condition not matched for operation \"%s\"", operation)); + + } + + + //-------------------------------------------------------------------------- + // Private - Operator definitions + //-------------------------------------------------------------------------- + + private boolean checkEqual(final A lhs, final B rhs) { + if (lhs instanceof String && rhs instanceof String && option == Option.CASE_INSENSITIVE) { + String lhsValue = lhs.toString(); + String rhsValue = rhs.toString(); + return lhsValue.equalsIgnoreCase(rhsValue); + } + + return lhs.equals(rhs); + } + + private boolean notEqual(final A lhs, final B rhs) { + return !checkEqual(lhs, rhs); + } + + private boolean startsWith(final A lhs, final B rhs) { + if (lhs instanceof String && rhs instanceof String) { + String lhsValue = lhs.toString(); + String rhsValue = rhs.toString(); + String matcherMode = option == ConditionEvaluator.Option.CASE_INSENSITIVE ? "(?i)" : ""; + return lhsValue.matches(matcherMode + Pattern.quote(rhsValue) + ".*"); + } + + return false; + } + + private boolean endsWith(final A lhs, final B rhs) { + if (lhs instanceof String && rhs instanceof String) { + String lhsValue = lhs.toString(); + String rhsValue = rhs.toString(); + String matcherMode = option == ConditionEvaluator.Option.CASE_INSENSITIVE ? "(?i)" : ""; + return lhsValue.matches(matcherMode + ".*" + Pattern.quote(rhsValue)); + } + + return false; + } + + private boolean exists(final A lhs) { + return lhs != null; + } + + private boolean notExists(final A lhs) { + return lhs == null; + } + + + //-------------------------------------------------------------------------- + // Private - Operator definitions coming soon + //-------------------------------------------------------------------------- + + private boolean greaterThan(final A lhs, final B rhs) { + Double resolvedLhs = tryParseDouble(lhs); + Double resolvedRhs = tryParseDouble(rhs); + + if (resolvedLhs == null || resolvedRhs == null) { + return false; + } else if (resolvedLhs > resolvedRhs) { + return true; + } + + return false; + } + + private boolean greaterThanEquals(final A lhs, final B rhs) { + Double resolvedLhs = tryParseDouble(lhs); + Double resolvedRhs = tryParseDouble(rhs); + + if (resolvedLhs == null || resolvedRhs == null) { + return false; + } else if (resolvedLhs >= resolvedRhs) { + return true; + } + + return false; + } + + private boolean lesserThan(final A lhs, final B rhs) { + Double resolvedLhs = tryParseDouble(lhs); + Double resolvedRhs = tryParseDouble(rhs); + + if (resolvedLhs == null || resolvedRhs == null) { + return false; + } else if (resolvedLhs < resolvedRhs) { + return true; + } + + return false; + } + + private boolean lesserThanOrEqual(final A lhs, final B rhs) { + Double resolvedLhs = tryParseDouble(lhs); + Double resolvedRhs = tryParseDouble(rhs); + + if (resolvedLhs == null || resolvedRhs == null) { + return false; + } else if (resolvedLhs <= resolvedRhs) { + return true; + } + + return false; + } + + private boolean contains(final A lhs, final B rhs) { + if (lhs instanceof String && rhs instanceof String) { + + String lhsValue = lhs.toString(); + String rhsValue = rhs.toString(); + + if (option == ConditionEvaluator.Option.CASE_INSENSITIVE) { + lhsValue = lhsValue.toLowerCase(); + rhsValue = rhsValue.toLowerCase(); + } + + if (lhsValue.contains(rhsValue)) { + return true; + } + } + + return false; + } + + private boolean notContains(final A lhs, final B rhs) { + return !contains(lhs, rhs); + } + + private Double tryParseDouble(final Object value) { + try { + return Double.valueOf(value.toString()); + } catch (Exception ex) { + return null; + } + } + +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Context.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Context.java new file mode 100644 index 000000000..4a8c36d9a --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Context.java @@ -0,0 +1,26 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + + +package com.adobe.marketing.mobile.rulesengine; + +public class Context { + public final TokenFinder tokenFinder; + public final Evaluating evaluator; + public final Transforming transformer; + + public Context(final TokenFinder tokenFinder, final Evaluating evaluator, final Transforming transformer) { + this.tokenFinder = tokenFinder; + this.evaluator = evaluator; + this.transformer = transformer; + } + +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/DelimiterPair.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/DelimiterPair.java new file mode 100644 index 000000000..bda8b9403 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/DelimiterPair.java @@ -0,0 +1,63 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + + +package com.adobe.marketing.mobile.rulesengine; + +/** + * Class representing delimiter pair for tokens. + * + * The default delimiter pair from launch rules are "{%" "%}" + * eg token: {%region.cityName%} + */ +class DelimiterPair { + private final String startTag; + private final String endTag; + + /** + * Constructor. + * + * @param startString + * @param endString + */ + DelimiterPair(final String startString, final String endString) { + this.startTag = startString; + this.endTag = endString; + } + + /** + * @return the startTag for this {@link DelimiterPair} + */ + String getStartTag() { + return startTag; + } + + /** + * @return the endTag for this {@link DelimiterPair} + */ + String getEndTag() { + return endTag; + } + + /** + * @return the character length of startTag of this delimiter + */ + int getStartLength() { + return startTag.length(); + } + + /** + * @return the character length of endTag of this delimiter + */ + int getEndTagLength() { + return endTag.length(); + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Evaluable.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Evaluable.java new file mode 100644 index 000000000..2c9c276c2 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Evaluable.java @@ -0,0 +1,24 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; + +/** + * Evaluable Interface. + * + * The classes that implements {@link Evaluable} are + * {@link ComparisonExpression} + * {@link LogicalExpression} + * {@link UnaryExpression} + */ +public interface Evaluable { + RulesResult evaluate(final Context context); +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Evaluating.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Evaluating.java new file mode 100644 index 000000000..428345a5e --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Evaluating.java @@ -0,0 +1,18 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + + +package com.adobe.marketing.mobile.rulesengine; + +public interface Evaluating { + RulesResult evaluate(final A lhs, final String operation, final B rhs); + RulesResult evaluate(final String operation, final A lhs); +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/FunctionBlock.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/FunctionBlock.java new file mode 100644 index 000000000..d1b728430 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/FunctionBlock.java @@ -0,0 +1,16 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; + +public interface FunctionBlock { + T execute(final Object... e); +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Log.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Log.java new file mode 100644 index 000000000..1caa34a84 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Log.java @@ -0,0 +1,67 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; + +public class Log { + private static Logging logging; + + public static void setLogging(Logging logging) { + Log.logging = logging; + } + + /** + * Used to print more verbose information. + * + * @param tag the tag of the localize message + * @param message the log message to be logged + */ + static void verbose(final String tag, final String message) { + log(LogLevel.VERBOSE, tag, message); + } + + /** + * Information provided to the debug method should contain high-level details about the data being processed. + * + * @param tag the tag of the localize message + * @param message the log message to be logged + */ + static void debug(final String tag, final String message) { + log(LogLevel.DEBUG, tag, message); + } + + /** + * Information provided to the warning method indicates that a request has been made to the SDK, but the SDK will be unable to perform the requested task. + * + * @param tag the tag of the localize message + * @param message the log message to be logged + */ + static void warning(final String tag, final String message) { + log(LogLevel.WARNING, tag, message); + } + + /** + * Information provided to the error method indicates that there has been an unrecoverable error. + * + * @param tag the tag of the localize message + * @param message the log message to be logged + */ + static void error(final String tag, final String message) { + log(LogLevel.ERROR, tag, message); + } + + private Log() {} + private static void log(final LogLevel level, final String tag, final String message) { + if (logging != null) { + logging.log(level, tag, message); + } + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/LogLevel.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/LogLevel.java new file mode 100644 index 000000000..38cd0a0ab --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/LogLevel.java @@ -0,0 +1,27 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + + +package com.adobe.marketing.mobile.rulesengine; + +public enum LogLevel { + ERROR(0), + WARNING(1), + DEBUG(2), + VERBOSE(3); + + public final int id; + LogLevel(final int identifier) { + id = identifier; + } +} + + diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Logging.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Logging.java new file mode 100644 index 000000000..15d7a5455 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Logging.java @@ -0,0 +1,17 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + + +package com.adobe.marketing.mobile.rulesengine; + +public interface Logging { + void log(final LogLevel level, final String tag, final String message); +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/LogicalExpression.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/LogicalExpression.java new file mode 100644 index 000000000..f6bdbcb4a --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/LogicalExpression.java @@ -0,0 +1,66 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; + +import java.util.ArrayList; +import java.util.List; + +public class LogicalExpression implements Evaluable { + public final List operands; + public final String operationName; + + public LogicalExpression(List operands, String operationName) { + this.operands = operands; + this.operationName = operationName; + } + + @Override + public RulesResult evaluate(Context context) { + ArrayList resolvedOperands = new ArrayList(); + + for (Evaluable evaluable : operands) { + resolvedOperands.add(evaluable.evaluate(context)); + } + + switch (operationName) { + case "and": + return performAndOperation(resolvedOperands); + + case "or": + return performOrOperation(resolvedOperands); + + default: + return new RulesResult(RulesResult.FailureType.MISSING_OPERATOR, String.format("Unknown conjunction operator - %s.", + operationName)); + } + } + + private RulesResult performAndOperation(final List resolvedOperands) { + for (RulesResult rulesResult : resolvedOperands) { + if (!rulesResult.isSuccess()) { + return new RulesResult(RulesResult.FailureType.CONDITION_FAILED, "AND operation returned false."); + } + } + + return RulesResult.SUCCESS; + } + + private RulesResult performOrOperation(final List resolvedOperands) { + for (RulesResult rulesResult : resolvedOperands) { + if (rulesResult.isSuccess()) { + return RulesResult.SUCCESS; + } + } + + return new RulesResult(RulesResult.FailureType.CONDITION_FAILED, "OR operation returned false."); + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/MustacheToken.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/MustacheToken.java new file mode 100644 index 000000000..67ca2531e --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/MustacheToken.java @@ -0,0 +1,79 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + + +package com.adobe.marketing.mobile.rulesengine; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Class representing mustache token. + * + * Once a token is identified in any part of the rule, use this class to obtain the resolved value of the token. + * A token can be of two types + * 1. Variable + * Following example demonstrates variable token to retrieve the "city" details from the SDK context. + * {{region.city}} + * 2. Function + * Following example demonstrates function token to urlEncode the provided webURL. + * {{urlencode(http://google.com)}} + */ +class MustacheToken { + + private final Type tokenType; + private final String tokenString; + + private String functionName; + private MustacheToken innerVariable; + + /** + * Constructor to initialize the mustache token. + * + * This constructor automatically recognizes the token type from the provided token string + * + * @param tokenString the token string without the delimiters representing the token + */ + MustacheToken(final String tokenString) { + Matcher functionMatcher = Pattern.compile("\\(([^)]+)\\)").matcher(tokenString); + this.tokenString = tokenString; + + // check if the token is a function + if (functionMatcher.find()) { + innerVariable = new MustacheToken(functionMatcher.group(1)); + functionName = tokenString.substring(0, functionMatcher.start()); + tokenType = Type.FUNCTION; + return; + } + + tokenType = Type.VARIABLE; + } + + /** + * Resolves the token into its corresponding value. + * + * @param tokenFinder A {@link TokenFinder} instance that contains SDK context to replace tokens + * @param transformers A set of transformers to evaluate the function tokens. + * + * @return value of resolved token. + */ + protected Object resolve(final TokenFinder tokenFinder, final Transforming transformers) { + if (tokenType == Type.FUNCTION) { + return transformers.transform(this.functionName, innerVariable.resolve(tokenFinder, transformers)); + } else { + return tokenFinder.get(tokenString); + } + } + + private enum Type { + FUNCTION, VARIABLE + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Operand.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Operand.java new file mode 100644 index 000000000..5d64c354f --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Operand.java @@ -0,0 +1,32 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; + + +/** + * Interface for Operands. + * + * The classes that implements {@link Operand} are + * {@link OperandLiteral} + * {@link OperandMustacheToken} + * {@link OperandFunction} + */ +public interface Operand { + + /** + * All operand's must implement this to retrieve its resolved value. + * + * @param context The context contains details for token swapping and transforming the operand if required. + * @return the resolved operand value + */ + T resolve(final Context context); +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/OperandFunction.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/OperandFunction.java new file mode 100644 index 000000000..0b5ccb2a0 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/OperandFunction.java @@ -0,0 +1,27 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; + +public class OperandFunction implements Operand { + private final FunctionBlock block; + private final Object[] functionParameters; + + public OperandFunction(final FunctionBlock block, final Object... functionParameters) { + this.block = block; + this.functionParameters = functionParameters; + } + + @Override + public T resolve(Context context) { + return block.execute(functionParameters); + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/OperandLiteral.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/OperandLiteral.java new file mode 100644 index 000000000..8a5ed279d --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/OperandLiteral.java @@ -0,0 +1,25 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; + +public class OperandLiteral implements Operand { + private final T value; + public OperandLiteral(final T value) { + this.value = value; + } + + @Override + public T resolve(final Context context) { + return this.value; + } + +} \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/OperandMustacheToken.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/OperandMustacheToken.java new file mode 100644 index 000000000..a746bb16c --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/OperandMustacheToken.java @@ -0,0 +1,75 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; +import java.util.List; + +/** + * Class to handle the mustache token Operand. + */ +public class OperandMustacheToken implements Operand { + private final MustacheToken mustacheToken; + + + /** + * Constructor. + * Initialize this operand using a tokenString. A valid tokenString has only one token. + * For example : + * {{region.city}} + * {%~state.com.adobe.marketing,mobile.lifecycle.contextdata.deviceName%} + * + * Following are considered are invalid token strings + * 1. region.city - (this string does not contain token delimiters) + * 2. {{region.city - (this string is not enclosed between delimiters) + * 3. some{{region.city}} - (this string does not start with a valid token) + * + * @param tokenString string representing a mustache token operand + */ + public OperandMustacheToken(final String tokenString) { + + // if token string is invalid make the mustache Token null. + // There by the operand always returns null on resolve. This is equivalent to OperandNone in swift. + if (tokenString == null || tokenString.isEmpty()) { + mustacheToken = null; + return; + } + + final List segmentList = TemplateParser.parse(tokenString); + + // Mustache token operands must have only one token. + // Hence we ignore other + if (segmentList.size() > 0 && segmentList.get(0) instanceof SegmentToken) { + SegmentToken segmentToken = (SegmentToken) segmentList.get(0); + mustacheToken = segmentToken.getMustacheToken(); + return; + } + + mustacheToken = new MustacheToken(tokenString); + } + + + /** + * Returns the resolved value of the MustacheToken operand. + * An invalid mustacheToken operand returns null. + * + * @param context The context contains details for token swapping and transforming the operand if required + * @return the resolved operand value + */ + @Override + public T resolve(final Context context) { + if (mustacheToken == null) { + return null; + } + + return (T) mustacheToken.resolve(context.tokenFinder, context.transformer); + } + +} \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Rule.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Rule.java new file mode 100644 index 000000000..94406a189 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Rule.java @@ -0,0 +1,19 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; + +/** + * Interface to be implemented by defining rule element. + */ +public interface Rule { + Evaluable getEvaluable(); +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/RulesEngine.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/RulesEngine.java new file mode 100644 index 000000000..7cfbae130 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/RulesEngine.java @@ -0,0 +1,48 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; + +import java.util.ArrayList; +import java.util.List; + +public class RulesEngine { + Evaluating evaluator; + Transforming transformer; + List rules; + + public RulesEngine(final Evaluating evaluator, final Transforming transformer) { + this.evaluator = evaluator; + this.transformer = transformer; + } + + public List evaluate(final TokenFinder tokenFinder) { + final Context context = new Context(tokenFinder, evaluator, transformer); + List triggerRules = new ArrayList<>(); + + for (final Rule rule : rules) { + if (rule.getEvaluable().evaluate(context).isSuccess()) { + triggerRules.add(rule); + } + } + + return triggerRules; + } + + public void addRules(final List newRules) { + rules.addAll(newRules); + } + + public void clearRules() { + rules.clear(); + } + +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/RulesResult.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/RulesResult.java new file mode 100644 index 000000000..892df1a19 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/RulesResult.java @@ -0,0 +1,49 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; + +public class RulesResult { + + public enum FailureType { + UNKNOWN, CONDITION_FAILED, TYPE_MISMATCHED, MISSING_OPERATOR, INVALID_OPERAND + } + + private final boolean isSuccess; + private final String failureMessage; + private final FailureType failureType; + + public static final RulesResult SUCCESS = new RulesResult(true); + + public RulesResult(final FailureType failureType, final String failureMessage) { + this.isSuccess = false; + this.failureMessage = failureMessage; + this.failureType = failureType; + } + + public boolean isSuccess() { + return isSuccess; + } + + public String getFailureMessage() { + return failureMessage; + } + + public FailureType getFailureType() { + return failureType; + } + + private RulesResult(final boolean isSuccess) { + this.isSuccess = isSuccess; + this.failureMessage = null; + this.failureType = null; + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Segment.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Segment.java new file mode 100644 index 000000000..e3412b652 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Segment.java @@ -0,0 +1,29 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; + +/** + * A segment represents a part of text that can be evaluated to a value. + * There are two types of Segment + * - {@link SegmentToken} + * - {@link SegmentText} + * + * The following string is parsed by {@link TemplateParser} to have 3 segments. + * "Hi {{username}}, Welcome to New York" + * 1. Hi --> (SegmentText) + * 2. {{username}} --> (SegmentToken) + * 3. , Welcome to New York --> (Segment Text) + * + */ +interface Segment { + String getContent(final TokenFinder tokenFinder, final Transforming transformers); +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/SegmentText.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/SegmentText.java new file mode 100644 index 000000000..6c184b6dc --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/SegmentText.java @@ -0,0 +1,28 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; + +/** + * SegmentText represents plain text. + */ +public class SegmentText implements Segment { + private final String content; + + public SegmentText(String content) { + this.content = content; + } + + @Override + public String getContent(final TokenFinder tokenFinder, final Transforming transformer) { + return content; + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/SegmentToken.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/SegmentToken.java new file mode 100644 index 000000000..259a9f207 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/SegmentToken.java @@ -0,0 +1,41 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; + +/** + * SegmentToken represents token, whose value is substituted by the {@link TokenFinder} or {@link Transforming}. + */ +public class SegmentToken implements Segment { + private final MustacheToken mustacheToken; + + public SegmentToken(String mustacheString) { + this.mustacheToken = new MustacheToken(mustacheString); + } + + public MustacheToken getMustacheToken() { + return mustacheToken; + } + + /** + * Retrieves the evaluated value of this {@link SegmentToken} + */ + @Override + public String getContent(final TokenFinder tokenFinder, final Transforming transformer) { + Object resolvedToken = mustacheToken.resolve(tokenFinder, transformer); + + if (resolvedToken != null) { + return resolvedToken.toString(); + } + + return ""; + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Template.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Template.java new file mode 100644 index 000000000..651a7ba93 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Template.java @@ -0,0 +1,37 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; + +import java.util.List; + +public class Template { + private final List tokens; + + public Template(final String templateString) { + this.tokens = TemplateParser.parse(templateString); + } + + public Template(final String templateString, DelimiterPair delimiterPair) { + this.tokens = TemplateParser.parse(templateString, delimiterPair); + } + + public String render(final TokenFinder tokenFinder, final Transforming transformer) { + StringBuilder stringBuilder = new StringBuilder(); + + for (Segment eachSegment : tokens) { + stringBuilder.append(eachSegment.getContent(tokenFinder, transformer)); + } + + return stringBuilder.toString(); + } + +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/TemplateParser.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/TemplateParser.java new file mode 100644 index 000000000..ade4df202 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/TemplateParser.java @@ -0,0 +1,106 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; + +import java.util.ArrayList; +import java.util.List; + +public class TemplateParser { + static private DelimiterPair defaultDelimiter = new DelimiterPair("{{", "}}"); + + static List parse(final String templateString) { + return TemplateParser.parse(templateString, defaultDelimiter); + } + + static List parse(final String templateString, final DelimiterPair delimiter) { + List tokens = new ArrayList<>(); + + if (templateString == null || templateString.isEmpty()) { + return tokens; + } + + DelimiterPair currentDelimiter = delimiter == null ? defaultDelimiter : delimiter; + int i = 0; + int end = templateString.length(); + Parser parser = new Parser(i, State.START); + + while (i < end) { + switch (parser.state) { + case START: + if (templateString.substring(i).startsWith(currentDelimiter.getStartTag())) { + parser.setState(i, State.TAG); + i = templateString.indexOf(currentDelimiter.getStartTag(), i) + 1; + } else { + parser.setState(i, State.TEXT); + } + + break; + + case TEXT: + if (templateString.substring(i).startsWith(currentDelimiter.getStartTag())) { + if (parser.index != i) { + tokens.add(new SegmentText(templateString.substring(parser.index, i))); + } + + parser.setState(i, State.TAG); + i = templateString.indexOf(currentDelimiter.getStartTag(), i) + 1; + } + + break; + + case TAG: + if (templateString.substring(i).startsWith(currentDelimiter.getEndTag())) { + int tokenContentStartIndex = parser.index + currentDelimiter.getStartLength(); + tokens.add(new SegmentToken(templateString.substring(tokenContentStartIndex, i))); + parser.state = State.START; + i = templateString.indexOf(currentDelimiter.getEndTag(), i) + 1; + } + + break; + } + + i++; + } + + switch (parser.state) { + case START: + break; + + case TEXT: + tokens.add(new SegmentText(templateString.substring(parser.index, i))); + break; + + case TAG: + return new ArrayList<>(); + } + + return tokens; + } +} + + +class Parser { + int index; + State state; + Parser(final int index, final State state) { + this.index = index; + this.state = state; + } + + public void setState(final int index, final State state) { + this.state = state; + this.index = index; + } +} +enum State { + START, TEXT, TAG +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/TokenFinder.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/TokenFinder.java new file mode 100644 index 000000000..03e8c0e7a --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/TokenFinder.java @@ -0,0 +1,16 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; + +public interface TokenFinder { + Object get(final String key); +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Transformer.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Transformer.java new file mode 100644 index 000000000..3245ce901 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Transformer.java @@ -0,0 +1,34 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; + +import java.util.HashMap; +import java.util.Map; + +public class Transformer implements Transforming { + Map transformations = new HashMap<>(); + + public void register(final String name, final TransformerBlock transformerBlock) { + transformations.put(name, transformerBlock); + } + + @Override + public Object transform(String name, Object parameter) { + TransformerBlock block = transformations.get(name); + + if (block == null) { + return parameter; + } + + return block.transform(parameter); + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/TransformerBlock.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/TransformerBlock.java new file mode 100644 index 000000000..e5683cdf7 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/TransformerBlock.java @@ -0,0 +1,16 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; + +public interface TransformerBlock { + T transform(final Object e); +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Transforming.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Transforming.java new file mode 100644 index 000000000..2634811fa --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Transforming.java @@ -0,0 +1,16 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; + +public interface Transforming { + Object transform(final String name, final Object parameter); +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/UnaryExpression.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/UnaryExpression.java new file mode 100644 index 000000000..6e4fc0c22 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/UnaryExpression.java @@ -0,0 +1,38 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine; + +public class UnaryExpression implements Evaluable { + private Operand lhs; + private String operationName; + + public UnaryExpression(Operand lhs, String operationName) { + this.lhs = lhs; + this.operationName = operationName; + } + + @Override + public RulesResult evaluate(Context context) { + A resolvedLhs = null; + + if (lhs != null) { + resolvedLhs = lhs.resolve(context); + } + + if (operationName == null || operationName.isEmpty()) { + return new RulesResult(RulesResult.FailureType.INVALID_OPERAND, String.format("Evaluating %s %s returned false", + resolvedLhs, operationName)); + } + + return context.evaluator.evaluate(operationName, resolvedLhs); + } +} \ No newline at end of file From 90f7e9309fa77bbee53e65b557b0bf72fe43be94 Mon Sep 17 00:00:00 2001 From: praveek Date: Fri, 11 Feb 2022 15:21:44 -0800 Subject: [PATCH 002/476] Add tests --- .../mobile/rulesengine/FakeTokenFinder.java | 33 +++ .../mobile/rulesengine/FakeTransformer.java | 36 ++++ .../rulesengine/LogicalExpressionTests.java | 105 +++++++++ .../rulesengine/MustacheTokenTests.java | 149 +++++++++++++ .../rulesengine/OperandFunctionTests.java | 53 +++++ .../rulesengine/OperatorContainsTests.java | 115 ++++++++++ .../rulesengine/OperatorEqualTests.java | 174 +++++++++++++++ .../rulesengine/OperatorGreaterThan.java | 117 ++++++++++ .../rulesengine/OperatorStartsWithTests.java | 200 ++++++++++++++++++ .../mobile/rulesengine/ParserTests.java | 125 +++++++++++ .../rulesengine/UnaryExpressionTests.java | 78 +++++++ 11 files changed, 1185 insertions(+) create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/FakeTokenFinder.java create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/FakeTransformer.java create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/LogicalExpressionTests.java create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/MustacheTokenTests.java create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperandFunctionTests.java create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorContainsTests.java create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorEqualTests.java create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorGreaterThan.java create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorStartsWithTests.java create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/ParserTests.java create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/UnaryExpressionTests.java diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/FakeTokenFinder.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/FakeTokenFinder.java new file mode 100644 index 000000000..e75c17176 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/FakeTokenFinder.java @@ -0,0 +1,33 @@ +/* ***************************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2018 Adobe Systems Incorporated + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe Systems Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Adobe Systems Incorporated and its + * suppliers and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe Systems Incorporated. + ******************************************************************************/ + +package com.adobe.marketing.mobile.rulesengine; + +import java.util.HashMap; + +class FakeTokenFinder implements TokenFinder { + HashMap map; + + public FakeTokenFinder(HashMap map) { + this.map = map; + } + + @Override + public Object get(String key) { + return map.get(key); + } +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/FakeTransformer.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/FakeTransformer.java new file mode 100644 index 000000000..8c87cc6e3 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/FakeTransformer.java @@ -0,0 +1,36 @@ +/* ***************************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2018 Adobe Systems Incorporated + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe Systems Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Adobe Systems Incorporated and its + * suppliers and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe Systems Incorporated. + ******************************************************************************/ + +package com.adobe.marketing.mobile.rulesengine; + +public class FakeTransformer { + + static Transforming create() { + Transformer transformer = new Transformer(); + transformer.register("addExtraString", new TransformerBlock() { + @Override + public String transform(Object e) { + if (e != null && e instanceof String) { + return e + " extra"; + } + + return ""; + } + }); + return transformer; + } +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/LogicalExpressionTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/LogicalExpressionTests.java new file mode 100644 index 000000000..f3b0179a6 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/LogicalExpressionTests.java @@ -0,0 +1,105 @@ +/* ***************************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2018 Adobe Systems Incorporated + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe Systems Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Adobe Systems Incorporated and its + * suppliers and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe Systems Incorporated. + ******************************************************************************/ + +package com.adobe.marketing.mobile.rulesengine; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; + +public class LogicalExpressionTests { + + private ComparisonExpression expressionTrue = new ComparisonExpression(new OperandLiteral("One"), "equals", + new OperandLiteral("One")); + private ComparisonExpression expressionFalse = new ComparisonExpression(new OperandLiteral("One"), "notEquals", + new OperandLiteral("One")); + + @Test + public void testLogicalExpression_AND() { + // setup + List operands = new ArrayList<>(); + operands.add(expressionTrue); + operands.add(expressionTrue); + + // test + RulesResult result = new LogicalExpression(operands, "and").evaluate(defaultContext()); + assertTrue(result.isSuccess()); + + // setup again + operands.add(expressionFalse); + + // test + RulesResult result2 = new LogicalExpression(operands, "and").evaluate(defaultContext()); + assertFalse(result2.isSuccess()); + assertEquals("AND operation returned false.", result2.getFailureMessage()); + } + + + @Test + public void testLogicalExpression_OR() { + // setup + List operands = new ArrayList<>(); + operands.add(expressionFalse); + operands.add(expressionFalse); + + // test + RulesResult result = new LogicalExpression(operands, "or").evaluate(defaultContext()); + assertFalse(result.isSuccess()); + assertEquals("OR operation returned false.", result.getFailureMessage()); + + // setup again + operands.add(expressionTrue); + + // test + RulesResult result2 = new LogicalExpression(operands, "or").evaluate(defaultContext()); + assertTrue(result2.isSuccess()); + } + + @Test + public void testLogicalExpression_UnknownOperator() { + // setup + List operands = new ArrayList<>(); + operands.add(expressionTrue); + operands.add(expressionTrue); + + // test + RulesResult result = new LogicalExpression(operands, "equal").evaluate(defaultContext()); + assertFalse(result.isSuccess()); + assertEquals("Unknown conjunction operator - equal.", result.getFailureMessage()); + } + + + @Test + public void test_test() { + MustacheToken mustacheToken = new MustacheToken("functi(variable)"); + + } + + + + private Context defaultContext() { + return new Context(new FakeTokenFinder(new HashMap<>()), new ConditionEvaluator(ConditionEvaluator.Option.DEFAULT), + null); + } + +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/MustacheTokenTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/MustacheTokenTests.java new file mode 100644 index 000000000..265e18936 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/MustacheTokenTests.java @@ -0,0 +1,149 @@ +/* ***************************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2018 Adobe Systems Incorporated + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe Systems Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Adobe Systems Incorporated and its + * suppliers and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe Systems Incorporated. + ******************************************************************************/ + +package com.adobe.marketing.mobile.rulesengine; + +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; + +import static org.junit.Assert.*; + +public class MustacheTokenTests { + + private ConditionEvaluator defaultEvaluator = new ConditionEvaluator(ConditionEvaluator.Option.DEFAULT); + + @Before() + public void setup() { } + + @Test + public void Operand_MustacheToken() { + // setup + final OperandMustacheToken tokenString = new OperandMustacheToken("{{Hero}}"); + final OperandMustacheToken tokenInteger = new OperandMustacheToken("{{integerToken}}"); + final OperandMustacheToken tokenBoolean = new OperandMustacheToken("{{booleanToken}}"); + + // test + final String result1 = tokenString.resolve(defaultContext(defaultEvaluator)); + final Number result2 = tokenInteger.resolve(defaultContext(defaultEvaluator)); + final Boolean result3 = tokenBoolean.resolve(defaultContext(defaultEvaluator)); + + // verify + assertEquals("Soldier", result1); + assertEquals(33, result2); + assertEquals(false, result3); + } + + @Test + public void Operand_MustacheToken_multipleToken() { + // setup + final OperandMustacheToken operand = new OperandMustacheToken("{{Beer}}{{Corona}}"); + + // test + final Object result = operand.resolve(defaultContext(defaultEvaluator)); + + // verify that operand mustache token resolves only the first token + assertEquals("Corona", result); + } + + @Test + public void Operand_MustacheToken_InvalidToken() { + // setup + final OperandMustacheToken example1 = new OperandMustacheToken("{{Beer"); + final OperandMustacheToken example2 = new OperandMustacheToken("Beer{{Hero}}"); + final OperandMustacheToken example3 = new OperandMustacheToken("onlyString"); + final OperandMustacheToken example4 = new OperandMustacheToken(""); + final OperandMustacheToken example5 = new OperandMustacheToken(null); + + // test and verify + assertNull(example1.resolve(defaultContext(defaultEvaluator))); + assertNull(example2.resolve(defaultContext(defaultEvaluator))); + assertNull(example3.resolve(defaultContext(defaultEvaluator))); + assertNull(example4.resolve(defaultContext(defaultEvaluator))); + assertNull(example5.resolve(defaultContext(defaultEvaluator))); + } + + @Test + public void Operand_WrongDataType_Token() { + // setup + final OperandMustacheToken wrongToken = new OperandMustacheToken("{{booleanToken}}"); + + // test + final Object result1 = wrongToken.resolve(defaultContext(defaultEvaluator)); + + // verify + assertTrue(result1 instanceof Boolean); + } + + @Test + public void testComparisonExpression_MustacheToken() { + // setup + final Operand lhs = new OperandMustacheToken("{{Hero}}"); + final Operand rhs = new OperandLiteral("Soldier"); + + // test + final ComparisonExpression expression = new ComparisonExpression(lhs, "equals", rhs); + + // verify + final RulesResult result = expression.evaluate(defaultContext(defaultEvaluator)); + assertTrue(result.isSuccess()); + } + + @Test + public void testComparisonExpression_MustacheFunction() { + // setup + final Operand lhs = new OperandMustacheToken("{{addExtraString(Beer)}}"); + final Operand rhs = new OperandLiteral("Corona extra"); + + // test + final ComparisonExpression expression = new ComparisonExpression(lhs, "equals", rhs); + + // verify + final RulesResult result = expression.evaluate(defaultContext(defaultEvaluator)); + assertTrue(result.isSuccess()); + } + + @Test + public void testComparisonExpression_MustacheFunction_and_Token() { + // setup + final Operand lhs = new OperandMustacheToken("{{addExtraString(Beer)}}"); + final Operand rhs = new OperandMustacheToken("{{answer}}"); + + // test + final ComparisonExpression expression = new ComparisonExpression(lhs, "equals", rhs); + + // verify + final RulesResult result = expression.evaluate(defaultContext(defaultEvaluator)); + assertTrue(result.isSuccess()); + } + + + /* ************************************************************************** + * Private methods + **************************************************************************/ + private Context defaultContext(final ConditionEvaluator conditionEvaluator) { + HashMap context = new HashMap(); + context.put("Beer", "Corona"); + context.put("Hero", "Soldier"); + context.put("Soda", "Pepsi"); + context.put("answer", "Corona extra"); + context.put("integerToken", 33); + context.put("booleanToken", false); + return new Context(new FakeTokenFinder(context), conditionEvaluator, FakeTransformer.create()); + } +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperandFunctionTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperandFunctionTests.java new file mode 100644 index 000000000..f19f98066 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperandFunctionTests.java @@ -0,0 +1,53 @@ +package com.adobe.marketing.mobile.rulesengine; + +import org.junit.Test; + +import java.util.HashMap; + +import static org.junit.Assert.assertTrue; + +public class OperandFunctionTests { + + private ConditionEvaluator defaultEvaluator = new ConditionEvaluator(ConditionEvaluator.Option.DEFAULT); + + @Test + public void test_OperandFunction() { + // setup + final Operand stringOperand = new OperandLiteral("bossbaby"); + final OperandFunction functionOperator = new OperandFunction(new FunctionBlock() { + @Override + public String execute(Object... args) { + StringBuilder builder = new StringBuilder(); + + for (Object each : args) { + builder.append(each.toString()); + } + + return builder.toString(); + } + + }, "boss", "baby"); + + // test + final RulesResult result = new ComparisonExpression(stringOperand, "equals", + functionOperator).evaluate(defaultContext(defaultEvaluator)); + + // verify + assertTrue(result.isSuccess()); + } + + /* ************************************************************************** + * Private methods + **************************************************************************/ + private Context defaultContext(final ConditionEvaluator conditionEvaluator) { + HashMap context = new HashMap(); + context.put("Beer", "Corona"); + context.put("Hero", "Soldier"); + context.put("Soda", "Pepsi"); + context.put("answer", "Corona extra"); + context.put("integerToken", 33); + context.put("booleanToken", false); + return new Context(new FakeTokenFinder(context), conditionEvaluator, FakeTransformer.create()); + } + +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorContainsTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorContainsTests.java new file mode 100644 index 000000000..df8683595 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorContainsTests.java @@ -0,0 +1,115 @@ +/* ***************************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2018 Adobe Systems Incorporated + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe Systems Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Adobe Systems Incorporated and its + * suppliers and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe Systems Incorporated. + ******************************************************************************/ + +package com.adobe.marketing.mobile.rulesengine; + +import org.junit.Test; +import java.util.HashMap; +import static org.junit.Assert.*; + +public class OperatorContainsTests { + + private ConditionEvaluator defaultEvaluator = new ConditionEvaluator(ConditionEvaluator.Option.DEFAULT); + private ConditionEvaluator caseSensitiveEvaluator = new ConditionEvaluator(ConditionEvaluator.Option.CASE_INSENSITIVE); + + /* ************************************************************************** + * Operand - Contains + **************************************************************************/ + @Test + public void test_operator_contains() { + // setup + final Operand op1 = new OperandLiteral("This is a big sentence that contains, contains."); + final Operand op2 = new OperandLiteral(", contains."); + + // test + final ComparisonExpression expression = new ComparisonExpression(op1, "contains", op2); + final RulesResult result = expression.evaluate(defaultContext(defaultEvaluator)); + + // verify + assertTrue(result.isSuccess()); + } + + @Test + public void test_operator_contains_notHappy() { + // setup + final Operand op1 = new OperandLiteral("This is a big sentence that contains, contains."); + final Operand op2 = new OperandLiteral("this isn't present"); + + // test + final ComparisonExpression expression = new ComparisonExpression(op1, "contains", op2); + final RulesResult result = expression.evaluate(defaultContext(defaultEvaluator)); + + // verify + assertFalse(result.isSuccess()); + assertEquals("Condition not matched for operation \"contains\"", result.getFailureMessage()); + } + + /* ************************************************************************** + * Operand - Not Contains + **************************************************************************/ + @Test + public void test_operator_notContains() { + // setup + final Operand op1 = new OperandLiteral("This doesn't have the secret word."); + final Operand op2 = new OperandLiteral("contains"); + + // test + final ComparisonExpression expression = new ComparisonExpression(op1, "notContains", op2); + final RulesResult result = expression.evaluate(defaultContext(defaultEvaluator)); + + // verify + assertTrue(result.isSuccess()); + } + + @Test + public void test_operator_notContains_notHappy() { + // setup + final Operand op1 = new OperandLiteral("This is a big sentence that contains, contains."); + final Operand op2 = new OperandLiteral("contains"); + + // test + final ComparisonExpression expression = new ComparisonExpression(op1, "notContains", op2); + final RulesResult result = expression.evaluate(defaultContext(defaultEvaluator)); + + // verify + assertFalse(result.isSuccess()); + assertEquals("Condition not matched for operation \"notContains\"", result.getFailureMessage()); + } + + @Test + public void test_operator_contain_caseSensitive() { + // setup + final Operand op1 = new OperandLiteral("This is a big sentence that contains, contains."); + final Operand op2 = new OperandLiteral(", CONTAINS."); + + // test + final ComparisonExpression expression = new ComparisonExpression(op1, "contains", op2); + final RulesResult result = expression.evaluate(defaultContext(caseSensitiveEvaluator)); + + // verify + assertTrue(result.isSuccess()); + } + + + /* ************************************************************************** + * Private methods + **************************************************************************/ + + private Context defaultContext(final ConditionEvaluator conditionEvaluator) { + return new Context(new FakeTokenFinder(new HashMap<>()), conditionEvaluator, null); + } +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorEqualTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorEqualTests.java new file mode 100644 index 000000000..de8e286f1 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorEqualTests.java @@ -0,0 +1,174 @@ +/* ***************************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2018 Adobe Systems Incorporated + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe Systems Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Adobe Systems Incorporated and its + * suppliers and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe Systems Incorporated. + ******************************************************************************/ + +package com.adobe.marketing.mobile.rulesengine; + +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; + +import static org.junit.Assert.*; + +public class OperatorEqualTests { + + private ConditionEvaluator defaultEvaluator = new ConditionEvaluator(ConditionEvaluator.Option.DEFAULT); + private ConditionEvaluator caseSensitiveEvaluator = new ConditionEvaluator(ConditionEvaluator.Option.CASE_INSENSITIVE); + + @Before() + public void setup() { + } + + @Test + public void testComparisonExpression_String_Equals_String() { + // setup + final Operand lhs = new OperandLiteral("This is a big string"); + final Operand rhs = new OperandLiteral("This is a big string"); + + // test + ComparisonExpression expression = new ComparisonExpression(lhs, "equals", rhs); + + // verify + RulesResult result = expression.evaluate(defaultContext(defaultEvaluator)); + assertTrue(result.isSuccess()); + } + + @Test + public void testComparisonExpression_Number_Equals_Number() { + // setup + final Operand lhs = new OperandLiteral(34); + final Operand rhs = new OperandLiteral(34); + + // test + ComparisonExpression expression = new ComparisonExpression(lhs, "equals", rhs); + + // verify + RulesResult result = expression.evaluate(defaultContext(defaultEvaluator)); + assertTrue(result.isSuccess()); + } + + @Test + public void testComparisonExpression_Double_Equals_Double() { + // setup + final Operand lhs = new OperandLiteral(55.55); + final Operand rhs = new OperandLiteral(55.55); + + // test + ComparisonExpression expression = new ComparisonExpression(lhs, "equals", rhs); + + // verify + RulesResult result = expression.evaluate(defaultContext(defaultEvaluator)); + assertTrue(result.isSuccess()); + } + + @Test + public void testComparisonExpression_Boolean_Equals_Boolean() { + // setup + final Operand lhs = new OperandLiteral(false); + final Operand rhs = new OperandLiteral(false); + + // test + ComparisonExpression expression = new ComparisonExpression(lhs, "equals", rhs); + + // verify + RulesResult result = expression.evaluate(defaultContext(defaultEvaluator)); + assertTrue(result.isSuccess()); + } + + @Test + public void testComparisonExpression_String_Equals_DifferentString() { + // setup + final Operand lhs = new OperandLiteral("This is a big string"); + final Operand rhs = new OperandLiteral("This is another big string"); + + // test + final ComparisonExpression expression = new ComparisonExpression(lhs, "equals", rhs); + + // verify + RulesResult result = expression.evaluate(defaultContext(defaultEvaluator)); + assertFalse(result.isSuccess()); + assertEquals(RulesResult.FailureType.CONDITION_FAILED, result.getFailureType()); + } + + @Test + public void testComparisonExpression_String_Equals_CapedString() { + // setup + Operand lhs = new OperandLiteral("Deal"); + Operand rhs = new OperandLiteral("DEAL"); + + ComparisonExpression expression = new ComparisonExpression(lhs, "equals", rhs); + + RulesResult result = expression.evaluate(defaultContext(defaultEvaluator)); + assertFalse(result.isSuccess()); + assertEquals(RulesResult.FailureType.CONDITION_FAILED, result.getFailureType()); + } + + @Test + public void testComparisonExpression_String_Equals_String_CaseInsensitive() { + // setup + Operand lhs = new OperandLiteral("This is a big string"); + Operand rhs = new OperandLiteral("This IS a BIG STRING"); + + // test + ComparisonExpression expression = new ComparisonExpression(lhs, "equals", rhs); + + // verify + RulesResult result = expression.evaluate(defaultContext(caseSensitiveEvaluator)); + assertTrue(result.isSuccess()); + } + + @Test + public void testComparisonExpression_NotEquals() { + // setup + final Operand op1 = new OperandLiteral(3); + final Operand op2 = new OperandLiteral("string"); + final Operand op3 = new OperandLiteral(false); + final Operand op4 = new OperandLiteral(3.3); + final Operand op5 = new OperandLiteral("diffString"); + + // test string not equals int + ComparisonExpression expression = new ComparisonExpression(op1, "notEquals", op2); + RulesResult result = expression.evaluate(defaultContext(caseSensitiveEvaluator)); + + assertTrue(result.isSuccess()); + + // test string not equals boolean + expression = new ComparisonExpression(op2, "notEquals", op3); + result = expression.evaluate(defaultContext(caseSensitiveEvaluator)); + assertTrue(result.isSuccess()); + + // test boolean not equals double + expression = new ComparisonExpression(op3, "notEquals", op4); + result = expression.evaluate(defaultContext(caseSensitiveEvaluator)); + assertTrue(result.isSuccess()); + + + // test string not equals different string + expression = new ComparisonExpression(op2, "notEquals", op5); + result = expression.evaluate(defaultContext(caseSensitiveEvaluator)); + assertTrue(result.isSuccess()); + } + + + /* ************************************************************************** + * Private methods + **************************************************************************/ + private Context defaultContext(final ConditionEvaluator conditionEvaluator) { + return new Context(new FakeTokenFinder(new HashMap<>()), conditionEvaluator, null); + } + +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorGreaterThan.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorGreaterThan.java new file mode 100644 index 000000000..62a2e80d9 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorGreaterThan.java @@ -0,0 +1,117 @@ +/* ***************************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2018 Adobe Systems Incorporated + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe Systems Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Adobe Systems Incorporated and its + * suppliers and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe Systems Incorporated. + ******************************************************************************/ + +package com.adobe.marketing.mobile.rulesengine; + +import org.junit.Test; + +import java.util.HashMap; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class OperatorGreaterThan { + + private ConditionEvaluator defaultEvaluator = new ConditionEvaluator(ConditionEvaluator.Option.DEFAULT); + + /* ************************************************************************** + * Operand - Contains + **************************************************************************/ + @Test + public void test_relationalOperator_Int_vs_Int() { + // setup + final Operand op1 = new OperandLiteral(66); + final Operand op2 = new OperandLiteral(55); + + // test + final RulesResult result1 = new ComparisonExpression(op1, "greaterThan", op2).evaluate(defaultContext()); + final RulesResult result2 = new ComparisonExpression(op1, "greaterEqual", op2).evaluate(defaultContext()); + final RulesResult result3 = new ComparisonExpression(op2, "lessThan", op1).evaluate(defaultContext()); + final RulesResult result4 = new ComparisonExpression(op2, "lessEqual", op1).evaluate(defaultContext()); + + // test failure + final RulesResult result5 = new ComparisonExpression(op2, "greaterThan", op1).evaluate(defaultContext()); + final RulesResult result6 = new ComparisonExpression(op2, "greaterEqual", op1).evaluate(defaultContext()); + final RulesResult result7 = new ComparisonExpression(op1, "lessThan", op2).evaluate(defaultContext()); + final RulesResult result8 = new ComparisonExpression(op1, "lessEqual", op2).evaluate(defaultContext()); + + // verify + assertTrue(result1.isSuccess()); + assertTrue(result2.isSuccess()); + assertTrue(result3.isSuccess()); + assertTrue(result4.isSuccess()); + + // verify failure + assertFalse(result5.isSuccess()); + assertFalse(result6.isSuccess()); + assertFalse(result7.isSuccess()); + assertFalse(result8.isSuccess()); + } + + @Test + public void test_relationalOperator_SameInt() { + // setup + final Operand op1 = new OperandLiteral(66); + + // test + final RulesResult result1 = new ComparisonExpression(op1, "greaterThan", op1).evaluate(defaultContext()); + final RulesResult result2 = new ComparisonExpression(op1, "greaterEqual", op1).evaluate(defaultContext()); + final RulesResult result3 = new ComparisonExpression(op1, "lessThan", op1).evaluate(defaultContext()); + final RulesResult result4 = new ComparisonExpression(op1, "lessEqual", op1).evaluate(defaultContext()); + + // verify + assertFalse(result1.isSuccess()); + assertTrue(result2.isSuccess()); + assertFalse(result3.isSuccess()); + assertTrue(result4.isSuccess()); + } + + @Test + public void test_relationalOperator_Int_vs_Float() { + // setup + final Operand intOp = new OperandLiteral(66); + final Operand floatOp = new OperandLiteral(55.55); + + // test + final RulesResult result = new ComparisonExpression(intOp, "greaterThan", floatOp).evaluate(defaultContext()); + + // verify + assertTrue(result.isSuccess()); + } + + @Test + public void test_relationalOperator_Int_vs_Long() { + // setup + final Operand intOp = new OperandLiteral(66); + final Operand longtOp = new OperandLiteral(22222222222.233); + + // test + final RulesResult result = new ComparisonExpression(longtOp, "greaterThan", intOp).evaluate(defaultContext()); + + // verify + assertTrue(result.isSuccess()); + } + + + /* ************************************************************************** + * Private methods + **************************************************************************/ + private Context defaultContext() { + return new Context(new FakeTokenFinder(new HashMap<>()), defaultEvaluator, null); + } + +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorStartsWithTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorStartsWithTests.java new file mode 100644 index 000000000..7ef2e8576 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorStartsWithTests.java @@ -0,0 +1,200 @@ +/* ***************************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2018 Adobe Systems Incorporated + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe Systems Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Adobe Systems Incorporated and its + * suppliers and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe Systems Incorporated. + ******************************************************************************/ + +package com.adobe.marketing.mobile.rulesengine; + +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class OperatorStartsWithTests { + private ConditionEvaluator defaultEvaluator = new ConditionEvaluator(ConditionEvaluator.Option.DEFAULT); + private ConditionEvaluator caseInSensitiveEvaluator = new ConditionEvaluator( + ConditionEvaluator.Option.CASE_INSENSITIVE); + + @Before() + public void setup() { } + + /* ************************************************************************** + * Operand - StartsWith + **************************************************************************/ + + @Test + public void testComparisonExpression_StartsWith_Happy() { + // setup + final Operand key = new OperandLiteral("mat"); + final Operand op1 = new OperandLiteral("mat"); + final Operand op2 = new OperandLiteral("match"); + final Operand op3 = new OperandLiteral("matchme since this sentence starts with match"); + + + // test when both strings match + ComparisonExpression expression = new ComparisonExpression(op1, "startsWith", key); + RulesResult result = expression.evaluate(defaultContext(defaultEvaluator)); + assertTrue(result.isSuccess()); + + // test when string starts with given key + expression = new ComparisonExpression(op2, "startsWith", key); + result = expression.evaluate(defaultContext(defaultEvaluator)); + assertTrue(result.isSuccess()); + + // test when string is a sentence that starts with given key + expression = new ComparisonExpression(op3, "startsWith", key); + result = expression.evaluate(defaultContext(defaultEvaluator)); + assertTrue(result.isSuccess()); + } + + @Test + public void testComparisonExpression_StartsWith_UnHappyCases() { + // setup + final Operand key = new OperandLiteral("300"); + final Operand booleanOperand = new OperandLiteral(false); + final Operand numericOperand = new OperandLiteral(300); + final Operand invalidStringOperand = new OperandLiteral("3300"); + + // test when comparison string is a boolean + ComparisonExpression expression = new ComparisonExpression(booleanOperand, "startsWith", key); + RulesResult result = expression.evaluate(defaultContext(defaultEvaluator)); + assertFalse(result.isSuccess()); + + // test when comparison string is a numeric + expression = new ComparisonExpression(numericOperand, "startsWith", key); + result = expression.evaluate(defaultContext(defaultEvaluator)); + assertFalse(result.isSuccess()); + + // test when comparison string doesnt not start with the given key + expression = new ComparisonExpression(invalidStringOperand, "startsWith", key); + result = expression.evaluate(defaultContext(defaultEvaluator)); + assertFalse(result.isSuccess()); + } + + @Test + public void testComparisonExpression_StartsWith_CaseSensitive() { + // setup + final Operand key = new OperandLiteral("MatchMe"); + final Operand caseMatch = new OperandLiteral("MatchMe I think it will"); + final Operand caseNotMatch = new OperandLiteral("matchme i Think nothing"); + + + // test default evaluator when case matches + ComparisonExpression expression = new ComparisonExpression(caseMatch, "startsWith", key); + RulesResult result = expression.evaluate(defaultContext(defaultEvaluator)); + assertTrue(result.isSuccess()); + + // test default evaluator when case does not match + expression = new ComparisonExpression(caseNotMatch, "startsWith", key); + result = expression.evaluate(defaultContext(defaultEvaluator)); + assertFalse(result.isSuccess()); + + // test case-insensitive evaluator when case does not match + expression = new ComparisonExpression(caseNotMatch, "startsWith", key); + result = expression.evaluate(defaultContext(caseInSensitiveEvaluator)); + assertTrue(result.isSuccess()); + } + + + /* ************************************************************************** + * Operand - EndsWith + **************************************************************************/ + + @Test + public void testComparisonExpression_EndsWith_Happy() { + // setup + final Operand key = new OperandLiteral("tch"); + final Operand op1 = new OperandLiteral("tch"); + final Operand op2 = new OperandLiteral("match"); + final Operand op3 = new OperandLiteral("matchme since this sentence starts with match"); + + + // test when both strings match + ComparisonExpression expression = new ComparisonExpression(op1, "endsWith", key); + RulesResult result = expression.evaluate(defaultContext(defaultEvaluator)); + assertTrue(result.isSuccess()); + + // test when string starts with given key + expression = new ComparisonExpression(op2, "endsWith", key); + result = expression.evaluate(defaultContext(defaultEvaluator)); + assertTrue(result.isSuccess()); + + // test when string is a sentence that starts with given key + expression = new ComparisonExpression(op3, "endsWith", key); + result = expression.evaluate(defaultContext(defaultEvaluator)); + assertTrue(result.isSuccess()); + } + + @Test + public void testComparisonExpression_EndsWith_UnHappyCases() { + // setup + final Operand key = new OperandLiteral("300"); + final Operand booleanOperand = new OperandLiteral(false); + final Operand numericOperand = new OperandLiteral(300); + final Operand invalidStringOperand = new OperandLiteral("2330"); + + // test when comparison string is a boolean + ComparisonExpression expression = new ComparisonExpression(booleanOperand, "endsWith", key); + RulesResult result = expression.evaluate(defaultContext(defaultEvaluator)); + assertFalse(result.isSuccess()); + + // test when comparison string is a numeric + expression = new ComparisonExpression(numericOperand, "endsWith", key); + result = expression.evaluate(defaultContext(defaultEvaluator)); + assertFalse(result.isSuccess()); + + // test when comparison string doesnt not start with the given key + expression = new ComparisonExpression(invalidStringOperand, "endsWith", key); + result = expression.evaluate(defaultContext(defaultEvaluator)); + assertFalse(result.isSuccess()); + } + + @Test + public void testComparisonExpression_EndsWith_CaseInSensitive() { + // setup + final Operand key = new OperandLiteral("nothing"); + final Operand caseMatch = new OperandLiteral("it is nothing"); + final Operand caseNotMatch = new OperandLiteral("it is NOTHING"); + + // test default evaluator when case matches + ComparisonExpression expression = new ComparisonExpression(caseMatch, "endsWith", key); + RulesResult result = expression.evaluate(defaultContext(defaultEvaluator)); + assertTrue(result.isSuccess()); + + // test default evaluator when case does not match + expression = new ComparisonExpression(caseNotMatch, "endsWith", key); + result = expression.evaluate(defaultContext(defaultEvaluator)); + assertFalse(result.isSuccess()); + + // test case-insensitive evaluator when case does not match + expression = new ComparisonExpression(caseNotMatch, "endsWith", key); + result = expression.evaluate(defaultContext(caseInSensitiveEvaluator)); + assertTrue(result.isSuccess()); + } + + + /* ************************************************************************** + * Private methods + * *************************************************************************/ + + private Context defaultContext(final ConditionEvaluator conditionEvaluator) { + return new Context(new FakeTokenFinder(new HashMap<>()), conditionEvaluator, null); + } + + +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/ParserTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/ParserTests.java new file mode 100644 index 000000000..2197b06f0 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/ParserTests.java @@ -0,0 +1,125 @@ +/* ***************************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2018 Adobe Systems Incorporated + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe Systems Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Adobe Systems Incorporated and its + * suppliers and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe Systems Incorporated. + ******************************************************************************/ + +package com.adobe.marketing.mobile.rulesengine; + +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class ParserTests { + + private static final HashMap tokens = new HashMap() { + { + put("one", true); + put("two", "2"); + put("three", 3); + } + }; + private TokenFinder tokenFinder = new FakeTokenFinder(tokens); + private Transformer transformer = new Transformer(); + + @Before() + public void setup() { } + + @Test + public void parse_text_token_text() { + // test + List tokens = TemplateParser.parse("aa{{two}}cc", null); + + // verify + assertEquals(3, tokens.size()); + assertEquals(SegmentText.class, tokens.get(0).getClass()); + assertEquals(SegmentToken.class, tokens.get(1).getClass()); + assertEquals(SegmentText.class, tokens.get(2).getClass()); + + assertEquals("aa", tokens.get(0).getContent(tokenFinder, transformer)); + assertEquals("2", tokens.get(1).getContent(tokenFinder, transformer)); + assertEquals("cc", tokens.get(2).getContent(tokenFinder, transformer)); + } + + @Test + public void parse_text() { + List tokens = TemplateParser.parse("puretext", null); + assertEquals(1, tokens.size()); + assertEquals(SegmentText.class, tokens.get(0).getClass()); + } + + @Test + public void parse_token() { + List tokens = TemplateParser.parse("{{token}}", null); + assertEquals(1, tokens.size()); + assertEquals(SegmentToken.class, tokens.get(0).getClass()); + } + + @Test + public void parse_token_type2() { + List tokens = TemplateParser.parse("{{one}{{two}}", null); + assertEquals(1, tokens.size()); + } + + @Test + public void parse_emptyToken() { + List tokens = TemplateParser.parse("{{}}", null); + assertEquals(1, tokens.size()); + assertEquals(SegmentToken.class, tokens.get(0).getClass()); + } + + @Test + public void parse_emptyText() { + List tokens = TemplateParser.parse("", null); + assertEquals(0, tokens.size()); + } + + @Test + public void parse_Null() { + List tokens = TemplateParser.parse(null, null); + assertEquals(0, tokens.size()); + } + + @Test + public void parse_token_differentDelimiter() { + List tokens = TemplateParser.parse("%!two!%", new DelimiterPair("%!", "!%")); + assertEquals(1, tokens.size()); + assertEquals(SegmentToken.class, tokens.get(0).getClass()); + } + + @Test + public void parse_token_token_token() { + Template template = new Template("{{one}}{{two}}{{three}}"); + String templateString = template.render(tokenFinder, transformer); + + assertEquals("true23", templateString); + + List tokens = TemplateParser.parse("{{one}}{{two}}{{three}}", null); + assertEquals(3, tokens.size()); + assertEquals(SegmentToken.class, tokens.get(0).getClass()); + assertEquals(SegmentToken.class, tokens.get(1).getClass()); + assertEquals(SegmentToken.class, tokens.get(2).getClass()); + } + + @Test + public void parse_invalidToken_type1() { + List tokens = TemplateParser.parse("{{one", null); + assertEquals(0, tokens.size()); + } + +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/UnaryExpressionTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/UnaryExpressionTests.java new file mode 100644 index 000000000..6e0471ad1 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/UnaryExpressionTests.java @@ -0,0 +1,78 @@ +/* ***************************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2018 Adobe Systems Incorporated + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe Systems Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Adobe Systems Incorporated and its + * suppliers and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe Systems Incorporated. + ******************************************************************************/ + +package com.adobe.marketing.mobile.rulesengine; + +import org.junit.Test; +import java.util.HashMap; + +import static org.junit.Assert.*; + +public class UnaryExpressionTests { + + @Test + public void test_operator_exists() { + // setup + final Operand op1 = new OperandLiteral("String"); + final Operand op2 = new OperandLiteral(2); + final Operand op3 = new OperandLiteral(true); + final Operand op4 = new OperandLiteral(33.33); + + // test + RulesResult result1 = new UnaryExpression(op1, "exists").evaluate(defaultContext()); + RulesResult result2 = new UnaryExpression(op2, "exists").evaluate(defaultContext()); + RulesResult result3 = new UnaryExpression(op3, "exists").evaluate(defaultContext()); + RulesResult result4 = new UnaryExpression(op4, "exists").evaluate(defaultContext()); + RulesResult result5 = new UnaryExpression(null, "exists").evaluate(defaultContext()); + + // verify + assertTrue(result1.isSuccess()); + assertTrue(result2.isSuccess()); + assertTrue(result3.isSuccess()); + assertTrue(result4.isSuccess()); + assertFalse(result5.isSuccess()); + } + + @Test + public void test_operator_notExists() { + // setup + final Operand op1 = new OperandLiteral("String"); + final Operand op2 = new OperandLiteral(2); + final Operand op3 = new OperandLiteral(true); + final Operand op4 = new OperandLiteral(33.33); + + // test + RulesResult result1 = new UnaryExpression(op1, "notExist").evaluate(defaultContext()); + RulesResult result2 = new UnaryExpression(op2, "notExist").evaluate(defaultContext()); + RulesResult result3 = new UnaryExpression(op3, "notExist").evaluate(defaultContext()); + RulesResult result4 = new UnaryExpression(op4, "notExist").evaluate(defaultContext()); + RulesResult result5 = new UnaryExpression(null, "notExist").evaluate(defaultContext()); + + // verify + assertFalse(result1.isSuccess()); + assertFalse(result2.isSuccess()); + assertFalse(result3.isSuccess()); + assertFalse(result4.isSuccess()); + assertTrue(result5.isSuccess()); + } + + private Context defaultContext() { + return new Context(new FakeTokenFinder(new HashMap<>()), new ConditionEvaluator(ConditionEvaluator.Option.DEFAULT), + null); + } + +} From 04affa2d2e1436d7719e4dabebb3af5c764fa996 Mon Sep 17 00:00:00 2001 From: Yansong Yang Date: Fri, 11 Feb 2022 16:53:38 -0700 Subject: [PATCH 003/476] Run CI jobs in Parallel (#3) (#4) --- .circleci/config.yml | 36 ++++++++++++++++---------- Makefile | 2 +- code/android-core-library/build.gradle | 2 +- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 36638abc2..f4fc2155e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,5 @@ # Use the latest 2.1 version of CircleCI pipeline process engine. # See: https://circleci.com/docs/2.0/configuration-reference -# For a detailed guide to building and testing on Android, read the docs: -# https://circleci.com/docs/2.0/language-android/ for more details. version: 2.1 # Orbs are reusable packages of CircleCI configuration that you may share across projects, enabling you to create encapsulated, parameterized commands, jobs, and executors that can be used across multiple projects. @@ -13,9 +11,10 @@ orbs: # See: https://circleci.com/docs/2.0/configuration-reference/#jobs jobs: # Below is the definition of your job to build and test your app, you can rename and customize it as you want. - build-and-test: + build-and-unit-test: # These next lines define the Android machine image executor. # See: https://circleci.com/docs/2.0/executor-types/ + # The Android machine image: https://circleci.com/docs/2.0/android-machine-image/ executor: name: android/android-machine resource-class: large @@ -35,24 +34,33 @@ jobs: - run: name: unit-test command: make unit-test - - store_artifacts: - path: code/android-core-library/build/reports/tests/testPhoneDebugUnitTest - - - android/start-emulator-and-run-tests: + - store_test_results: + path: code/android-core-library/build/test-results/testPhoneDebugUnitTest + + functional-test: + executor: + name: android/android-machine + resource-class: large + tag: 2022.01.1 + + steps: + # Checkout the code as the first step. + - checkout + + - android/start-emulator-and-run-tests: # It should match the name seen in the "sdkmanager --list" output - system-image: system-images;android-29;default;x86 + system-image: system-images;android-29;default;x86 # The command to be run, while waiting for emulator startup - post-emulator-launch-assemble-command: make assemble-phone + post-emulator-launch-assemble-command: make assemble-phone # The test command - test-command: make functional-test - - store_artifacts: - path: code/android-core-library/build/reports/androidTests/connected/flavors/PHONE - + test-command: make functional-test # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows workflows: + version: 2 aepsdk-core-ci: # This is the name of the workflow, feel free to change it to better match your workflow. # Inside the workflow, you define the jobs you want to run. jobs: - - build-and-test + - build-and-unit-test + - functional-test diff --git a/Makefile b/Makefile index 5a1e74e5f..328716026 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ setup: clean: (rm -rf ci) - (./code/gradlew clean) + (./code/gradlew -p code clean) checkstyle: (./code/gradlew -p code/android-core-library checkstyle) diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index 65cb9eed9..c7099fc62 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -75,7 +75,7 @@ task jacocoTestReport(type: JacocoReport, dependsOn: ['testPhoneDebugUnitTest',' "outputs/code_coverage/phoneDebugAndroidTest/connected/*coverage.ec" ]) reports { - xml.enabled false + xml.enabled true csv.enabled false html.enabled true } From c770bb0e2a732ce38d202566dd2fc16b958f4245 Mon Sep 17 00:00:00 2001 From: praveek Date: Mon, 14 Feb 2022 09:45:52 -0800 Subject: [PATCH 004/476] Fix license headers --- .../mobile/rulesengine/FakeTokenFinder.java | 26 +++++++------------ .../mobile/rulesengine/FakeTransformer.java | 26 +++++++------------ .../rulesengine/LogicalExpressionTests.java | 26 +++++++------------ .../rulesengine/MustacheTokenTests.java | 26 +++++++------------ .../rulesengine/OperandFunctionTests.java | 11 ++++++++ .../rulesengine/OperatorContainsTests.java | 26 +++++++------------ .../rulesengine/OperatorEqualTests.java | 26 +++++++------------ .../rulesengine/OperatorGreaterThan.java | 26 +++++++------------ .../rulesengine/OperatorStartsWithTests.java | 26 +++++++------------ .../mobile/rulesengine/ParserTests.java | 26 +++++++------------ .../rulesengine/UnaryExpressionTests.java | 26 +++++++------------ 11 files changed, 111 insertions(+), 160 deletions(-) diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/FakeTokenFinder.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/FakeTokenFinder.java index e75c17176..27915c882 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/FakeTokenFinder.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/FakeTokenFinder.java @@ -1,19 +1,13 @@ -/* ***************************************************************************** - * ADOBE CONFIDENTIAL - * ___________________ - * - * Copyright 2018 Adobe Systems Incorporated - * All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Adobe Systems Incorporated and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to Adobe Systems Incorporated and its - * suppliers and are protected by trade secret or copyright law. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from Adobe Systems Incorporated. - ******************************************************************************/ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ package com.adobe.marketing.mobile.rulesengine; diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/FakeTransformer.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/FakeTransformer.java index 8c87cc6e3..9592817f0 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/FakeTransformer.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/FakeTransformer.java @@ -1,19 +1,13 @@ -/* ***************************************************************************** - * ADOBE CONFIDENTIAL - * ___________________ - * - * Copyright 2018 Adobe Systems Incorporated - * All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Adobe Systems Incorporated and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to Adobe Systems Incorporated and its - * suppliers and are protected by trade secret or copyright law. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from Adobe Systems Incorporated. - ******************************************************************************/ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ package com.adobe.marketing.mobile.rulesengine; diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/LogicalExpressionTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/LogicalExpressionTests.java index f3b0179a6..f9215bad1 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/LogicalExpressionTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/LogicalExpressionTests.java @@ -1,19 +1,13 @@ -/* ***************************************************************************** - * ADOBE CONFIDENTIAL - * ___________________ - * - * Copyright 2018 Adobe Systems Incorporated - * All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Adobe Systems Incorporated and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to Adobe Systems Incorporated and its - * suppliers and are protected by trade secret or copyright law. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from Adobe Systems Incorporated. - ******************************************************************************/ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ package com.adobe.marketing.mobile.rulesengine; diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/MustacheTokenTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/MustacheTokenTests.java index 265e18936..f687a8046 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/MustacheTokenTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/MustacheTokenTests.java @@ -1,19 +1,13 @@ -/* ***************************************************************************** - * ADOBE CONFIDENTIAL - * ___________________ - * - * Copyright 2018 Adobe Systems Incorporated - * All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Adobe Systems Incorporated and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to Adobe Systems Incorporated and its - * suppliers and are protected by trade secret or copyright law. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from Adobe Systems Incorporated. - ******************************************************************************/ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ package com.adobe.marketing.mobile.rulesengine; diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperandFunctionTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperandFunctionTests.java index f19f98066..eb25a1ae8 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperandFunctionTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperandFunctionTests.java @@ -1,3 +1,14 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + package com.adobe.marketing.mobile.rulesengine; import org.junit.Test; diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorContainsTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorContainsTests.java index df8683595..c0e80bba0 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorContainsTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorContainsTests.java @@ -1,19 +1,13 @@ -/* ***************************************************************************** - * ADOBE CONFIDENTIAL - * ___________________ - * - * Copyright 2018 Adobe Systems Incorporated - * All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Adobe Systems Incorporated and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to Adobe Systems Incorporated and its - * suppliers and are protected by trade secret or copyright law. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from Adobe Systems Incorporated. - ******************************************************************************/ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ package com.adobe.marketing.mobile.rulesengine; diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorEqualTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorEqualTests.java index de8e286f1..e3f75ae30 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorEqualTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorEqualTests.java @@ -1,19 +1,13 @@ -/* ***************************************************************************** - * ADOBE CONFIDENTIAL - * ___________________ - * - * Copyright 2018 Adobe Systems Incorporated - * All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Adobe Systems Incorporated and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to Adobe Systems Incorporated and its - * suppliers and are protected by trade secret or copyright law. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from Adobe Systems Incorporated. - ******************************************************************************/ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ package com.adobe.marketing.mobile.rulesengine; diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorGreaterThan.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorGreaterThan.java index 62a2e80d9..d6b879634 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorGreaterThan.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorGreaterThan.java @@ -1,19 +1,13 @@ -/* ***************************************************************************** - * ADOBE CONFIDENTIAL - * ___________________ - * - * Copyright 2018 Adobe Systems Incorporated - * All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Adobe Systems Incorporated and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to Adobe Systems Incorporated and its - * suppliers and are protected by trade secret or copyright law. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from Adobe Systems Incorporated. - ******************************************************************************/ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ package com.adobe.marketing.mobile.rulesengine; diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorStartsWithTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorStartsWithTests.java index 7ef2e8576..b5206ddec 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorStartsWithTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperatorStartsWithTests.java @@ -1,19 +1,13 @@ -/* ***************************************************************************** - * ADOBE CONFIDENTIAL - * ___________________ - * - * Copyright 2018 Adobe Systems Incorporated - * All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Adobe Systems Incorporated and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to Adobe Systems Incorporated and its - * suppliers and are protected by trade secret or copyright law. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from Adobe Systems Incorporated. - ******************************************************************************/ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ package com.adobe.marketing.mobile.rulesengine; diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/ParserTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/ParserTests.java index 2197b06f0..a1d1ad546 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/ParserTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/ParserTests.java @@ -1,19 +1,13 @@ -/* ***************************************************************************** - * ADOBE CONFIDENTIAL - * ___________________ - * - * Copyright 2018 Adobe Systems Incorporated - * All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Adobe Systems Incorporated and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to Adobe Systems Incorporated and its - * suppliers and are protected by trade secret or copyright law. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from Adobe Systems Incorporated. - ******************************************************************************/ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ package com.adobe.marketing.mobile.rulesengine; diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/UnaryExpressionTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/UnaryExpressionTests.java index 6e0471ad1..327bbf553 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/UnaryExpressionTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/UnaryExpressionTests.java @@ -1,19 +1,13 @@ -/* ***************************************************************************** - * ADOBE CONFIDENTIAL - * ___________________ - * - * Copyright 2018 Adobe Systems Incorporated - * All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Adobe Systems Incorporated and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to Adobe Systems Incorporated and its - * suppliers and are protected by trade secret or copyright law. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from Adobe Systems Incorporated. - ******************************************************************************/ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ package com.adobe.marketing.mobile.rulesengine; From fe842fbfef75fedda4c5672a0d930d4bdddacd65 Mon Sep 17 00:00:00 2001 From: praveek Date: Mon, 14 Feb 2022 12:10:57 -0800 Subject: [PATCH 005/476] Fix casting issues with RulesEngine --- .../rulesengine/ComparisonExpression.java | 2 +- .../mobile/rulesengine/FunctionBlock.java | 1 + .../rulesengine/OperandMustacheToken.java | 31 ++++++++--------- .../mobile/rulesengine/RulesEngine.java | 8 ++--- .../mobile/rulesengine/TransformerBlock.java | 1 + .../rulesengine/OperandFunctionTests.java | 16 +++------ ...ts.java => OperandMustacheTokenTests.java} | 34 +++++++++---------- 7 files changed, 44 insertions(+), 49 deletions(-) rename code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/{MustacheTokenTests.java => OperandMustacheTokenTests.java} (86%) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ComparisonExpression.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ComparisonExpression.java index 0e986b5bd..c59e3f2f0 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ComparisonExpression.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ComparisonExpression.java @@ -12,7 +12,7 @@ package com.adobe.marketing.mobile.rulesengine; /** - * {@link ComparisonExpression} allows for comparision of two operands and evaluates to a True or False. + * {@link ComparisonExpression} allows for comparison of two operands and evaluates to a True or False. * Comparison operators include LessThan, LessThanOrEqual to, Equal, NotEqual, * GreaterThan, GreaterThanOrEqual to, Contains and notContains. */ diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/FunctionBlock.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/FunctionBlock.java index d1b728430..3cd0d459e 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/FunctionBlock.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/FunctionBlock.java @@ -11,6 +11,7 @@ package com.adobe.marketing.mobile.rulesengine; +@FunctionalInterface public interface FunctionBlock { T execute(final Object... e); } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/OperandMustacheToken.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/OperandMustacheToken.java index a746bb16c..c4ff362d1 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/OperandMustacheToken.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/OperandMustacheToken.java @@ -16,8 +16,9 @@ * Class to handle the mustache token Operand. */ public class OperandMustacheToken implements Operand { + private static final String LOG_TAG = "OperandMustacheToken"; private final MustacheToken mustacheToken; - + private final Class tClass; /** * Constructor. @@ -32,27 +33,19 @@ public class OperandMustacheToken implements Operand { * 3. some{{region.city}} - (this string does not start with a valid token) * * @param tokenString string representing a mustache token operand + * @param tClass string representing a mustache token operand */ - public OperandMustacheToken(final String tokenString) { - - // if token string is invalid make the mustache Token null. - // There by the operand always returns null on resolve. This is equivalent to OperandNone in swift. - if (tokenString == null || tokenString.isEmpty()) { - mustacheToken = null; - return; - } - + public OperandMustacheToken(final String tokenString, Class tClass) { + MustacheToken mustacheToken = null; + // Mustache token operands must have only one token, ignore others. final List segmentList = TemplateParser.parse(tokenString); - - // Mustache token operands must have only one token. - // Hence we ignore other if (segmentList.size() > 0 && segmentList.get(0) instanceof SegmentToken) { SegmentToken segmentToken = (SegmentToken) segmentList.get(0); mustacheToken = segmentToken.getMustacheToken(); - return; } - mustacheToken = new MustacheToken(tokenString); + this.mustacheToken = mustacheToken; + this.tClass = tClass; } @@ -69,7 +62,13 @@ public T resolve(final Context context) { return null; } - return (T) mustacheToken.resolve(context.tokenFinder, context.transformer); + Object resolvedValue = mustacheToken.resolve(context.tokenFinder, context.transformer); + try { + return tClass.cast(resolvedValue); + } catch (ClassCastException ex) { + Log.debug(LOG_TAG, "resolve: Error casting value to type " + tClass.toString()); + return null; + } } } \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/RulesEngine.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/RulesEngine.java index 7cfbae130..ba48f7685 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/RulesEngine.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/RulesEngine.java @@ -22,13 +22,14 @@ public class RulesEngine { public RulesEngine(final Evaluating evaluator, final Transforming transformer) { this.evaluator = evaluator; this.transformer = transformer; + this.rules = new ArrayList<>(); } - public List evaluate(final TokenFinder tokenFinder) { + public List evaluate(final TokenFinder tokenFinder) { final Context context = new Context(tokenFinder, evaluator, transformer); - List triggerRules = new ArrayList<>(); + List triggerRules = new ArrayList<>(); - for (final Rule rule : rules) { + for (final T rule : rules) { if (rule.getEvaluable().evaluate(context).isSuccess()) { triggerRules.add(rule); } @@ -44,5 +45,4 @@ public void addRules(final List newRules) { public void clearRules() { rules.clear(); } - } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/TransformerBlock.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/TransformerBlock.java index e5683cdf7..7030065c9 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/TransformerBlock.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/TransformerBlock.java @@ -11,6 +11,7 @@ package com.adobe.marketing.mobile.rulesengine; +@FunctionalInterface public interface TransformerBlock { T transform(final Object e); } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperandFunctionTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperandFunctionTests.java index eb25a1ae8..94cf70de7 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperandFunctionTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperandFunctionTests.java @@ -25,18 +25,12 @@ public class OperandFunctionTests { public void test_OperandFunction() { // setup final Operand stringOperand = new OperandLiteral("bossbaby"); - final OperandFunction functionOperator = new OperandFunction(new FunctionBlock() { - @Override - public String execute(Object... args) { - StringBuilder builder = new StringBuilder(); - - for (Object each : args) { - builder.append(each.toString()); - } - - return builder.toString(); + final OperandFunction functionOperator = new OperandFunction<>((Object... args) -> { + StringBuilder builder = new StringBuilder(); + for (Object each : args) { + builder.append(each.toString()); } - + return builder.toString(); }, "boss", "baby"); // test diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/MustacheTokenTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperandMustacheTokenTests.java similarity index 86% rename from code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/MustacheTokenTests.java rename to code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperandMustacheTokenTests.java index f687a8046..9dbebf79f 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/MustacheTokenTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperandMustacheTokenTests.java @@ -18,7 +18,7 @@ import static org.junit.Assert.*; -public class MustacheTokenTests { +public class OperandMustacheTokenTests { private ConditionEvaluator defaultEvaluator = new ConditionEvaluator(ConditionEvaluator.Option.DEFAULT); @@ -28,9 +28,9 @@ public void setup() { } @Test public void Operand_MustacheToken() { // setup - final OperandMustacheToken tokenString = new OperandMustacheToken("{{Hero}}"); - final OperandMustacheToken tokenInteger = new OperandMustacheToken("{{integerToken}}"); - final OperandMustacheToken tokenBoolean = new OperandMustacheToken("{{booleanToken}}"); + final OperandMustacheToken tokenString = new OperandMustacheToken("{{Hero}}", String.class); + final OperandMustacheToken tokenInteger = new OperandMustacheToken("{{integerToken}}", Number.class); + final OperandMustacheToken tokenBoolean = new OperandMustacheToken("{{booleanToken}}", Boolean.class); // test final String result1 = tokenString.resolve(defaultContext(defaultEvaluator)); @@ -46,7 +46,7 @@ public void Operand_MustacheToken() { @Test public void Operand_MustacheToken_multipleToken() { // setup - final OperandMustacheToken operand = new OperandMustacheToken("{{Beer}}{{Corona}}"); + final OperandMustacheToken operand = new OperandMustacheToken("{{Beer}}{{Corona}}", String.class); // test final Object result = operand.resolve(defaultContext(defaultEvaluator)); @@ -58,11 +58,11 @@ public void Operand_MustacheToken_multipleToken() { @Test public void Operand_MustacheToken_InvalidToken() { // setup - final OperandMustacheToken example1 = new OperandMustacheToken("{{Beer"); - final OperandMustacheToken example2 = new OperandMustacheToken("Beer{{Hero}}"); - final OperandMustacheToken example3 = new OperandMustacheToken("onlyString"); - final OperandMustacheToken example4 = new OperandMustacheToken(""); - final OperandMustacheToken example5 = new OperandMustacheToken(null); + final OperandMustacheToken example1 = new OperandMustacheToken("{{Beer", String.class); + final OperandMustacheToken example2 = new OperandMustacheToken("Beer{{Hero}}", String.class); + final OperandMustacheToken example3 = new OperandMustacheToken("onlyString", String.class); + final OperandMustacheToken example4 = new OperandMustacheToken("", String.class); + final OperandMustacheToken example5 = new OperandMustacheToken(null, String.class); // test and verify assertNull(example1.resolve(defaultContext(defaultEvaluator))); @@ -75,19 +75,19 @@ public void Operand_MustacheToken_InvalidToken() { @Test public void Operand_WrongDataType_Token() { // setup - final OperandMustacheToken wrongToken = new OperandMustacheToken("{{booleanToken}}"); + final OperandMustacheToken wrongToken = new OperandMustacheToken("{{booleanToken}}", String.class); // test - final Object result1 = wrongToken.resolve(defaultContext(defaultEvaluator)); + final Object result = wrongToken.resolve(defaultContext(defaultEvaluator)); // verify - assertTrue(result1 instanceof Boolean); + assertNull(result); } @Test public void testComparisonExpression_MustacheToken() { // setup - final Operand lhs = new OperandMustacheToken("{{Hero}}"); + final Operand lhs = new OperandMustacheToken("{{Hero}}", String.class); final Operand rhs = new OperandLiteral("Soldier"); // test @@ -101,7 +101,7 @@ public void testComparisonExpression_MustacheToken() { @Test public void testComparisonExpression_MustacheFunction() { // setup - final Operand lhs = new OperandMustacheToken("{{addExtraString(Beer)}}"); + final Operand lhs = new OperandMustacheToken("{{addExtraString(Beer)}}", String.class); final Operand rhs = new OperandLiteral("Corona extra"); // test @@ -115,8 +115,8 @@ public void testComparisonExpression_MustacheFunction() { @Test public void testComparisonExpression_MustacheFunction_and_Token() { // setup - final Operand lhs = new OperandMustacheToken("{{addExtraString(Beer)}}"); - final Operand rhs = new OperandMustacheToken("{{answer}}"); + final Operand lhs = new OperandMustacheToken("{{addExtraString(Beer)}}", String.class); + final Operand rhs = new OperandMustacheToken("{{answer}}", String.class); // test final ComparisonExpression expression = new ComparisonExpression(lhs, "equals", rhs); From b85744185164d3674d97936b219c3e7d2f2e7ff4 Mon Sep 17 00:00:00 2001 From: Praveen Date: Thu, 17 Feb 2022 16:22:01 -0800 Subject: [PATCH 006/476] Build changes to support kotlin (#7) --- code/android-core-library/build.gradle | 12 ++++++++++++ .../marketing/mobile/internal/eventhub/EventHub.kt | 7 +++++++ .../src/test/kotlin/EventHubTests.kt | 11 +++++++++++ code/build.gradle | 1 + code/gradle.properties | 2 +- 5 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt create mode 100644 code/android-core-library/src/test/kotlin/EventHubTests.kt diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index c7099fc62..50a1853c7 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' android { compileSdkVersion 30 @@ -17,6 +18,7 @@ android { java{ srcDirs += "src/legacy/test-common/java" srcDirs += "src/legacy/test-module/java" + srcDirs += "src/test/kotlin" } resources{ srcDirs += "src/legacy/test-module/resources" @@ -61,6 +63,10 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } } apply plugin: 'jacoco' task jacocoTestReport(type: JacocoReport, dependsOn: ['testPhoneDebugUnitTest','createPhoneDebugCoverageReport']) { @@ -104,6 +110,7 @@ apply from: 'release.gradle' dependencies { //noinspection GradleCompatible implementation 'com.android.support:appcompat-v7:27.1.1' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" // unit tests testImplementation "junit:junit:4.13.2" //noinspection GradleDependency @@ -114,6 +121,11 @@ dependencies { testImplementation 'org.robolectric:robolectric:3.6.2' //noinspection GradleDependency testImplementation 'org.json:json:20160810' + testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" // instrumentation tests androidTestImplementation "com.android.support.test:rules:1.0.2" } + +repositories { + mavenCentral() +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt new file mode 100644 index 000000000..dfb4f6db4 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -0,0 +1,7 @@ +package com.adobe.marketing.mobile.internal.eventhub + +class EventHub { + companion object { + val version = "2.0.0" + } +} \ No newline at end of file diff --git a/code/android-core-library/src/test/kotlin/EventHubTests.kt b/code/android-core-library/src/test/kotlin/EventHubTests.kt new file mode 100644 index 000000000..303970ecb --- /dev/null +++ b/code/android-core-library/src/test/kotlin/EventHubTests.kt @@ -0,0 +1,11 @@ +import org.junit.Test +import kotlin.test.assertEquals +import com.adobe.marketing.mobile.internal.eventhub.EventHub + +internal class EventHubTests { + + @Test + fun testVersion() { + assertEquals(EventHub.version, "2.0.0") + } +} \ No newline at end of file diff --git a/code/build.gradle b/code/build.gradle index 3a393264f..75bea9caf 100644 --- a/code/build.gradle +++ b/code/build.gradle @@ -10,6 +10,7 @@ buildscript { //noinspection AndroidGradlePluginVersion classpath 'com.android.tools.build:gradle:4.0.1' classpath "org.jacoco:org.jacoco.core:0.8.7" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/code/gradle.properties b/code/gradle.properties index 98d80cbd2..f317f1686 100644 --- a/code/gradle.properties +++ b/code/gradle.properties @@ -22,5 +22,5 @@ mavenIdentityVersion=1.2.2 mavenLifecycleVersion=1.1.0 #android.enableUnitTestBinaryResources=false - +kotlin_version = 1.4.0 From 08def59f5f2076bd72367cbf462f38c141224011 Mon Sep 17 00:00:00 2001 From: Yansong Yang Date: Thu, 17 Feb 2022 21:42:32 -0700 Subject: [PATCH 007/476] Merge latest dev-v2.0.0 changes (#8) * Run CI jobs in Parallel (#3) (#4) * Build changes to support kotlin (#7) Co-authored-by: Praveen --- .circleci/config.yml | 36 +++++++++++-------- Makefile | 2 +- code/android-core-library/build.gradle | 14 +++++++- .../mobile/internal/eventhub/EventHub.kt | 7 ++++ .../src/test/kotlin/EventHubTests.kt | 11 ++++++ code/build.gradle | 1 + code/gradle.properties | 2 +- 7 files changed, 56 insertions(+), 17 deletions(-) create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt create mode 100644 code/android-core-library/src/test/kotlin/EventHubTests.kt diff --git a/.circleci/config.yml b/.circleci/config.yml index 36638abc2..f4fc2155e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,5 @@ # Use the latest 2.1 version of CircleCI pipeline process engine. # See: https://circleci.com/docs/2.0/configuration-reference -# For a detailed guide to building and testing on Android, read the docs: -# https://circleci.com/docs/2.0/language-android/ for more details. version: 2.1 # Orbs are reusable packages of CircleCI configuration that you may share across projects, enabling you to create encapsulated, parameterized commands, jobs, and executors that can be used across multiple projects. @@ -13,9 +11,10 @@ orbs: # See: https://circleci.com/docs/2.0/configuration-reference/#jobs jobs: # Below is the definition of your job to build and test your app, you can rename and customize it as you want. - build-and-test: + build-and-unit-test: # These next lines define the Android machine image executor. # See: https://circleci.com/docs/2.0/executor-types/ + # The Android machine image: https://circleci.com/docs/2.0/android-machine-image/ executor: name: android/android-machine resource-class: large @@ -35,24 +34,33 @@ jobs: - run: name: unit-test command: make unit-test - - store_artifacts: - path: code/android-core-library/build/reports/tests/testPhoneDebugUnitTest - - - android/start-emulator-and-run-tests: + - store_test_results: + path: code/android-core-library/build/test-results/testPhoneDebugUnitTest + + functional-test: + executor: + name: android/android-machine + resource-class: large + tag: 2022.01.1 + + steps: + # Checkout the code as the first step. + - checkout + + - android/start-emulator-and-run-tests: # It should match the name seen in the "sdkmanager --list" output - system-image: system-images;android-29;default;x86 + system-image: system-images;android-29;default;x86 # The command to be run, while waiting for emulator startup - post-emulator-launch-assemble-command: make assemble-phone + post-emulator-launch-assemble-command: make assemble-phone # The test command - test-command: make functional-test - - store_artifacts: - path: code/android-core-library/build/reports/androidTests/connected/flavors/PHONE - + test-command: make functional-test # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows workflows: + version: 2 aepsdk-core-ci: # This is the name of the workflow, feel free to change it to better match your workflow. # Inside the workflow, you define the jobs you want to run. jobs: - - build-and-test + - build-and-unit-test + - functional-test diff --git a/Makefile b/Makefile index 5a1e74e5f..328716026 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ setup: clean: (rm -rf ci) - (./code/gradlew clean) + (./code/gradlew -p code clean) checkstyle: (./code/gradlew -p code/android-core-library checkstyle) diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index 65cb9eed9..50a1853c7 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' android { compileSdkVersion 30 @@ -17,6 +18,7 @@ android { java{ srcDirs += "src/legacy/test-common/java" srcDirs += "src/legacy/test-module/java" + srcDirs += "src/test/kotlin" } resources{ srcDirs += "src/legacy/test-module/resources" @@ -61,6 +63,10 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } } apply plugin: 'jacoco' task jacocoTestReport(type: JacocoReport, dependsOn: ['testPhoneDebugUnitTest','createPhoneDebugCoverageReport']) { @@ -75,7 +81,7 @@ task jacocoTestReport(type: JacocoReport, dependsOn: ['testPhoneDebugUnitTest',' "outputs/code_coverage/phoneDebugAndroidTest/connected/*coverage.ec" ]) reports { - xml.enabled false + xml.enabled true csv.enabled false html.enabled true } @@ -104,6 +110,7 @@ apply from: 'release.gradle' dependencies { //noinspection GradleCompatible implementation 'com.android.support:appcompat-v7:27.1.1' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" // unit tests testImplementation "junit:junit:4.13.2" //noinspection GradleDependency @@ -114,6 +121,11 @@ dependencies { testImplementation 'org.robolectric:robolectric:3.6.2' //noinspection GradleDependency testImplementation 'org.json:json:20160810' + testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" // instrumentation tests androidTestImplementation "com.android.support.test:rules:1.0.2" } + +repositories { + mavenCentral() +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt new file mode 100644 index 000000000..dfb4f6db4 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -0,0 +1,7 @@ +package com.adobe.marketing.mobile.internal.eventhub + +class EventHub { + companion object { + val version = "2.0.0" + } +} \ No newline at end of file diff --git a/code/android-core-library/src/test/kotlin/EventHubTests.kt b/code/android-core-library/src/test/kotlin/EventHubTests.kt new file mode 100644 index 000000000..303970ecb --- /dev/null +++ b/code/android-core-library/src/test/kotlin/EventHubTests.kt @@ -0,0 +1,11 @@ +import org.junit.Test +import kotlin.test.assertEquals +import com.adobe.marketing.mobile.internal.eventhub.EventHub + +internal class EventHubTests { + + @Test + fun testVersion() { + assertEquals(EventHub.version, "2.0.0") + } +} \ No newline at end of file diff --git a/code/build.gradle b/code/build.gradle index 3a393264f..75bea9caf 100644 --- a/code/build.gradle +++ b/code/build.gradle @@ -10,6 +10,7 @@ buildscript { //noinspection AndroidGradlePluginVersion classpath 'com.android.tools.build:gradle:4.0.1' classpath "org.jacoco:org.jacoco.core:0.8.7" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/code/gradle.properties b/code/gradle.properties index 98d80cbd2..f317f1686 100644 --- a/code/gradle.properties +++ b/code/gradle.properties @@ -22,5 +22,5 @@ mavenIdentityVersion=1.2.2 mavenLifecycleVersion=1.1.0 #android.enableUnitTestBinaryResources=false - +kotlin_version = 1.4.0 From cebd38254f3fde564d1c2cd7f471b9984fbfb450 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Mon, 7 Mar 2022 18:26:07 -0800 Subject: [PATCH 008/476] [MOB-15383] Migrate Database from cacheDir to filesDir --- ...oidCacheToFilesDatabaseMigrationTests.java | 62 +++++++++++++++++++ .../marketing/mobile/AndroidCursorTests.java | 6 +- .../AndroidDatabaseMigrationTestHelper.java | 58 +++++++++++++++++ .../mobile/AndroidDatabaseServiceTests.java | 42 ++++++------- .../mobile/AndroidDatabaseTests.java | 4 +- .../com/adobe/marketing/mobile/TestUtils.java | 23 +++++++ .../services/SqliteDatabaseHelperTests.java | 2 +- .../mobile/E2EAndroidSystemInfoService.java | 8 +++ .../mobile/MockSystemInfoService.java | 6 ++ .../mobile/AndroidDatabaseService.java | 6 +- .../mobile/CacheToFilesDatabaseMigration.java | 42 +++++++++++++ .../adobe/marketing/mobile/CoreConstants.java | 15 +++++ .../com/adobe/marketing/mobile/FileUtil.java | 29 +++++++++ .../marketing/mobile/SystemInfoService.java | 9 +++ .../mobile/services/DeviceInforming.java | 9 +++ .../mobile/AndroidSystemInfoService.java | 11 ++++ .../adobe/marketing/mobile/MobileCore.java | 3 + .../mobile/services/DataQueueService.java | 2 +- .../mobile/services/DeviceInfoService.java | 11 ++++ .../mobile/services/SQLiteDataQueue.java | 4 +- .../mobile/AndroidSystemInfoServiceTests.java | 16 +++++ .../marketing/mobile/FileTestHelper.java | 25 ++++++++ .../adobe/marketing/mobile/FileUtilTests.java | 55 +++++++++++++++- .../testapp/PlatformServicesFragment.java | 1 + 24 files changed, 415 insertions(+), 34 deletions(-) create mode 100644 code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCacheToFilesDatabaseMigrationTests.java create mode 100644 code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseMigrationTestHelper.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/CacheToFilesDatabaseMigration.java diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCacheToFilesDatabaseMigrationTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCacheToFilesDatabaseMigrationTests.java new file mode 100644 index 000000000..ce943e835 --- /dev/null +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCacheToFilesDatabaseMigrationTests.java @@ -0,0 +1,62 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile; + +import static com.adobe.marketing.mobile.AndroidDatabaseMigrationTestHelper.MOCK_FILE_CONTENT; +import static junit.framework.Assert.assertEquals; +import static junit.framework.TestCase.assertNull; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; + +import com.adobe.marketing.mobile.services.ServiceProvider; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; + +public class AndroidCacheToFilesDatabaseMigrationTests { + + private CacheToFilesDatabaseMigration databaseMigrationTool; + private AndroidDatabaseMigrationTestHelper migrationTestHelper; + + @Before + public void setup() { + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + ServiceProvider.getInstance().setContext(appContext); + databaseMigrationTool = new CacheToFilesDatabaseMigration(); + migrationTestHelper = new AndroidDatabaseMigrationTestHelper(); + } + + @After + public void tearDown() { + migrationTestHelper.deleteDirectory(ServiceProvider.getInstance().getDeviceInfoService().getApplicationCacheDir()); + migrationTestHelper.deleteDirectory(ServiceProvider.getInstance().getDeviceInfoService().getApplicationFilesDir()); + } + + @Test + public void testDatabaseMigration_ExistingDatabase() { + migrationTestHelper.createMockEdgeDatabaseInCacheDir(); + databaseMigrationTool.migrate(); + assertEquals(MOCK_FILE_CONTENT, FileUtil.readStringFromFile(new File(ServiceProvider.getInstance().getDeviceInfoService().getApplicationFilesDir(), + CoreConstants.ExtensionNames.Edge.EDGE_EXTENSION_NAME))); + } + + @Test + public void testDatabaseMigration_NoDatabase() { + databaseMigrationTool.migrate(); + assertNull(FileUtil.readStringFromFile(new File(ServiceProvider.getInstance().getDeviceInfoService().getApplicationFilesDir(), + CoreConstants.ExtensionNames.Edge.EDGE_EXTENSION_NAME))); + } +} diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java index b34831dd6..2f289568a 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java @@ -44,14 +44,14 @@ public class AndroidCursorTests { private DatabaseService androidDatabaseService; private DatabaseService.Database androidDatabase; - private String cacheDir; + private String filesDir; private DatabaseService.QueryResult queryResult; private Query testQuery; @Before public void beforeEach() { androidDatabaseService = new AndroidDatabaseService(null); - androidDatabase = androidDatabaseService.openDatabase(TestUtils.getCacheDir( + androidDatabase = androidDatabaseService.openDatabase(TestUtils.getFilesDir( InstrumentationRegistry.getInstrumentation().getTargetContext()) + "/" + name.getMethodName()); } @@ -62,7 +62,7 @@ public void afterEach() { queryResult.close(); } - TestUtils.deleteAllFilesInCacheDir(InstrumentationRegistry.getInstrumentation().getTargetContext()); + TestUtils.deleteAllFilesInFilesDir(InstrumentationRegistry.getInstrumentation().getTargetContext()); } @Test diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseMigrationTestHelper.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseMigrationTestHelper.java new file mode 100644 index 000000000..e9fc4d046 --- /dev/null +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseMigrationTestHelper.java @@ -0,0 +1,58 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile; + +import com.adobe.marketing.mobile.services.ServiceProvider; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +class AndroidDatabaseMigrationTestHelper { + + static final String MOCK_FILE_CONTENT = "Sample database file contents"; + + void createMockEdgeDatabaseInCacheDir() { + File cacheFile = new File(ServiceProvider.getInstance().getDeviceInfoService().getApplicationCacheDir(), + CoreConstants.ExtensionNames.Edge.EDGE_EXTENSION_NAME); + + try { + FileWriter fileWriter = new FileWriter(cacheFile); + fileWriter.write(MOCK_FILE_CONTENT); + fileWriter.flush(); + fileWriter.close(); + } catch (IOException ex) {} + } + + /** + * Removes the cache directory recursively + */ + void deleteDirectory(final File directory) { + if (directory != null) { + String[] files = directory.list(); + + if (files != null) { + for (String file : files) { + File currentFile = new File(directory.getPath(), file); + + if (currentFile.isDirectory()) { + deleteDirectory(currentFile); + } else { + currentFile.delete(); + } + } + } + + directory.delete(); + } + } +} diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java index fae6d1d60..9e4de01c4 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java @@ -30,31 +30,31 @@ public class AndroidDatabaseServiceTests { private AndroidDatabaseService androidDatabaseService; - private String cacheDir; + private String filesDir; private Context appContext; @Before public void beforeEach() { appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - cacheDir = appContext.getCacheDir().getPath(); + filesDir = appContext.getFilesDir().getPath(); androidDatabaseService = new AndroidDatabaseService(null); App.setAppContext(appContext); } @After public void afterEach() { - appContext.getCacheDir().delete(); + appContext.getFilesDir().delete(); } @Test public void testOpenDatabase_Happy() throws Exception { - assertNotNull(androidDatabaseService.openDatabase(cacheDir + "/testOpenDatabase_Happy")); + assertNotNull(androidDatabaseService.openDatabase(filesDir + "/testOpenDatabase_Happy")); } @Test public void testOpenDatabase_Exists() throws Exception { - assertNotNull(androidDatabaseService.openDatabase(cacheDir + "/testOpenDatabase_Happy")); - assertNotNull(androidDatabaseService.openDatabase(cacheDir + "/testOpenDatabase_Happy")); + assertNotNull(androidDatabaseService.openDatabase(filesDir + "/testOpenDatabase_Happy")); + assertNotNull(androidDatabaseService.openDatabase(filesDir + "/testOpenDatabase_Happy")); } @Test @@ -69,13 +69,13 @@ public void testOpenDatabase_EmptyFilePath() throws Exception { @Test public void testDeleteDatabase_Happy() { - assertNotNull(androidDatabaseService.openDatabase(cacheDir + "/testDeleteDatabase_Happy")); - assertTrue(androidDatabaseService.deleteDatabase(cacheDir + "/testDeleteDatabase_Happy")); + assertNotNull(androidDatabaseService.openDatabase(filesDir + "/testDeleteDatabase_Happy")); + assertTrue(androidDatabaseService.deleteDatabase(filesDir + "/testDeleteDatabase_Happy")); } @Test public void testDeleteDatabase_DoesNotExist() { - assertFalse(androidDatabaseService.deleteDatabase(cacheDir + "/testDeleteDatabase_DoesNotExist")); + assertFalse(androidDatabaseService.deleteDatabase(filesDir + "/testDeleteDatabase_DoesNotExist")); } @Test @@ -90,38 +90,38 @@ public void testDeleteDatabase_EmptyFilePath() { @Test public void testDeleteDatabase_RelativePathBackslashClearnedUp() { - assertNotNull(androidDatabaseService.openDatabase(cacheDir + "/mydatabase\\..\\..\\database1")); - assertTrue(androidDatabaseService.deleteDatabase(cacheDir + "/mydatabase\\..\\..\\database1")); + assertNotNull(androidDatabaseService.openDatabase(filesDir + "/mydatabase\\..\\..\\database1")); + assertTrue(androidDatabaseService.deleteDatabase(filesDir + "/mydatabase\\..\\..\\database1")); } @Test public void testDeleteDatabase_RelativePathForwardslashClearnedUp() { - assertNotNull(androidDatabaseService.openDatabase(cacheDir + "/mydatabase/../../database1")); - assertTrue(androidDatabaseService.deleteDatabase(cacheDir + "/mydatabase/../../database1")); + assertNotNull(androidDatabaseService.openDatabase(filesDir + "/mydatabase/../../database1")); + assertTrue(androidDatabaseService.deleteDatabase(filesDir + "/mydatabase/../../database1")); } @Test public void testDeleteDatabase_RelativePathBackslashDoesNotChangeDir() { - assertNotNull(androidDatabaseService.openDatabase(cacheDir + "/mydatabase\\..\\database1")); - assertFalse(androidDatabaseService.deleteDatabase(cacheDir + "/database1")); + assertNotNull(androidDatabaseService.openDatabase(filesDir + "/mydatabase\\..\\database1")); + assertFalse(androidDatabaseService.deleteDatabase(filesDir + "/database1")); } @Test public void testDeleteDatabase_RelativePathForwardslashDoesNotChangeDir() { - assertNotNull(androidDatabaseService.openDatabase(cacheDir + "/mydatabase/../database1")); - assertFalse(androidDatabaseService.deleteDatabase(cacheDir + "/database1")); + assertNotNull(androidDatabaseService.openDatabase(filesDir + "/mydatabase/../database1")); + assertFalse(androidDatabaseService.deleteDatabase(filesDir + "/database1")); } @Test public void testDeleteDatabase_RelativePathMixedWorkTheSameWhenNotMatch() { - assertNotNull(androidDatabaseService.openDatabase(cacheDir + "/mydatabase\\..\\database1")); - assertTrue(androidDatabaseService.deleteDatabase(cacheDir + "/mydatabase/../../database1")); + assertNotNull(androidDatabaseService.openDatabase(filesDir + "/mydatabase\\..\\database1")); + assertTrue(androidDatabaseService.deleteDatabase(filesDir + "/mydatabase/../../database1")); } @Test public void testDeleteDatabase_RelativePathMixedWorkTheSameWhenMatch() { - assertNotNull(androidDatabaseService.openDatabase(cacheDir + "/mydatabase\\..\\database1")); - assertTrue(androidDatabaseService.deleteDatabase(cacheDir + "/mydatabase/../database1")); + assertNotNull(androidDatabaseService.openDatabase(filesDir + "/mydatabase\\..\\database1")); + assertTrue(androidDatabaseService.deleteDatabase(filesDir + "/mydatabase/../database1")); } @Test diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseTests.java index 07775dc55..8ca49dea6 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseTests.java @@ -60,14 +60,14 @@ public class AndroidDatabaseTests { public void beforeEach() { Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); androidDatabaseService = new AndroidDatabaseService(null); - androidDatabase = androidDatabaseService.openDatabase(TestUtils.getCacheDir( + androidDatabase = androidDatabaseService.openDatabase(TestUtils.getFilesDir( InstrumentationRegistry.getInstrumentation().getTargetContext()) + "/" + name.getMethodName()); } @After public void afterEach() { - TestUtils.deleteAllFilesInCacheDir(InstrumentationRegistry.getInstrumentation().getTargetContext()); + TestUtils.deleteAllFilesInFilesDir(InstrumentationRegistry.getInstrumentation().getTargetContext()); if (queryResult != null) { queryResult.close(); diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/TestUtils.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/TestUtils.java index f27a4ab09..e9178c8e0 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/TestUtils.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/TestUtils.java @@ -33,6 +33,21 @@ public static void deleteAllFilesInCacheDir(Context appContext) { } } + public static void deleteAllFilesInFilesDir(Context appContext) { + if (appContext == null) { + return; + } + + File filesDir = appContext.getFilesDir(); + File[] files = filesDir.listFiles(); + + if (files != null) { + for (File file : files) { + file.delete(); + } + } + } + public static String getCacheDir(Context appContext) { if (appContext == null) { return null; @@ -41,6 +56,14 @@ public static String getCacheDir(Context appContext) { return appContext.getCacheDir().getPath(); } + public static String getFilesDir(Context appContext) { + if (appContext == null) { + return null; + } + + return appContext.getFilesDir().getPath(); + } + public static boolean almostEqual(long actual, long expected, long tolerance) { return Math.abs(actual - expected) < tolerance; } diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDatabaseHelperTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDatabaseHelperTests.java index e0fc37cc0..8b442c4e3 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDatabaseHelperTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDatabaseHelperTests.java @@ -38,7 +38,7 @@ public class SqliteDatabaseHelperTests { @Before public void setUp() { - dbPath = new File(InstrumentationRegistry.getContext().getCacheDir(), "test.sqlite").getPath(); + dbPath = new File(InstrumentationRegistry.getContext().getFilesDir(), "test.sqlite").getPath(); sqLiteDatabaseHelper = new SQLiteDatabaseHelper(); createTable(); } diff --git a/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/E2EAndroidSystemInfoService.java b/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/E2EAndroidSystemInfoService.java index addd3b0c8..9c2d85975 100644 --- a/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/E2EAndroidSystemInfoService.java +++ b/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/E2EAndroidSystemInfoService.java @@ -12,4 +12,12 @@ public File getApplicationCacheDir() { tempDir.mkdir(); return tempDir; } + + @Override + public File getApplicationFilesDir() { + File systemFilesDir = super.getApplicationFilesDir(); + File tempDir = new File(systemFilesDir, String.valueOf(UUID.randomUUID()).replaceAll("-", "")); + tempDir.mkdir(); + return tempDir; + } } \ No newline at end of file diff --git a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockSystemInfoService.java b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockSystemInfoService.java index f3e13640d..9303ce927 100755 --- a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockSystemInfoService.java +++ b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockSystemInfoService.java @@ -54,6 +54,12 @@ public File getApplicationCacheDir() { return applicationCacheDir; } + public File applicationFilesDir; + @Override + public File getApplicationFilesDir() { + return applicationFilesDir; + } + public InputStream assetStream; public Map assetStreams = new HashMap<>(); @Override diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidDatabaseService.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidDatabaseService.java index ae3b5d52e..2f01da5ca 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidDatabaseService.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidDatabaseService.java @@ -69,13 +69,13 @@ public Database openDatabase(final String filePath) { final String cleanedPath = removeRelativePath(filePath); - if (this.systemInfoService != null && this.systemInfoService.getApplicationCacheDir() != null) { + if (this.systemInfoService != null && this.systemInfoService.getApplicationFilesDir() != null) { try { - final String cacheDirCanonicalPath = this.systemInfoService.getApplicationCacheDir().getCanonicalPath(); + final String filesDirCanonicalPath = this.systemInfoService.getApplicationFilesDir().getCanonicalPath(); final File file = new File(cleanedPath); final String dbFileCanonicalPath = file.getCanonicalPath(); - if (!dbFileCanonicalPath.startsWith(cacheDirCanonicalPath)) { + if (!dbFileCanonicalPath.startsWith(filesDirCanonicalPath)) { Log.warning(TAG, "Invalid database file path (%s)", cleanedPath); return null; } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/CacheToFilesDatabaseMigration.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/CacheToFilesDatabaseMigration.java new file mode 100644 index 000000000..eebfa8595 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/CacheToFilesDatabaseMigration.java @@ -0,0 +1,42 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile; + +import com.adobe.marketing.mobile.services.ServiceProvider; + +import java.io.File; + +class CacheToFilesDatabaseMigration { + private static final String LOG_TAG = "CacheToFilesDatabaseMigration"; + + protected void migrate() { + final File applicationCacheDir = ServiceProvider.getInstance().getDeviceInfoService().getApplicationCacheDir(); + if (applicationCacheDir != null) { + final File cacheDirEdgeDataQueue = new File(applicationCacheDir, CoreConstants.ExtensionNames.Edge.EDGE_EXTENSION_NAME); + + if (cacheDirEdgeDataQueue.exists()) { + final File applicationFilesDir = ServiceProvider.getInstance().getDeviceInfoService().getApplicationFilesDir(); + if (applicationFilesDir != null) { + final File filesDirEdgeDataQueue = new File(applicationFilesDir, CoreConstants.ExtensionNames.Edge.EDGE_EXTENSION_NAME); + try { + FileUtil.copyFile(cacheDirEdgeDataQueue, filesDirEdgeDataQueue); + Log.debug(LOG_TAG, String.format("Successfully moved DataQueue for database (%s) from cache directory to files directory", + CoreConstants.ExtensionNames.Edge.EDGE_EXTENSION_NAME)); + } catch (Exception e) { + Log.warning(LOG_TAG, String.format("Failed in moving DataQueue for database (%s), Files dir is null.", + CoreConstants.ExtensionNames.Edge.EDGE_EXTENSION_NAME)); + } + } + } + } + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/CoreConstants.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/CoreConstants.java index d70ae7080..8cc8753e2 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/CoreConstants.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/CoreConstants.java @@ -100,4 +100,19 @@ private Signal() {} } } + static class ExtensionNames { + + private ExtensionNames() {} + + /** + * Hold extension name for the {@code Edge} module. + */ + static final class Edge { + static final String EDGE_EXTENSION_NAME = "com.adobe.edge"; + + private Edge() { + } + } + } + } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/FileUtil.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/FileUtil.java index 8264b30d1..44e1dcb31 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/FileUtil.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/FileUtil.java @@ -80,4 +80,33 @@ static String readStringFromFile(final File file) { static boolean isValidDirectory(final File directory) { return directory != null && directory.isDirectory() && directory.canWrite(); } + + /** + * Copies the contents from {@code src} to {@code dest}. + * + * @param src {@link File} from which the contents are read + * @param dest {@link File} to which contents are written to + * @throws IOException if {@code src} or {@code dest} is not present or it does not have read permissions + */ + static void copyFile(final File src, final File dest) throws IOException, NullPointerException{ + final String logPrefix = "File Copy"; + final int STREAM_READ_BUFFER_SIZE = 1024; + + InputStream input = new FileInputStream(src); + try { + OutputStream output = new FileOutputStream(dest); + try { + byte[] buffer = new byte[STREAM_READ_BUFFER_SIZE]; + int length; + while ((length = input.read(buffer)) != -1) { + output.write(buffer, 0, length); + } + Log.debug(logPrefix, "Successfully copied (%s) to (%s)", src.getCanonicalPath(), dest.getCanonicalPath()); + } finally { + output.close(); + } + } finally { + input.close(); + } + } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SystemInfoService.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SystemInfoService.java index f879a7602..a1076fa60 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SystemInfoService.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SystemInfoService.java @@ -94,6 +94,15 @@ interface DisplayInformation { */ File getApplicationCacheDir(); + /** + * Returns the application specific directory where files are created and stored. + * The application will be able to read and write to the directory and + * the files stored in the directory are persisted (it is not deleted by the system). + * + * @return A {@link File} representing the application file directory, or null if not available on the platform. + */ + File getApplicationFilesDir(); + /** * Open the requested asset returns an InputStream to read its contents. * diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/DeviceInforming.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/DeviceInforming.java index 2bd2fd8ea..504fa610c 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/DeviceInforming.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/DeviceInforming.java @@ -109,6 +109,15 @@ interface DisplayInformation { */ File getApplicationCacheDir(); + /** + * Returns the application specific directory where files are created and stored. + * The application will be able to read and write to the directory and + * the files stored in the directory are persisted (it is not deleted by the system). + * + * @return A {@link File} representing the application file directory, or null if not available on the platform. + */ + File getApplicationFilesDir(); + /** * Open the requested asset returns an InputStream to read its contents. * diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/AndroidSystemInfoService.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/AndroidSystemInfoService.java index 9feb24ff2..8e88bb9d7 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/AndroidSystemInfoService.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/AndroidSystemInfoService.java @@ -70,6 +70,17 @@ public File getApplicationCacheDir() { return context.getCacheDir(); } + @Override + public File getApplicationFilesDir() { + Context context = App.getAppContext(); + + if (context == null) { + return null; + } + + return context.getFilesDir(); + } + @Override public InputStream getAsset(String fileName) { Context context = App.getAppContext(); diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java index 7f2fbe0fe..98daca09f 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java @@ -91,6 +91,9 @@ public static void setApplication(final Application app) { V4ToV5Migration migrationTool = new V4ToV5Migration(); migrationTool.migrate(); + CacheToFilesDatabaseMigration dbMigration = new CacheToFilesDatabaseMigration(); + dbMigration.migrate(); + if (core == null) { synchronized (mutex) { if (platformServices == null) { diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java index 03c626a71..47e8caa5d 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java @@ -43,7 +43,7 @@ public DataQueue getDataQueue(final String databaseName) { dataQueue = dataQueueCache.get(databaseName); if (dataQueue == null) { - final File cacheDir = ServiceProvider.getInstance().getDeviceInfoService().getApplicationCacheDir(); + final File cacheDir = ServiceProvider.getInstance().getDeviceInfoService().getApplicationFilesDir(); if (cacheDir == null) { MobileCore.log(LoggingMode.WARNING, LOG_TAG, diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DeviceInfoService.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DeviceInfoService.java index 0722efa29..c8c79052d 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DeviceInfoService.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DeviceInfoService.java @@ -285,6 +285,17 @@ public File getApplicationCacheDir() { return context.getCacheDir(); } + @Override + public File getApplicationFilesDir() { + final Context context = getApplicationContext(); + + if (context == null) { + return null; + } + + return context.getFilesDir(); + } + @Override public InputStream getAsset(String fileName) { final Context context = getApplicationContext(); diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java index 7addb1d4d..d50721f08 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java @@ -39,9 +39,9 @@ final class SQLiteDataQueue implements DataQueue { private boolean isClose = false; private final Object dbMutex = new Object(); - SQLiteDataQueue(final File cacheDir, final String databaseName, final SQLiteDatabaseHelper databaseHelper) { + SQLiteDataQueue(final File filesDir, final String databaseName, final SQLiteDatabaseHelper databaseHelper) { this.databaseHelper = databaseHelper; - this.databasePath = new File(cacheDir, removeRelativePath(databaseName)).getPath(); + this.databasePath = new File(filesDir, removeRelativePath(databaseName)).getPath(); createTableIfNotExists(); } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/AndroidSystemInfoServiceTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/AndroidSystemInfoServiceTests.java index e736f36fa..09b1661c3 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/AndroidSystemInfoServiceTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/AndroidSystemInfoServiceTests.java @@ -146,6 +146,22 @@ public void testGetApplicationCacheDir_NullContext() throws Exception { assertNull(systemInfoService.getApplicationCacheDir()); } + @Test + public void testGetApplicationFilesDir_Happy() throws Exception { + AndroidSystemInfoService systemInfoService = new AndroidSystemInfoService(); + File testFilesDir = new File("testFilesDir"); + when(mockContext.getFilesDir()).thenReturn(testFilesDir); + assertEquals(testFilesDir, systemInfoService.getApplicationFilesDir()); + } + + @Test + public void testGetApplicationFilesDir_NullContext() throws Exception { + AndroidSystemInfoService systemInfoService = new AndroidSystemInfoService(); + App.setAppContext(null); + Runtime.getRuntime().gc(); + assertNull(systemInfoService.getApplicationFilesDir()); + } + @Test public void testGetApplicationName_Happy() throws Exception { AndroidSystemInfoService systemInfoService = new AndroidSystemInfoService(); diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileTestHelper.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileTestHelper.java index f6bba0a7e..997dc3f45 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileTestHelper.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileTestHelper.java @@ -23,6 +23,7 @@ class FileTestHelper { static final String CACHE_DIRECTORY = "adbdownloadcache"; + static final String FILE_DIRECTORY = "adbdownloadfile"; static final String MOCK_FILE_NAME = "c0a6221b2b55775b6bc5761fdb1ac0c965cc823c55e8db0b3f903b24f82fcb90.1484711165000_someETag"; static final String MOCK_CONFIG_JSON = "{'someJsonKey':'someJsonValue'}"; @@ -127,6 +128,30 @@ File placeSampleCacheDirectory(final String dirName, final String fileName) { return cacheDirectory; } + File createEmptyFile(final String dirName, final String fileName) { + File fileDirectory = new File(getCacheDirectory(dirName) + File.separator); + fileDirectory.mkdir(); + File dest = new File(getCacheDirectory(dirName) + File.separator + fileName); + try { + dest.createNewFile(); + } catch (IOException ex) { + fail("Could not create test directory and files " + ex); + } + + return dest; + } + + void writeToFile(final File file, final String content) { + try { + FileWriter fileWriter = new FileWriter(file); + fileWriter.write(content); + fileWriter.flush(); + fileWriter.close(); + } catch (IOException ex) { + fail("Could not write to file " + ex); + } + } + private File createFile(final String fileName) { return createFile(null, fileName); } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileUtilTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileUtilTests.java index 57f81944c..1c83e0e8c 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileUtilTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileUtilTests.java @@ -16,7 +16,9 @@ import org.junit.Test; import java.io.File; +import java.io.IOException; +import static com.adobe.marketing.mobile.FileTestHelper.FILE_DIRECTORY; import static com.adobe.marketing.mobile.FileTestHelper.MOCK_CONFIG_JSON; import static com.adobe.marketing.mobile.FileTestHelper.MOCK_FILE_NAME; import static org.junit.Assert.*; @@ -34,7 +36,7 @@ public void setup() { @After public void tearDown() { fileTestHelper.deleteTempCacheDirectory(); - + fileTestHelper.deleteTempCacheDirectory(FILE_DIRECTORY); } @Test @@ -96,4 +98,55 @@ public void testReadJsonStringFromFile_When_DirectoryInsteadOfFile_Then_ReturnsN "testFileName")); assertNull(content); } + + // ================================================================================================================= + // protected void copyFile(final File src, final File dest) + // ================================================================================================================= + @Test + public void testCopyFile_When_ValidSrcFile_And_ValidEmptyDestFile() throws Exception { + File src = fileTestHelper.placeSampleCacheFile(); + File dest = fileTestHelper.createEmptyFile(FILE_DIRECTORY, "testFileName"); + assertTrue(StringUtils.isNullOrEmpty(FileUtil.readStringFromFile(dest))); + FileUtil.copyFile(src, dest); + assertEquals(MOCK_CONFIG_JSON, FileUtil.readStringFromFile(dest)); + } + + @Test + public void testCopyFile_When_ValidSrcFile_And_ValidNonEmptyDestFile() throws Exception{ + File src = fileTestHelper.placeSampleCacheFile(); + File dest = fileTestHelper.createEmptyFile(FILE_DIRECTORY, "testFileName"); + fileTestHelper.writeToFile(dest, "testContent"); + assertEquals("testContent", FileUtil.readStringFromFile(dest)); + FileUtil.copyFile(src, dest); + assertEquals(MOCK_CONFIG_JSON, FileUtil.readStringFromFile(dest)); + } + + @Test(expected = NullPointerException.class) + public void testCopyFile_When_ValidSrcFile_And_NullDestFile() throws Exception { + File src = fileTestHelper.placeSampleCacheFile(); + FileUtil.copyFile(src, null); + } + + @Test(expected = IOException.class) + public void testCopyFile_When_ValidSrcFile_And_InvalidDestFile() throws Exception { + File src = fileTestHelper.placeSampleCacheFile(); + File dest = fileTestHelper.createEmptyFile(FILE_DIRECTORY, "testFileName"); + fileTestHelper.deleteTempCacheDirectory(FILE_DIRECTORY); + FileUtil.copyFile(src, dest); + } + + @Test(expected = NullPointerException.class) + public void testCopyFile_When_NullSrcFile() throws Exception { + File src = fileTestHelper.placeSampleCacheFile(); + File dest = fileTestHelper.createEmptyFile(FILE_DIRECTORY, "testFileName"); + FileUtil.copyFile(null, dest); + } + + @Test(expected = IOException.class) + public void testCopyFile_When_InvalidSrcFile() throws Exception { + File src = fileTestHelper.placeSampleCacheFile(); + File dest = fileTestHelper.createEmptyFile(FILE_DIRECTORY, "testFileName"); + fileTestHelper.deleteTempCacheDirectory(); + FileUtil.copyFile(src, dest); + } } \ No newline at end of file diff --git a/code/testapp/src/main/java/com/adobe/testapp/PlatformServicesFragment.java b/code/testapp/src/main/java/com/adobe/testapp/PlatformServicesFragment.java index 201f4a11f..0a83185b4 100644 --- a/code/testapp/src/main/java/com/adobe/testapp/PlatformServicesFragment.java +++ b/code/testapp/src/main/java/com/adobe/testapp/PlatformServicesFragment.java @@ -56,6 +56,7 @@ public void onClick(View view) { stringBuffer.append("\ngetApplicationVersionCode() - " + deviceInforming.getApplicationVersionCode()); stringBuffer.append("\ngetApplicationBaseDir() - " + deviceInforming.getApplicationBaseDir()); stringBuffer.append("\ngetApplicationCacheDir() - " + deviceInforming.getApplicationCacheDir()); + stringBuffer.append("\ngetApplicationFilesDir() - " + deviceInforming.getApplicationFilesDir()); stringBuffer.append("\ngetActiveLocale() - " + deviceInforming.getActiveLocale()); stringBuffer.append("\ngetCanonicalPlatformName() - " + deviceInforming.getCanonicalPlatformName()); // stringBuffer.append("\ngetCoreVersion() - " + deviceInforming.getCoreVersion()); From e6818d9c9eb888b27e98ef21e1716623fc45aeca Mon Sep 17 00:00:00 2001 From: Yansong Date: Tue, 8 Mar 2022 10:46:23 -0700 Subject: [PATCH 009/476] Project cleanup --- code/android-core-library/build.gradle | 5 +-- .../rulesengine/ConditionEvaluator.java | 19 ++------ .../mobile/rulesengine/Template.java | 8 ++-- .../com/adobe/marketing/mobile/AppTests.java | 18 ++++---- .../src/test/kotlin/EventHubTests.kt | 11 ----- .../mobile/internal/eventhub/EventHubTests.kt | 22 ++++++++++ code/testapp/build.gradle | 18 ++++++-- .../main/java/com/adobe/testapp/TestApp.java | 44 +++++++++---------- 8 files changed, 77 insertions(+), 68 deletions(-) delete mode 100644 code/android-core-library/src/test/kotlin/EventHubTests.kt create mode 100644 code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index 50a1853c7..13ac118f5 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -110,6 +110,7 @@ apply from: 'release.gradle' dependencies { //noinspection GradleCompatible implementation 'com.android.support:appcompat-v7:27.1.1' + //noinspection GradleDependency implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" // unit tests testImplementation "junit:junit:4.13.2" @@ -125,7 +126,3 @@ dependencies { // instrumentation tests androidTestImplementation "com.android.support.test:rules:1.0.2" } - -repositories { - mavenCentral() -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ConditionEvaluator.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ConditionEvaluator.java index 347399d68..8a5eb5670 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ConditionEvaluator.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ConditionEvaluator.java @@ -199,11 +199,8 @@ private boolean greaterThanEquals(final A lhs, final B rhs) { if (resolvedLhs == null || resolvedRhs == null) { return false; - } else if (resolvedLhs >= resolvedRhs) { - return true; } - - return false; + return resolvedLhs >= resolvedRhs; } private boolean lesserThan(final A lhs, final B rhs) { @@ -212,11 +209,8 @@ private boolean lesserThan(final A lhs, final B rhs) { if (resolvedLhs == null || resolvedRhs == null) { return false; - } else if (resolvedLhs < resolvedRhs) { - return true; } - - return false; + return resolvedLhs < resolvedRhs; } private boolean lesserThanOrEqual(final A lhs, final B rhs) { @@ -225,11 +219,8 @@ private boolean lesserThanOrEqual(final A lhs, final B rhs) { if (resolvedLhs == null || resolvedRhs == null) { return false; - } else if (resolvedLhs <= resolvedRhs) { - return true; } - - return false; + return resolvedLhs <= resolvedRhs; } private boolean contains(final A lhs, final B rhs) { @@ -243,9 +234,7 @@ private boolean contains(final A lhs, final B rhs) { rhsValue = rhsValue.toLowerCase(); } - if (lhsValue.contains(rhsValue)) { - return true; - } + return lhsValue.contains(rhsValue); } return false; diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Template.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Template.java index 651a7ba93..dbf30d8b0 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Template.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/Template.java @@ -14,20 +14,20 @@ import java.util.List; public class Template { - private final List tokens; + private final List segments; public Template(final String templateString) { - this.tokens = TemplateParser.parse(templateString); + this.segments = TemplateParser.parse(templateString); } public Template(final String templateString, DelimiterPair delimiterPair) { - this.tokens = TemplateParser.parse(templateString, delimiterPair); + this.segments = TemplateParser.parse(templateString, delimiterPair); } public String render(final TokenFinder tokenFinder, final Transforming transformer) { StringBuilder stringBuilder = new StringBuilder(); - for (Segment eachSegment : tokens) { + for (Segment eachSegment : segments) { stringBuilder.append(eachSegment.getContent(tokenFinder, transformer)); } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/AppTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/AppTests.java index bb8113f04..3b7f1bcf3 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/AppTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/AppTests.java @@ -83,12 +83,10 @@ public SharedPreferences.Editor answer(InvocationOnMock invocation) throws Throw } @Test - public void testSetLargeIconResourceId_ValidIdSetTwice() { + public void testSetSmallIconResourceId_ValidIdSet() { //Setup final int expectedValueStored = 123456; - App.setLargeIconResourceID(111111); - - when(mockPreferenceEditor.putInt(eq(DATASTORE_KEY_LARGE_ICON), + when(mockPreferenceEditor.putInt(eq(DATASTORE_KEY_SMALL_ICON), anyInt())).thenAnswer(new Answer() { @Override public SharedPreferences.Editor answer(InvocationOnMock invocation) throws Throwable { @@ -99,16 +97,18 @@ public SharedPreferences.Editor answer(InvocationOnMock invocation) throws Throw }); //Test - App.setLargeIconResourceID(expectedValueStored); + App.setSmallIconResourceID(expectedValueStored); } @Test - public void testSetSmallIconResourceId_ValidIdSet() { + public void testSetLargeIconResourceId_ValidIdSetTwice() { //Setup final int expectedValueStored = 123456; - when(mockPreferenceEditor.putInt(eq(DATASTORE_KEY_SMALL_ICON), - anyInt())).thenAnswer(new Answer() { + App.setLargeIconResourceID(111111); + + when(mockPreferenceEditor.putInt(eq(DATASTORE_KEY_LARGE_ICON), + anyInt())).thenAnswer(new Answer() { @Override public SharedPreferences.Editor answer(InvocationOnMock invocation) throws Throwable { int actualValueStored = invocation.getArgument(1); @@ -118,7 +118,7 @@ public SharedPreferences.Editor answer(InvocationOnMock invocation) throws Throw }); //Test - App.setSmallIconResourceID(expectedValueStored); + App.setLargeIconResourceID(expectedValueStored); } diff --git a/code/android-core-library/src/test/kotlin/EventHubTests.kt b/code/android-core-library/src/test/kotlin/EventHubTests.kt deleted file mode 100644 index 303970ecb..000000000 --- a/code/android-core-library/src/test/kotlin/EventHubTests.kt +++ /dev/null @@ -1,11 +0,0 @@ -import org.junit.Test -import kotlin.test.assertEquals -import com.adobe.marketing.mobile.internal.eventhub.EventHub - -internal class EventHubTests { - - @Test - fun testVersion() { - assertEquals(EventHub.version, "2.0.0") - } -} \ No newline at end of file diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt new file mode 100644 index 000000000..6592a385f --- /dev/null +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt @@ -0,0 +1,22 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.internal.eventhub + +import org.junit.Test +import kotlin.test.assertEquals + +internal class EventHubTests { + + @Test + fun testVersion() { + assertEquals(EventHub.version, "2.0.0") + } +} \ No newline at end of file diff --git a/code/testapp/build.gradle b/code/testapp/build.gradle index 5f6d29857..ee174546e 100644 --- a/code/testapp/build.gradle +++ b/code/testapp/build.gradle @@ -14,12 +14,15 @@ android { missingDimensionStrategy 'target', 'phone' testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + // Required when setting minSdkVersion to 20 or lower + multiDexEnabled true } buildTypes { - debug { - testCoverageEnabled true - } +// debug { +// testCoverageEnabled true +// } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' @@ -29,11 +32,19 @@ android { testOptions { animationsDisabled true } + compileOptions { + // Flag to enable support for the new language APIs + coreLibraryDesugaringEnabled true + // Sets Java compatibility to Java 8 + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { // implementation fileTree(include: ['*.jar'], dir: 'libs') def withoutCore = { exclude group: 'com.adobe.marketing.mobile', module: 'core' } + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support:design:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' @@ -42,6 +53,7 @@ dependencies { implementation 'com.adobe.marketing.mobile:identity:1.+', withoutCore implementation 'com.adobe.marketing.mobile:signal:1.0.3', withoutCore implementation 'com.adobe.marketing.mobile:analytics:1.2.4', withoutCore + implementation 'com.appdynamics:appdynamics-runtime:21.11.1' // implementation 'com.adobe.marketing.mobile:sdk-core:1.+' testImplementation 'junit:junit:4.12' diff --git a/code/testapp/src/main/java/com/adobe/testapp/TestApp.java b/code/testapp/src/main/java/com/adobe/testapp/TestApp.java index 6f525f444..3ff1c0259 100644 --- a/code/testapp/src/main/java/com/adobe/testapp/TestApp.java +++ b/code/testapp/src/main/java/com/adobe/testapp/TestApp.java @@ -86,28 +86,28 @@ public void call(Object value) { // } // }).start(); - try { - - - PackageInfo packageInfo = this.getApplicationContext().getPackageManager().getPackageInfo( - this.getApplicationContext().getPackageName(), 0); - Method method = packageInfo.getClass().getDeclaredMethod("getLongVersionCode"); - Long reflectVer = (Long)method.invoke(packageInfo); - Log.d("X", "********" + (reflectVer & 0x00000000ffffffff)); - - long version = this.getApplicationContext().getPackageManager().getPackageInfo( - this.getApplicationContext().getPackageName(), 0).getLongVersionCode(); - int versionCodeMajor = (int)(version >> 32); - int versionCode = (int)version; - Log.d("X", "-----" + this.getApplicationContext().getPackageManager().getPackageInfo( - this.getApplicationContext().getPackageName(), 0).versionCode); - Log.d("X", "!!!!!!!!" + (version >> 32)); - Log.d("X", "++++++" + (version & 0x00000000ffffffff)); - - } catch (PackageManager.NameNotFoundException | NoSuchMethodException | IllegalAccessException | - InvocationTargetException e) { - e.printStackTrace(); - } +// try { +// +// +// PackageInfo packageInfo = this.getApplicationContext().getPackageManager().getPackageInfo( +// this.getApplicationContext().getPackageName(), 0); +// Method method = packageInfo.getClass().getDeclaredMethod("getLongVersionCode"); +// Long reflectVer = (Long)method.invoke(packageInfo); +// Log.d("X", "********" + (reflectVer & 0x00000000ffffffff)); +// +// long version = this.getApplicationContext().getPackageManager().getPackageInfo( +// this.getApplicationContext().getPackageName(), 0).getLongVersionCode(); +// int versionCodeMajor = (int)(version >> 32); +// int versionCode = (int)version; +// Log.d("X", "-----" + this.getApplicationContext().getPackageManager().getPackageInfo( +// this.getApplicationContext().getPackageName(), 0).versionCode); +// Log.d("X", "!!!!!!!!" + (version >> 32)); +// Log.d("X", "++++++" + (version & 0x00000000ffffffff)); +// +// } catch (PackageManager.NameNotFoundException | NoSuchMethodException | IllegalAccessException | +// InvocationTargetException e) { +// e.printStackTrace(); +// } From e034d14df77d99d1eca0436b2cf49f5577fce458 Mon Sep 17 00:00:00 2001 From: Yansong Yang Date: Fri, 11 Mar 2022 11:43:34 -0700 Subject: [PATCH 010/476] Add two public methods in UIService -> setURIHandler & getIntentWithURI (#15) * add 2 public APIs in UIService -> setURIHandler & getIntentWithURI * add tests * address review comments * fix tests --- .../mobile/AndroidFullscreenMessage.java | 4 +-- .../mobile/services/ui/UIService.java | 15 +++++++++ .../mobile/services/ui/URIHandler.java | 27 ++++++++++++++++ .../mobile/services/ServiceProvider.java | 9 ++++++ .../mobile/services/ui/AEPMessage.java | 3 +- .../mobile/services/ui/AndroidUIService.java | 32 ++++++++++++------- .../mobile/AndroidUIServiceTests.java | 21 ++++++++++++ .../services/ui/AndroidUIServiceTests.java | 26 +++++++++++++-- 8 files changed, 120 insertions(+), 17 deletions(-) create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/ui/URIHandler.java diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidFullscreenMessage.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidFullscreenMessage.java index 9c90dad3c..09b98a5b7 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidFullscreenMessage.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidFullscreenMessage.java @@ -34,6 +34,7 @@ import android.webkit.WebView; import android.webkit.WebViewClient; +import com.adobe.marketing.mobile.services.ServiceProvider; import com.adobe.marketing.mobile.services.ui.internal.MessagesMonitor; import com.adobe.marketing.mobile.internal.context.App; @@ -127,8 +128,7 @@ public void show() { @Override public void openUrl(final String url) { try { - final Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); + final Intent intent = ServiceProvider.getInstance().getUIService().getIntentWithURI(url); if (messageFullScreenActivity != null) { messageFullScreenActivity.startActivity(intent); diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/ui/UIService.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/ui/UIService.java index 03efff144..c24cb30e4 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/ui/UIService.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/ui/UIService.java @@ -11,6 +11,8 @@ package com.adobe.marketing.mobile.services.ui; +import android.content.Intent; + /** * Interface for displaying alerts, local notifications, and fullscreen web views. */ @@ -33,6 +35,19 @@ public interface UIService { boolean showUrl(String url); + /** + * Provides an {@link URIHandler} to decide the destination of the given URI + * @param uriHandler An {@link URIHandler} instance used to decide the Android link's destination + */ + void setURIHandler(URIHandler uriHandler); + + /** + * Returns a destination Intent for the given URI. + * @param uri the URI to open + * @return an {@link Intent} instance + */ + Intent getIntentWithURI(String uri); + /** * Creates a floating button instance * diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/ui/URIHandler.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/ui/URIHandler.java new file mode 100644 index 000000000..71b530ff9 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/ui/URIHandler.java @@ -0,0 +1,27 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.services.ui; + +import android.content.Intent; + +/** + * Interface for handling Android links + */ +public interface URIHandler { + /** + * Returns a destination of the given URI. + * + * @param uri the URI to open + * @return an {@link Intent} instance + */ + Intent getURIDestination(String uri); +} diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ServiceProvider.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ServiceProvider.java index 03e1fb6c2..fd26b24e1 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ServiceProvider.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ServiceProvider.java @@ -16,6 +16,7 @@ import com.adobe.marketing.mobile.services.ui.AndroidUIService; import com.adobe.marketing.mobile.services.ui.FullscreenMessageDelegate; import com.adobe.marketing.mobile.services.ui.UIService; +import com.adobe.marketing.mobile.services.ui.URIHandler; import java.lang.ref.WeakReference; @@ -159,6 +160,14 @@ public void setMessageDelegate(final FullscreenMessageDelegate messageDelegate) this.messageDelegate = messageDelegate; } + /** + * Provides an {@link URIHandler} to decide the destination of the given URI + * @param uriHandler An {@link URIHandler} instance used to decide the Android link's destination + */ + public void setURIHandler(URIHandler uriHandler){ + this.getUIService().setURIHandler(uriHandler); + } + /** * Reset the {@code ServiceProvider} to its default state. * Any previously set services are reset to their default state. diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java index 11e915df6..e564c183d 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java @@ -198,8 +198,7 @@ public void openUrl(final String url) { } try { - final Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); + final Intent intent = ServiceProvider.getInstance().getUIService().getIntentWithURI(url); App.getInstance().getCurrentActivity().startActivity(intent); } catch (final NullPointerException ex) { MobileCore.log(LoggingMode.WARNING, TAG, "Could not open the url from the message " + ex.getMessage()); diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/AndroidUIService.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/AndroidUIService.java index 6f3cfffb1..1263de7d1 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/AndroidUIService.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/AndroidUIService.java @@ -48,6 +48,7 @@ public class AndroidUIService implements UIService { public static final int NOTIFICATION_SENDER_CODE = 750183; public static final String NOTIFICATION_REQUEST_CODE_KEY = "NOTIFICATION_REQUEST_CODE"; public static final String NOTIFICATION_TITLE = "NOTIFICATION_TITLE"; + private URIHandler uriHandler; MessagesMonitor messagesMonitor = MessagesMonitor.getInstance(); @@ -245,7 +246,7 @@ public boolean showUrl(final String url) { } try { - final Intent intent = getIntentWithUrl(url); + final Intent intent = getIntentWithURI(url); currentActivity.startActivity(intent); return true; } catch (Exception ex) { @@ -255,16 +256,25 @@ public boolean showUrl(final String url) { return false; } - /** - * Creates a new instance of an {@code Intent} with its {@code data} set to the {@code URI} parsed from the {@code url} passed in. - * @param url The {@link String} url that needs to be set as data. - * @return A new instance of an {@link Intent} - * - * @throws NullPointerException If the url is null - */ - protected Intent getIntentWithUrl(String url) { - final Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); + @Override + public void setURIHandler(URIHandler uriHandler){ + this.uriHandler = uriHandler; + } + + @Override + public Intent getIntentWithURI(String uri) { + URIHandler handler = this.uriHandler; + Intent intent = null; + if (handler != null){ + intent = handler.getURIDestination(uri); + if (intent == null){ + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("%s is not handled with a custom Intent, use SDK's default Intent instead.", uri)); + } + } + if (intent == null){ + intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(uri)); + } return intent; } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/AndroidUIServiceTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/AndroidUIServiceTests.java index e1a4fac7e..c7f37dc29 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/AndroidUIServiceTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/AndroidUIServiceTests.java @@ -32,6 +32,8 @@ import static org.mockito.Mockito.when; import com.adobe.marketing.mobile.internal.context.App; +import com.adobe.marketing.mobile.services.ServiceProvider; +import com.adobe.marketing.mobile.services.ui.URIHandler; import com.adobe.marketing.mobile.services.ui.internal.MessagesMonitor; @RunWith(MockitoJUnitRunner.Silent.class) @@ -153,4 +155,23 @@ public void messageMonitorDisplayedCalled_When_FullscreenMessageShown() { verify(mockMessagesMonitor).displayed(); } + + public void setURIHandlerUsage(){ + ServiceProvider.getInstance().setURIHandler(new URIHandler() { + @Override + public Intent getURIDestination(String uri) { + if (uri !=null && uri.startsWith("my_company_links_prefix")){ + Context applicationContext = null; + Intent intent = new Intent(applicationContext, MyCompanyLinkHandlerClass.class); + return intent; + } + return null; + } + }); + } + + +} +class MyCompanyLinkHandlerClass{ + } \ No newline at end of file diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/ui/AndroidUIServiceTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/ui/AndroidUIServiceTests.java index eac622065..29cdf5412 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/ui/AndroidUIServiceTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/ui/AndroidUIServiceTests.java @@ -31,6 +31,8 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNotSame; +import static junit.framework.Assert.assertSame; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; @@ -44,6 +46,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.adobe.marketing.mobile.internal.context.App; +import com.adobe.marketing.mobile.services.ServiceProvider; import com.adobe.marketing.mobile.services.ui.internal.MessagesMonitor; @RunWith(MockitoJUnitRunner.Silent.class) @@ -348,7 +351,7 @@ public void showUrlStartsActivity_When_ValidUrl() { doNothing().when(mockActivity).startActivity(intentArgumentCaptor.capture()); AndroidUIService spyUIService = spy(new AndroidUIService()); - doReturn(mockIntent).when(spyUIService).getIntentWithUrl("myappscheme://host"); + doReturn(mockIntent).when(spyUIService).getIntentWithURI("myappscheme://host"); //test spyUIService.showUrl("myappscheme://host"); //verify @@ -363,7 +366,7 @@ public void showUrlDoesNotStartsActivity_When_NullUrl() { appContextProvider.setContext(mockContext); AndroidUIService spyUIService = spy(new AndroidUIService()); - doReturn(mockIntent).when(spyUIService).getIntentWithUrl(anyString()); + doReturn(mockIntent).when(spyUIService).getIntentWithURI(anyString()); //test spyUIService.showUrl(null); //verify @@ -422,6 +425,25 @@ public void messageMonitorDisplayedNotCalled_When_LocalNotificationShown() { } + @Test + public void testSetURIHandler(){ + final String specialURI = "abc.com"; + Intent specialIntent = new Intent(); + ServiceProvider.getInstance().setURIHandler(new URIHandler() { + @Override + public Intent getURIDestination(String uri) { + if(specialURI.equals(uri)){ + return specialIntent; + } + return null; + } + }); + Intent intent = ServiceProvider.getInstance().getUIService().getIntentWithURI("abc.com"); + assertSame(specialIntent, intent); + Intent defaultIntent = ServiceProvider.getInstance().getUIService().getIntentWithURI("xyz.com"); + assertNotSame(specialIntent, defaultIntent); + } + private long getTriggerTimeForFireDate(long fireDate) { Calendar calendar = Calendar.getInstance(); From c012ca5e45d4f402bac14879ac62af02cd5529ab Mon Sep 17 00:00:00 2001 From: Yansong Yang Date: Thu, 24 Mar 2022 15:33:58 -0600 Subject: [PATCH 011/476] JSONRulesParser Part I (#17) * part I * fix TODOs * add more tests & refine code * rename tests * code reordering * remove none used optional return * address review comments and add more kotlin docs * add more docs --- code/android-core-library/build.gradle | 1 + .../mobile/internal/utility/JSONUtils.kt | 69 ++++++++ .../mobile/launch/rulesengine/LaunchRule.kt | 23 +++ .../launch/rulesengine/RuleConsequence.kt | 26 +++ .../launch/rulesengine/json/GroupCondition.kt | 49 ++++++ .../rulesengine/json/HistoricalCondition.kt | 20 +++ .../launch/rulesengine/json/JSONCondition.kt | 96 +++++++++++ .../rulesengine/json/JSONConsequence.kt | 49 ++++++ .../launch/rulesengine/json/JSONDefinition.kt | 113 +++++++++++++ .../launch/rulesengine/json/JSONRule.kt | 80 +++++++++ .../launch/rulesengine/json/JSONRuleRoot.kt | 55 ++++++ .../rulesengine/json/JSONRulesParser.kt | 51 ++++++ .../rulesengine/json/MatcherCondition.kt | 93 ++++++++++ .../rulesengine/ConditionEvaluator.java | 1 - .../rulesengine/json/JSONConditionTests.kt | 159 ++++++++++++++++++ .../rulesengine/json/JSONConsequenceTests.kt | 51 ++++++ .../rulesengine/json/JSONRuleRootTests.kt | 89 ++++++++++ .../launch/rulesengine/json/JSONRuleTests.kt | 81 +++++++++ .../rulesengine/json/JSONRuleUtilities.kt | 27 +++ .../rulesengine/json/JSONRulesParserTests.kt | 35 ++++ .../rulesengine/OperandMustacheTokenKTests.kt | 80 +++++++++ .../rules_parser/launch_rule_root.json | 87 ++++++++++ 22 files changed, 1334 insertions(+), 1 deletion(-) create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/JSONUtils.kt create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRule.kt create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence.kt create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/GroupCondition.kt create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONCondition.kt create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequence.kt create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONDefinition.kt create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRule.kt create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRoot.kt create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParser.kt create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/MatcherCondition.kt create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConditionTests.kt create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequenceTests.kt create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRootTests.kt create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleTests.kt create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleUtilities.kt create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParserTests.kt create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperandMustacheTokenKTests.kt create mode 100644 code/android-core-library/src/test/resources/rules_parser/launch_rule_root.json diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index 13ac118f5..2ea5c01be 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -116,6 +116,7 @@ dependencies { testImplementation "junit:junit:4.13.2" //noinspection GradleDependency testImplementation "org.mockito:mockito-core:2.22.0" + testImplementation "org.mockito.kotlin:mockito-kotlin:2.2.11" testImplementation 'org.powermock:powermock-api-mockito2:2.0.0' testImplementation 'org.powermock:powermock-module-junit4:2.0.0' testImplementation 'commons-codec:commons-codec:1.15' diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/JSONUtils.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/JSONUtils.kt new file mode 100644 index 000000000..a82b38441 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/JSONUtils.kt @@ -0,0 +1,69 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.internal.utility + +import org.json.JSONArray +import org.json.JSONObject + +/** + * Returns a list containing the results of applying the given [transform] function + * to each element in the [JSONArray]. + * + */ +@JvmSynthetic +internal fun JSONArray.map(transform: (Any) -> T): List { + return (0 until this.length()).asSequence().map { transform(this.get(it)) }.toList() +} + +/** + * Converts the [JSONObject] to a map of the contained contents. + * + */ +@JvmSynthetic +internal fun JSONObject.toMap(): Map { + return this.keys().asSequence().associateWith { key -> + when (val value = this.get(key)) { + is JSONObject -> { + value.toMap() + } + is JSONArray -> { + value.toList() + } + JSONObject.NULL -> null + else -> value + } + } +} + +/** + * Converts the [JSONArray] to a list of the contained contents, + * the list could contains [JSONObject], [JSONArray], `null` or the `primitive types`. + * + */ +@JvmSynthetic +internal fun JSONArray.toList(): List { + val list = mutableListOf() + (0 until this.length()).forEach { index -> + when (val value = this.get(index)) { + is JSONObject -> { + list.add(value.toMap()) + } + is JSONArray -> { + list.add(value.toList()) + } + JSONObject.NULL -> list.add(null) + else -> list.add(value) + } + } + return list +} + diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRule.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRule.kt new file mode 100644 index 000000000..87e082286 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRule.kt @@ -0,0 +1,23 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.launch.rulesengine + +import com.adobe.marketing.mobile.rulesengine.Evaluable + +/** + * The data class representing the given [Evaluable] and a list of [RuleConsequence] objects. + * + * @property condition an object of [Evaluable] + * @property consequenceList a list of [RuleConsequence] objects + * @constructor Constructs a new [LaunchRule] + */ +data class LaunchRule(val condition: Evaluable, val consequenceList: List) \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence.kt new file mode 100644 index 000000000..1c1ee2d86 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence.kt @@ -0,0 +1,26 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.launch.rulesengine + +/** + * The data class representing a rule's consequence object + * + * @property id the consequence id + * @property type the consequence type + * @property detail the meta data of the consequence object + * @constructor Constructs a new [RuleConsequence] + */ +data class RuleConsequence( + val id: String, + val type: String, + var detail: Map? = null +) \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/GroupCondition.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/GroupCondition.kt new file mode 100644 index 000000000..34a3a19c5 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/GroupCondition.kt @@ -0,0 +1,49 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.launch.rulesengine.json + +import com.adobe.marketing.mobile.LoggingMode +import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.rulesengine.Evaluable +import com.adobe.marketing.mobile.rulesengine.LogicalExpression +import java.util.* + +/** + * The class representing a group of [JSONCondition]s + */ +internal class GroupCondition(val definition: JSONDefinition) : JSONCondition() { + companion object { + private const val LOG_TAG = "GroupCondition" + private val LOGICAL_OPERATORS = listOf("or", "and") + } + + @JvmSynthetic + override fun toEvaluable(): Evaluable? { + + if (definition.logic !is String || definition.conditions !is List<*> || definition.conditions.isEmpty()) { + return null + } + val logicalOperator = definition.logic.toLowerCase(Locale.ROOT) + + if (logicalOperator !in LOGICAL_OPERATORS) { + MobileCore.log( + LoggingMode.ERROR, + LOG_TAG, + "Unsupported logical operator: $logicalOperator" + ) + return null + } + val evaluableList = definition.conditions.map { it.toEvaluable() } + return LogicalExpression(evaluableList, logicalOperator) + } + +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt new file mode 100644 index 000000000..d1d6770cc --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt @@ -0,0 +1,20 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.launch.rulesengine.json + +import com.adobe.marketing.mobile.rulesengine.Evaluable + +internal class HistoricalCondition(val definition: JSONDefinition) : JSONCondition() { + + override fun toEvaluable(): Evaluable? { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONCondition.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONCondition.kt new file mode 100644 index 000000000..aa295ce1f --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONCondition.kt @@ -0,0 +1,96 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.launch.rulesengine.json + +import com.adobe.marketing.mobile.LoggingMode +import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.rulesengine.Evaluable +import org.json.JSONObject + +/** + * The class representing a Rule's condition + * + * @constructor Constructs a new [JSONCondition] + */ +internal abstract class JSONCondition { + + companion object { + private const val LOG_TAG = "JSONCondition" + private const val KEY_TYPE = "type" + private const val KEY_DEFINITION = "definition" + private const val TYPE_VALUE_GROUP = "group" + private const val TYPE_VALUE_MATCHER = "matcher" + private const val TYPE_VALUE_HISTORICAL = "historical" + + /** + * Build a subclass of [JSONCondition] + * + * @param jsonCondition a JSON object of a Rule's condition + * @return a subclass of [JSONCondition] + */ + @JvmSynthetic + internal fun build(jsonCondition: JSONObject?): JSONCondition? { + if (jsonCondition !is JSONObject) return null + return try { + when (val type = jsonCondition.getString(KEY_TYPE)) { + TYPE_VALUE_GROUP -> GroupCondition( + JSONDefinition.buildDefinitionFromJSON( + jsonCondition.getJSONObject( + KEY_DEFINITION + ) + ) + ) + TYPE_VALUE_MATCHER -> MatcherCondition( + JSONDefinition.buildDefinitionFromJSON( + jsonCondition.getJSONObject( + KEY_DEFINITION + ) + ) + ) + TYPE_VALUE_HISTORICAL -> HistoricalCondition( + JSONDefinition.buildDefinitionFromJSON( + jsonCondition.getJSONObject( + KEY_DEFINITION + ) + ) + ) + else -> { + MobileCore.log( + LoggingMode.ERROR, + LOG_TAG, + "Unsupported condition type - $type" + ) + null + } + } + } catch (e: Exception) { + MobileCore.log( + LoggingMode.ERROR, + LOG_TAG, + "Failed to parse [rule.condition] JSON, the error is: ${e.message}" + ) + null + } + } + } + + /** + * Converts itself to a [Evaluable] + * + * @return an optional [Evaluable] + */ + @JvmSynthetic + abstract fun toEvaluable(): Evaluable? +} + + + diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequence.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequence.kt new file mode 100644 index 000000000..cde57a838 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequence.kt @@ -0,0 +1,49 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.launch.rulesengine.json + +import com.adobe.marketing.mobile.internal.utility.toMap +import com.adobe.marketing.mobile.launch.rulesengine.RuleConsequence +import org.json.JSONObject + +/** + * The class representing a Rule's consequence + */ +internal class JSONConsequence private constructor( + private val id: String, + private val type: String, + private val detail: Map? +) { + companion object { + private const val KEY_ID = "id" + private const val KEY_TYPE = "type" + private const val KEY_DETAIL = "detail" + operator fun invoke(jsonObject: JSONObject?): JSONConsequence? { + if (jsonObject !is JSONObject) return null + return JSONConsequence( + jsonObject.optString(KEY_ID), + jsonObject.optString(KEY_TYPE), + jsonObject.optJSONObject(KEY_DETAIL)?.toMap() + ) + } + } + + /** + * Converts itself to a [RuleConsequence] object + * + * @return an object of [RuleConsequence] + */ + @JvmSynthetic + internal fun toRuleConsequence(): RuleConsequence { + return RuleConsequence(this.id, this.type, this.detail) + } +} \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONDefinition.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONDefinition.kt new file mode 100644 index 000000000..4046eab84 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONDefinition.kt @@ -0,0 +1,113 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.launch.rulesengine.json + +import com.adobe.marketing.mobile.internal.utility.map +import com.adobe.marketing.mobile.internal.utility.toMap +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject + +/** + * The class representing a Rule condition's definition + * + * @property logic the `logic` filed of the JSON object + * @property conditions the `conditions` filed of the JSON object + * @property key the `key` filed of the JSON object + * @property matcher the `matcher` filed of the JSON object + * @property values the `values` filed of the JSON object + * @property events the `events` filed of the JSON object + * @property value the `value` filed of the JSON object + * @property from the `from` filed of the JSON object + * @property to the `to` filed of the JSON object + * @property searchType the `searchType` filed of the JSON object + * @constructor Constructs a new [JSONDefinition] + */ +internal data class JSONDefinition( + val logic: String?, + val conditions: List?, + val key: String?, + val matcher: String?, + val values: List?, + val events: List>?, + val value: Any?, + val from: Int?, + val to: Int?, + val searchType: String?, +) { + companion object { + private const val DEFINITION_KEY_LOGIC = "logic" + private const val DEFINITION_KEY_CONDITIONS = "conditions" + private const val DEFINITION_KEY_KEY = "key" + private const val DEFINITION_KEY_MATCHER = "matcher" + private const val DEFINITION_KEY_VALUES = "values" + private const val DEFINITION_KEY_EVENTS = "events" + private const val DEFINITION_KEY_VALUE = "value" + private const val DEFINITION_KEY_FROM = "from" + private const val DEFINITION_KEY_TO = "to" + private const val DEFINITION_KEY_SEARCH_TYPE = "searchType" + + /** + * Builds a new [JSONDefinition] + * + * @param jsonObject a [JSONObject] of a Rule condition's definition + * @return a new [JSONDefinition] + */ + @JvmSynthetic + internal fun buildDefinitionFromJSON(jsonObject: JSONObject): JSONDefinition { + val logic = jsonObject.opt(DEFINITION_KEY_LOGIC) as? String + val conditions = + buildConditionList(jsonObject.optJSONArray(DEFINITION_KEY_CONDITIONS)) + val key = jsonObject.opt(DEFINITION_KEY_KEY) as? String + val matcher = jsonObject.opt(DEFINITION_KEY_MATCHER) as? String + val values = + buildValueList(jsonObject.optJSONArray(DEFINITION_KEY_VALUES)) + val events = + buildValueMapList(jsonObject.optJSONArray(DEFINITION_KEY_EVENTS)) + val value = jsonObject.opt(DEFINITION_KEY_VALUE) + val from = jsonObject.opt(DEFINITION_KEY_FROM) as? Int + val to = jsonObject.opt(DEFINITION_KEY_TO) as? Int + val searchType = jsonObject.opt(DEFINITION_KEY_SEARCH_TYPE) as? String + return JSONDefinition( + logic, + conditions, + key, + matcher, + values, + events, + value, + from, + to, + searchType + ) + } + + private fun buildConditionList(jsonArray: JSONArray?): List? { + return jsonArray?.map { + JSONCondition.build(it as? JSONObject) + ?: throw JSONException("Unsupported [rule.condition] JSON format: $it ") + } + } + + private fun buildValueList(jsonArray: JSONArray?): List? { + return jsonArray?.map { it } + } + + private fun buildValueMapList(jsonArray: JSONArray?): List>? { + return jsonArray?.map { + (it as? JSONObject)?.toMap() + ?: throw JSONException("Unsupported [rule.condition.historical.events] JSON format: $it ") + } + } + + } +} + diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRule.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRule.kt new file mode 100644 index 000000000..b3d94b1a2 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRule.kt @@ -0,0 +1,80 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.launch.rulesengine.json + +import com.adobe.marketing.mobile.LoggingMode +import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.internal.utility.map +import com.adobe.marketing.mobile.launch.rulesengine.LaunchRule +import com.adobe.marketing.mobile.rulesengine.Evaluable +import org.json.JSONArray +import org.json.JSONObject + +/** + * The class representing a Rule + */ +internal class JSONRule private constructor( + val condition: JSONObject, + val consequences: JSONArray +) { + companion object { + + private const val LOG_TAG = "JSONRule" + private const val KEY_CONDITION = "condition" + private const val KEY_CONSEQUENCES = "consequences" + + /** + * Optionally constructs a new [JSONRule] + * + * @param jsonObject a [JSONObject] of the Rule + * @return a new [JSONRule] or null + */ + operator fun invoke(jsonObject: JSONObject?): JSONRule? { + if (jsonObject !is JSONObject) return null + val condition = jsonObject.getJSONObject(KEY_CONDITION) + val consequences = jsonObject.getJSONArray(KEY_CONSEQUENCES) + if (condition !is JSONObject || consequences !is JSONArray) { + MobileCore.log( + LoggingMode.ERROR, + LOG_TAG, + "Failed to extract [rule.condition] or [rule.consequences]." + ) + return null + } + return JSONRule(condition, consequences) + } + + } + + /** + * Converts itself to a [LaunchRule] + * + * @return an object of [LaunchRule] + */ + @JvmSynthetic + internal fun toLaunchRule(): LaunchRule? { + val evaluable = JSONCondition.build(condition)?.toEvaluable() + if (evaluable !is Evaluable) { + MobileCore.log( + LoggingMode.ERROR, + LOG_TAG, + "Failed to build LaunchRule from JSON, the [rule.condition] can't be parsed to Evaluable." + ) + return null + } + val consequenceList = consequences.map { + JSONConsequence(it as? JSONObject)?.toRuleConsequence() ?: throw Exception() + } + return LaunchRule(evaluable, consequenceList) + } + +} \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRoot.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRoot.kt new file mode 100644 index 000000000..6a9528859 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRoot.kt @@ -0,0 +1,55 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.launch.rulesengine.json + +import com.adobe.marketing.mobile.LoggingMode +import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.internal.utility.map +import com.adobe.marketing.mobile.launch.rulesengine.LaunchRule +import org.json.JSONArray +import org.json.JSONObject + +/** + * The class representing a set of Rules + */ +internal class JSONRuleRoot private constructor(val version: String, val jsonArray: JSONArray) { + companion object { + private const val LOG_TAG = "JSONRuleRoot" + private const val KEY_VERSION = "version" + private const val KEY_RULES = "rules" + operator fun invoke(jsonObject: JSONObject): JSONRuleRoot? { + val version = jsonObject.optString(KEY_VERSION, "0") + val rules = jsonObject.optJSONArray(KEY_RULES) + if (rules !is JSONArray) { + MobileCore.log( + LoggingMode.ERROR, + LOG_TAG, + "Failed to extract [launch_json.rules]" + ) + return null + } + return JSONRuleRoot(version, rules) + } + } + + /** + * Converts itself to a list of [LaunchRule]s + * + * @return a list of [LaunchRule]s + */ + @JvmSynthetic + fun toLaunchRules(): List { + return jsonArray.map { + JSONRule(it as? JSONObject)?.toLaunchRule() ?: throw Exception() + } + } +} \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParser.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParser.kt new file mode 100644 index 000000000..a9c131272 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParser.kt @@ -0,0 +1,51 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.launch.rulesengine.json + +import com.adobe.marketing.mobile.LoggingMode +import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.launch.rulesengine.LaunchRule +import org.json.JSONObject +import org.json.JSONTokener + +/** + * Parses the JSON string to a list of [LaunchRule]s + */ +object JSONRulesParser { + private const val LOG_TAG = "JSONRulesParser" + + /** + * Parses a set of JSON rules to a list of [LaunchRule]s + * + * @param jsonString a JSON string + * @return a list of [LaunchRule]s + */ + @JvmStatic + fun parse(jsonString: String): List? { + try { + val jsonObject = JSONTokener(jsonString).nextValue() + if (jsonObject is JSONObject) { + return JSONRuleRoot(jsonObject)?.toLaunchRules() + } + } catch (e: Exception) { + MobileCore.log( + LoggingMode.ERROR, + LOG_TAG, + "Failed to parse launch rules JSON: \n $jsonString" + ) + } + return null + } +} + + + diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/MatcherCondition.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/MatcherCondition.kt new file mode 100644 index 000000000..ab9e220d5 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/MatcherCondition.kt @@ -0,0 +1,93 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.launch.rulesengine.json + +import com.adobe.marketing.mobile.LoggingMode +import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.rulesengine.* + +/** + * The class representing a matcher condition + */ +internal class MatcherCondition(val definition: JSONDefinition) : JSONCondition() { + + companion object { + private const val LOG_TAG = "MatcherCondition" + private const val OPERATION_NAME_OR = "or" + private val MATCHER_MAPPING = mapOf( + "eq" to "equals", + "ne" to "notEquals", + "gt" to "greaterThan", + "ge" to "greaterEqual", + "lt" to "lessThan", + "le" to "lessEqual", + "co" to "contains", + "nc" to "notContains", + "sw" to "startsWith", + "ew" to "endsWith", + "ex" to "exists", + "nx" to "notExist" + ) + } + + @JvmSynthetic + override fun toEvaluable(): Evaluable? { + if (definition.matcher !is String || definition.key !is String) { + MobileCore.log( + LoggingMode.ERROR, + LOG_TAG, + "[key] or [matcher] is not String, failed to build Evaluable from definition JSON: \n $definition" + ) + return null + } + val values: List = definition.values ?: listOf() + return when (values.size) { + 0 -> convert(definition.key, definition.matcher, "") + 1 -> convert(definition.key, definition.matcher, values[0]) + in 2..Int.MAX_VALUE -> { + val operands = values.map { convert(definition.key, definition.matcher, it) } + if (operands.isEmpty()) null else LogicalExpression(operands, OPERATION_NAME_OR) + } + else -> null + } + } + + private fun convert(key: String, matcher: String, value: Any?): Evaluable? { + val operationName = MATCHER_MAPPING[matcher] + if (operationName == null) { + MobileCore.log( + LoggingMode.ERROR, + LOG_TAG, + "Failed to build Evaluable from [type:matcher] json, [definition.matcher = $matcher] is not supported." + ) + return null + } + val javaClass: Any? = when (value) { + is String -> String::class.java + is Int -> Number::class.java + is Double -> Number::class.java + //note: Kotlin.Boolean is not mapped to java.lang.Boolean correctly + is Boolean -> java.lang.Boolean::class.java + is Float -> Number::class.java + else -> null + } + return if (javaClass != null) { + ComparisonExpression( + OperandMustacheToken("{{$key}}", javaClass as Class<*>), + operationName, + OperandLiteral(value) + ) + } else { + null + } + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ConditionEvaluator.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ConditionEvaluator.java index 8a5eb5670..375f559cc 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ConditionEvaluator.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ConditionEvaluator.java @@ -13,7 +13,6 @@ package com.adobe.marketing.mobile.rulesengine; import java.util.regex.Pattern; - public class ConditionEvaluator implements Evaluating { private final Option option; private static final String OPERATOR_EQUALS = "equals"; diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConditionTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConditionTests.kt new file mode 100644 index 000000000..533996d98 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConditionTests.kt @@ -0,0 +1,159 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.launch.rulesengine.json + +import com.adobe.marketing.mobile.rulesengine.ComparisonExpression +import com.adobe.marketing.mobile.rulesengine.Evaluable +import com.adobe.marketing.mobile.rulesengine.LogicalExpression +import org.junit.Test +import kotlin.test.assertTrue + +class JSONConditionTests { + @Test + fun testBadJsonFormatWithoutMatcherValue() { + val jsonConditionString = """ + { + "type": "matcher", + "definition": { + "key": "~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches", + "values": [ + 2 + ] + } + } + """.trimIndent() + val jsonCondition = JSONCondition.build(buildJSONObject(jsonConditionString)) + assertTrue(jsonCondition is MatcherCondition) + val evaluable = jsonCondition.toEvaluable() + assertTrue(evaluable == null) + } + + @Test + fun testBadJsonFormatWithUnsupportedMatcherValue() { + val jsonConditionString = """ + { + "type": "matcher", + "definition": { + "key": "~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches", + "matcher": "ge1", + "values": [ + 2 + ] + } + } + """.trimIndent() + val jsonCondition = JSONCondition.build(buildJSONObject(jsonConditionString)) + assertTrue(jsonCondition is MatcherCondition) + val evaluable = jsonCondition.toEvaluable() + assertTrue(evaluable == null) + } + + @Test + fun testMatcherWithOneValue() { + val jsonConditionString = """ + { + "type": "matcher", + "definition": { + "key": "~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches", + "matcher": "ge", + "values": [ + 2 + ] + } + } + """.trimIndent() + val jsonCondition = JSONCondition.build(buildJSONObject(jsonConditionString)) + assertTrue(jsonCondition is MatcherCondition) + val evaluable = jsonCondition.toEvaluable() + assertTrue(evaluable is Evaluable) + assertTrue(evaluable is ComparisonExpression<*, *>) + } + + @Test + fun testMatcherWithoutValue() { + val jsonConditionString = """ + { + "type": "matcher", + "definition": { + "key": "~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches", + "matcher": "ge", + "values": [] + } + } + """.trimIndent() + val jsonCondition = JSONCondition.build(buildJSONObject(jsonConditionString)) + assertTrue(jsonCondition is MatcherCondition) + val evaluable = jsonCondition.toEvaluable() + assertTrue(evaluable is Evaluable) + assertTrue(evaluable is ComparisonExpression<*, *>) + } + + @Test + fun testMatcherWithMultipleValues() { + val jsonConditionString = """ + { + "type": "matcher", + "definition": { + "key": "~state.com.adobe.module.lifecycle/lifecyclecontextdata.carriername", + "matcher": "co", + "values": [ + "ATT", + "VERIZON" + ] + } + } + """.trimIndent() + val jsonCondition = JSONCondition.build(buildJSONObject(jsonConditionString)) + assertTrue(jsonCondition is MatcherCondition) + val evaluable = jsonCondition.toEvaluable() + assertTrue(evaluable is Evaluable) + assertTrue(evaluable is LogicalExpression) + } + + @Test + fun testAndGroup() { + val jsonConditionString = """ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.responseContent" + ] + } + } + ] + } + } + """.trimIndent() + val jsonCondition = JSONCondition.build(buildJSONObject(jsonConditionString)) + assertTrue(jsonCondition is GroupCondition) + val evaluable = jsonCondition.toEvaluable() + assertTrue(evaluable is Evaluable) + assertTrue(evaluable is LogicalExpression) + } +} \ No newline at end of file diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequenceTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequenceTests.kt new file mode 100644 index 000000000..ad2c5572a --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequenceTests.kt @@ -0,0 +1,51 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.launch.rulesengine.json + +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +class JSONConsequenceTests { + @Test + fun testJSONConsequence() { + val jsonString = """ + { + "id": "RCa839e401f54a459a9049328f9b609a07", + "type": "add", + "detail": { + "eventdata": { + "attached_data": { + "key1": "value1", + "launches": "{%~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches%}" + } + } + } + } + """ + + val jsonObject = buildJSONObject(jsonString) + val consequence = JSONConsequence(jsonObject)?.toRuleConsequence() + assertEquals("RCa839e401f54a459a9049328f9b609a07", consequence?.id) + assertEquals("add", consequence?.type) + assertNotNull(consequence?.detail) + assertEquals(1, consequence?.detail?.size) + assertEquals(true, consequence?.detail?.containsKey("eventdata")) + val eventdata = consequence?.detail?.get("eventdata") as? Map<*, *> + assertNotNull(eventdata) + assertEquals(1, eventdata.size) + val attachedData = eventdata.get("attached_data") as? Map<*, *> + assertNotNull(attachedData) + assertEquals(2, attachedData.size) + assertEquals("value1", attachedData["key1"] as? String) + assertEquals("{%~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches%}", attachedData["launches"] as? String) + } +} \ No newline at end of file diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRootTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRootTests.kt new file mode 100644 index 000000000..f1efe4aab --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRootTests.kt @@ -0,0 +1,89 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.launch.rulesengine.json + +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class JSONRuleRootTests { + @Test + fun testNormal() { + val jsonString = """ + { + "version": 1, + "rules": [ + { + "condition": { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + "consequences": [ + { + "id": "RC2500e6b0140744d49e6fd503a55a66d4", + "type": "pb", + "detail": { + "timeout": 0, + "templateurl": "http://www.adobe.com" + } + } + ] + } + ] + } + """.trimIndent() + val jsonRuleRoot = JSONRuleRoot(buildJSONObject(jsonString)) + assertTrue(jsonRuleRoot is JSONRuleRoot) + val launchRules = jsonRuleRoot.toLaunchRules() + assertEquals(1, launchRules.size) + } + @Test + fun testWithoutVersion() { + val jsonString = """ + { + "rules": [ + { + "condition": { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + "consequences": [ + { + "id": "RC2500e6b0140744d49e6fd503a55a66d4", + "type": "pb", + "detail": { + "timeout": 0, + "templateurl": "http://www.adobe.com" + } + } + ] + } + ] + } + """.trimIndent() + val jsonRuleRoot = JSONRuleRoot(buildJSONObject(jsonString)) + assertTrue(jsonRuleRoot is JSONRuleRoot) + assertEquals("0", jsonRuleRoot.version) + } +} \ No newline at end of file diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleTests.kt new file mode 100644 index 000000000..ff140cdc5 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleTests.kt @@ -0,0 +1,81 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.launch.rulesengine.json + +import com.adobe.marketing.mobile.launch.rulesengine.LaunchRule +import com.adobe.marketing.mobile.rulesengine.ComparisonExpression +import org.json.JSONException +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class JSONRuleTests { + @Test + fun testNormal() { + val jsonString = """ + { + "condition": { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + "consequences": [ + { + "id": "RC2500e6b0140744d49e6fd503a55a66d4", + "type": "pb", + "detail": { + "timeout": 0, + "templateurl": "http://www.adobe.com" + } + } + ] + } + """.trimIndent() + val jsonRule = JSONRule(buildJSONObject(jsonString)) + assertTrue(jsonRule is JSONRule) + val launchRule = jsonRule.toLaunchRule() + assertTrue(launchRule is LaunchRule) + assertEquals(1, launchRule.consequenceList.size) + assertEquals("pb", launchRule.consequenceList[0].type) + assertTrue(launchRule.condition is ComparisonExpression<*, *>) + } + + @Test + fun testNullInput() { + assertNull(JSONRule(null)) + } + + @Test(expected = JSONException::class) + fun testBadJSONFormat() { + val jsonString = """ + { + "condition": { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + } + } + """.trimIndent() + JSONRule(buildJSONObject(jsonString)) + } +} \ No newline at end of file diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleUtilities.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleUtilities.kt new file mode 100644 index 000000000..a91d2eb51 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleUtilities.kt @@ -0,0 +1,27 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.launch.rulesengine.json + +import org.json.JSONArray +import org.json.JSONObject +import org.json.JSONTokener + +internal fun buildJSONObject(jsonObject: String): JSONObject { + val jsonObj = JSONTokener(jsonObject).nextValue() as? JSONObject + if (jsonObj !is JSONObject) throw IllegalArgumentException() + return jsonObj +} + +internal fun buildJSONArray(jsonArray: String): JSONArray { + val jsonAry = JSONTokener(jsonArray).nextValue() as? JSONArray + if (jsonAry !is JSONArray) throw IllegalArgumentException() + return jsonAry +} \ No newline at end of file diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParserTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParserTests.kt new file mode 100644 index 000000000..3f93c60d3 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParserTests.kt @@ -0,0 +1,35 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.launch.rulesengine.json + +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class JSONRulesParserTests { + @Test + fun testBadJSONFormat() { + val result = JSONRulesParser.parse("") + assertNull(result) + } + + @Test + fun testNormal() { + val fileTxt = this::class.java.classLoader.getResource("rules_parser/launch_rule_root.json") + .readText() + val result = JSONRulesParser.parse(fileTxt) + assertNotNull(result) + assertEquals(1, result.size) + } + +} \ No newline at end of file diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperandMustacheTokenKTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperandMustacheTokenKTests.kt new file mode 100644 index 000000000..81acb48c2 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/rulesengine/OperandMustacheTokenKTests.kt @@ -0,0 +1,80 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.rulesengine + +import org.junit.Assert +import org.junit.Before +import org.junit.Test + +class OperandMustacheTokenKTests { + private val defaultEvaluator: ConditionEvaluator<*, *> = + ConditionEvaluator(ConditionEvaluator.Option.DEFAULT) + + @Before + fun setup() { + } + + @Test + fun testPassingJavaTypesInKotlin() { + // setup + val tokenString = OperandMustacheToken( + "{{Hero}}", + String::class.java + ) + val tokenInteger = OperandMustacheToken( + "{{integerToken}}", + Number::class.java + ) + val tokenBoolean = OperandMustacheToken( + "{{booleanToken}}", + java.lang.Boolean::class.java + ) + val tokenDouble = OperandMustacheToken( + "{{doubleToken}}", + Number::class.java + ) + val tokenFloat = OperandMustacheToken( + "{{floatToken}}", + Number::class.java + ) + + // test + val result1 = tokenString.resolve(defaultContext(defaultEvaluator)) + val result2 = tokenInteger.resolve(defaultContext(defaultEvaluator)) + val result3 = tokenBoolean.resolve(defaultContext(defaultEvaluator)) + val result4 = tokenDouble.resolve(defaultContext(defaultEvaluator)) + val result5 = tokenFloat.resolve(defaultContext(defaultEvaluator)) + + // verify + Assert.assertEquals("Soldier", result1) + Assert.assertEquals(33, result2) + Assert.assertEquals(false, result3) + Assert.assertEquals(3.3, result4) + Assert.assertEquals(3.3f, result5) + } + + /* ************************************************************************** + * Private methods + **************************************************************************/ + private fun defaultContext(conditionEvaluator: ConditionEvaluator<*, *>): Context { + val context = HashMap() + context["Beer"] = "Corona" + context["Hero"] = "Soldier" + context["Soda"] = "Pepsi" + context["answer"] = "Corona extra" + context["integerToken"] = 33 + context["doubleToken"] = 3.3 + context["floatToken"] = 3.3f + context["booleanToken"] = false + return Context(FakeTokenFinder(context), conditionEvaluator, FakeTransformer.create()) + } +} diff --git a/code/android-core-library/src/test/resources/rules_parser/launch_rule_root.json b/code/android-core-library/src/test/resources/rules_parser/launch_rule_root.json new file mode 100644 index 000000000..99428198d --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_parser/launch_rule_root.json @@ -0,0 +1,87 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.responseContent" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.launchevent", + "matcher": "ex", + "values": [] + } + } + ] + } + } + ] + } + }, + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~state.com.adobe.module.lifecycle/lifecyclecontextdata.carriername", + "matcher": "co", + "values": [ + "AT" + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "RCc223ec648df44fbbaab737e6cc6da50e", + "type": "pb", + "detail": { + "timeout": 0, + "templateurl": "http://www.adobe.com" + } + } + ] + } + ] +} \ No newline at end of file From 79d118451dcca51a7326220e3ca7258fecfd8442 Mon Sep 17 00:00:00 2001 From: praveek Date: Thu, 31 Mar 2022 13:23:56 -0700 Subject: [PATCH 012/476] Cleanup ExtensionApi and refactor EventHub (Part 1) --- .../java/com/adobe/marketing/mobile/Core.java | 11 +- .../com/adobe/marketing/mobile/EventHub.java | 104 +- .../com/adobe/marketing/mobile/Extension.java | 7 +- .../adobe/marketing/mobile/ExtensionApi.java | 263 +- .../marketing/mobile/ExtensionHelper.java | 50 + .../marketing/mobile/ExtensionListener.java | 27 +- .../marketing/mobile/InternalExtension.java | 19 + .../mobile/internal/eventhub/EventHub.kt | 104 +- .../internal/eventhub/EventHubConstants.kt | 16 + .../mobile/internal/eventhub/EventHubError.kt | 10 + .../eventhub/EventHubPlaceholderExtension.kt | 15 + .../internal/eventhub/ExtensionContainer.kt | 129 + .../mobile/internal/eventhub/Extensions.kt | 52 + .../adobe/marketing/mobile/EventHubTest.java | 4082 ++++++++--------- .../marketing/mobile/ExtensionApiTests.java | 1698 +++---- .../mobile/ExtensionListenerTest.java | 300 +- .../adobe/marketing/mobile/ModuleTest.java | 624 +-- .../src/test/kotlin/EventHubTests.kt | 11 - .../mobile/internal/eventhub/EventHubTests.kt | 143 + .../internal/eventhub/MockExtension.java | 15 + 20 files changed, 3971 insertions(+), 3709 deletions(-) create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionHelper.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/InternalExtension.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubConstants.kt create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubError.kt create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubPlaceholderExtension.kt create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/Extensions.kt delete mode 100644 code/android-core-library/src/test/kotlin/EventHubTests.kt create mode 100644 code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt create mode 100644 code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/MockExtension.java diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Core.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Core.java index 30337c5a4..00ea1772e 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Core.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Core.java @@ -55,16 +55,7 @@ class Core { */ void registerExtension(final Class extensionClass, final ExtensionErrorCallback errorCallback) { - try { - eventHub.registerExtension(extensionClass); - } catch (InvalidModuleException e) { - Log.debug(LOG_TAG, "Core.registerExtension - Failed to register extension class %s (%s)", - extensionClass.getSimpleName(), e); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.UNEXPECTED_ERROR); - } - } + eventHub.registerExtensionWithCallback(extensionClass, errorCallback); } /** diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java index e102a75f0..6469183f1 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java @@ -10,11 +10,16 @@ */ package com.adobe.marketing.mobile; +import com.adobe.marketing.mobile.internal.eventhub.EventHubError; + import java.lang.reflect.Constructor; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; +import kotlin.Unit; +import kotlin.jvm.functions.Function1; + /** * defines a core event loop for sdk activity * @@ -432,81 +437,28 @@ final void registerModule(final Class moduleClass, * When the registration is completed, the extension class will be initialized with the {@link ExtensionApi} * * @param extensionClass a class that extends {@link Extension} - * @param type of the extension class - * @throws InvalidModuleException will be thrown if the {@code extensionClass} is null */ - final - void registerExtension(final Class extensionClass) throws InvalidModuleException { - if (extensionClass == null) { - throw new InvalidModuleException(LOG_CLASS_WAS_NULL); - } - - // register the module on the event hub thread - final EventHub hub = this; - this.eventHubThreadService.submit(new Runnable() { + void registerExtensionWithCallback(final Class extensionClass, final ExtensionErrorCallback errorCallback) { + com.adobe.marketing.mobile.internal.eventhub.EventHub.Companion.getShared().registerExtension(extensionClass, new Function1() { @Override - public void run() { - try { - ExtensionApi module = new ExtensionApi(hub); - - Constructor moduleConstructor = extensionClass.getDeclaredConstructor(ExtensionApi.class); - moduleConstructor.setAccessible(true); - final Extension extension = moduleConstructor.newInstance(module); - - - if (StringUtils.isNullOrEmpty(extension.getName())) { - Log.error(logPrefix, "Failed to register extension, extension name should not be null or empty", - extension.getName()); - extension.onUnexpectedError(new ExtensionUnexpectedError( - String.format("Failed to register extension with name (%s), %s class", - extension.getName(), extensionClass.getSimpleName()), - ExtensionError.BAD_NAME)); - return; - } - - if (isRegisteredModule(extension.getName())) { - Log.error(logPrefix, "Failed to register extension, an extension with the same name (%s) already exists", - extension.getName()); - extension.onUnexpectedError(new ExtensionUnexpectedError( - String.format("Failed to register extension with name %s, %s class", - extension.getName(), extensionClass.getSimpleName()), - ExtensionError.DUPLICATE_NAME)); - return; - } - - activeModules.put(normalizeName(extension.getName()), module); - - moduleListeners.putIfAbsent(module, new ConcurrentLinkedQueue()); - - module.setExtension(extension); - - // add external module details to event hub shared state - module.setModuleDetails(new ModuleDetails() { - @Override - public String getName() { - return extension.getFriendlyName(); - } - - @Override - public String getVersion() { - return extension.getVersion(); - } - - @Override - public Map getAdditionalInfo() { - return new HashMap(); - } - }); - addModuleToEventHubSharedState(module); + public Unit invoke(EventHubError e) { + if (errorCallback == null || EventHubError.none.equals(e)) { + return null; + } - Log.debug(logPrefix, "Extension with name %s was registered successfully", module.getModuleName()); - } catch (Exception e) { - Log.error(logPrefix, "Unable to create instance of provided extension %s: %s", extensionClass.getSimpleName(), e); + if (EventHubError.invalidExtensionName.equals(e)) { + errorCallback.error(ExtensionError.BAD_NAME); + } else if (EventHubError.duplicateExtensionName.equals(e)) { + errorCallback.error(ExtensionError.DUPLICATE_NAME); + } else { + errorCallback.error(ExtensionError.UNEXPECTED_ERROR); } + return null; } }); } + /** * Unregisters a Module *

@@ -633,9 +585,9 @@ public void run() { // if this is an extension, call the error callback if (ExtensionApi.class.isAssignableFrom(module.getClass())) { - ExtensionApi ext = (ExtensionApi) module; - ext.getExtension().onUnexpectedError(new ExtensionUnexpectedError("Failed to register listener", - ExtensionError.UNEXPECTED_ERROR)); + //ExtensionApi ext = (ExtensionApi) module; + //ext.getExtension().onUnexpectedError(new ExtensionUnexpectedError("Failed to register listener", + // ExtensionError.UNEXPECTED_ERROR)); } } } @@ -667,12 +619,12 @@ public void run() { Log.error(logPrefix, "Failed to register listener for class %s (%s)", listenerClass.getSimpleName(), e); - // if this is an extension, call the error callback - if (ExtensionApi.class.isAssignableFrom(module.getClass())) { - ExtensionApi ext = (ExtensionApi) module; - ext.getExtension().onUnexpectedError(new ExtensionUnexpectedError("Failed to register listener", e, - ExtensionError.UNEXPECTED_ERROR)); - } +// // if this is an extension, call the error callback +// if (ExtensionApi.class.isAssignableFrom(module.getClass())) { +// ExtensionApi ext = (ExtensionApi) module; +// ext.getExtension().onUnexpectedError(new ExtensionUnexpectedError("Failed to register listener", e, +// ExtensionError.UNEXPECTED_ERROR)); +// } } } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Extension.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Extension.java index 50b295a18..1b597565e 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Extension.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Extension.java @@ -14,8 +14,6 @@ /** * Abstract class that defines an {@code Extension} * - * @author Adobe Systems Incorporated - * @version 5.0 */ public abstract class Extension { private ExtensionApi extensionApi; @@ -89,11 +87,10 @@ public final ExtensionApi getApi() { } /** - * Get the log tag for this extension. If {@code extensionApi} is not null, then the result of - * {@link ExtensionApi#getLogTag()} is returned. Otherwise, the result of {@link #getName()} is returned. + * Get the log tag for this extension. * @return a log tag for this extension */ private String getLogTag() { - return extensionApi != null ? extensionApi.getLogTag() : getName(); + return getName() + "(" + getVersion() + ")"; } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionApi.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionApi.java index 549a08209..43eaa481e 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionApi.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionApi.java @@ -14,50 +14,9 @@ import java.util.Map; /** - * Class that defines all the public methods an {@code Extension} may call to interface with the Adobe Cloud Platform SDK. - * - * @author Adobe Systems Incorporated - * @version 5.0 + * Class that defines all the public methods an {@code Extension} may call to interface with the AEP SDK. */ -public final class ExtensionApi extends Module { - private static final String LOG_TAG = ExtensionApi.class.getSimpleName(); - private Extension extension; - - ExtensionApi(final EventHub hub) { - super(null, hub); // moduleName is placeholder -- it will be set by `setExtension` - this.extension = null; - } - - /** - * Sets the {@code Extension} that this object is tied to. This should only be called by event hub. - * All calls after the first one will be ignored. - * @param extension the {@link Extension} the event hub will be calling back into from this object. - */ - final void setExtension(final Extension extension) { - if (this.extension == null) { - this.extension = extension; - this.setModuleName(extension.getName()); - this.setModuleVersion(extension.getVersion()); - } - } - - /** - * Gets the {@code Extension} that this object is tied to. Used to distinguish internal from external extensions. - * @return {@link Extension} reference - */ - final Extension getExtension() { - return this.extension; - } - - /** - * Called by the event hub when the extension is unregistered. - */ - protected final void onUnregistered() { - if (this.extension != null) { - this.extension.onUnregistered(); - } - } - +public abstract class ExtensionApi { /** * Registers a new event listener for current extension for the provided event type and source. *

@@ -71,48 +30,10 @@ protected final void onUnregistered() { * @param type of current event listener * @return {@code boolean} indicating the listener registration status */ - public final boolean registerEventListener(final String eventType, - final String eventSource, - final Class extensionListenerClass, - final ExtensionErrorCallback errorCallback) { - - if (StringUtils.isNullOrEmpty(eventType)) { - Log.debug(getLogTag(), "%s.registerEventListener Event type cannot be null or empty.", LOG_TAG); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.EVENT_TYPE_NOT_SUPPORTED); - } - - return false; - } - - if (StringUtils.isNullOrEmpty(eventSource)) { - Log.debug(getLogTag(), "%s.registerEventListener Event source cannot be null or empty.", LOG_TAG); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.EVENT_SOURCE_NOT_SUPPORTED); - } - - return false; - } - - if (extensionListenerClass == null) { - Log.debug(getLogTag(), "%s (%s.registerEventListener Event listener class)", Log.UNEXPECTED_NULL_VALUE, LOG_TAG); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.UNEXPECTED_ERROR); - } - - return false; - } - - Log.trace(getLogTag(), "%s.registerEventListener called for event type '%s' and source '%s'.", LOG_TAG, eventType, - eventSource); - EventType type = EventType.get(eventType); - EventSource source = EventSource.get(eventSource); - super.registerListener(type, source, extensionListenerClass); - return true; - } + public abstract boolean registerEventListener(final String eventType, + final String eventSource, + final Class extensionListenerClass, + final ExtensionErrorCallback errorCallback); /** * Registers a new wildcard event listener for the current extension. This listener will receive all events that are @@ -132,27 +53,11 @@ public final boolean registerEventListener(final S * @param type of current event listener * @return {@code boolean} indicating the listener registration status */ - public final boolean registerWildcardListener( - final Class extensionListenerClass, - final ExtensionErrorCallback errorCallback) { - - if (extensionListenerClass == null) { - Log.debug(getLogTag(), "%s (%s.registerWildcardListener Event listener class)", Log.UNEXPECTED_NULL_VALUE, LOG_TAG); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.UNEXPECTED_ERROR); - } - - return false; - } - - Log.debug(getLogTag(), - "Registering a wildcard listener. If this is a production environment, consider using the regular listener instead."); - super.registerWildcardListener(extensionListenerClass); - return true; - } + public abstract boolean registerWildcardListener( + final Class extensionListenerClass, + final ExtensionErrorCallback errorCallback); - /** + /** * Called by extension to set a shared state for itself. Usually called from a listener during event processing. * * @param state {@code Map} representing current state of this extension. Passing null will set the extension's @@ -163,10 +68,9 @@ public final boolean registerWildcardListener( * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred * @return {@code boolean} indicating if the shared state was successfully set */ - public final boolean setSharedEventState(final Map state, final Event event, - final ExtensionErrorCallback errorCallback) { - return setSharedStateCommon(state, event, errorCallback, SharedStateType.STANDARD); - } + public abstract boolean setSharedEventState(final Map state, final Event event, + final ExtensionErrorCallback errorCallback); + /** * Called by extension to set an XDM shared state for itself. Usually called from a listener during event processing. @@ -181,44 +85,8 @@ public final boolean setSharedEventState(final Map state, final * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred * @return {@code boolean} indicating if the XDM shared state was successfully set */ - public final boolean setXDMSharedEventState(final Map state, final Event event, - final ExtensionErrorCallback errorCallback) { - return setSharedStateCommon(state, event, errorCallback, SharedStateType.XDM); - } - - private boolean setSharedStateCommon(final Map state, final Event event, - final ExtensionErrorCallback errorCallback, final SharedStateType sharedStateType) { - boolean success = false; - - try { - final EventData eventData = state != null ? EventData.fromObjectMap(state) : EventHub.SHARED_STATE_PENDING; - - if (event == null) { - if (sharedStateType == SharedStateType.XDM) { - createOrUpdateXDMSharedState(eventData); - } else { - createOrUpdateSharedState(eventData); - } - } else { - if (sharedStateType == SharedStateType.XDM) { - createOrUpdateXDMSharedState(event.getEventNumber(), eventData); - } else { - createOrUpdateSharedState(event.getEventNumber(), eventData); - } - } - - success = true; - } catch (final Exception e) { - String callerName = sharedStateType == SharedStateType.XDM ? "setXDMSharedEventState" : "setSharedEventState"; - Log.warning(getLogTag(), "%s.%s Failed to set the shared state. %s", LOG_TAG, callerName, e); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.UNEXPECTED_ERROR); - } - } - - return success; - } + public abstract boolean setXDMSharedEventState(final Map state, final Event event, + final ExtensionErrorCallback errorCallback); /** * Called by extension to clear all shared state it has previously set. Usually called during {@code Extension.onUnregistered()}. @@ -227,9 +95,7 @@ private boolean setSharedStateCommon(final Map state, final Even * @return {@code boolean} indicating if the shared states were successfully cleared * @see Extension#onUnregistered() */ - public final boolean clearSharedEventStates(final ExtensionErrorCallback errorCallback) { - return clearSharedStateCommon(errorCallback, SharedStateType.STANDARD); - } + public abstract boolean clearSharedEventStates(final ExtensionErrorCallback errorCallback); /** * Called by extension to clear XDM shared state it has previously set. Usually called during {@code Extension.onUnregistered()}. @@ -238,29 +104,7 @@ public final boolean clearSharedEventStates(final ExtensionErrorCallback errorCallback) { - return clearSharedStateCommon(errorCallback, SharedStateType.XDM); - } - - private boolean clearSharedStateCommon(final ExtensionErrorCallback errorCallback, - final SharedStateType sharedStateType) { - try { - if (sharedStateType == SharedStateType.XDM) { - return super.clearXDMSharedStates(); - } else { - return super.clearSharedStates(); - } - } catch (final Exception e) { - String callerName = sharedStateType == SharedStateType.XDM ? "clearXDMSharedEventStates" : "clearSharedEventStates"; - Log.warning(getLogTag(), "%s.%s Failed to clear the shared states. %s", LOG_TAG, callerName, e); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.UNEXPECTED_ERROR); - } - } - - return false; - } + public abstract boolean clearXDMSharedEventStates(final ExtensionErrorCallback errorCallback); /** * Called by extension to get another extension's shared state. Usually called from a listener during event processing. @@ -271,11 +115,8 @@ private boolean clearSharedStateCommon(final ExtensionErrorCallback} containing shared state data at that version. Returns null if state does not exists, * is PENDING, or an error is returned in the {@code errorCallback} */ - public final Map getSharedEventState(final String stateName, final Event event, - final ExtensionErrorCallback errorCallback) { - - return getSharedStateCommon(stateName, event, errorCallback, SharedStateType.STANDARD); - } + public abstract Map getSharedEventState(final String stateName, final Event event, + final ExtensionErrorCallback errorCallback); /** @@ -288,51 +129,9 @@ public final Map getSharedEventState(final String stateName, fin * @return {@code Map} containing XDM shared state data at that version. Returns null if state does not exists, * is PENDING, or an error is returned in the {@code errorCallback} */ - public final Map getXDMSharedEventState(final String stateName, final Event event, - final ExtensionErrorCallback errorCallback) { + public abstract Map getXDMSharedEventState(final String stateName, final Event event, + final ExtensionErrorCallback errorCallback); - return getSharedStateCommon(stateName, event, errorCallback, SharedStateType.XDM); - } - - private Map getSharedStateCommon(final String stateName, final Event event, - final ExtensionErrorCallback errorCallback, final SharedStateType sharedStateType) { - if (stateName == null) { - String callerName = sharedStateType == SharedStateType.XDM ? "getXDMSharedEventState" : "getSharedEventState"; - Log.debug(getLogTag(), "%s (%s.%s State name)", - Log.UNEXPECTED_NULL_VALUE, - LOG_TAG, - callerName); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.UNEXPECTED_ERROR); - } - - return null; - } - - try { - final EventData eventData; - - if (sharedStateType == SharedStateType.XDM) { - eventData = super.getXDMSharedEventState(stateName, event); - } else { - eventData = super.getSharedEventState(stateName, event); - } - - // if shared state is PENDING, eventData will be null - return eventData == null ? null : eventData.toObjectMap(); - } catch (final Exception e) { - String callerName = sharedStateType == SharedStateType.XDM ? "getXDMSharedEventState" : "getSharedEventState"; - Log.warning(getLogTag(), "%s.%s Failed to retrieve the shared state %s, %s", - LOG_TAG, callerName, stateName, e); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.UNEXPECTED_ERROR); - } - } - - return null; - } /** * Unregisters current extension. @@ -342,23 +141,5 @@ private Map getSharedStateCommon(final String stateName, final E * * @see Extension#onUnregistered() */ - public final void unregisterExtension() { - super.unregisterModule(); - } - - /** - * Returns the logging tag which can be either the module name if valid, or the class name otherwise - * @return {@link String} to be used as logging tag - */ - String getLogTag() { - if (extension == null) { - return LOG_TAG; - } - - if (extension.getVersion() == null) { - return extension.getName(); - } - - return extension.getName() + "(" + extension.getVersion() + ")"; - } + public abstract void unregisterExtension(); } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionHelper.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionHelper.java new file mode 100644 index 000000000..7d33fa84b --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionHelper.java @@ -0,0 +1,50 @@ +package com.adobe.marketing.mobile; + +/** + * Helper methods to access protected Extension methods from different packages + */ + +public class ExtensionHelper { + public static String getName(Extension extension) { + try { + if (extension != null) { + return extension.getName(); + } + } catch (Exception ex) { + + } + return null; + } + + public static String getFriendlyName(Extension extension) { + try { + if (extension != null) { + return extension.getFriendlyName(); + } + } catch (Exception ex) { + + } + return null; + } + + public static String getVersion(Extension extension) { + try { + if (extension != null) { + return extension.getVersion(); + } + } catch (Exception ex) { + + } + return null; + } + + public static void onUnregistered(Extension extension) { + try { + if (extension != null) { + extension.onUnregistered(); + } + } catch (Exception ex) { + + } + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionListener.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionListener.java index 3d3815e99..98d5750ad 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionListener.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionListener.java @@ -11,33 +11,40 @@ package com.adobe.marketing.mobile; +import com.adobe.marketing.mobile.internal.eventhub.ExtensionRuntime; + /** * Abstract class that defines the {@code Event} listener for an {@code Extension}. * * @author Adobe Systems Incorporated * @version 5.0 */ -public abstract class ExtensionListener extends ModuleEventListener { - private static final String LOG_TAG = ExtensionListener.class.getSimpleName(); +public abstract class ExtensionListener { + private static final String LOG_TAG = "ExtensionListener"; + private final ExtensionApi extensionApi; + private final String type; + private final String source; /** * Extension listener constructor. Must be implemented by any extending classes and must be called from all * the implementors * - * @param extension parent {@link ExtensionApi} that owns this listener + * @param extensionApi parent {@link ExtensionApi} that owns this listener * @param type {@link String} event type to register this listener for * @param source {@link String} event source to register this listener for */ - protected ExtensionListener(final ExtensionApi extension, final String type, final String source) { - super(extension, EventType.get(type), EventSource.get(source)); + protected ExtensionListener(final ExtensionApi extensionApi, final String type, final String source) { + this.extensionApi = extensionApi; + this.type = type; + this.source = source; } - @Override public void onUnregistered() { - String logTag = super.parentModule != null ? super.parentModule.getLogTag() : LOG_TAG; - Log.debug(logTag, "Extension listener was unregistered successfully"); + Log.debug(LOG_TAG, "Extension listener was unregistered successfully"); } + public abstract void hear(Event event); + /** * This provides access to the parent extension that registered this listener in order to process * the received event and to use the extension services. @@ -48,8 +55,6 @@ public void onUnregistered() { * @return the {@link Extension} registered with the {@link EventHub} */ protected Extension getParentExtension() { - return super.parentModule.getExtension(); + return ((ExtensionRuntime)this.extensionApi).getExtension(); } - - } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/InternalExtension.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/InternalExtension.java new file mode 100644 index 000000000..869b8aff5 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/InternalExtension.java @@ -0,0 +1,19 @@ +package com.adobe.marketing.mobile; + + +/** + * Provide additional helper methods to easily port internal modules as third party extensions. + */ +abstract class InternalExtension extends Extension { + + /** + * Construct the extension and initialize with the {@code ExtensionApi}. + * + * @param extensionApi the {@link ExtensionApi} this extension will use + */ + protected InternalExtension(ExtensionApi extensionApi) { + super(extensionApi); + } + + +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index dfb4f6db4..4315f8d8c 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -1,7 +1,105 @@ package com.adobe.marketing.mobile.internal.eventhub -class EventHub { +import com.adobe.marketing.mobile.* +import java.util.concurrent.* + +/** + * EventHub class is responsible for delivering events to listeners and maintaining registered extension's lifecycle. + */ +internal class EventHub { companion object { - val version = "2.0.0" + val LOG_TAG = "EventHub" + public var shared = EventHub() + } + + private val eventHubExecutor: ExecutorService by lazy { Executors.newSingleThreadExecutor() } + private val registeredExtensions: ConcurrentHashMap = ConcurrentHashMap() + private var hubStarted = false; + + + init { + registerExtension(EventHubPlaceholderExtension::class.java) {} } -} \ No newline at end of file + + /** + * `EventHub` will begin processing `Event`s when this API is invoked. + */ + fun start() { + eventHubExecutor.submit { + this.hubStarted = true + + this.shareEventHubSharedState() + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Event Hub successfully started") + } + } + + /** + * Registers a new `Extension` to the `EventHub`. This `Extension` must extends `Extension` class + * + * @property extensionClass The class of extension to register + * @property completion Invoked when the extension has been registered or failed to register + */ + fun registerExtension(extensionClass: Class?, completion: (error: EventHubError) -> Unit) { + eventHubExecutor.submit { + if (extensionClass == null) { + completion(EventHubError.extensionInitializationFailure) + return@submit + } + + val extensionName = extensionClass.extensionTypeName + if (registeredExtensions.containsKey(extensionName)) { + completion(EventHubError.duplicateExtensionName) + return@submit + } + + val executor = Executors.newSingleThreadExecutor() + val container = ExtensionContainer(extensionClass, executor, completion) + registeredExtensions[extensionName] = container + } + } + + /** + * Unregisters the extension from the `EventHub` if registered + * @property extensionClass The class of extension to unregister + * @property completion Invoked when the extension has been unregistered or failed to unregister + */ + fun unregisterExtension(extensionClass: Class?, completion: ((error: EventHubError) -> Unit)) { + eventHubExecutor.submit { + val extensionName = extensionClass?.extensionTypeName + val container = registeredExtensions.remove(extensionName) + if (container != null) { + container?.shutdown() + shareEventHubSharedState() + completion(EventHubError.none) + } else { + completion(EventHubError.extensionNotRegistered) + } + } + } + + /** + * Stops processing events and shuts down all registered extensions. + */ + fun shutdown() { + // Todo : Stop event processing + + // Shutdown and clear all the extensions. + eventHubExecutor.submit { + // Unregister all extensions + registeredExtensions.forEach { (_, extensionContainer) -> + extensionContainer.shutdown() + } + registeredExtensions.clear() + } + eventHubExecutor.shutdown() + } + + private fun shareEventHubSharedState() { + if (!hubStarted) return + // Update shared state with registered extensions + } +} + +/// Helper to get extension type name +private val Class.extensionTypeName + get() = this.name diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubConstants.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubConstants.kt new file mode 100644 index 000000000..394010ba2 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubConstants.kt @@ -0,0 +1,16 @@ +package com.adobe.marketing.mobile.internal.eventhub + +internal object EventHubConstants { + const val NAME = "com.adobe.module.eventhub" + const val FRIENDLY_NAME = "EventHub" + const val VERSION_NUMBER = "2.0.0" + + object EventDataKeys { + const val VERSION = "version" + const val EXTENSIONS = "extensions" + const val WRAPPER = "wrapper" + const val TYPE = "type" + const val METADATA = "metadata" + const val FRIENDLY_NAME = "friendlyName" + } +} \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubError.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubError.kt new file mode 100644 index 000000000..5a87fe607 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubError.kt @@ -0,0 +1,10 @@ +package com.adobe.marketing.mobile.internal.eventhub + +internal enum class EventHubError { + invalidExtensionName, + duplicateExtensionName, + extensionInitializationFailure, + extensionNotRegistered, + unknown, + none +} \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubPlaceholderExtension.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubPlaceholderExtension.kt new file mode 100644 index 000000000..8dac986a2 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubPlaceholderExtension.kt @@ -0,0 +1,15 @@ +package com.adobe.marketing.mobile.internal.eventhub + +import com.adobe.marketing.mobile.Extension +import com.adobe.marketing.mobile.ExtensionApi + +/** + * An `Extension` for `EventHub`. This serves no purpose other than to allow `EventHub` to store share state and manage event listeners. + */ + +internal class EventHubPlaceholderExtension(val extensionApi: ExtensionApi): Extension(extensionApi) { + override fun getName() = EventHubConstants.NAME + override fun getFriendlyName() = EventHubConstants.FRIENDLY_NAME + override fun getVersion() = EventHubConstants.VERSION_NUMBER +} + diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt new file mode 100644 index 000000000..9d5e77824 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt @@ -0,0 +1,129 @@ +package com.adobe.marketing.mobile.internal.eventhub + +import com.adobe.marketing.mobile.* +import java.util.concurrent.ExecutorService + +internal class ExtensionRuntime(): ExtensionApi() { + var extension: Extension? = null + set(value) { + field = value + extensionName = value?.name + extensionFriendlyName = value?.friendlyName + extensionVersion = value?.version + } + + // Fetch these values on initialization + var extensionName: String? = null + private set + var extensionFriendlyName: String? = null + private set + var extensionVersion: String? = null + private set + + override fun registerEventListener( + eventType: String?, + eventSource: String?, + extensionListenerClass: Class?, + errorCallback: ExtensionErrorCallback? + ): Boolean { + return false + } + + override fun registerWildcardListener( + extensionListenerClass: Class?, + errorCallback: ExtensionErrorCallback? + ): Boolean { + return false + } + + override fun setSharedEventState( + state: MutableMap?, + event: Event?, + errorCallback: ExtensionErrorCallback? + ): Boolean { + TODO("Not yet implemented") + } + + override fun setXDMSharedEventState( + state: MutableMap?, + event: Event?, + errorCallback: ExtensionErrorCallback? + ): Boolean { + TODO("Not yet implemented") + } + + override fun clearSharedEventStates(errorCallback: ExtensionErrorCallback?): Boolean { + TODO("Not yet implemented") + } + + override fun clearXDMSharedEventStates(errorCallback: ExtensionErrorCallback?): Boolean { + TODO("Not yet implemented") + } + + override fun getSharedEventState( + stateName: String?, + event: Event?, + errorCallback: ExtensionErrorCallback? + ): MutableMap { + TODO("Not yet implemented") + } + + override fun getXDMSharedEventState( + stateName: String?, + event: Event?, + errorCallback: ExtensionErrorCallback? + ): MutableMap { + TODO("Not yet implemented") + } + + override fun unregisterExtension() { + if (extension == null) { + return + } + EventHub.shared.unregisterExtension(extension?.javaClass) {} + } +} + +internal class ExtensionContainer constructor( + private val extensionClass: Class, + private val taskExecutor: ExecutorService, + callback: (EventHubError) -> Unit +) { + + private val extensionRuntime = ExtensionRuntime() + + val sharedStateName: String? + get() = extensionRuntime.extensionName + + val friendlyName: String? + get() = extensionRuntime.extensionFriendlyName + + val version: String? + get() = extensionRuntime.extensionVersion + + init { + taskExecutor.submit { + val extension = extensionClass.initWith(extensionRuntime) + if (extension == null) { + callback(EventHubError.extensionInitializationFailure) + return@submit + } + + if (extension.name == null) { + callback(EventHubError.invalidExtensionName) + return@submit + } + + // We set this circular reference because ExtensionApi exposes an API to get the underlying extension. + extensionRuntime.extension = extension + callback(EventHubError.none) + } + } + + fun shutdown() { + taskExecutor.run { + extensionRuntime.extension?.onUnregistered() + } + taskExecutor.shutdown() + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/Extensions.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/Extensions.kt new file mode 100644 index 000000000..15098d9e0 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/Extensions.kt @@ -0,0 +1,52 @@ +package com.adobe.marketing.mobile.internal.eventhub + +import com.adobe.marketing.mobile.* +import java.lang.Exception + +/// Type extensions for [Extension] to allow for easier usage + +/// Function to initialize Extension with [ExtensionApi] +internal fun Class.initWith(extensionApi: ExtensionApi): Extension? { + try { + val extensionConstructor = this.getDeclaredConstructor(ExtensionApi::class.java) + extensionConstructor.setAccessible(true) + return extensionConstructor.newInstance(extensionApi) + } catch(ex: Exception) { + MobileCore.log(LoggingMode.DEBUG,"Extension", "Initializing Extension $this failed with $ex") + } + + return null +} + +/// Property to get Extension name +internal val Extension.name: String? + get() = ExtensionHelper.getName(this) + +/// Property to get Extension version +internal val Extension.version: String? + get() = ExtensionHelper.getVersion(this) + +/// Property to get Extension friendly name +internal val Extension.friendlyName: String? + get() = ExtensionHelper.getFriendlyName(this) + +/// Function to notify that the Extension has been unregistered +internal fun Extension.onUnregistered() { + ExtensionHelper.onUnregistered(this) +} + +/// Type extensions for [ExtensionListener] to allow for easier usage + +/// Function to initialize ExtensionListener with [ExtensionApi], type and source. +internal fun Class.initWith(extensionApi: ExtensionApi, type: String, source: String): ExtensionListener? { + try { + val extensionListenerConstructor = this.getDeclaredConstructor(ExtensionApi::class.java, String::class.java, String::class.java) + extensionListenerConstructor.setAccessible(true) + return extensionListenerConstructor.newInstance(extensionApi, type, source) + } catch (ex: Exception) { + MobileCore.log(LoggingMode.DEBUG,"Extension", "Initializing Extension $this failed with $ex") + } + + return null +} + diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/EventHubTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/EventHubTest.java index 527c792c4..fb240c380 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/EventHubTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/EventHubTest.java @@ -1,2041 +1,2041 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import org.junit.After; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -import static org.junit.Assert.*; -import static org.junit.Assert.assertEquals; - -public class EventHubTest { - private static int EVENTHUB_WAIT_MS = 50; - - - private static FakePlatformServices services = new FakePlatformServices(); - private static CountDownLatch latch = new CountDownLatch(1); - - @After - public void tearDown() { - TestExtension.extensionName = "test extension"; - TestModuleNoListeners.moduleName = "TestModule"; - - } - - @Test - public void eventCounting() throws Exception { - final EventHub hub = new EventHub("Event Count Test Hub", services); - final CountDownLatch initializationLatch = new CountDownLatch(1); - - hub.finishModulesRegistration(new AdobeCallback() { - @Override - public void call(Void value) { - initializationLatch.countDown(); - } - }); - - initializationLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - - for (int i = 2; i < 101; i++) { // boot event is always #0, shared state is #1 so we count from 2 on. - final Event newEvent = new Event.Builder("Test Event", EventType.CUSTOM, EventSource.NONE).build(); - hub.dispatch(newEvent); - assertEquals("eventNumber incorrect for event", i, newEvent.getEventNumber()); - } - } - - private static final Semaphore circularModuleSemaphore = new Semaphore(0, true); - - public static class CircularModule1 extends Module { - public CircularModule1(final EventHub hub) { - super("CircularModule1", hub); - registerListener(EventType.ANALYTICS, EventSource.NONE, EndListener.class); - registerListener(EventType.CUSTOM, EventSource.NONE, StateListener.class); - } - - - public static class StateListener extends ModuleEventListener { - public StateListener(final CircularModule1 m, final EventType type, final EventSource source) { - super(m, type, source); - } - - public void hear(final Event e) { - parentModule.getSharedEventState("CircularModule2", e); - } - - } - - public static class EndListener extends ModuleEventListener { - public EndListener(final CircularModule1 m, final EventType type, final EventSource source) { - super(m, type, source); - } - - public void hear(final Event e) { - circularModuleSemaphore.release(); - } - } - } - - public static class CircularModule2 extends Module { - public CircularModule2(final EventHub hub) { - super("CircularModule2", hub); - registerListener(EventType.CUSTOM, EventSource.NONE, StateListener.class); - } - - public static class StateListener extends ModuleEventListener { - public StateListener(final CircularModule2 m, final EventType type, final EventSource source) { - super(m, type, source); - } - - public void hear(final Event e) { - parentModule.getSharedEventState("CircularModule1", e); - } - } - } - - @Test(timeout = 1000) - public void circularDependency() throws Exception { - Log.setLogLevel(LoggingMode.DEBUG); - Log.setLoggingService(services.fakeLoggingService); - final EventHub hub = new EventHub("Circular Dependency Test", services); - hub.registerModule(CircularModule1.class); - hub.registerModule(CircularModule2.class); - - // wait for modules to register - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - - final Event sharedStateReadEvent = new Event.Builder("Test Event", EventType.CUSTOM, EventSource.NONE).build(); - final Event doneEvent = new Event.Builder("Done Event", EventType.ANALYTICS, EventSource.NONE).build(); - hub.dispatch(sharedStateReadEvent); - hub.dispatch(doneEvent); - hub.finishModulesRegistration(null); - - circularModuleSemaphore.tryAcquire(1000, TimeUnit.MILLISECONDS); - - // two potential outcomes for the live-lock log depending on the speed of which the listeners run. - final boolean potentialErrorLog1 = services.fakeLoggingService.containsWarningLog("EventHub(Circular Dependency " + - "Test)", - "Circular shared-state dependency between CircularModule2 and CircularModule1, you may have a " + - "live-lock."); - final boolean potentialErrorLog2 = services.fakeLoggingService.containsWarningLog("EventHub(Circular Dependency " + - "Test)", - "Circular shared-state dependency between CircularModule1 and CircularModule2, you may have a " + - "live-lock."); - - // check for either outcome - assertTrue(potentialErrorLog1 || potentialErrorLog2); - } - - private static final Semaphore longRunningListenerModuleSemaphore = new Semaphore(0, true); - - public static class LongRunningListenerModule extends Module { - public LongRunningListenerModule(final EventHub hub) { - super("LongRunningListenerModule", hub); - registerListener(EventType.ANALYTICS, EventSource.NONE, LongRunningListener.class); - registerListener(EventType.CUSTOM, EventSource.NONE, EndListener.class); - } - - public static class LongRunningListener extends ModuleEventListener { - public LongRunningListener(final LongRunningListenerModule m, final EventType type, final EventSource - source) { - super(m, type, source); - } - - public void hear(final Event e) { - try { - longRunningListenerModuleSemaphore.tryAcquire(1500, TimeUnit.MILLISECONDS); - } catch (final Exception ignored) { - } - } - } - - public static class EndListener extends ModuleEventListener { - public EndListener(final LongRunningListenerModule m, final EventType type, final EventSource source) { - super(m, type, source); - } - - public void hear(final Event e) { - longRunningListenerModuleSemaphore.release(); - } - } - } - - @Test(timeout = 2000) - public void listenerTimeout() throws Exception { - Log.setLogLevel(LoggingMode.DEBUG); - Log.setLoggingService(services.fakeLoggingService); - final EventHub hub = new EventHub("Listener Timeout Test", services); - hub.registerModule(LongRunningListenerModule.class); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - hub.finishModulesRegistration(null); - final Event longRunningEvent = new Event.Builder("Heard By Long Listener", EventType.ANALYTICS, - EventSource.NONE).build(); - final Event endEvent = new Event.Builder("Test Event", EventType.CUSTOM, EventSource.NONE).build(); - - hub.dispatch(longRunningEvent); - hub.dispatch(endEvent); - - longRunningListenerModuleSemaphore.tryAcquire(1500, TimeUnit.MILLISECONDS); - assertTrue(services.fakeLoggingService.containsErrorLog("EventBus(EventHub)", - "Listener com.adobe.marketing.mobile.EventHubTest$LongRunningListenerModule$LongRunningListener " + - "exceeded runtime limit of 1000 milliseconds (java.util.concurrent.TimeoutException)")); - - } - - private final static EventData sharedState1 = new EventData(); - private final static EventData sharedState2 = new EventData(); - private final static String SharedStateFirstLastModuleName = "SharedStateFirstLast"; - - public static class SharedStateFirstLastModule extends Module { - SharedStateFirstLastModule(final EventHub hub) { - super(SharedStateFirstLastModuleName, hub); - createSharedState(500, sharedState1); - - for (int i = 500; i < 1000; i += 15) { - createSharedState(i, new EventData()); - } - - createSharedState(3500, sharedState2); - } - - } - - @Test(timeout = 1000) - public void staticFirstLastEvents() throws Exception { - final EventHub hub = new EventHub("First Last Events Test", services); - hub.registerModule(SharedStateFirstLastModule.class); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - - Module module = new SharedStateFirstLastModule(hub); - EventData oldest = hub.getSharedEventState(SharedStateFirstLastModuleName, Event.SHARED_STATE_OLDEST, module); - EventData newest = hub.getSharedEventState(SharedStateFirstLastModuleName, Event.SHARED_STATE_NEWEST, module); - - assertEquals("Oldest shared state should equal 'sharedState1'", sharedState1, oldest); - assertEquals("Newest shared state should equal 'sharedState2'", sharedState2, newest); - } - - @Test(expected = IllegalArgumentException.class) - public void getSharedEventState_NullStateName() { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.getSharedEventState(null, - new Event.Builder(null, (EventType)null, null).build(), - new Module("testmodule", eventHub) { - }); - } - - @Test - public void getSharedEventState_ReturnsLatest_OnNullEvent() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - Module testModule = new Module("testStateName", eventHub) {}; - - Event event1 = new Event.Builder("EventHubTest", EventType.CUSTOM, EventSource.NONE).setEventNumber(1).build(); - Event event2 = new Event.Builder("EventHubTest", EventType.CUSTOM, EventSource.NONE).setEventNumber(2).build(); - - - EventData state1 = new EventData(); - state1.putString("key", "value1"); - EventData state2 = new EventData(); - state2.putString("key", "value2"); - - eventHub.createSharedState(testModule, event1.getEventNumber(), state1); - eventHub.createSharedState(testModule, event2.getEventNumber(), state2); - - assertEquals(state1, eventHub.getSharedEventState(testModule.getModuleName(), event1, testModule)); - assertEquals(state2, eventHub.getSharedEventState(testModule.getModuleName(), event2, testModule)); - assertEquals(state2, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule)); - } - - @Test - public void getXDMSharedEventState_ReturnsLatest_OnNullEvent() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - Module testModule = new Module("testStateName", eventHub) {}; - - Event event1 = new Event.Builder("EventHubTest", EventType.CUSTOM, EventSource.NONE).setEventNumber(1).build(); - Event event2 = new Event.Builder("EventHubTest", EventType.CUSTOM, EventSource.NONE).setEventNumber(2).build(); - - - EventData state1 = new EventData(); - state1.putString("key", "value1"); - EventData state2 = new EventData(); - state2.putString("key", "value2"); - - eventHub.createSharedState(testModule, event1.getEventNumber(), state1, SharedStateType.XDM); - eventHub.createSharedState(testModule, event2.getEventNumber(), state2, SharedStateType.XDM); - - assertEquals(state1, eventHub.getSharedEventState(testModule.getModuleName(), event1, testModule, SharedStateType.XDM)); - assertEquals(state2, eventHub.getSharedEventState(testModule.getModuleName(), event2, testModule, SharedStateType.XDM)); - assertEquals(state2, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule, SharedStateType.XDM)); - } - - @Test(expected = IllegalArgumentException.class) - public void hasSharedEventState_NullStateName() { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.hasSharedEventState(null); - } - - @Test(expected = InvalidModuleException.class) - public void unregisterModuleListener_NullModule() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.unregisterModuleListener(null, null, null); - } - - @Test - public void unregisterModuleListener_NullType() throws Exception { - Log.setLogLevel(LoggingMode.VERBOSE); - Log.setLoggingService(services.fakeLoggingService); - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerModule(TestModule.class); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - Module testModule = eventHub.getActiveModules().iterator().next(); - assertNotNull(testModule); - eventHub.unregisterModuleListener(testModule, null, EventSource.BOOTED); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(services.fakeLoggingService.containsDebugLog("EventHub(eventhub)", - "Failed to unregister listener (no registered listener)")); - } - - @Test - public void unregisterModuleListener_NullSource() throws Exception { - Log.setLogLevel(LoggingMode.VERBOSE); - Log.setLoggingService(services.fakeLoggingService); - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerModule(TestModule.class); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - Module testModule = eventHub.getActiveModules().iterator().next(); - assertNotNull(testModule); - eventHub.unregisterModuleListener(testModule, EventType.HUB, null); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(services.fakeLoggingService.containsDebugLog("EventHub(eventhub)", - "Failed to unregister listener (no registered listener)")); - } - - @Test - public void unregisterModuleListener_NoRegisteredListeners() throws Exception { - Log.setLogLevel(LoggingMode.VERBOSE); - Log.setLoggingService(services.fakeLoggingService); - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerModule(TestModule.class); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - Module testModule = eventHub.getActiveModules().iterator().next(); - assertNotNull(testModule); - eventHub.unregisterModuleListener(testModule, EventType.HUB, EventSource.BOOTED); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(services.fakeLoggingService.containsDebugLog("EventHub(eventhub)", - "Failed to unregister listener (no registered listener)")); - } - - @SuppressWarnings("all") - static class TestListener extends ModuleEventListener { - static boolean onUnregisteredWasCalled = false; - static boolean hearWasCalled = false; - static List eventsListened = new ArrayList(); - protected TestListener(final Module module, final EventType type, final EventSource source) { - super(module, type, source); - } - - @Override - public void hear(final Event e) { - hearWasCalled = true; - eventsListened.add(e); - } - @Override - public void onUnregistered() { - onUnregisteredWasCalled = true; - } - } - - @SuppressWarnings("all") - static class TestExtensionListener extends ExtensionListener { - static boolean onUnregisteredWasCalled = false; - protected TestExtensionListener(final ExtensionApi module, final String type, final String source) { - super(module, type, source); - } - - @Override - public void hear(final Event e) { - } - @Override - public void onUnregistered() { - onUnregisteredWasCalled = true; - } - } - - @SuppressWarnings("all") - static class TestModule extends Module { - static boolean onUnregisteredWasCalled = false; - TestModule(final EventHub hub, - boolean registerTestListener, - boolean registerTestProcessor) { - super("TestModule", hub); - - if (registerTestListener) { - registerListener(EventType.CUSTOM, - EventSource.NONE, - TestListener.class); - } - - } - - TestModule(final EventHub hub) { - super("TestModule", hub); - registerListener(EventType.CUSTOM, EventSource.NONE, TestListener.class); - - } - - @Override - protected void onUnregistered() { - onUnregisteredWasCalled = true; - } - } - - static class TestExtension extends Extension { - static boolean onUnregisteredWasCalled = false; - static boolean onUnexpectedErrorWasCalled = false; - static ExtensionUnexpectedError onUnexpectedErrorParamError; - static String extensionName = "test extension"; - static String friendlyName = "test extension friendly name"; - - protected TestExtension(final ExtensionApi extensionApi) { - super(extensionApi); - getApi().registerEventListener(EventType.ANALYTICS.getName(), EventSource.REQUEST_CONTENT.getName(), - TestExtensionListener.class, null); - onUnregisteredWasCalled = false; - onUnexpectedErrorWasCalled = false; - onUnexpectedErrorParamError = null; - } - - protected String getName() { - return extensionName; - } - - @Override - protected String getFriendlyName() { - return friendlyName; - } - - protected String getVersion() { - return "1.1.0"; - } - - protected void onUnregistered() { - onUnregisteredWasCalled = true; - } - - protected void onUnexpectedError(final ExtensionUnexpectedError extensionUnexpectedError) { - onUnexpectedErrorWasCalled = true; - onUnexpectedErrorParamError = extensionUnexpectedError; - } - } - - static class TestModuleNoProcessors extends Module { - TestModuleNoProcessors(final EventHub hub) { - super("TestModule", hub); - registerListener(EventType.CUSTOM, EventSource.NONE, TestListener.class); - } - } - - static class TestModuleNoListeners extends Module { - static String moduleName = "TestModule"; - TestModuleNoListeners(final EventHub hub) { - super(moduleName, hub); - registerListener(EventType.CUSTOM, EventSource.NONE, TestListener.class); - } - } - - @Test(expected = InvalidModuleException.class) - public void unregisterModule_NullModule() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.unregisterModule(null); - } - - @Test - public void unregisterModule_NoRegisteredListeners() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerModule(TestModuleNoListeners.class); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - Module testModule = eventHub.getActiveModules().iterator().next(); - assertNotNull(testModule); - eventHub.unregisterModule(testModule); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertEquals(0, eventHub.getActiveModules().size()); - } - - @Test - public void unregisterModule_NoRegisteredProcessors() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerModule(TestModuleNoProcessors.class); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - Module testModule = eventHub.getActiveModules().iterator().next(); - assertNotNull(testModule); - eventHub.unregisterModule(testModule); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertEquals(0, eventHub.getActiveModules().size()); - } - - @Test - public void unregisterModule_ModuleNotRegisteredWithHub() throws Exception { - Log.setLoggingService(services.fakeLoggingService); - Log.setLogLevel(LoggingMode.VERBOSE); - EventHub eventHub = new EventHub("eventhub", services); - Module testModule = new TestModule(eventHub, true, true); - eventHub.unregisterModule(testModule); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(services.fakeLoggingService.containsErrorLog("EventHub(eventhub)", - "Failed to unregister module, Module (TestModule) is not registered")); - } - - @Test - public void unregisterModule_Happy() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerModule(TestModule.class); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertEquals(1, eventHub.getActiveModules().size()); - Module testModule = eventHub.getActiveModules().iterator().next(); - assertNotNull(testModule); - eventHub.unregisterModule(testModule); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertEquals(0, eventHub.getActiveModules().size()); - } - - @Test - public void unregisterModule_extensionCase_Happy() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerExtension(TestExtension.class); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertEquals(1, eventHub.getActiveModules().size()); - Module testExtension = eventHub.getActiveModules().iterator().next(); - assertNotNull(testExtension); - eventHub.unregisterModule(testExtension); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertEquals(0, eventHub.getActiveModules().size()); - assertTrue(TestExtension.onUnregisteredWasCalled); - } - - @Test - public void unregisterExtension_Listener_Happy() throws Exception { - TestExtension.onUnregisteredWasCalled = false; - TestExtensionListener.onUnregisteredWasCalled = false; - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerExtension(TestExtension.class); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertEquals(1, eventHub.getActiveModules().size()); - Module testExtension = eventHub.getActiveModules().iterator().next(); - assertNotNull(testExtension); - eventHub.registerModuleListener(testExtension, EventType.CUSTOM, EventSource.NONE, "pairId", - TestExtensionListener.class); - eventHub.unregisterModule(testExtension); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertEquals(0, eventHub.getActiveModules().size()); - - assertTrue(TestExtension.onUnregisteredWasCalled); - assertTrue(TestExtensionListener.onUnregisteredWasCalled); - - } - @Test - public void unregisterModule_Listener_Happy() throws Exception { - TestModule.onUnregisteredWasCalled = false; - TestListener.onUnregisteredWasCalled = false; - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerModule(TestModule.class); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertEquals(1, eventHub.getActiveModules().size()); - Module testModule = eventHub.getActiveModules().iterator().next(); - assertNotNull(testModule); - eventHub.registerModuleListener(testModule, EventType.CUSTOM, EventSource.NONE, "pairId", - TestListener.class); - eventHub.unregisterModule(testModule); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertEquals(0, eventHub.getActiveModules().size()); - - assertTrue(TestModule.onUnregisteredWasCalled); - assertTrue(TestListener.onUnregisteredWasCalled); - - } - - @Test - public void registerModuleListener_Happy() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerModule(TestModule.class); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - Module testModule = eventHub.getActiveModules().iterator().next(); - assertNotNull(testModule); - eventHub.registerModuleListener(testModule, EventType.CUSTOM, EventSource.NONE, "pairId", TestListener.class); - } - - @Test(expected = InvalidModuleException.class) - public void registerModuleListener_NullModule() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerModuleListener(null, EventType.CUSTOM, EventSource.NONE, "pairId", TestListener.class); - } - - @Test - public void registerModuleListener_NullListener() throws Exception { - Log.setLogLevel(LoggingMode.VERBOSE); - Log.setLoggingService(services.fakeLoggingService); - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerModule(TestModule.class); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - Module testModule = eventHub.getActiveModules().iterator().next(); - assertNotNull(testModule); - eventHub.registerModuleListener(testModule, EventType.CUSTOM, EventSource.NONE, "pairId", null); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(services.fakeLoggingService.containsDebugLog("EventHub(eventhub)", - "Unexpected Null Value (listenerClass, type or source), failed to register listener")); - } - - @Test - public void registerModuleListener_ExtensionListener_Happy() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerExtension(TestExtension.class); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - Module testExtension = eventHub.getActiveModules().iterator().next(); - assertNotNull(testExtension); - - eventHub.registerModuleListener(testExtension, EventType.TARGET, EventSource.RESPONSE_CONTENT, null, - TestExtensionListener.class); - - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - ConcurrentLinkedQueue listeners = eventHub.getModuleListeners(testExtension); - assertEquals(2, listeners.size()); - EventListener listener1 = listeners.poll(); - EventListener listener2 = listeners.poll(); - assertEquals(EventSource.REQUEST_CONTENT, listener1.getEventSource()); - assertEquals(EventType.ANALYTICS, listener1.getEventType()); - assertEquals(EventSource.RESPONSE_CONTENT, listener2.getEventSource()); - assertEquals(EventType.TARGET, listener2.getEventType()); - } - - @Test(expected = InvalidModuleException.class) - public void createSharedState_NullModule() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.createSharedState(null, 0, null); - } - - @Test(expected = InvalidModuleException.class) - public void createXDMSharedState_NullModule() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.createSharedState(null, 0, null, SharedStateType.XDM); - } - - @Test(expected = InvalidModuleException.class) - public void createSharedState_NullStateName() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.createSharedState(new Module(null, eventHub) { - }, 0, null); - } - - @Test(expected = InvalidModuleException.class) - public void createXDMSharedState_NullStateName() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.createSharedState(new Module(null, eventHub) { - }, 0, null, SharedStateType.XDM); - } - - @Test(expected = InvalidModuleException.class) - public void updateSharedState_NullModule() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.updateSharedState(null, 0, null); - } - - @Test(expected = InvalidModuleException.class) - public void updateXDMSharedState_NullModule() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.updateSharedState(null, 0, null, SharedStateType.XDM); - } - - @Test(expected = InvalidModuleException.class) - public void updateSharedState_NullStateName() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.updateSharedState(new Module(null, eventHub) { - }, 0, null); - } - - @Test(expected = InvalidModuleException.class) - public void updateXDMSharedState_NullStateName() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.updateSharedState(new Module(null, eventHub) { - }, 0, null, SharedStateType.XDM); - } - - @Test(expected = InvalidModuleException.class) - public void createOrUpdateWithVersionSharedState_NullModule() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.createOrUpdateSharedState(null, 0, null); - } - - @Test(expected = InvalidModuleException.class) - public void createOrUpdateWithVersionXDMSharedState_NullModule() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.createOrUpdateSharedState(null, 0, null, SharedStateType.XDM); - } - - @Test(expected = InvalidModuleException.class) - public void createOrUpdateWithVersionSharedState_NullStateName() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.createOrUpdateSharedState(new Module(null, eventHub) { - }, 0, null); - } - - @Test(expected = InvalidModuleException.class) - public void createOrUpdateWithVersionXDMSharedState_NullStateName() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.createOrUpdateSharedState(new Module(null, eventHub) { - }, 0, null, SharedStateType.XDM); - } - - @Test(expected = InvalidModuleException.class) - public void createOrUpdateSharedState_NullModule() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.createOrUpdateSharedState(null, null); - } - - @Test(expected = InvalidModuleException.class) - public void createOrUpdateXDMSharedState_NullModule() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.createOrUpdateSharedState(null, null, SharedStateType.XDM); - } - - @Test(expected = InvalidModuleException.class) - public void createOrUpdateSharedState_NullStateName() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.createOrUpdateSharedState(new Module(null, eventHub) { - }, null); - } - - @Test(expected = InvalidModuleException.class) - public void createOrUpdateXDMSharedState_NullStateName() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.createOrUpdateSharedState(new Module(null, eventHub) { - }, null, SharedStateType.XDM); - } - - @Test - public void createOrUpdateWithVersionSharedState_Happy() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - Module testModule = new Module("testStateName", eventHub) {}; - - EventData state1 = new EventData(); - state1.putString("key", "value1"); - EventData state2 = new EventData(); - state2.putString("key", "value2"); - - eventHub.createOrUpdateSharedState(testModule, 1, state1); - assertEquals(state1, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule)); - - eventHub.createOrUpdateSharedState(testModule, 2, EventHub.SHARED_STATE_PENDING); - assertEquals(EventHub.SHARED_STATE_PENDING, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule)); - - eventHub.createOrUpdateSharedState(testModule, 2, state2); - assertEquals(state2, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule)); - - } - - @Test - public void createOrUpdateWithVersionXDMSharedState_Happy() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - Module testModule = new Module("testStateName", eventHub) {}; - - EventData state1 = new EventData(); - state1.putString("key", "value1"); - EventData state2 = new EventData(); - state2.putString("key", "value2"); - - eventHub.createOrUpdateSharedState(testModule, 1, state1, SharedStateType.XDM); - assertEquals(state1, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule, SharedStateType.XDM)); - - eventHub.createOrUpdateSharedState(testModule, 2, EventHub.SHARED_STATE_PENDING, SharedStateType.XDM); - assertEquals(EventHub.SHARED_STATE_PENDING, eventHub.getSharedEventState(testModule.getModuleName(), null, - testModule, SharedStateType.XDM)); - - eventHub.createOrUpdateSharedState(testModule, 2, state2, SharedStateType.XDM); - assertEquals(state2, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule, SharedStateType.XDM)); - } - - @Test - public void createOrUpdateWithVersionSharedState_AndXDMSharedState_Happy() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - Module testModule = new Module("testStateName", eventHub) {}; - - EventData state1 = new EventData(); - state1.putString("key", "value1"); - EventData state2 = new EventData(); - state2.putString("key", "value2"); - - eventHub.createOrUpdateSharedState(testModule, 1, state1); - assertEquals(state1, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule)); - - eventHub.createOrUpdateSharedState(testModule, 2, EventHub.SHARED_STATE_PENDING); - assertEquals(EventHub.SHARED_STATE_PENDING, eventHub.getSharedEventState(testModule.getModuleName(), null, - testModule)); - - eventHub.createOrUpdateSharedState(testModule, 2, state2); - assertEquals(state2, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule)); - - eventHub.createOrUpdateSharedState(testModule, 1, state1, SharedStateType.XDM); - assertEquals(state1, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule, SharedStateType.XDM)); - - eventHub.createOrUpdateSharedState(testModule, 2, EventHub.SHARED_STATE_PENDING, SharedStateType.XDM); - assertEquals(EventHub.SHARED_STATE_PENDING, eventHub.getSharedEventState(testModule.getModuleName(), null, - testModule, SharedStateType.XDM)); - - eventHub.createOrUpdateSharedState(testModule, 2, state2, SharedStateType.XDM); - assertEquals(state2, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule, SharedStateType.XDM)); - } - - static List EVENT_LIST = new ArrayList(); - - public static class TestableModule extends InternalModule { - public TestableModule(final EventHub hub, final PlatformServices services) { - super("TestableModule", hub, services); - this.registerWildcardListener(EndListener.class); - } - - public static class EndListener extends ModuleEventListener { - public EndListener(final TestableModule module, final EventType type, final EventSource source) { - super(module, type, source); - } - - public void hear(final Event e) { - EVENT_LIST.add(e); - } - } - } - - private boolean onEventReprocessingCompleteGetCalled = false; - private boolean rulesFlag = false; - @Test(timeout = 200) - public void replaceRulesAndEvaluateEvents_happy() throws Exception { - EVENT_LIST.clear(); - List rules = new ArrayList(); - - List consequenceEvents = new ArrayList(); - EventData eventData = new EventData(); - - Map consequenceMap = new HashMap(); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_JSON_ID, Variant.fromString("analyticsId")); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_JSON_TYPE, - Variant.fromString(RulesEngineConstantsTests.ConsequenceTypes.SEND_DATA_TO_ANALYTICS)); - Map detailMap = new HashMap(); - detailMap.put("key", "value"); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL, Variant.fromStringMap(detailMap)); - eventData.putVariantMap(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_TRIGGERED, consequenceMap); - - consequenceEvents.add(new Event.Builder("Test1", EventType.ANALYTICS, - EventSource.REQUEST_CONTENT).setData(eventData).build()); - consequenceEvents.add(new Event.Builder("Test2", EventType.ANALYTICS, - EventSource.REQUEST_CONTENT).setData(eventData).build()); - consequenceEvents.add(new Event.Builder("Test3", EventType.ANALYTICS, - EventSource.REQUEST_CONTENT).setData(eventData).build()); - rules.add(new Rule(new RuleCondition() { - @Override - protected boolean evaluate(final RuleTokenParser tokenParser, final Event event) { - //Make it evaluate to true - return rulesFlag; - } - @Override - public String toString() { - return null; - } - }, consequenceEvents)); - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerModule(TestableModule.class); - final CountDownLatch waitForBootLatch = new CountDownLatch(1); - eventHub.finishModulesRegistration(new AdobeCallback() { - @Override - public void call(Void value) { - waitForBootLatch.countDown(); - } - }); - waitForBootLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - Module activeModule = eventHub.getActiveModules().iterator().next(); - - eventHub.replaceRulesAndEvaluateEvents(activeModule, rules, new ReprocessEventsHandler() { - @Override - public List getEvents() { - rulesFlag = true; - List list = new ArrayList(); - list.add(new Event.Builder("Test", EventType.HUB, EventSource.NONE).build()); - return list; - } - @Override - public void onEventReprocessingComplete() { - onEventReprocessingCompleteGetCalled = true; - rulesFlag = false; - } - }); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(onEventReprocessingCompleteGetCalled); - assertEquals(5, EVENT_LIST.size()); - } - - @Test - public void createOrUpdateSharedState_WithPendingStatus() throws Exception { - EVENT_LIST.clear(); - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerModule(TestableModule.class); - - final CountDownLatch waitForBootLatch = new CountDownLatch(1); - eventHub.finishModulesRegistration(new AdobeCallback() { - @Override - public void call(Void value) { - waitForBootLatch.countDown(); - } - }); - - waitForBootLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - Module module = eventHub.getActiveModules().iterator().next(); - - EventData state1 = new EventData(); - state1.putString("key", "value1"); - EventData state2 = new EventData(); - state2.putString("key", "value2"); - - eventHub.createOrUpdateSharedState(module, 1, state1); - assertEquals(state1, eventHub.getSharedEventState(module.getModuleName(), null, module)); - - eventHub.createOrUpdateSharedState(module, 2, EventHub.SHARED_STATE_INVALID); - eventHub.createOrUpdateSharedState(module, 3, EventHub.SHARED_STATE_PENDING); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - - assertEquals(4, - EVENT_LIST.size()); // 4 events, 1 boot event, 1 initial shared state event, and two shared state updates for module. - assertEquals(EventHub.SHARED_STATE_PENDING, eventHub.getSharedEventState(module.getModuleName(), null, module)); - } - - @Test - public void createOrUpdateXDMSharedState_WithPendingStatus() throws Exception { - EVENT_LIST.clear(); - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerModule(TestableModule.class); - - final CountDownLatch waitForBootLatch = new CountDownLatch(1); - eventHub.finishModulesRegistration(new AdobeCallback() { - @Override - public void call(Void value) { - waitForBootLatch.countDown(); - } - }); - - waitForBootLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - Module module = eventHub.getActiveModules().iterator().next(); - - EventData state1 = new EventData(); - state1.putString("key", "value1"); - EventData state2 = new EventData(); - state2.putString("key", "value2"); - - eventHub.createOrUpdateSharedState(module, 1, state1, SharedStateType.XDM); - assertEquals(state1, eventHub.getSharedEventState(module.getModuleName(), null, module, SharedStateType.XDM)); - - eventHub.createOrUpdateSharedState(module, 2, EventHub.SHARED_STATE_INVALID, SharedStateType.XDM); - eventHub.createOrUpdateSharedState(module, 3, EventHub.SHARED_STATE_PENDING, SharedStateType.XDM); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - - assertEquals(4, - EVENT_LIST.size()); // 4 events, 1 boot event, 1 initial XDM shared state event, and two XDM shared state updates for module. - assertEquals(EventHub.SHARED_STATE_PENDING, eventHub.getSharedEventState(module.getModuleName(), null, module, - SharedStateType.XDM)); - } - - @Test - public void createSharedState_DispatchesStateChangeEvent() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - - eventHub.registerModule(TestModule.class); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - - Module testModule = eventHub.getActiveModules().iterator().next(); - assertNotNull(testModule); - EventData state = new EventData(); - state.putString("key", "value1"); - - TestListener.hearWasCalled = false; - eventHub.registerModuleListener(testModule, EventType.HUB, EventSource.SHARED_STATE, null, TestListener.class); - eventHub.finishModulesRegistration(null); - - // test - eventHub.createSharedState(testModule, 0, state); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - - // verify Shared state change event is dispatched - assertTrue(TestListener.hearWasCalled); - Event sharedStateChangeEvent = TestListener.eventsListened.get(TestListener.eventsListened.size() - 1); - - // verify the details in the sharedState change event details - assertEquals("Shared state change", sharedStateChangeEvent.getName()); - // no need to verify for type and source, since the listener only listener to HUB SHARED_STATE - assertEquals("TestModule", sharedStateChangeEvent.getData().getString2("stateowner")); - } - - - @Test - public void createXDMSharedState_DispatchesStateChangeEvent() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - - eventHub.registerModule(TestModule.class); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - - Module testModule = eventHub.getActiveModules().iterator().next(); - assertNotNull(testModule); - EventData state = new EventData(); - state.putString("key", "value1"); - - TestListener.hearWasCalled = false; - eventHub.registerModuleListener(testModule, EventType.HUB, EventSource.SHARED_STATE, null, TestListener.class); - eventHub.finishModulesRegistration(null); - - // test - eventHub.createSharedState(testModule, 0, state, SharedStateType.XDM); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - - // verify Shared state change event is dispatched - assertTrue(TestListener.hearWasCalled); - Event sharedStateChangeEvent = TestListener.eventsListened.get(TestListener.eventsListened.size() - 1); - - // verify the details in the sharedState change event details - assertEquals("Shared state change (XDM)", sharedStateChangeEvent.getName()); - // no need to verify for type and source, since the listener only listener to HUB SHARED_STATE - assertEquals("TestModule", sharedStateChangeEvent.getData().getString2("stateowner")); - } - - @Test - public void createSharedStateAndDispatchEvent_Happy() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - - - eventHub.registerModule(TestModule.class); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - - Module testModule = eventHub.getActiveModules().iterator().next(); - assertNotNull(testModule); - EventData state = new EventData(); - state.putString("key", "value1"); - final EventData eventData = new EventData() - .putString("initialkey", "initialvalue"); - final Event newEvent = new Event.Builder("Test Event", EventType.CUSTOM, EventSource.NONE).setData(eventData) - .build(); - TestListener.hearWasCalled = false; - eventHub.registerModuleListener(testModule, EventType.CUSTOM, EventSource.NONE, null, TestListener.class); - eventHub.createSharedStateAndDispatchEvent(testModule, state, newEvent); - assertEquals(state, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule)); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(TestListener.hearWasCalled); - - } - - @Test - public void createXDMSharedStateAndDispatchEvent_Happy() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - - - eventHub.registerModule(TestModule.class); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - - Module testModule = eventHub.getActiveModules().iterator().next(); - assertNotNull(testModule); - EventData state = new EventData(); - state.putString("key", "value1"); - final EventData eventData = new EventData() - .putString("initialkey", "initialvalue"); - final Event newEvent = new Event.Builder("Test Event", EventType.CUSTOM, EventSource.NONE).setData(eventData) - .build(); - TestListener.hearWasCalled = false; - eventHub.registerModuleListener(testModule, EventType.CUSTOM, EventSource.NONE, null, TestListener.class); - eventHub.createSharedStateAndDispatchEvent(testModule, state, newEvent, SharedStateType.XDM); - assertEquals(state, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule, SharedStateType.XDM)); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(TestListener.hearWasCalled); - - } - - - @Test - public void createOrUpdateWithoutVersionSharedState_Happy() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - Module testModule = new Module("testStateName", eventHub) {}; - - EventData state1 = new EventData(); - state1.putString("key", "value1"); - EventData state2 = new EventData(); - state2.putString("key", "value2"); - - eventHub.createOrUpdateSharedState(testModule, state1); - assertEquals(state1, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule)); - - eventHub.createOrUpdateSharedState(testModule, state2); - assertEquals(state2, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule)); - - } - - @Test - public void createOrUpdateWithoutVersionSharedState_EventNumberIncrementedSequentially() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - Module testModule = new Module("testStateName", eventHub) {}; - - - EventData state1 = new EventData(); - state1.putString("key", "value1"); - EventData state2 = new EventData(); - state2.putString("key", "value2"); - - - Event event1 = new Event.Builder("testName", EventType.CUSTOM, EventSource.NONE).build(); - eventHub.dispatch(event1); - assertEquals(1, event1.getEventNumber()); - - //createOrUpdateSharedState() fires a state change event - eventHub.createOrUpdateSharedState(testModule, state1); - - Event event2 = new Event.Builder("testName", EventType.CUSTOM, EventSource.NONE).build(); - eventHub.dispatch(event2); - assertEquals(3, event2.getEventNumber()); - } - - @Test - public void createOrUpdateWithoutVersionXDMSharedState_Happy() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - Module testModule = new Module("testStateName", eventHub) {}; - - EventData state1 = new EventData(); - state1.putString("key", "value1"); - EventData state2 = new EventData(); - state2.putString("key", "value2"); - - eventHub.createOrUpdateSharedState(testModule, state1, SharedStateType.XDM); - assertEquals(state1, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule, SharedStateType.XDM)); - - eventHub.createOrUpdateSharedState(testModule, state2, SharedStateType.XDM); - assertEquals(state2, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule, SharedStateType.XDM)); - - } - - @Test - public void createOrUpdateWithoutVersionXDMSharedState_EventNumberIncrementedSequentially() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - Module testModule = new Module("testStateName", eventHub) {}; - - EventData state1 = new EventData(); - state1.putString("key", "value1"); - EventData state2 = new EventData(); - state2.putString("key", "value2"); - - Event event1 = new Event.Builder("testName", EventType.CUSTOM, EventSource.NONE).build(); - eventHub.dispatch(event1); - assertEquals(1, event1.getEventNumber()); - - //createOrUpdateSharedState() fires a state change event - eventHub.createOrUpdateSharedState(testModule, state1, SharedStateType.XDM); - - Event event2 = new Event.Builder("testName", EventType.CUSTOM, EventSource.NONE).build(); - eventHub.dispatch(event2); - assertEquals(3, event2.getEventNumber()); - } - - @Test(expected = InvalidModuleException.class) - public void clearSharedStates_NullStateName() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.clearSharedStates(new Module(null, eventHub) {}); - } - - @Test(expected = InvalidModuleException.class) - public void clearXDMSharedStates_NullStateName() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.clearSharedStates(new Module(null, eventHub) {}, SharedStateType.XDM); - } - - @Test(expected = InvalidModuleException.class) - public void clearSharedStates_NullModule() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.clearSharedStates(null); - } - - @Test(expected = InvalidModuleException.class) - public void clearXDMSharedStates_NullModule() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.clearSharedStates(null, SharedStateType.XDM); - } - - @Test - public void clearSharedStates_ValidModuleAndStateName() throws Exception { - // setup - EventHub eventHub = new EventHub("eventhub", services); - Module testModule = new Module("testStateName", eventHub) {}; - EventData data = new EventData(); - data.putString("testing", "clear"); - data.putString("shared", "state"); - Event event = new Event.Builder("testName", EventType.CUSTOM, EventSource.NONE).build(); - eventHub.createSharedState(testModule, 0, data); - assertEquals(data, eventHub.getSharedEventState(testModule.getModuleName(), event, testModule)); - - // test - eventHub.clearSharedStates(testModule); - - // verify - assertNull(eventHub.getSharedEventState(testModule.getModuleName(), event, testModule)); - } - - @Test - public void clearXDMSharedStates_ValidModuleAndStateName() throws Exception { - // setup - EventHub eventHub = new EventHub("eventhub", services); - Module testModule = new Module("testStateName", eventHub) {}; - EventData data = new EventData(); - data.putString("testing", "clear"); - data.putString("shared", "state"); - Event event = new Event.Builder("testName", EventType.CUSTOM, EventSource.NONE).build(); - eventHub.createSharedState(testModule, 0, data, SharedStateType.XDM); - assertEquals(data, eventHub.getSharedEventState(testModule.getModuleName(), event, testModule, SharedStateType.XDM)); - - // test - eventHub.clearSharedStates(testModule, SharedStateType.XDM); - - // verify - assertNull(eventHub.getSharedEventState(testModule.getModuleName(), event, testModule, SharedStateType.XDM)); - } - - @Test(expected = IllegalArgumentException.class) - public void constructor_NullPlatformServices() { - new EventHub("eventhub", null); - } - - @Test(expected = InvalidModuleException.class) - public void registerModule_NullClass() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerModule(null); - } - - @Test - public void registerModule_SameModuleNameMultipleTimes_ShouldRegisterOnlyOnce() throws Exception { - Log.setLoggingService(services.fakeLoggingService); - Log.setLogLevel(LoggingMode.VERBOSE); - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerModule(TestModuleNoListeners.class); - eventHub.registerModule(TestModuleNoListeners.class); - - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(services.fakeLoggingService.containsWarningLog("EventHub(eventhub)", - "Failed to register extension, an extension with the same name (TestModule) already exists")); - } - @Test - public void registerModule_SameInternalModuleNameMultipleTimes_ShouldRegisterOnlyOnce() throws Exception { - Log.setLoggingService(services.fakeLoggingService); - Log.setLogLevel(LoggingMode.VERBOSE); - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerModule(TestableModule.class); - eventHub.registerModule(TestableModule.class); - - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(services.fakeLoggingService.containsWarningLog("EventHub(eventhub)", - "Failed to register extension, an extension with the same name (TestableModule) already exists")); - } - - @Test - public void registerExtension_SameExtensionNameMultipleTimes_ShouldRegisterOnlyOnce() throws Exception { - Log.setLoggingService(services.fakeLoggingService); - Log.setLogLevel(LoggingMode.VERBOSE); - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerExtension(TestExtension.class); - eventHub.registerExtension(TestExtension.class); - - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(TestExtension.onUnexpectedErrorWasCalled); - assertEquals(ExtensionError.DUPLICATE_NAME, TestExtension.onUnexpectedErrorParamError.getErrorCode()); - assertTrue(services.fakeLoggingService.containsErrorLog("EventHub(eventhub)", - "Failed to register extension, an extension with the same name (test extension) already exists")); - } - - @Test - public void registerExtension_SameExtensionNameAsInternalModules_ShouldFail() throws Exception { - Log.setLoggingService(services.fakeLoggingService); - Log.setLogLevel(LoggingMode.VERBOSE); - EventHub eventHub = new EventHub("eventhub", services); - TestExtension.extensionName = "test extension"; - TestModuleNoListeners.moduleName = "test extension"; - eventHub.registerModule(TestModuleNoListeners.class); - eventHub.registerExtension(TestExtension.class); - - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(TestExtension.onUnexpectedErrorWasCalled); - assertEquals(ExtensionError.DUPLICATE_NAME, TestExtension.onUnexpectedErrorParamError.getErrorCode()); - assertTrue(services.fakeLoggingService.containsErrorLog("EventHub(eventhub)", - "Failed to register extension, an extension with the same name (test extension) already exists")); - } - - @Test - public void registerExtension_EmptyName_ShouldFailWithBadName() throws Exception { - Log.setLoggingService(services.fakeLoggingService); - Log.setLogLevel(LoggingMode.VERBOSE); - EventHub eventHub = new EventHub("eventhub", services); - TestExtension.extensionName = ""; - eventHub.registerExtension(TestExtension.class); - - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(TestExtension.onUnexpectedErrorWasCalled); - assertEquals(ExtensionError.BAD_NAME, TestExtension.onUnexpectedErrorParamError.getErrorCode()); - assertTrue(services.fakeLoggingService.containsErrorLog("EventHub(eventhub)", - "Failed to register extension, extension name should not be null or empty")); - } - - @Test - public void registerExtension_NullName_ShouldFailWithBadName() throws Exception { - Log.setLoggingService(services.fakeLoggingService); - Log.setLogLevel(LoggingMode.VERBOSE); - EventHub eventHub = new EventHub("eventhub", services); - TestExtension.extensionName = null; - eventHub.registerExtension(TestExtension.class); - - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(TestExtension.onUnexpectedErrorWasCalled); - assertEquals(ExtensionError.BAD_NAME, TestExtension.onUnexpectedErrorParamError.getErrorCode()); - assertTrue(services.fakeLoggingService.containsErrorLog("EventHub(eventhub)", - "Failed to register extension, extension name should not be null or empty")); - } - - @Test - public void registerOneTimeListener_NullBlock() throws Exception { - Log.setLoggingService(services.fakeLoggingService); - Log.setLogLevel(LoggingMode.VERBOSE); - final CountDownLatch latch = new CountDownLatch(1); - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerOneTimeListener("pairId", null); - - latch.await(1000, TimeUnit.MILLISECONDS); - - assertTrue(services.fakeLoggingService.containsDebugLog("EventHub(eventhub)", - "Unexpected Null Value (callback block), failed to register one-time listener")); - } - - @Test - public void registerOneTimeListener_WithTimeOut() throws Exception { - Log.setLoggingService(services.fakeLoggingService); - Log.setLogLevel(LoggingMode.VERBOSE); - final CountDownLatch latch = new CountDownLatch(1); - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerOneTimeListener("parid", new Module.OneTimeListenerBlock() { - - @Override - public void call(Event e) { - - } - }, new AdobeCallbackWithError() { - @Override - public void fail(AdobeError error) { - latch.countDown(); - } - - @Override - public void call(Object value) { - - } - }, 100); - - latch.await(1000, TimeUnit.MILLISECONDS); - - assertEquals("Failure callback should get called", 0, latch.getCount()); - } - - private static CountDownLatch finishModulesRegistrationLatch; - private static List finishModulesRegistrationEvents; - private static int moduleNumber = 1; - private static class FinishModulesRegistrationModule extends Module { - public FinishModulesRegistrationModule(final EventHub hub) { - super("FinishModulesRegistrationModule" + moduleNumber++, hub); - registerListener(EventType.HUB, EventSource.BOOTED, myListener.class); - - try { - Thread.sleep(100); - } catch (Exception ex) { - - } - } - - static class myListener extends ModuleEventListener { - public myListener(final FinishModulesRegistrationModule module, final EventType type, final EventSource source) { - super(module, type, source); - } - @Override - public void hear(Event e) { - finishModulesRegistrationEvents.add(e); - finishModulesRegistrationLatch.countDown(); - } - } - } - private static class FinishModulesRegistrationModule1 extends FinishModulesRegistrationModule { - public FinishModulesRegistrationModule1(final EventHub hub) { - super(hub); - } - } - private static class FinishModulesRegistrationModule2 extends FinishModulesRegistrationModule { - public FinishModulesRegistrationModule2(final EventHub hub) { - super(hub); - } - } - - @Test - public void bootedEventIsDispatched_When_FinishModulesRegistrationIsCalled() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - finishModulesRegistrationLatch = new CountDownLatch(1); - finishModulesRegistrationEvents = new ArrayList(); - final CountDownLatch callbackLatch = new CountDownLatch(1); - - eventHub.registerModule(FinishModulesRegistrationModule.class); - - eventHub.finishModulesRegistration(new AdobeCallback() { - @Override - public void call(Void value) { - callbackLatch.countDown(); - } - }); - - assertTrue("Listeners were not called in time!!", finishModulesRegistrationLatch.await(1000, TimeUnit.MILLISECONDS)); - assertEquals(1, finishModulesRegistrationEvents.size()); - assertTrue("Callback were not called in time!!", callbackLatch.await(1000, TimeUnit.MILLISECONDS)); - - - } - - @Test - public void bootedEventIsDispatched_When_FinishModulesRegistrationIsCalled_For_MultipleModulesRegistering() throws - Exception { - Log.setLoggingService(services.fakeLoggingService); - EventHub eventHub = new EventHub("eventhub", services); - - // reset counters - finishModulesRegistrationEvents = new ArrayList(); - finishModulesRegistrationLatch = new CountDownLatch(3); - - // register 3 modules - eventHub.registerModule(FinishModulesRegistrationModule.class); - eventHub.registerModule(FinishModulesRegistrationModule1.class); - eventHub.registerModule(FinishModulesRegistrationModule2.class); - - // finish module registration and start eventhub - eventHub.finishModulesRegistration(null); - - - assertTrue("Listeners were not called in time: " + finishModulesRegistrationLatch.getCount(), - finishModulesRegistrationLatch.await(5000, TimeUnit.MILLISECONDS)); - assertEquals("Expecting " + 3 + " boot events heard!", 3, finishModulesRegistrationEvents.size()); - - } - - @Test - public void bootedEventIsDispatchedOnlyOnce_When_FinishModulesRegistrationIsCalledMultitpleTimes() throws Exception { - EventHub eventHub = new EventHub("eventhub", services); - finishModulesRegistrationLatch = new CountDownLatch(1); - finishModulesRegistrationEvents = new ArrayList(); - - eventHub.registerModule(FinishModulesRegistrationModule.class); - - eventHub.finishModulesRegistration(null); - eventHub.finishModulesRegistration(null); - eventHub.finishModulesRegistration(null); - - assertTrue("Listeners were not called in time!!", finishModulesRegistrationLatch.await(1000, TimeUnit.MILLISECONDS)); - assertEquals(1, finishModulesRegistrationEvents.size()); - - } - - class TestableEventHub extends EventHub { - final String eventHubSharedStateName = "com.adobe.module.eventhub"; - TestableEventHub() { - super("eventhub", services, "1.2.3"); - } - } - - @Test - public void testConstructor_WithCoreVersion() { - final TestableEventHub eventHub = new TestableEventHub(); - assertEquals("1.2.3", eventHub.coreVersion); - } - - @Test - public void testRegisterModuleWithCallback_WillAddModuleToSharedState() throws Exception { - // setup - final TestableEventHub eventHub = new TestableEventHub(); - final Module testModule = new TestModule(eventHub); - final ModuleDetails moduleDetails = new ModuleDetails() { - @Override - public String getName() { - return "testModule"; - } - - @Override - public String getVersion() { - return "testVersion"; - } - - @Override - public Map getAdditionalInfo() { - return null; - } - }; - - // test - try { - eventHub.registerModuleWithCallback(TestModule.class, moduleDetails, null); - } catch (InvalidModuleException ex) { - // nothing - } - - final CountDownLatch waitForBootLatch = new CountDownLatch(1); - eventHub.finishModulesRegistration(new AdobeCallback() { - @Override - public void call(Void value) { - waitForBootLatch.countDown(); - } - }); - - waitForBootLatch.await(100, TimeUnit.MILLISECONDS); - // verify - final Map extensions = eventHub.eventHubSharedState.optVariantMap("extensions", null); - assertNotNull(extensions); - assertTrue(extensions.containsKey(testModule.getModuleName())); - final Map moduleInSharedState = extensions.get(testModule.getModuleName()).optVariantMap(null); - assertTrue(moduleInSharedState.containsKey("version")); - assertEquals(moduleDetails.getVersion(), moduleInSharedState.get("version").optString(null)); - assertEquals(moduleDetails.getName(), moduleInSharedState.get("friendlyName").optString(null)); - } - - @Test - public void testUnregisterModule_When_ModuleIsInSharedState() { - // setup - final TestableEventHub eventHub = new TestableEventHub(); - final Module testModule = new TestModule(eventHub); - final ModuleDetails moduleDetails = new ModuleDetails() { - @Override - public String getName() { - return "testModule"; - } - - @Override - public String getVersion() { - return "testVersion"; - } - - @Override - public Map getAdditionalInfo() { - return null; - } - }; - testModule.setModuleDetails(moduleDetails); - final Map extensions = new HashMap(); - final Map module = new HashMap(); - module.put("version", Variant.fromString(moduleDetails.getVersion())); - extensions.put(testModule.getModuleName(), Variant.fromVariantMap(module)); - eventHub.eventHubSharedState.putVariantMap("extensions", extensions); - - // test - try { - eventHub.unregisterModule(testModule); - } catch (final InvalidModuleException ex) {} - - // verify - final Map newExtensions = eventHub.eventHubSharedState.optVariantMap("extensions", null); - assertNotNull(newExtensions); - assertFalse(newExtensions.containsKey(moduleDetails.getName())); - } - - @Test - public void testCreateEventHubSharedState() { - // setup - final TestableEventHub eventHub = new TestableEventHub(); - final Module testModule = new TestModule(eventHub); - - // test - eventHub.createEventHubSharedState(0); - eventHub.finishModulesRegistration(null); - - // verify - assertEquals(eventHub.eventHubSharedState, eventHub.getSharedEventState(eventHub.eventHubSharedStateName, null, - testModule)); - } - - @Test - public void testAddModuleToEventHubSharedState_When_Happy() { - // setup - final TestableEventHub eventHub = new TestableEventHub(); - eventHub.isBooted = true; - final Module testModule = new TestModule(eventHub); - final ModuleDetails moduleDetails = new ModuleDetails() { - @Override - public String getName() { - return "testModule"; - } - - @Override - public String getVersion() { - return "testVersion"; - } - - @Override - public Map getAdditionalInfo() { - return null; - } - }; - testModule.setModuleDetails(moduleDetails); - - // test - eventHub.addModuleToEventHubSharedState(testModule); - - // verify - assertNotNull(eventHub.eventHubSharedState); - final Map extensions = eventHub.eventHubSharedState.optVariantMap("extensions", null); - assertTrue(extensions.containsKey(testModule.getModuleName())); - final Map module = extensions.get(testModule.getModuleName()).optVariantMap(null); - assertTrue(module.containsKey("version")); - assertEquals(moduleDetails.getVersion(), module.get("version").optString(null)); - assertEquals(moduleDetails.getName(), module.get("friendlyName").optString(null)); - assertEquals(eventHub.eventHubSharedState, eventHub.getSharedEventState(eventHub.eventHubSharedStateName, null, - testModule)); - } - - @Test - public void testAddModuleToEventHubSharedState_When_ModuleIsNull_Then_NothingHappens() { - // setup - final TestableEventHub eventHub = new TestableEventHub(); - eventHub.isBooted = true; - - // test - eventHub.addModuleToEventHubSharedState(null); - - // verify - assertNotNull(eventHub.eventHubSharedState); - assertEquals(0, eventHub.eventHubSharedState.optVariantMap("extensions", null).size()); - } - - @Test - public void testAddModuleToEventHubSharedState_When_ModuleDetailsIsNull_Then_UseModuleGetName() { - // setup - final TestableEventHub eventHub = new TestableEventHub(); - eventHub.isBooted = true; - final Module testModule = new TestModule(eventHub); - - // test - eventHub.addModuleToEventHubSharedState(testModule); - - // verify - assertNotNull(eventHub.eventHubSharedState); - final Map extensions = eventHub.eventHubSharedState.optVariantMap("extensions", null); - assertEquals(1, extensions.size()); - assertTrue(extensions.containsKey(testModule.getModuleName())); - } - - @Test - public void testAddModuleToEventHubSharedState_When_ModuleDetailsNameIsEmpty_Then_FriendlyNameIsEmpty() { - // setup - final TestableEventHub eventHub = new TestableEventHub(); - eventHub.isBooted = true; - final Module testModule = new TestModule(eventHub); - final ModuleDetails moduleDetails = new ModuleDetails() { - @Override - public String getName() { - return ""; - } - - @Override - public String getVersion() { - return "testVersion"; - } - - @Override - public Map getAdditionalInfo() { - return null; - } - }; - testModule.setModuleDetails(moduleDetails); - - // test - eventHub.addModuleToEventHubSharedState(testModule); - - // verify - assertNotNull(eventHub.eventHubSharedState); - final Map extensions = eventHub.eventHubSharedState.optVariantMap("extensions", null); - assertEquals(1, extensions.size()); - final Map module = extensions.get(testModule.getModuleName()).optVariantMap(null); - assertTrue(module.containsKey("version")); - assertEquals(moduleDetails.getVersion(), module.get("version").optString(null)); - assertEquals(moduleDetails.getName(), module.get("friendlyName").optString(null)); - } - - @Test - public void testAddModuleToEventHubSharedState_When_ModuleDetailsNameIsNull_Then_UseModuleGetNameForFriendlyName() { - // setup - final TestableEventHub eventHub = new TestableEventHub(); - eventHub.isBooted = true; - final Module testModule = new TestModule(eventHub); - final ModuleDetails moduleDetails = new ModuleDetails() { - @Override - public String getName() { - return null; - } - - @Override - public String getVersion() { - return "testVersion"; - } - - @Override - public Map getAdditionalInfo() { - return null; - } - }; - testModule.setModuleDetails(moduleDetails); - - // test - eventHub.addModuleToEventHubSharedState(testModule); - - // verify - assertNotNull(eventHub.eventHubSharedState); - final Map extensions = eventHub.eventHubSharedState.optVariantMap("extensions", null); - assertEquals(1, extensions.size()); - final Map module = extensions.get(testModule.getModuleName()).optVariantMap(null); - assertTrue(module.containsKey("version")); - assertEquals(moduleDetails.getVersion(), module.get("version").optString(null)); - assertEquals(testModule.getModuleName(), module.get("friendlyName").optString(null)); - } - - @Test - public void testAddModuleToEventHubSharedState_When_EventHubNotFinishedBooting_Then_StateNotShared() { - // setup - final TestableEventHub eventHub = new TestableEventHub(); - eventHub.isBooted = false; - final Module testModule = new TestModule(eventHub); - final ModuleDetails moduleDetails = new ModuleDetails() { - @Override - public String getName() { - return "testModule"; - } - - @Override - public String getVersion() { - return "testVersion"; - } - - @Override - public Map getAdditionalInfo() { - return null; - } - }; - testModule.setModuleDetails(moduleDetails); - - // test - eventHub.addModuleToEventHubSharedState(testModule); - - // verify - assertNotNull(eventHub.eventHubSharedState); - final Map extensions = eventHub.eventHubSharedState.optVariantMap("extensions", null); - assertTrue(extensions.containsKey(testModule.getModuleName())); - final Map module = extensions.get(testModule.getModuleName()).optVariantMap(null); - assertTrue(module.containsKey("version")); - assertEquals(moduleDetails.getVersion(), module.get("version").optString(null)); - assertEquals(moduleDetails.getName(), module.get("friendlyName").optString(null)); - assertNotEquals(eventHub.eventHubSharedState, eventHub.getSharedEventState(eventHub.eventHubSharedStateName, null, - testModule)); - } - - @Test - public void testAddModuleToEventHubSharedState_When_AddingExtension() throws Exception { - // setup - final TestableEventHub eventHub = new TestableEventHub(); - eventHub.isBooted = false; - - // test - eventHub.registerExtension(TestExtension.class); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - - // verify - assertNotNull(eventHub.eventHubSharedState); - final Map extensions = eventHub.eventHubSharedState.optVariantMap("extensions", null); - assertTrue(extensions.containsKey("test extension")); - final Map module = extensions.get("test extension").optVariantMap(null); - assertTrue(module.containsKey("version")); - assertEquals("1.1.0", module.get("version").optString(null)); - assertEquals("test extension friendly name", module.get("friendlyName").optString(null)); - } - - @Test - public void testRemoveModuleFromEventHubSharedState_When_Happy() { - // setup - final TestableEventHub eventHub = new TestableEventHub(); - eventHub.isBooted = true; - final Module testModule = new TestModule(eventHub); - final ModuleDetails moduleDetails = new ModuleDetails() { - @Override - public String getName() { - return "testModule"; - } - - @Override - public String getVersion() { - return "testVersion"; - } - - @Override - public Map getAdditionalInfo() { - return null; - } - }; - testModule.setModuleDetails(moduleDetails); - final Map preExtensions = new HashMap(); - final Map preModule = new HashMap(); - preModule.put("version", Variant.fromString(moduleDetails.getVersion())); - preExtensions.put(testModule.getModuleName(), Variant.fromVariantMap(preModule)); - eventHub.eventHubSharedState.putVariantMap("extensions", preExtensions); - - // test - eventHub.removeModuleFromEventHubSharedState(testModule); - - // verify - assertNotNull(eventHub.eventHubSharedState); - assertEquals(0, eventHub.eventHubSharedState.optVariantMap("extensions", null).size()); - assertEquals(eventHub.eventHubSharedState, eventHub.getSharedEventState(eventHub.eventHubSharedStateName, null, - testModule)); - } - - @Test - public void testRemoveModuleFromEventHubSharedState_When_EventHubNotFinishedBooting_Then_StateNotShared() { - // setup - final TestableEventHub eventHub = new TestableEventHub(); - eventHub.isBooted = true; - final Module testModule = new TestModule(eventHub); - final ModuleDetails moduleDetails = new ModuleDetails() { - @Override - public String getName() { - return "testModule"; - } - - @Override - public String getVersion() { - return "testVersion"; - } - - @Override - public Map getAdditionalInfo() { - return null; - } - }; - testModule.setModuleDetails(moduleDetails); - eventHub.addModuleToEventHubSharedState(testModule); - eventHub.isBooted = false; - - // test - eventHub.removeModuleFromEventHubSharedState(testModule); - - // verify - assertNotNull(eventHub.eventHubSharedState); - assertEquals(0, eventHub.eventHubSharedState.optVariantMap("extensions", null).size()); - final EventData sharedState = eventHub.getSharedEventState(eventHub.eventHubSharedStateName, null, - testModule); - // TODO: the line below doesn't pass, which may be a problem with eventhub - // assertNotEquals(eventHub.eventHubSharedState, sharedState); - } - - @Test - public void testRemoveModuleFromEventHubSharedState_When_ModuleIsNull_Then_NothingHappens() { - // setup - final TestableEventHub eventHub = new TestableEventHub(); - eventHub.isBooted = true; - final Module testModule = new TestModule(eventHub); - final ModuleDetails moduleDetails = new ModuleDetails() { - @Override - public String getName() { - return "testModule"; - } - - @Override - public String getVersion() { - return "testVersion"; - } - - @Override - public Map getAdditionalInfo() { - return null; - } - }; - testModule.setModuleDetails(moduleDetails); - final Map preExtensions = new HashMap(); - final Map preModule = new HashMap(); - preModule.put("version", Variant.fromString(moduleDetails.getVersion())); - preExtensions.put(moduleDetails.getName(), Variant.fromVariantMap(preModule)); - eventHub.eventHubSharedState.putVariantMap("extensions", preExtensions); - - // test - eventHub.removeModuleFromEventHubSharedState(null); - - // verify - assertNotNull(eventHub.eventHubSharedState); - final Map extensions = eventHub.eventHubSharedState.optVariantMap("extensions", null); - assertTrue(extensions.containsKey(moduleDetails.getName())); - final Map module = extensions.get(moduleDetails.getName()).optVariantMap(null); - assertTrue(module.containsKey("version")); - assertEquals(moduleDetails.getVersion(), module.get("version").optString(null)); - } - - @Test - public void testRemoveModuleFromEventHubSharedState_When_ModuleDetailsIsNull_Then_NothingHappens() { - // setup - final TestableEventHub eventHub = new TestableEventHub(); - eventHub.isBooted = true; - final Module testModule = new TestModule(eventHub); - final ModuleDetails moduleDetails = new ModuleDetails() { - @Override - public String getName() { - return "testModule"; - } - - @Override - public String getVersion() { - return "testVersion"; - } - - @Override - public Map getAdditionalInfo() { - return null; - } - }; - final Map preExtensions = new HashMap(); - final Map preModule = new HashMap(); - preModule.put("version", Variant.fromString(moduleDetails.getVersion())); - preExtensions.put(moduleDetails.getName(), Variant.fromVariantMap(preModule)); - eventHub.eventHubSharedState.putVariantMap("extensions", preExtensions); - - // test - eventHub.removeModuleFromEventHubSharedState(testModule); - - // verify - assertNotNull(eventHub.eventHubSharedState); - final Map extensions = eventHub.eventHubSharedState.optVariantMap("extensions", null); - assertTrue(extensions.containsKey(moduleDetails.getName())); - final Map module = extensions.get(moduleDetails.getName()).optVariantMap(null); - assertTrue(module.containsKey("version")); - assertEquals(moduleDetails.getVersion(), module.get("version").optString(null)); - } - - @Test - public void testGetInitialEventHubSharedState_DefaultWrapperNone() { - // setup - final TestableEventHub eventHub = new TestableEventHub(); - - // test - final EventData result = eventHub.getInitialEventHubSharedState(); - - // verify - assertNotNull(result); - assertTrue(result.containsKey("version")); - assertTrue(result.containsKey("extensions")); - assertTrue(result.containsKey("wrapper")); - assertEquals("1.2.3", result.optString("version", null)); - final Map extensions = result.optVariantMap("extensions", null); - assertEquals(0, extensions.size()); - - //verify wrapper details - final Map wrapper = result.optVariantMap("wrapper", null); - assertEquals("N", wrapper.get("type").optString("")); - assertEquals("None", wrapper.get("friendlyName").optString("")); - } - - @Test - public void testGetInitialEventHubSharedState() { - // setup - final TestableEventHub eventHub = new TestableEventHub(); - eventHub.setWrapperType(WrapperType.REACT_NATIVE); - - // test - final EventData result = eventHub.getInitialEventHubSharedState(); - - // verify - assertNotNull(result); - assertTrue(result.containsKey("version")); - assertTrue(result.containsKey("extensions")); - assertTrue(result.containsKey("wrapper")); - assertEquals("1.2.3", result.optString("version", null)); - final Map extensions = result.optVariantMap("extensions", null); - assertEquals(0, extensions.size()); - - //verify wrapper details - final Map wrapper = result.optVariantMap("wrapper", null); - assertEquals("R", wrapper.get("type").optString("")); - assertEquals("React Native", wrapper.get("friendlyName").optString("")); - } - - @Test - public void testSetWrapperType_HubNotBooted_Happy() { - // setup - final TestableEventHub eventHub = new TestableEventHub(); - eventHub.isBooted = false; - - // test - eventHub.setWrapperType(WrapperType.FLUTTER); - - // verify - assertEquals(WrapperType.FLUTTER, eventHub.getWrapperType()); - } - - @Test - public void testSetWrapperType_HubBooted_Fail() { - // setup - final TestableEventHub eventHub = new TestableEventHub(); - eventHub.isBooted = true; - - // test - eventHub.setWrapperType(WrapperType.FLUTTER); - - // verify - assertEquals(WrapperType.NONE, eventHub.getWrapperType()); - } - - @Test - public void testSetWrapperType_HubNotBooted_AllowMultipleUpdates_shouldNotUpdateAfterBooting() { - // setup - final TestableEventHub eventHub = new TestableEventHub(); - eventHub.isBooted = false; - - // test - eventHub.setWrapperType(WrapperType.FLUTTER); - assertEquals(WrapperType.FLUTTER, eventHub.getWrapperType()); - - eventHub.setWrapperType(WrapperType.CORDOVA); - assertEquals(WrapperType.CORDOVA, eventHub.getWrapperType()); - - eventHub.isBooted = true; - eventHub.setWrapperType(WrapperType.REACT_NATIVE); - assertEquals(WrapperType.CORDOVA, eventHub.getWrapperType()); - } - - @Test - public void testGetWrapperType_defaultWrapperNone() {// setup - final TestableEventHub eventHub = new TestableEventHub(); - assertEquals(WrapperType.NONE, eventHub.getWrapperType()); - } - - @Test - public void testGetSDKVersion_wrapperNone() { - final TestableEventHub eventHub = new TestableEventHub(); - assertEquals("1.2.3", eventHub.getSdkVersion()); - } - - @Test - public void testGetSDKVersion_wrapperTypeSet() { - final TestableEventHub eventHub = new TestableEventHub(); - eventHub.setWrapperType(WrapperType.REACT_NATIVE); - assertEquals("1.2.3-R", eventHub.getSdkVersion()); - } -} +///* +// Copyright 2022 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may obtain a copy +// of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under +// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +// OF ANY KIND, either express or implied. See the License for the specific language +// governing permissions and limitations under the License. +// */ +// +//package com.adobe.marketing.mobile; +// +//import org.junit.After; +//import org.junit.Test; +// +//import java.util.ArrayList; +//import java.util.HashMap; +//import java.util.List; +//import java.util.Map; +//import java.util.concurrent.ConcurrentLinkedQueue; +//import java.util.concurrent.CountDownLatch; +//import java.util.concurrent.Semaphore; +//import java.util.concurrent.TimeUnit; +// +//import static org.junit.Assert.*; +//import static org.junit.Assert.assertEquals; +// +//public class EventHubTest { +// private static int EVENTHUB_WAIT_MS = 50; +// +// +// private static FakePlatformServices services = new FakePlatformServices(); +// private static CountDownLatch latch = new CountDownLatch(1); +// +// @After +// public void tearDown() { +// TestExtension.extensionName = "test extension"; +// TestModuleNoListeners.moduleName = "TestModule"; +// +// } +// +// @Test +// public void eventCounting() throws Exception { +// final EventHub hub = new EventHub("Event Count Test Hub", services); +// final CountDownLatch initializationLatch = new CountDownLatch(1); +// +// hub.finishModulesRegistration(new AdobeCallback() { +// @Override +// public void call(Void value) { +// initializationLatch.countDown(); +// } +// }); +// +// initializationLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// +// for (int i = 2; i < 101; i++) { // boot event is always #0, shared state is #1 so we count from 2 on. +// final Event newEvent = new Event.Builder("Test Event", EventType.CUSTOM, EventSource.NONE).build(); +// hub.dispatch(newEvent); +// assertEquals("eventNumber incorrect for event", i, newEvent.getEventNumber()); +// } +// } +// +// private static final Semaphore circularModuleSemaphore = new Semaphore(0, true); +// +// public static class CircularModule1 extends Module { +// public CircularModule1(final EventHub hub) { +// super("CircularModule1", hub); +// registerListener(EventType.ANALYTICS, EventSource.NONE, EndListener.class); +// registerListener(EventType.CUSTOM, EventSource.NONE, StateListener.class); +// } +// +// +// public static class StateListener extends ModuleEventListener { +// public StateListener(final CircularModule1 m, final EventType type, final EventSource source) { +// super(m, type, source); +// } +// +// public void hear(final Event e) { +// parentModule.getSharedEventState("CircularModule2", e); +// } +// +// } +// +// public static class EndListener extends ModuleEventListener { +// public EndListener(final CircularModule1 m, final EventType type, final EventSource source) { +// super(m, type, source); +// } +// +// public void hear(final Event e) { +// circularModuleSemaphore.release(); +// } +// } +// } +// +// public static class CircularModule2 extends Module { +// public CircularModule2(final EventHub hub) { +// super("CircularModule2", hub); +// registerListener(EventType.CUSTOM, EventSource.NONE, StateListener.class); +// } +// +// public static class StateListener extends ModuleEventListener { +// public StateListener(final CircularModule2 m, final EventType type, final EventSource source) { +// super(m, type, source); +// } +// +// public void hear(final Event e) { +// parentModule.getSharedEventState("CircularModule1", e); +// } +// } +// } +// +// @Test(timeout = 1000) +// public void circularDependency() throws Exception { +// Log.setLogLevel(LoggingMode.DEBUG); +// Log.setLoggingService(services.fakeLoggingService); +// final EventHub hub = new EventHub("Circular Dependency Test", services); +// hub.registerModule(CircularModule1.class); +// hub.registerModule(CircularModule2.class); +// +// // wait for modules to register +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// +// final Event sharedStateReadEvent = new Event.Builder("Test Event", EventType.CUSTOM, EventSource.NONE).build(); +// final Event doneEvent = new Event.Builder("Done Event", EventType.ANALYTICS, EventSource.NONE).build(); +// hub.dispatch(sharedStateReadEvent); +// hub.dispatch(doneEvent); +// hub.finishModulesRegistration(null); +// +// circularModuleSemaphore.tryAcquire(1000, TimeUnit.MILLISECONDS); +// +// // two potential outcomes for the live-lock log depending on the speed of which the listeners run. +// final boolean potentialErrorLog1 = services.fakeLoggingService.containsWarningLog("EventHub(Circular Dependency " + +// "Test)", +// "Circular shared-state dependency between CircularModule2 and CircularModule1, you may have a " + +// "live-lock."); +// final boolean potentialErrorLog2 = services.fakeLoggingService.containsWarningLog("EventHub(Circular Dependency " + +// "Test)", +// "Circular shared-state dependency between CircularModule1 and CircularModule2, you may have a " + +// "live-lock."); +// +// // check for either outcome +// assertTrue(potentialErrorLog1 || potentialErrorLog2); +// } +// +// private static final Semaphore longRunningListenerModuleSemaphore = new Semaphore(0, true); +// +// public static class LongRunningListenerModule extends Module { +// public LongRunningListenerModule(final EventHub hub) { +// super("LongRunningListenerModule", hub); +// registerListener(EventType.ANALYTICS, EventSource.NONE, LongRunningListener.class); +// registerListener(EventType.CUSTOM, EventSource.NONE, EndListener.class); +// } +// +// public static class LongRunningListener extends ModuleEventListener { +// public LongRunningListener(final LongRunningListenerModule m, final EventType type, final EventSource +// source) { +// super(m, type, source); +// } +// +// public void hear(final Event e) { +// try { +// longRunningListenerModuleSemaphore.tryAcquire(1500, TimeUnit.MILLISECONDS); +// } catch (final Exception ignored) { +// } +// } +// } +// +// public static class EndListener extends ModuleEventListener { +// public EndListener(final LongRunningListenerModule m, final EventType type, final EventSource source) { +// super(m, type, source); +// } +// +// public void hear(final Event e) { +// longRunningListenerModuleSemaphore.release(); +// } +// } +// } +// +// @Test(timeout = 2000) +// public void listenerTimeout() throws Exception { +// Log.setLogLevel(LoggingMode.DEBUG); +// Log.setLoggingService(services.fakeLoggingService); +// final EventHub hub = new EventHub("Listener Timeout Test", services); +// hub.registerModule(LongRunningListenerModule.class); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// hub.finishModulesRegistration(null); +// final Event longRunningEvent = new Event.Builder("Heard By Long Listener", EventType.ANALYTICS, +// EventSource.NONE).build(); +// final Event endEvent = new Event.Builder("Test Event", EventType.CUSTOM, EventSource.NONE).build(); +// +// hub.dispatch(longRunningEvent); +// hub.dispatch(endEvent); +// +// longRunningListenerModuleSemaphore.tryAcquire(1500, TimeUnit.MILLISECONDS); +// assertTrue(services.fakeLoggingService.containsErrorLog("EventBus(EventHub)", +// "Listener com.adobe.marketing.mobile.EventHubTest$LongRunningListenerModule$LongRunningListener " + +// "exceeded runtime limit of 1000 milliseconds (java.util.concurrent.TimeoutException)")); +// +// } +// +// private final static EventData sharedState1 = new EventData(); +// private final static EventData sharedState2 = new EventData(); +// private final static String SharedStateFirstLastModuleName = "SharedStateFirstLast"; +// +// public static class SharedStateFirstLastModule extends Module { +// SharedStateFirstLastModule(final EventHub hub) { +// super(SharedStateFirstLastModuleName, hub); +// createSharedState(500, sharedState1); +// +// for (int i = 500; i < 1000; i += 15) { +// createSharedState(i, new EventData()); +// } +// +// createSharedState(3500, sharedState2); +// } +// +// } +// +// @Test(timeout = 1000) +// public void staticFirstLastEvents() throws Exception { +// final EventHub hub = new EventHub("First Last Events Test", services); +// hub.registerModule(SharedStateFirstLastModule.class); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// +// Module module = new SharedStateFirstLastModule(hub); +// EventData oldest = hub.getSharedEventState(SharedStateFirstLastModuleName, Event.SHARED_STATE_OLDEST, module); +// EventData newest = hub.getSharedEventState(SharedStateFirstLastModuleName, Event.SHARED_STATE_NEWEST, module); +// +// assertEquals("Oldest shared state should equal 'sharedState1'", sharedState1, oldest); +// assertEquals("Newest shared state should equal 'sharedState2'", sharedState2, newest); +// } +// +// @Test(expected = IllegalArgumentException.class) +// public void getSharedEventState_NullStateName() { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.getSharedEventState(null, +// new Event.Builder(null, (EventType)null, null).build(), +// new Module("testmodule", eventHub) { +// }); +// } +// +// @Test +// public void getSharedEventState_ReturnsLatest_OnNullEvent() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// Module testModule = new Module("testStateName", eventHub) {}; +// +// Event event1 = new Event.Builder("EventHubTest", EventType.CUSTOM, EventSource.NONE).setEventNumber(1).build(); +// Event event2 = new Event.Builder("EventHubTest", EventType.CUSTOM, EventSource.NONE).setEventNumber(2).build(); +// +// +// EventData state1 = new EventData(); +// state1.putString("key", "value1"); +// EventData state2 = new EventData(); +// state2.putString("key", "value2"); +// +// eventHub.createSharedState(testModule, event1.getEventNumber(), state1); +// eventHub.createSharedState(testModule, event2.getEventNumber(), state2); +// +// assertEquals(state1, eventHub.getSharedEventState(testModule.getModuleName(), event1, testModule)); +// assertEquals(state2, eventHub.getSharedEventState(testModule.getModuleName(), event2, testModule)); +// assertEquals(state2, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule)); +// } +// +// @Test +// public void getXDMSharedEventState_ReturnsLatest_OnNullEvent() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// Module testModule = new Module("testStateName", eventHub) {}; +// +// Event event1 = new Event.Builder("EventHubTest", EventType.CUSTOM, EventSource.NONE).setEventNumber(1).build(); +// Event event2 = new Event.Builder("EventHubTest", EventType.CUSTOM, EventSource.NONE).setEventNumber(2).build(); +// +// +// EventData state1 = new EventData(); +// state1.putString("key", "value1"); +// EventData state2 = new EventData(); +// state2.putString("key", "value2"); +// +// eventHub.createSharedState(testModule, event1.getEventNumber(), state1, SharedStateType.XDM); +// eventHub.createSharedState(testModule, event2.getEventNumber(), state2, SharedStateType.XDM); +// +// assertEquals(state1, eventHub.getSharedEventState(testModule.getModuleName(), event1, testModule, SharedStateType.XDM)); +// assertEquals(state2, eventHub.getSharedEventState(testModule.getModuleName(), event2, testModule, SharedStateType.XDM)); +// assertEquals(state2, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule, SharedStateType.XDM)); +// } +// +// @Test(expected = IllegalArgumentException.class) +// public void hasSharedEventState_NullStateName() { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.hasSharedEventState(null); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void unregisterModuleListener_NullModule() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.unregisterModuleListener(null, null, null); +// } +// +// @Test +// public void unregisterModuleListener_NullType() throws Exception { +// Log.setLogLevel(LoggingMode.VERBOSE); +// Log.setLoggingService(services.fakeLoggingService); +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerModule(TestModule.class); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// Module testModule = eventHub.getActiveModules().iterator().next(); +// assertNotNull(testModule); +// eventHub.unregisterModuleListener(testModule, null, EventSource.BOOTED); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertTrue(services.fakeLoggingService.containsDebugLog("EventHub(eventhub)", +// "Failed to unregister listener (no registered listener)")); +// } +// +// @Test +// public void unregisterModuleListener_NullSource() throws Exception { +// Log.setLogLevel(LoggingMode.VERBOSE); +// Log.setLoggingService(services.fakeLoggingService); +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerModule(TestModule.class); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// Module testModule = eventHub.getActiveModules().iterator().next(); +// assertNotNull(testModule); +// eventHub.unregisterModuleListener(testModule, EventType.HUB, null); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertTrue(services.fakeLoggingService.containsDebugLog("EventHub(eventhub)", +// "Failed to unregister listener (no registered listener)")); +// } +// +// @Test +// public void unregisterModuleListener_NoRegisteredListeners() throws Exception { +// Log.setLogLevel(LoggingMode.VERBOSE); +// Log.setLoggingService(services.fakeLoggingService); +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerModule(TestModule.class); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// Module testModule = eventHub.getActiveModules().iterator().next(); +// assertNotNull(testModule); +// eventHub.unregisterModuleListener(testModule, EventType.HUB, EventSource.BOOTED); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertTrue(services.fakeLoggingService.containsDebugLog("EventHub(eventhub)", +// "Failed to unregister listener (no registered listener)")); +// } +// +// @SuppressWarnings("all") +// static class TestListener extends ModuleEventListener { +// static boolean onUnregisteredWasCalled = false; +// static boolean hearWasCalled = false; +// static List eventsListened = new ArrayList(); +// protected TestListener(final Module module, final EventType type, final EventSource source) { +// super(module, type, source); +// } +// +// @Override +// public void hear(final Event e) { +// hearWasCalled = true; +// eventsListened.add(e); +// } +// @Override +// public void onUnregistered() { +// onUnregisteredWasCalled = true; +// } +// } +// +// @SuppressWarnings("all") +// static class TestExtensionListener extends ExtensionListener { +// static boolean onUnregisteredWasCalled = false; +// protected TestExtensionListener(final ExtensionApi module, final String type, final String source) { +// super(module, type, source); +// } +// +// @Override +// public void hear(final Event e) { +// } +// @Override +// public void onUnregistered() { +// onUnregisteredWasCalled = true; +// } +// } +// +// @SuppressWarnings("all") +// static class TestModule extends Module { +// static boolean onUnregisteredWasCalled = false; +// TestModule(final EventHub hub, +// boolean registerTestListener, +// boolean registerTestProcessor) { +// super("TestModule", hub); +// +// if (registerTestListener) { +// registerListener(EventType.CUSTOM, +// EventSource.NONE, +// TestListener.class); +// } +// +// } +// +// TestModule(final EventHub hub) { +// super("TestModule", hub); +// registerListener(EventType.CUSTOM, EventSource.NONE, TestListener.class); +// +// } +// +// @Override +// protected void onUnregistered() { +// onUnregisteredWasCalled = true; +// } +// } +// +// static class TestExtension extends Extension { +// static boolean onUnregisteredWasCalled = false; +// static boolean onUnexpectedErrorWasCalled = false; +// static ExtensionUnexpectedError onUnexpectedErrorParamError; +// static String extensionName = "test extension"; +// static String friendlyName = "test extension friendly name"; +// +// protected TestExtension(final ExtensionApi extensionApi) { +// super(extensionApi); +// getApi().registerEventListener(EventType.ANALYTICS.getName(), EventSource.REQUEST_CONTENT.getName(), +// TestExtensionListener.class, null); +// onUnregisteredWasCalled = false; +// onUnexpectedErrorWasCalled = false; +// onUnexpectedErrorParamError = null; +// } +// +// protected String getName() { +// return extensionName; +// } +// +// @Override +// protected String getFriendlyName() { +// return friendlyName; +// } +// +// protected String getVersion() { +// return "1.1.0"; +// } +// +// protected void onUnregistered() { +// onUnregisteredWasCalled = true; +// } +// +// protected void onUnexpectedError(final ExtensionUnexpectedError extensionUnexpectedError) { +// onUnexpectedErrorWasCalled = true; +// onUnexpectedErrorParamError = extensionUnexpectedError; +// } +// } +// +// static class TestModuleNoProcessors extends Module { +// TestModuleNoProcessors(final EventHub hub) { +// super("TestModule", hub); +// registerListener(EventType.CUSTOM, EventSource.NONE, TestListener.class); +// } +// } +// +// static class TestModuleNoListeners extends Module { +// static String moduleName = "TestModule"; +// TestModuleNoListeners(final EventHub hub) { +// super(moduleName, hub); +// registerListener(EventType.CUSTOM, EventSource.NONE, TestListener.class); +// } +// } +// +// @Test(expected = InvalidModuleException.class) +// public void unregisterModule_NullModule() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.unregisterModule(null); +// } +// +// @Test +// public void unregisterModule_NoRegisteredListeners() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerModule(TestModuleNoListeners.class); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// Module testModule = eventHub.getActiveModules().iterator().next(); +// assertNotNull(testModule); +// eventHub.unregisterModule(testModule); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertEquals(0, eventHub.getActiveModules().size()); +// } +// +// @Test +// public void unregisterModule_NoRegisteredProcessors() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerModule(TestModuleNoProcessors.class); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// Module testModule = eventHub.getActiveModules().iterator().next(); +// assertNotNull(testModule); +// eventHub.unregisterModule(testModule); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertEquals(0, eventHub.getActiveModules().size()); +// } +// +// @Test +// public void unregisterModule_ModuleNotRegisteredWithHub() throws Exception { +// Log.setLoggingService(services.fakeLoggingService); +// Log.setLogLevel(LoggingMode.VERBOSE); +// EventHub eventHub = new EventHub("eventhub", services); +// Module testModule = new TestModule(eventHub, true, true); +// eventHub.unregisterModule(testModule); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertTrue(services.fakeLoggingService.containsErrorLog("EventHub(eventhub)", +// "Failed to unregister module, Module (TestModule) is not registered")); +// } +// +// @Test +// public void unregisterModule_Happy() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerModule(TestModule.class); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertEquals(1, eventHub.getActiveModules().size()); +// Module testModule = eventHub.getActiveModules().iterator().next(); +// assertNotNull(testModule); +// eventHub.unregisterModule(testModule); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertEquals(0, eventHub.getActiveModules().size()); +// } +// +// @Test +// public void unregisterModule_extensionCase_Happy() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerExtension(TestExtension.class); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertEquals(1, eventHub.getActiveModules().size()); +// Module testExtension = eventHub.getActiveModules().iterator().next(); +// assertNotNull(testExtension); +// eventHub.unregisterModule(testExtension); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertEquals(0, eventHub.getActiveModules().size()); +// assertTrue(TestExtension.onUnregisteredWasCalled); +// } +// +// @Test +// public void unregisterExtension_Listener_Happy() throws Exception { +// TestExtension.onUnregisteredWasCalled = false; +// TestExtensionListener.onUnregisteredWasCalled = false; +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerExtension(TestExtension.class); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertEquals(1, eventHub.getActiveModules().size()); +// Module testExtension = eventHub.getActiveModules().iterator().next(); +// assertNotNull(testExtension); +// eventHub.registerModuleListener(testExtension, EventType.CUSTOM, EventSource.NONE, "pairId", +// TestExtensionListener.class); +// eventHub.unregisterModule(testExtension); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertEquals(0, eventHub.getActiveModules().size()); +// +// assertTrue(TestExtension.onUnregisteredWasCalled); +// assertTrue(TestExtensionListener.onUnregisteredWasCalled); +// +// } +// @Test +// public void unregisterModule_Listener_Happy() throws Exception { +// TestModule.onUnregisteredWasCalled = false; +// TestListener.onUnregisteredWasCalled = false; +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerModule(TestModule.class); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertEquals(1, eventHub.getActiveModules().size()); +// Module testModule = eventHub.getActiveModules().iterator().next(); +// assertNotNull(testModule); +// eventHub.registerModuleListener(testModule, EventType.CUSTOM, EventSource.NONE, "pairId", +// TestListener.class); +// eventHub.unregisterModule(testModule); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertEquals(0, eventHub.getActiveModules().size()); +// +// assertTrue(TestModule.onUnregisteredWasCalled); +// assertTrue(TestListener.onUnregisteredWasCalled); +// +// } +// +// @Test +// public void registerModuleListener_Happy() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerModule(TestModule.class); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// Module testModule = eventHub.getActiveModules().iterator().next(); +// assertNotNull(testModule); +// eventHub.registerModuleListener(testModule, EventType.CUSTOM, EventSource.NONE, "pairId", TestListener.class); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void registerModuleListener_NullModule() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerModuleListener(null, EventType.CUSTOM, EventSource.NONE, "pairId", TestListener.class); +// } +// +// @Test +// public void registerModuleListener_NullListener() throws Exception { +// Log.setLogLevel(LoggingMode.VERBOSE); +// Log.setLoggingService(services.fakeLoggingService); +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerModule(TestModule.class); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// Module testModule = eventHub.getActiveModules().iterator().next(); +// assertNotNull(testModule); +// eventHub.registerModuleListener(testModule, EventType.CUSTOM, EventSource.NONE, "pairId", null); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertTrue(services.fakeLoggingService.containsDebugLog("EventHub(eventhub)", +// "Unexpected Null Value (listenerClass, type or source), failed to register listener")); +// } +// +// @Test +// public void registerModuleListener_ExtensionListener_Happy() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerExtension(TestExtension.class); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// Module testExtension = eventHub.getActiveModules().iterator().next(); +// assertNotNull(testExtension); +// +// eventHub.registerModuleListener(testExtension, EventType.TARGET, EventSource.RESPONSE_CONTENT, null, +// TestExtensionListener.class); +// +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// ConcurrentLinkedQueue listeners = eventHub.getModuleListeners(testExtension); +// assertEquals(2, listeners.size()); +// EventListener listener1 = listeners.poll(); +// EventListener listener2 = listeners.poll(); +// assertEquals(EventSource.REQUEST_CONTENT, listener1.getEventSource()); +// assertEquals(EventType.ANALYTICS, listener1.getEventType()); +// assertEquals(EventSource.RESPONSE_CONTENT, listener2.getEventSource()); +// assertEquals(EventType.TARGET, listener2.getEventType()); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void createSharedState_NullModule() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.createSharedState(null, 0, null); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void createXDMSharedState_NullModule() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.createSharedState(null, 0, null, SharedStateType.XDM); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void createSharedState_NullStateName() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.createSharedState(new Module(null, eventHub) { +// }, 0, null); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void createXDMSharedState_NullStateName() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.createSharedState(new Module(null, eventHub) { +// }, 0, null, SharedStateType.XDM); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void updateSharedState_NullModule() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.updateSharedState(null, 0, null); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void updateXDMSharedState_NullModule() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.updateSharedState(null, 0, null, SharedStateType.XDM); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void updateSharedState_NullStateName() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.updateSharedState(new Module(null, eventHub) { +// }, 0, null); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void updateXDMSharedState_NullStateName() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.updateSharedState(new Module(null, eventHub) { +// }, 0, null, SharedStateType.XDM); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void createOrUpdateWithVersionSharedState_NullModule() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.createOrUpdateSharedState(null, 0, null); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void createOrUpdateWithVersionXDMSharedState_NullModule() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.createOrUpdateSharedState(null, 0, null, SharedStateType.XDM); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void createOrUpdateWithVersionSharedState_NullStateName() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.createOrUpdateSharedState(new Module(null, eventHub) { +// }, 0, null); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void createOrUpdateWithVersionXDMSharedState_NullStateName() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.createOrUpdateSharedState(new Module(null, eventHub) { +// }, 0, null, SharedStateType.XDM); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void createOrUpdateSharedState_NullModule() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.createOrUpdateSharedState(null, null); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void createOrUpdateXDMSharedState_NullModule() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.createOrUpdateSharedState(null, null, SharedStateType.XDM); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void createOrUpdateSharedState_NullStateName() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.createOrUpdateSharedState(new Module(null, eventHub) { +// }, null); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void createOrUpdateXDMSharedState_NullStateName() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.createOrUpdateSharedState(new Module(null, eventHub) { +// }, null, SharedStateType.XDM); +// } +// +// @Test +// public void createOrUpdateWithVersionSharedState_Happy() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// Module testModule = new Module("testStateName", eventHub) {}; +// +// EventData state1 = new EventData(); +// state1.putString("key", "value1"); +// EventData state2 = new EventData(); +// state2.putString("key", "value2"); +// +// eventHub.createOrUpdateSharedState(testModule, 1, state1); +// assertEquals(state1, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule)); +// +// eventHub.createOrUpdateSharedState(testModule, 2, EventHub.SHARED_STATE_PENDING); +// assertEquals(EventHub.SHARED_STATE_PENDING, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule)); +// +// eventHub.createOrUpdateSharedState(testModule, 2, state2); +// assertEquals(state2, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule)); +// +// } +// +// @Test +// public void createOrUpdateWithVersionXDMSharedState_Happy() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// Module testModule = new Module("testStateName", eventHub) {}; +// +// EventData state1 = new EventData(); +// state1.putString("key", "value1"); +// EventData state2 = new EventData(); +// state2.putString("key", "value2"); +// +// eventHub.createOrUpdateSharedState(testModule, 1, state1, SharedStateType.XDM); +// assertEquals(state1, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule, SharedStateType.XDM)); +// +// eventHub.createOrUpdateSharedState(testModule, 2, EventHub.SHARED_STATE_PENDING, SharedStateType.XDM); +// assertEquals(EventHub.SHARED_STATE_PENDING, eventHub.getSharedEventState(testModule.getModuleName(), null, +// testModule, SharedStateType.XDM)); +// +// eventHub.createOrUpdateSharedState(testModule, 2, state2, SharedStateType.XDM); +// assertEquals(state2, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule, SharedStateType.XDM)); +// } +// +// @Test +// public void createOrUpdateWithVersionSharedState_AndXDMSharedState_Happy() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// Module testModule = new Module("testStateName", eventHub) {}; +// +// EventData state1 = new EventData(); +// state1.putString("key", "value1"); +// EventData state2 = new EventData(); +// state2.putString("key", "value2"); +// +// eventHub.createOrUpdateSharedState(testModule, 1, state1); +// assertEquals(state1, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule)); +// +// eventHub.createOrUpdateSharedState(testModule, 2, EventHub.SHARED_STATE_PENDING); +// assertEquals(EventHub.SHARED_STATE_PENDING, eventHub.getSharedEventState(testModule.getModuleName(), null, +// testModule)); +// +// eventHub.createOrUpdateSharedState(testModule, 2, state2); +// assertEquals(state2, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule)); +// +// eventHub.createOrUpdateSharedState(testModule, 1, state1, SharedStateType.XDM); +// assertEquals(state1, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule, SharedStateType.XDM)); +// +// eventHub.createOrUpdateSharedState(testModule, 2, EventHub.SHARED_STATE_PENDING, SharedStateType.XDM); +// assertEquals(EventHub.SHARED_STATE_PENDING, eventHub.getSharedEventState(testModule.getModuleName(), null, +// testModule, SharedStateType.XDM)); +// +// eventHub.createOrUpdateSharedState(testModule, 2, state2, SharedStateType.XDM); +// assertEquals(state2, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule, SharedStateType.XDM)); +// } +// +// static List EVENT_LIST = new ArrayList(); +// +// public static class TestableModule extends InternalModule { +// public TestableModule(final EventHub hub, final PlatformServices services) { +// super("TestableModule", hub, services); +// this.registerWildcardListener(EndListener.class); +// } +// +// public static class EndListener extends ModuleEventListener { +// public EndListener(final TestableModule module, final EventType type, final EventSource source) { +// super(module, type, source); +// } +// +// public void hear(final Event e) { +// EVENT_LIST.add(e); +// } +// } +// } +// +// private boolean onEventReprocessingCompleteGetCalled = false; +// private boolean rulesFlag = false; +// @Test(timeout = 200) +// public void replaceRulesAndEvaluateEvents_happy() throws Exception { +// EVENT_LIST.clear(); +// List rules = new ArrayList(); +// +// List consequenceEvents = new ArrayList(); +// EventData eventData = new EventData(); +// +// Map consequenceMap = new HashMap(); +// consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_JSON_ID, Variant.fromString("analyticsId")); +// consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_JSON_TYPE, +// Variant.fromString(RulesEngineConstantsTests.ConsequenceTypes.SEND_DATA_TO_ANALYTICS)); +// Map detailMap = new HashMap(); +// detailMap.put("key", "value"); +// consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL, Variant.fromStringMap(detailMap)); +// eventData.putVariantMap(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_TRIGGERED, consequenceMap); +// +// consequenceEvents.add(new Event.Builder("Test1", EventType.ANALYTICS, +// EventSource.REQUEST_CONTENT).setData(eventData).build()); +// consequenceEvents.add(new Event.Builder("Test2", EventType.ANALYTICS, +// EventSource.REQUEST_CONTENT).setData(eventData).build()); +// consequenceEvents.add(new Event.Builder("Test3", EventType.ANALYTICS, +// EventSource.REQUEST_CONTENT).setData(eventData).build()); +// rules.add(new Rule(new RuleCondition() { +// @Override +// protected boolean evaluate(final RuleTokenParser tokenParser, final Event event) { +// //Make it evaluate to true +// return rulesFlag; +// } +// @Override +// public String toString() { +// return null; +// } +// }, consequenceEvents)); +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerModule(TestableModule.class); +// final CountDownLatch waitForBootLatch = new CountDownLatch(1); +// eventHub.finishModulesRegistration(new AdobeCallback() { +// @Override +// public void call(Void value) { +// waitForBootLatch.countDown(); +// } +// }); +// waitForBootLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// Module activeModule = eventHub.getActiveModules().iterator().next(); +// +// eventHub.replaceRulesAndEvaluateEvents(activeModule, rules, new ReprocessEventsHandler() { +// @Override +// public List getEvents() { +// rulesFlag = true; +// List list = new ArrayList(); +// list.add(new Event.Builder("Test", EventType.HUB, EventSource.NONE).build()); +// return list; +// } +// @Override +// public void onEventReprocessingComplete() { +// onEventReprocessingCompleteGetCalled = true; +// rulesFlag = false; +// } +// }); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertTrue(onEventReprocessingCompleteGetCalled); +// assertEquals(5, EVENT_LIST.size()); +// } +// +// @Test +// public void createOrUpdateSharedState_WithPendingStatus() throws Exception { +// EVENT_LIST.clear(); +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerModule(TestableModule.class); +// +// final CountDownLatch waitForBootLatch = new CountDownLatch(1); +// eventHub.finishModulesRegistration(new AdobeCallback() { +// @Override +// public void call(Void value) { +// waitForBootLatch.countDown(); +// } +// }); +// +// waitForBootLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// Module module = eventHub.getActiveModules().iterator().next(); +// +// EventData state1 = new EventData(); +// state1.putString("key", "value1"); +// EventData state2 = new EventData(); +// state2.putString("key", "value2"); +// +// eventHub.createOrUpdateSharedState(module, 1, state1); +// assertEquals(state1, eventHub.getSharedEventState(module.getModuleName(), null, module)); +// +// eventHub.createOrUpdateSharedState(module, 2, EventHub.SHARED_STATE_INVALID); +// eventHub.createOrUpdateSharedState(module, 3, EventHub.SHARED_STATE_PENDING); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// +// assertEquals(4, +// EVENT_LIST.size()); // 4 events, 1 boot event, 1 initial shared state event, and two shared state updates for module. +// assertEquals(EventHub.SHARED_STATE_PENDING, eventHub.getSharedEventState(module.getModuleName(), null, module)); +// } +// +// @Test +// public void createOrUpdateXDMSharedState_WithPendingStatus() throws Exception { +// EVENT_LIST.clear(); +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerModule(TestableModule.class); +// +// final CountDownLatch waitForBootLatch = new CountDownLatch(1); +// eventHub.finishModulesRegistration(new AdobeCallback() { +// @Override +// public void call(Void value) { +// waitForBootLatch.countDown(); +// } +// }); +// +// waitForBootLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// Module module = eventHub.getActiveModules().iterator().next(); +// +// EventData state1 = new EventData(); +// state1.putString("key", "value1"); +// EventData state2 = new EventData(); +// state2.putString("key", "value2"); +// +// eventHub.createOrUpdateSharedState(module, 1, state1, SharedStateType.XDM); +// assertEquals(state1, eventHub.getSharedEventState(module.getModuleName(), null, module, SharedStateType.XDM)); +// +// eventHub.createOrUpdateSharedState(module, 2, EventHub.SHARED_STATE_INVALID, SharedStateType.XDM); +// eventHub.createOrUpdateSharedState(module, 3, EventHub.SHARED_STATE_PENDING, SharedStateType.XDM); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// +// assertEquals(4, +// EVENT_LIST.size()); // 4 events, 1 boot event, 1 initial XDM shared state event, and two XDM shared state updates for module. +// assertEquals(EventHub.SHARED_STATE_PENDING, eventHub.getSharedEventState(module.getModuleName(), null, module, +// SharedStateType.XDM)); +// } +// +// @Test +// public void createSharedState_DispatchesStateChangeEvent() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// +// eventHub.registerModule(TestModule.class); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// +// Module testModule = eventHub.getActiveModules().iterator().next(); +// assertNotNull(testModule); +// EventData state = new EventData(); +// state.putString("key", "value1"); +// +// TestListener.hearWasCalled = false; +// eventHub.registerModuleListener(testModule, EventType.HUB, EventSource.SHARED_STATE, null, TestListener.class); +// eventHub.finishModulesRegistration(null); +// +// // test +// eventHub.createSharedState(testModule, 0, state); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// +// // verify Shared state change event is dispatched +// assertTrue(TestListener.hearWasCalled); +// Event sharedStateChangeEvent = TestListener.eventsListened.get(TestListener.eventsListened.size() - 1); +// +// // verify the details in the sharedState change event details +// assertEquals("Shared state change", sharedStateChangeEvent.getName()); +// // no need to verify for type and source, since the listener only listener to HUB SHARED_STATE +// assertEquals("TestModule", sharedStateChangeEvent.getData().getString2("stateowner")); +// } +// +// +// @Test +// public void createXDMSharedState_DispatchesStateChangeEvent() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// +// eventHub.registerModule(TestModule.class); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// +// Module testModule = eventHub.getActiveModules().iterator().next(); +// assertNotNull(testModule); +// EventData state = new EventData(); +// state.putString("key", "value1"); +// +// TestListener.hearWasCalled = false; +// eventHub.registerModuleListener(testModule, EventType.HUB, EventSource.SHARED_STATE, null, TestListener.class); +// eventHub.finishModulesRegistration(null); +// +// // test +// eventHub.createSharedState(testModule, 0, state, SharedStateType.XDM); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// +// // verify Shared state change event is dispatched +// assertTrue(TestListener.hearWasCalled); +// Event sharedStateChangeEvent = TestListener.eventsListened.get(TestListener.eventsListened.size() - 1); +// +// // verify the details in the sharedState change event details +// assertEquals("Shared state change (XDM)", sharedStateChangeEvent.getName()); +// // no need to verify for type and source, since the listener only listener to HUB SHARED_STATE +// assertEquals("TestModule", sharedStateChangeEvent.getData().getString2("stateowner")); +// } +// +// @Test +// public void createSharedStateAndDispatchEvent_Happy() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// +// +// eventHub.registerModule(TestModule.class); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// +// Module testModule = eventHub.getActiveModules().iterator().next(); +// assertNotNull(testModule); +// EventData state = new EventData(); +// state.putString("key", "value1"); +// final EventData eventData = new EventData() +// .putString("initialkey", "initialvalue"); +// final Event newEvent = new Event.Builder("Test Event", EventType.CUSTOM, EventSource.NONE).setData(eventData) +// .build(); +// TestListener.hearWasCalled = false; +// eventHub.registerModuleListener(testModule, EventType.CUSTOM, EventSource.NONE, null, TestListener.class); +// eventHub.createSharedStateAndDispatchEvent(testModule, state, newEvent); +// assertEquals(state, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule)); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertTrue(TestListener.hearWasCalled); +// +// } +// +// @Test +// public void createXDMSharedStateAndDispatchEvent_Happy() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// +// +// eventHub.registerModule(TestModule.class); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// +// Module testModule = eventHub.getActiveModules().iterator().next(); +// assertNotNull(testModule); +// EventData state = new EventData(); +// state.putString("key", "value1"); +// final EventData eventData = new EventData() +// .putString("initialkey", "initialvalue"); +// final Event newEvent = new Event.Builder("Test Event", EventType.CUSTOM, EventSource.NONE).setData(eventData) +// .build(); +// TestListener.hearWasCalled = false; +// eventHub.registerModuleListener(testModule, EventType.CUSTOM, EventSource.NONE, null, TestListener.class); +// eventHub.createSharedStateAndDispatchEvent(testModule, state, newEvent, SharedStateType.XDM); +// assertEquals(state, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule, SharedStateType.XDM)); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertTrue(TestListener.hearWasCalled); +// +// } +// +// +// @Test +// public void createOrUpdateWithoutVersionSharedState_Happy() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// Module testModule = new Module("testStateName", eventHub) {}; +// +// EventData state1 = new EventData(); +// state1.putString("key", "value1"); +// EventData state2 = new EventData(); +// state2.putString("key", "value2"); +// +// eventHub.createOrUpdateSharedState(testModule, state1); +// assertEquals(state1, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule)); +// +// eventHub.createOrUpdateSharedState(testModule, state2); +// assertEquals(state2, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule)); +// +// } +// +// @Test +// public void createOrUpdateWithoutVersionSharedState_EventNumberIncrementedSequentially() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// Module testModule = new Module("testStateName", eventHub) {}; +// +// +// EventData state1 = new EventData(); +// state1.putString("key", "value1"); +// EventData state2 = new EventData(); +// state2.putString("key", "value2"); +// +// +// Event event1 = new Event.Builder("testName", EventType.CUSTOM, EventSource.NONE).build(); +// eventHub.dispatch(event1); +// assertEquals(1, event1.getEventNumber()); +// +// //createOrUpdateSharedState() fires a state change event +// eventHub.createOrUpdateSharedState(testModule, state1); +// +// Event event2 = new Event.Builder("testName", EventType.CUSTOM, EventSource.NONE).build(); +// eventHub.dispatch(event2); +// assertEquals(3, event2.getEventNumber()); +// } +// +// @Test +// public void createOrUpdateWithoutVersionXDMSharedState_Happy() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// Module testModule = new Module("testStateName", eventHub) {}; +// +// EventData state1 = new EventData(); +// state1.putString("key", "value1"); +// EventData state2 = new EventData(); +// state2.putString("key", "value2"); +// +// eventHub.createOrUpdateSharedState(testModule, state1, SharedStateType.XDM); +// assertEquals(state1, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule, SharedStateType.XDM)); +// +// eventHub.createOrUpdateSharedState(testModule, state2, SharedStateType.XDM); +// assertEquals(state2, eventHub.getSharedEventState(testModule.getModuleName(), null, testModule, SharedStateType.XDM)); +// +// } +// +// @Test +// public void createOrUpdateWithoutVersionXDMSharedState_EventNumberIncrementedSequentially() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// Module testModule = new Module("testStateName", eventHub) {}; +// +// EventData state1 = new EventData(); +// state1.putString("key", "value1"); +// EventData state2 = new EventData(); +// state2.putString("key", "value2"); +// +// Event event1 = new Event.Builder("testName", EventType.CUSTOM, EventSource.NONE).build(); +// eventHub.dispatch(event1); +// assertEquals(1, event1.getEventNumber()); +// +// //createOrUpdateSharedState() fires a state change event +// eventHub.createOrUpdateSharedState(testModule, state1, SharedStateType.XDM); +// +// Event event2 = new Event.Builder("testName", EventType.CUSTOM, EventSource.NONE).build(); +// eventHub.dispatch(event2); +// assertEquals(3, event2.getEventNumber()); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void clearSharedStates_NullStateName() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.clearSharedStates(new Module(null, eventHub) {}); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void clearXDMSharedStates_NullStateName() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.clearSharedStates(new Module(null, eventHub) {}, SharedStateType.XDM); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void clearSharedStates_NullModule() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.clearSharedStates(null); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void clearXDMSharedStates_NullModule() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.clearSharedStates(null, SharedStateType.XDM); +// } +// +// @Test +// public void clearSharedStates_ValidModuleAndStateName() throws Exception { +// // setup +// EventHub eventHub = new EventHub("eventhub", services); +// Module testModule = new Module("testStateName", eventHub) {}; +// EventData data = new EventData(); +// data.putString("testing", "clear"); +// data.putString("shared", "state"); +// Event event = new Event.Builder("testName", EventType.CUSTOM, EventSource.NONE).build(); +// eventHub.createSharedState(testModule, 0, data); +// assertEquals(data, eventHub.getSharedEventState(testModule.getModuleName(), event, testModule)); +// +// // test +// eventHub.clearSharedStates(testModule); +// +// // verify +// assertNull(eventHub.getSharedEventState(testModule.getModuleName(), event, testModule)); +// } +// +// @Test +// public void clearXDMSharedStates_ValidModuleAndStateName() throws Exception { +// // setup +// EventHub eventHub = new EventHub("eventhub", services); +// Module testModule = new Module("testStateName", eventHub) {}; +// EventData data = new EventData(); +// data.putString("testing", "clear"); +// data.putString("shared", "state"); +// Event event = new Event.Builder("testName", EventType.CUSTOM, EventSource.NONE).build(); +// eventHub.createSharedState(testModule, 0, data, SharedStateType.XDM); +// assertEquals(data, eventHub.getSharedEventState(testModule.getModuleName(), event, testModule, SharedStateType.XDM)); +// +// // test +// eventHub.clearSharedStates(testModule, SharedStateType.XDM); +// +// // verify +// assertNull(eventHub.getSharedEventState(testModule.getModuleName(), event, testModule, SharedStateType.XDM)); +// } +// +// @Test(expected = IllegalArgumentException.class) +// public void constructor_NullPlatformServices() { +// new EventHub("eventhub", null); +// } +// +// @Test(expected = InvalidModuleException.class) +// public void registerModule_NullClass() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerModule(null); +// } +// +// @Test +// public void registerModule_SameModuleNameMultipleTimes_ShouldRegisterOnlyOnce() throws Exception { +// Log.setLoggingService(services.fakeLoggingService); +// Log.setLogLevel(LoggingMode.VERBOSE); +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerModule(TestModuleNoListeners.class); +// eventHub.registerModule(TestModuleNoListeners.class); +// +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertTrue(services.fakeLoggingService.containsWarningLog("EventHub(eventhub)", +// "Failed to register extension, an extension with the same name (TestModule) already exists")); +// } +// @Test +// public void registerModule_SameInternalModuleNameMultipleTimes_ShouldRegisterOnlyOnce() throws Exception { +// Log.setLoggingService(services.fakeLoggingService); +// Log.setLogLevel(LoggingMode.VERBOSE); +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerModule(TestableModule.class); +// eventHub.registerModule(TestableModule.class); +// +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertTrue(services.fakeLoggingService.containsWarningLog("EventHub(eventhub)", +// "Failed to register extension, an extension with the same name (TestableModule) already exists")); +// } +// +// @Test +// public void registerExtension_SameExtensionNameMultipleTimes_ShouldRegisterOnlyOnce() throws Exception { +// Log.setLoggingService(services.fakeLoggingService); +// Log.setLogLevel(LoggingMode.VERBOSE); +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerExtension(TestExtension.class); +// eventHub.registerExtension(TestExtension.class); +// +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertTrue(TestExtension.onUnexpectedErrorWasCalled); +// assertEquals(ExtensionError.DUPLICATE_NAME, TestExtension.onUnexpectedErrorParamError.getErrorCode()); +// assertTrue(services.fakeLoggingService.containsErrorLog("EventHub(eventhub)", +// "Failed to register extension, an extension with the same name (test extension) already exists")); +// } +// +// @Test +// public void registerExtension_SameExtensionNameAsInternalModules_ShouldFail() throws Exception { +// Log.setLoggingService(services.fakeLoggingService); +// Log.setLogLevel(LoggingMode.VERBOSE); +// EventHub eventHub = new EventHub("eventhub", services); +// TestExtension.extensionName = "test extension"; +// TestModuleNoListeners.moduleName = "test extension"; +// eventHub.registerModule(TestModuleNoListeners.class); +// eventHub.registerExtension(TestExtension.class); +// +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertTrue(TestExtension.onUnexpectedErrorWasCalled); +// assertEquals(ExtensionError.DUPLICATE_NAME, TestExtension.onUnexpectedErrorParamError.getErrorCode()); +// assertTrue(services.fakeLoggingService.containsErrorLog("EventHub(eventhub)", +// "Failed to register extension, an extension with the same name (test extension) already exists")); +// } +// +// @Test +// public void registerExtension_EmptyName_ShouldFailWithBadName() throws Exception { +// Log.setLoggingService(services.fakeLoggingService); +// Log.setLogLevel(LoggingMode.VERBOSE); +// EventHub eventHub = new EventHub("eventhub", services); +// TestExtension.extensionName = ""; +// eventHub.registerExtension(TestExtension.class); +// +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertTrue(TestExtension.onUnexpectedErrorWasCalled); +// assertEquals(ExtensionError.BAD_NAME, TestExtension.onUnexpectedErrorParamError.getErrorCode()); +// assertTrue(services.fakeLoggingService.containsErrorLog("EventHub(eventhub)", +// "Failed to register extension, extension name should not be null or empty")); +// } +// +// @Test +// public void registerExtension_NullName_ShouldFailWithBadName() throws Exception { +// Log.setLoggingService(services.fakeLoggingService); +// Log.setLogLevel(LoggingMode.VERBOSE); +// EventHub eventHub = new EventHub("eventhub", services); +// TestExtension.extensionName = null; +// eventHub.registerExtension(TestExtension.class); +// +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertTrue(TestExtension.onUnexpectedErrorWasCalled); +// assertEquals(ExtensionError.BAD_NAME, TestExtension.onUnexpectedErrorParamError.getErrorCode()); +// assertTrue(services.fakeLoggingService.containsErrorLog("EventHub(eventhub)", +// "Failed to register extension, extension name should not be null or empty")); +// } +// +// @Test +// public void registerOneTimeListener_NullBlock() throws Exception { +// Log.setLoggingService(services.fakeLoggingService); +// Log.setLogLevel(LoggingMode.VERBOSE); +// final CountDownLatch latch = new CountDownLatch(1); +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerOneTimeListener("pairId", null); +// +// latch.await(1000, TimeUnit.MILLISECONDS); +// +// assertTrue(services.fakeLoggingService.containsDebugLog("EventHub(eventhub)", +// "Unexpected Null Value (callback block), failed to register one-time listener")); +// } +// +// @Test +// public void registerOneTimeListener_WithTimeOut() throws Exception { +// Log.setLoggingService(services.fakeLoggingService); +// Log.setLogLevel(LoggingMode.VERBOSE); +// final CountDownLatch latch = new CountDownLatch(1); +// EventHub eventHub = new EventHub("eventhub", services); +// eventHub.registerOneTimeListener("parid", new Module.OneTimeListenerBlock() { +// +// @Override +// public void call(Event e) { +// +// } +// }, new AdobeCallbackWithError() { +// @Override +// public void fail(AdobeError error) { +// latch.countDown(); +// } +// +// @Override +// public void call(Object value) { +// +// } +// }, 100); +// +// latch.await(1000, TimeUnit.MILLISECONDS); +// +// assertEquals("Failure callback should get called", 0, latch.getCount()); +// } +// +// private static CountDownLatch finishModulesRegistrationLatch; +// private static List finishModulesRegistrationEvents; +// private static int moduleNumber = 1; +// private static class FinishModulesRegistrationModule extends Module { +// public FinishModulesRegistrationModule(final EventHub hub) { +// super("FinishModulesRegistrationModule" + moduleNumber++, hub); +// registerListener(EventType.HUB, EventSource.BOOTED, myListener.class); +// +// try { +// Thread.sleep(100); +// } catch (Exception ex) { +// +// } +// } +// +// static class myListener extends ModuleEventListener { +// public myListener(final FinishModulesRegistrationModule module, final EventType type, final EventSource source) { +// super(module, type, source); +// } +// @Override +// public void hear(Event e) { +// finishModulesRegistrationEvents.add(e); +// finishModulesRegistrationLatch.countDown(); +// } +// } +// } +// private static class FinishModulesRegistrationModule1 extends FinishModulesRegistrationModule { +// public FinishModulesRegistrationModule1(final EventHub hub) { +// super(hub); +// } +// } +// private static class FinishModulesRegistrationModule2 extends FinishModulesRegistrationModule { +// public FinishModulesRegistrationModule2(final EventHub hub) { +// super(hub); +// } +// } +// +// @Test +// public void bootedEventIsDispatched_When_FinishModulesRegistrationIsCalled() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// finishModulesRegistrationLatch = new CountDownLatch(1); +// finishModulesRegistrationEvents = new ArrayList(); +// final CountDownLatch callbackLatch = new CountDownLatch(1); +// +// eventHub.registerModule(FinishModulesRegistrationModule.class); +// +// eventHub.finishModulesRegistration(new AdobeCallback() { +// @Override +// public void call(Void value) { +// callbackLatch.countDown(); +// } +// }); +// +// assertTrue("Listeners were not called in time!!", finishModulesRegistrationLatch.await(1000, TimeUnit.MILLISECONDS)); +// assertEquals(1, finishModulesRegistrationEvents.size()); +// assertTrue("Callback were not called in time!!", callbackLatch.await(1000, TimeUnit.MILLISECONDS)); +// +// +// } +// +// @Test +// public void bootedEventIsDispatched_When_FinishModulesRegistrationIsCalled_For_MultipleModulesRegistering() throws +// Exception { +// Log.setLoggingService(services.fakeLoggingService); +// EventHub eventHub = new EventHub("eventhub", services); +// +// // reset counters +// finishModulesRegistrationEvents = new ArrayList(); +// finishModulesRegistrationLatch = new CountDownLatch(3); +// +// // register 3 modules +// eventHub.registerModule(FinishModulesRegistrationModule.class); +// eventHub.registerModule(FinishModulesRegistrationModule1.class); +// eventHub.registerModule(FinishModulesRegistrationModule2.class); +// +// // finish module registration and start eventhub +// eventHub.finishModulesRegistration(null); +// +// +// assertTrue("Listeners were not called in time: " + finishModulesRegistrationLatch.getCount(), +// finishModulesRegistrationLatch.await(5000, TimeUnit.MILLISECONDS)); +// assertEquals("Expecting " + 3 + " boot events heard!", 3, finishModulesRegistrationEvents.size()); +// +// } +// +// @Test +// public void bootedEventIsDispatchedOnlyOnce_When_FinishModulesRegistrationIsCalledMultitpleTimes() throws Exception { +// EventHub eventHub = new EventHub("eventhub", services); +// finishModulesRegistrationLatch = new CountDownLatch(1); +// finishModulesRegistrationEvents = new ArrayList(); +// +// eventHub.registerModule(FinishModulesRegistrationModule.class); +// +// eventHub.finishModulesRegistration(null); +// eventHub.finishModulesRegistration(null); +// eventHub.finishModulesRegistration(null); +// +// assertTrue("Listeners were not called in time!!", finishModulesRegistrationLatch.await(1000, TimeUnit.MILLISECONDS)); +// assertEquals(1, finishModulesRegistrationEvents.size()); +// +// } +// +// class TestableEventHub extends EventHub { +// final String eventHubSharedStateName = "com.adobe.module.eventhub"; +// TestableEventHub() { +// super("eventhub", services, "1.2.3"); +// } +// } +// +// @Test +// public void testConstructor_WithCoreVersion() { +// final TestableEventHub eventHub = new TestableEventHub(); +// assertEquals("1.2.3", eventHub.coreVersion); +// } +// +// @Test +// public void testRegisterModuleWithCallback_WillAddModuleToSharedState() throws Exception { +// // setup +// final TestableEventHub eventHub = new TestableEventHub(); +// final Module testModule = new TestModule(eventHub); +// final ModuleDetails moduleDetails = new ModuleDetails() { +// @Override +// public String getName() { +// return "testModule"; +// } +// +// @Override +// public String getVersion() { +// return "testVersion"; +// } +// +// @Override +// public Map getAdditionalInfo() { +// return null; +// } +// }; +// +// // test +// try { +// eventHub.registerModuleWithCallback(TestModule.class, moduleDetails, null); +// } catch (InvalidModuleException ex) { +// // nothing +// } +// +// final CountDownLatch waitForBootLatch = new CountDownLatch(1); +// eventHub.finishModulesRegistration(new AdobeCallback() { +// @Override +// public void call(Void value) { +// waitForBootLatch.countDown(); +// } +// }); +// +// waitForBootLatch.await(100, TimeUnit.MILLISECONDS); +// // verify +// final Map extensions = eventHub.eventHubSharedState.optVariantMap("extensions", null); +// assertNotNull(extensions); +// assertTrue(extensions.containsKey(testModule.getModuleName())); +// final Map moduleInSharedState = extensions.get(testModule.getModuleName()).optVariantMap(null); +// assertTrue(moduleInSharedState.containsKey("version")); +// assertEquals(moduleDetails.getVersion(), moduleInSharedState.get("version").optString(null)); +// assertEquals(moduleDetails.getName(), moduleInSharedState.get("friendlyName").optString(null)); +// } +// +// @Test +// public void testUnregisterModule_When_ModuleIsInSharedState() { +// // setup +// final TestableEventHub eventHub = new TestableEventHub(); +// final Module testModule = new TestModule(eventHub); +// final ModuleDetails moduleDetails = new ModuleDetails() { +// @Override +// public String getName() { +// return "testModule"; +// } +// +// @Override +// public String getVersion() { +// return "testVersion"; +// } +// +// @Override +// public Map getAdditionalInfo() { +// return null; +// } +// }; +// testModule.setModuleDetails(moduleDetails); +// final Map extensions = new HashMap(); +// final Map module = new HashMap(); +// module.put("version", Variant.fromString(moduleDetails.getVersion())); +// extensions.put(testModule.getModuleName(), Variant.fromVariantMap(module)); +// eventHub.eventHubSharedState.putVariantMap("extensions", extensions); +// +// // test +// try { +// eventHub.unregisterModule(testModule); +// } catch (final InvalidModuleException ex) {} +// +// // verify +// final Map newExtensions = eventHub.eventHubSharedState.optVariantMap("extensions", null); +// assertNotNull(newExtensions); +// assertFalse(newExtensions.containsKey(moduleDetails.getName())); +// } +// +// @Test +// public void testCreateEventHubSharedState() { +// // setup +// final TestableEventHub eventHub = new TestableEventHub(); +// final Module testModule = new TestModule(eventHub); +// +// // test +// eventHub.createEventHubSharedState(0); +// eventHub.finishModulesRegistration(null); +// +// // verify +// assertEquals(eventHub.eventHubSharedState, eventHub.getSharedEventState(eventHub.eventHubSharedStateName, null, +// testModule)); +// } +// +// @Test +// public void testAddModuleToEventHubSharedState_When_Happy() { +// // setup +// final TestableEventHub eventHub = new TestableEventHub(); +// eventHub.isBooted = true; +// final Module testModule = new TestModule(eventHub); +// final ModuleDetails moduleDetails = new ModuleDetails() { +// @Override +// public String getName() { +// return "testModule"; +// } +// +// @Override +// public String getVersion() { +// return "testVersion"; +// } +// +// @Override +// public Map getAdditionalInfo() { +// return null; +// } +// }; +// testModule.setModuleDetails(moduleDetails); +// +// // test +// eventHub.addModuleToEventHubSharedState(testModule); +// +// // verify +// assertNotNull(eventHub.eventHubSharedState); +// final Map extensions = eventHub.eventHubSharedState.optVariantMap("extensions", null); +// assertTrue(extensions.containsKey(testModule.getModuleName())); +// final Map module = extensions.get(testModule.getModuleName()).optVariantMap(null); +// assertTrue(module.containsKey("version")); +// assertEquals(moduleDetails.getVersion(), module.get("version").optString(null)); +// assertEquals(moduleDetails.getName(), module.get("friendlyName").optString(null)); +// assertEquals(eventHub.eventHubSharedState, eventHub.getSharedEventState(eventHub.eventHubSharedStateName, null, +// testModule)); +// } +// +// @Test +// public void testAddModuleToEventHubSharedState_When_ModuleIsNull_Then_NothingHappens() { +// // setup +// final TestableEventHub eventHub = new TestableEventHub(); +// eventHub.isBooted = true; +// +// // test +// eventHub.addModuleToEventHubSharedState(null); +// +// // verify +// assertNotNull(eventHub.eventHubSharedState); +// assertEquals(0, eventHub.eventHubSharedState.optVariantMap("extensions", null).size()); +// } +// +// @Test +// public void testAddModuleToEventHubSharedState_When_ModuleDetailsIsNull_Then_UseModuleGetName() { +// // setup +// final TestableEventHub eventHub = new TestableEventHub(); +// eventHub.isBooted = true; +// final Module testModule = new TestModule(eventHub); +// +// // test +// eventHub.addModuleToEventHubSharedState(testModule); +// +// // verify +// assertNotNull(eventHub.eventHubSharedState); +// final Map extensions = eventHub.eventHubSharedState.optVariantMap("extensions", null); +// assertEquals(1, extensions.size()); +// assertTrue(extensions.containsKey(testModule.getModuleName())); +// } +// +// @Test +// public void testAddModuleToEventHubSharedState_When_ModuleDetailsNameIsEmpty_Then_FriendlyNameIsEmpty() { +// // setup +// final TestableEventHub eventHub = new TestableEventHub(); +// eventHub.isBooted = true; +// final Module testModule = new TestModule(eventHub); +// final ModuleDetails moduleDetails = new ModuleDetails() { +// @Override +// public String getName() { +// return ""; +// } +// +// @Override +// public String getVersion() { +// return "testVersion"; +// } +// +// @Override +// public Map getAdditionalInfo() { +// return null; +// } +// }; +// testModule.setModuleDetails(moduleDetails); +// +// // test +// eventHub.addModuleToEventHubSharedState(testModule); +// +// // verify +// assertNotNull(eventHub.eventHubSharedState); +// final Map extensions = eventHub.eventHubSharedState.optVariantMap("extensions", null); +// assertEquals(1, extensions.size()); +// final Map module = extensions.get(testModule.getModuleName()).optVariantMap(null); +// assertTrue(module.containsKey("version")); +// assertEquals(moduleDetails.getVersion(), module.get("version").optString(null)); +// assertEquals(moduleDetails.getName(), module.get("friendlyName").optString(null)); +// } +// +// @Test +// public void testAddModuleToEventHubSharedState_When_ModuleDetailsNameIsNull_Then_UseModuleGetNameForFriendlyName() { +// // setup +// final TestableEventHub eventHub = new TestableEventHub(); +// eventHub.isBooted = true; +// final Module testModule = new TestModule(eventHub); +// final ModuleDetails moduleDetails = new ModuleDetails() { +// @Override +// public String getName() { +// return null; +// } +// +// @Override +// public String getVersion() { +// return "testVersion"; +// } +// +// @Override +// public Map getAdditionalInfo() { +// return null; +// } +// }; +// testModule.setModuleDetails(moduleDetails); +// +// // test +// eventHub.addModuleToEventHubSharedState(testModule); +// +// // verify +// assertNotNull(eventHub.eventHubSharedState); +// final Map extensions = eventHub.eventHubSharedState.optVariantMap("extensions", null); +// assertEquals(1, extensions.size()); +// final Map module = extensions.get(testModule.getModuleName()).optVariantMap(null); +// assertTrue(module.containsKey("version")); +// assertEquals(moduleDetails.getVersion(), module.get("version").optString(null)); +// assertEquals(testModule.getModuleName(), module.get("friendlyName").optString(null)); +// } +// +// @Test +// public void testAddModuleToEventHubSharedState_When_EventHubNotFinishedBooting_Then_StateNotShared() { +// // setup +// final TestableEventHub eventHub = new TestableEventHub(); +// eventHub.isBooted = false; +// final Module testModule = new TestModule(eventHub); +// final ModuleDetails moduleDetails = new ModuleDetails() { +// @Override +// public String getName() { +// return "testModule"; +// } +// +// @Override +// public String getVersion() { +// return "testVersion"; +// } +// +// @Override +// public Map getAdditionalInfo() { +// return null; +// } +// }; +// testModule.setModuleDetails(moduleDetails); +// +// // test +// eventHub.addModuleToEventHubSharedState(testModule); +// +// // verify +// assertNotNull(eventHub.eventHubSharedState); +// final Map extensions = eventHub.eventHubSharedState.optVariantMap("extensions", null); +// assertTrue(extensions.containsKey(testModule.getModuleName())); +// final Map module = extensions.get(testModule.getModuleName()).optVariantMap(null); +// assertTrue(module.containsKey("version")); +// assertEquals(moduleDetails.getVersion(), module.get("version").optString(null)); +// assertEquals(moduleDetails.getName(), module.get("friendlyName").optString(null)); +// assertNotEquals(eventHub.eventHubSharedState, eventHub.getSharedEventState(eventHub.eventHubSharedStateName, null, +// testModule)); +// } +// +// @Test +// public void testAddModuleToEventHubSharedState_When_AddingExtension() throws Exception { +// // setup +// final TestableEventHub eventHub = new TestableEventHub(); +// eventHub.isBooted = false; +// +// // test +// eventHub.registerExtension(TestExtension.class); +// latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// +// // verify +// assertNotNull(eventHub.eventHubSharedState); +// final Map extensions = eventHub.eventHubSharedState.optVariantMap("extensions", null); +// assertTrue(extensions.containsKey("test extension")); +// final Map module = extensions.get("test extension").optVariantMap(null); +// assertTrue(module.containsKey("version")); +// assertEquals("1.1.0", module.get("version").optString(null)); +// assertEquals("test extension friendly name", module.get("friendlyName").optString(null)); +// } +// +// @Test +// public void testRemoveModuleFromEventHubSharedState_When_Happy() { +// // setup +// final TestableEventHub eventHub = new TestableEventHub(); +// eventHub.isBooted = true; +// final Module testModule = new TestModule(eventHub); +// final ModuleDetails moduleDetails = new ModuleDetails() { +// @Override +// public String getName() { +// return "testModule"; +// } +// +// @Override +// public String getVersion() { +// return "testVersion"; +// } +// +// @Override +// public Map getAdditionalInfo() { +// return null; +// } +// }; +// testModule.setModuleDetails(moduleDetails); +// final Map preExtensions = new HashMap(); +// final Map preModule = new HashMap(); +// preModule.put("version", Variant.fromString(moduleDetails.getVersion())); +// preExtensions.put(testModule.getModuleName(), Variant.fromVariantMap(preModule)); +// eventHub.eventHubSharedState.putVariantMap("extensions", preExtensions); +// +// // test +// eventHub.removeModuleFromEventHubSharedState(testModule); +// +// // verify +// assertNotNull(eventHub.eventHubSharedState); +// assertEquals(0, eventHub.eventHubSharedState.optVariantMap("extensions", null).size()); +// assertEquals(eventHub.eventHubSharedState, eventHub.getSharedEventState(eventHub.eventHubSharedStateName, null, +// testModule)); +// } +// +// @Test +// public void testRemoveModuleFromEventHubSharedState_When_EventHubNotFinishedBooting_Then_StateNotShared() { +// // setup +// final TestableEventHub eventHub = new TestableEventHub(); +// eventHub.isBooted = true; +// final Module testModule = new TestModule(eventHub); +// final ModuleDetails moduleDetails = new ModuleDetails() { +// @Override +// public String getName() { +// return "testModule"; +// } +// +// @Override +// public String getVersion() { +// return "testVersion"; +// } +// +// @Override +// public Map getAdditionalInfo() { +// return null; +// } +// }; +// testModule.setModuleDetails(moduleDetails); +// eventHub.addModuleToEventHubSharedState(testModule); +// eventHub.isBooted = false; +// +// // test +// eventHub.removeModuleFromEventHubSharedState(testModule); +// +// // verify +// assertNotNull(eventHub.eventHubSharedState); +// assertEquals(0, eventHub.eventHubSharedState.optVariantMap("extensions", null).size()); +// final EventData sharedState = eventHub.getSharedEventState(eventHub.eventHubSharedStateName, null, +// testModule); +// // TODO: the line below doesn't pass, which may be a problem with eventhub +// // assertNotEquals(eventHub.eventHubSharedState, sharedState); +// } +// +// @Test +// public void testRemoveModuleFromEventHubSharedState_When_ModuleIsNull_Then_NothingHappens() { +// // setup +// final TestableEventHub eventHub = new TestableEventHub(); +// eventHub.isBooted = true; +// final Module testModule = new TestModule(eventHub); +// final ModuleDetails moduleDetails = new ModuleDetails() { +// @Override +// public String getName() { +// return "testModule"; +// } +// +// @Override +// public String getVersion() { +// return "testVersion"; +// } +// +// @Override +// public Map getAdditionalInfo() { +// return null; +// } +// }; +// testModule.setModuleDetails(moduleDetails); +// final Map preExtensions = new HashMap(); +// final Map preModule = new HashMap(); +// preModule.put("version", Variant.fromString(moduleDetails.getVersion())); +// preExtensions.put(moduleDetails.getName(), Variant.fromVariantMap(preModule)); +// eventHub.eventHubSharedState.putVariantMap("extensions", preExtensions); +// +// // test +// eventHub.removeModuleFromEventHubSharedState(null); +// +// // verify +// assertNotNull(eventHub.eventHubSharedState); +// final Map extensions = eventHub.eventHubSharedState.optVariantMap("extensions", null); +// assertTrue(extensions.containsKey(moduleDetails.getName())); +// final Map module = extensions.get(moduleDetails.getName()).optVariantMap(null); +// assertTrue(module.containsKey("version")); +// assertEquals(moduleDetails.getVersion(), module.get("version").optString(null)); +// } +// +// @Test +// public void testRemoveModuleFromEventHubSharedState_When_ModuleDetailsIsNull_Then_NothingHappens() { +// // setup +// final TestableEventHub eventHub = new TestableEventHub(); +// eventHub.isBooted = true; +// final Module testModule = new TestModule(eventHub); +// final ModuleDetails moduleDetails = new ModuleDetails() { +// @Override +// public String getName() { +// return "testModule"; +// } +// +// @Override +// public String getVersion() { +// return "testVersion"; +// } +// +// @Override +// public Map getAdditionalInfo() { +// return null; +// } +// }; +// final Map preExtensions = new HashMap(); +// final Map preModule = new HashMap(); +// preModule.put("version", Variant.fromString(moduleDetails.getVersion())); +// preExtensions.put(moduleDetails.getName(), Variant.fromVariantMap(preModule)); +// eventHub.eventHubSharedState.putVariantMap("extensions", preExtensions); +// +// // test +// eventHub.removeModuleFromEventHubSharedState(testModule); +// +// // verify +// assertNotNull(eventHub.eventHubSharedState); +// final Map extensions = eventHub.eventHubSharedState.optVariantMap("extensions", null); +// assertTrue(extensions.containsKey(moduleDetails.getName())); +// final Map module = extensions.get(moduleDetails.getName()).optVariantMap(null); +// assertTrue(module.containsKey("version")); +// assertEquals(moduleDetails.getVersion(), module.get("version").optString(null)); +// } +// +// @Test +// public void testGetInitialEventHubSharedState_DefaultWrapperNone() { +// // setup +// final TestableEventHub eventHub = new TestableEventHub(); +// +// // test +// final EventData result = eventHub.getInitialEventHubSharedState(); +// +// // verify +// assertNotNull(result); +// assertTrue(result.containsKey("version")); +// assertTrue(result.containsKey("extensions")); +// assertTrue(result.containsKey("wrapper")); +// assertEquals("1.2.3", result.optString("version", null)); +// final Map extensions = result.optVariantMap("extensions", null); +// assertEquals(0, extensions.size()); +// +// //verify wrapper details +// final Map wrapper = result.optVariantMap("wrapper", null); +// assertEquals("N", wrapper.get("type").optString("")); +// assertEquals("None", wrapper.get("friendlyName").optString("")); +// } +// +// @Test +// public void testGetInitialEventHubSharedState() { +// // setup +// final TestableEventHub eventHub = new TestableEventHub(); +// eventHub.setWrapperType(WrapperType.REACT_NATIVE); +// +// // test +// final EventData result = eventHub.getInitialEventHubSharedState(); +// +// // verify +// assertNotNull(result); +// assertTrue(result.containsKey("version")); +// assertTrue(result.containsKey("extensions")); +// assertTrue(result.containsKey("wrapper")); +// assertEquals("1.2.3", result.optString("version", null)); +// final Map extensions = result.optVariantMap("extensions", null); +// assertEquals(0, extensions.size()); +// +// //verify wrapper details +// final Map wrapper = result.optVariantMap("wrapper", null); +// assertEquals("R", wrapper.get("type").optString("")); +// assertEquals("React Native", wrapper.get("friendlyName").optString("")); +// } +// +// @Test +// public void testSetWrapperType_HubNotBooted_Happy() { +// // setup +// final TestableEventHub eventHub = new TestableEventHub(); +// eventHub.isBooted = false; +// +// // test +// eventHub.setWrapperType(WrapperType.FLUTTER); +// +// // verify +// assertEquals(WrapperType.FLUTTER, eventHub.getWrapperType()); +// } +// +// @Test +// public void testSetWrapperType_HubBooted_Fail() { +// // setup +// final TestableEventHub eventHub = new TestableEventHub(); +// eventHub.isBooted = true; +// +// // test +// eventHub.setWrapperType(WrapperType.FLUTTER); +// +// // verify +// assertEquals(WrapperType.NONE, eventHub.getWrapperType()); +// } +// +// @Test +// public void testSetWrapperType_HubNotBooted_AllowMultipleUpdates_shouldNotUpdateAfterBooting() { +// // setup +// final TestableEventHub eventHub = new TestableEventHub(); +// eventHub.isBooted = false; +// +// // test +// eventHub.setWrapperType(WrapperType.FLUTTER); +// assertEquals(WrapperType.FLUTTER, eventHub.getWrapperType()); +// +// eventHub.setWrapperType(WrapperType.CORDOVA); +// assertEquals(WrapperType.CORDOVA, eventHub.getWrapperType()); +// +// eventHub.isBooted = true; +// eventHub.setWrapperType(WrapperType.REACT_NATIVE); +// assertEquals(WrapperType.CORDOVA, eventHub.getWrapperType()); +// } +// +// @Test +// public void testGetWrapperType_defaultWrapperNone() {// setup +// final TestableEventHub eventHub = new TestableEventHub(); +// assertEquals(WrapperType.NONE, eventHub.getWrapperType()); +// } +// +// @Test +// public void testGetSDKVersion_wrapperNone() { +// final TestableEventHub eventHub = new TestableEventHub(); +// assertEquals("1.2.3", eventHub.getSdkVersion()); +// } +// +// @Test +// public void testGetSDKVersion_wrapperTypeSet() { +// final TestableEventHub eventHub = new TestableEventHub(); +// eventHub.setWrapperType(WrapperType.REACT_NATIVE); +// assertEquals("1.2.3-R", eventHub.getSdkVersion()); +// } +//} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ExtensionApiTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ExtensionApiTests.java index 11ab6ee3b..fb4da8155 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ExtensionApiTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ExtensionApiTests.java @@ -1,849 +1,849 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import org.junit.Before; -import org.junit.Test; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import static junit.framework.TestCase.assertTrue; -import static org.junit.Assert.*; - -class MockListener extends ExtensionListener { - static boolean hearWasCalled; - static boolean listenerInitialized; - static boolean onUnregisteredWasCalled; - - public MockListener(final ExtensionApi module, final String type, final String source) { - super(module, type, source); - listenerInitialized = true; - } - - @Override - public void hear(Event e) { - hearWasCalled = true; - } - - @Override - public void onUnregistered() { - onUnregisteredWasCalled = true; - } -} - -class MockListenerThatThrows extends ExtensionListener { - public MockListenerThatThrows(final ExtensionApi module, final String type, final String source) throws Exception { - super(module, type, source); - throw new ExtensionUnexpectedError(ExtensionError.UNEXPECTED_ERROR); - } - - @Override - public void hear(Event e) { } - - @Override - public void onUnregistered() { } -} - -public class ExtensionApiTests { - private static CountDownLatch eventHubLatch = new CountDownLatch(1); - private static int EVENTHUB_WAIT_MS = 50; - private static String TEST_SHARED_STATE = "com.adobe.module.configuration"; - private ExtensionApi extensionApi; - private MockEventHubUnitTest mockEventHub; - private ExtensionErrorCallback errorCallback; - private final ExtensionError[] returnedError = new ExtensionError[1]; - - static class MockExtension extends Extension { - static boolean onUnregisteredWasCalled; - static boolean onUnexpectedErrorWasCalled; - - MockExtension(final ExtensionApi api) { - super(api); - } - - @Override - public String getName() { - return "testExtension"; - } - - @Override - public String getVersion() { - return "1.0"; - } - - @Override - public void onUnregistered() { - onUnregisteredWasCalled = true; - } - - @Override - public void onUnexpectedError(final ExtensionUnexpectedError extensionUnexpectedError) { - onUnexpectedErrorWasCalled = true; - } - } - - static class MockExtension2 extends Extension { - MockExtension2(final ExtensionApi api) { - super(api); - } - - @Override - public String getName() { - return "testExtension2"; - } - - @Override - public String getVersion() { - return "2.0"; - } - - @Override - public void onUnregistered() {} - } - - @Before - public void setup() { - - // initialize static flags - MockListener.hearWasCalled = false; - MockListener.listenerInitialized = false; - MockListener.onUnregisteredWasCalled = false; - MockExtension.onUnregisteredWasCalled = false; - - try { - // register the MockExtension - mockEventHub = new MockEventHubUnitTest("hub", new MockPlatformServices()); - mockEventHub.registerExtension(MockExtension.class); - eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - extensionApi = (ExtensionApi)mockEventHub.getActiveModules().iterator().next(); - assertNotNull(extensionApi); - MockExtension mockExtension = (MockExtension)extensionApi.getExtension(); - assertNotNull(mockExtension); - returnedError[0] = null; - errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - returnedError[0] = extensionError; - } - }; - - } catch (Exception e) { - fail("Failure initializing test"); - } - } - - @Test - public void testSetExtension_works() { - ExtensionApi extensionApi = new ExtensionApi(mockEventHub); - extensionApi.setExtension(new MockExtension(extensionApi)); - assertEquals("testExtension", extensionApi.getModuleName()); - } - - @Test - public void testSetExtension_doesNot_updateExtensionIfInitialized() { - extensionApi.setExtension(new MockExtension2(extensionApi)); - assertEquals("testExtension", extensionApi.getModuleName()); - assertEquals("1.0", extensionApi.getModuleVersion()); - } - - @Test - public void testOnUnregistered_works() throws Exception { - extensionApi.onUnregistered(); - eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(MockExtension.onUnregisteredWasCalled); - } - - @Test - public void testOnUnregistered_doesNot_crash_when_extensionNotInitialized() { - // test and verify does not throw - try { - ExtensionApi extensionApi = new ExtensionApi(mockEventHub); - extensionApi.onUnregistered(); - } catch (Exception e) { - fail("On unregistered should not have thrown any exception when extension not initialized"); - } - - assertFalse(MockExtension.onUnregisteredWasCalled); - } - - @Test - public void testRegisterEventListener_works() throws Exception { - assertTrue(extensionApi.registerEventListener("new.event.type", "new.event.source", MockListener.class, null)); - eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(MockListener.listenerInitialized); - ConcurrentLinkedQueue listeners = mockEventHub.getModuleListeners(extensionApi); - eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - Log.error("testRegisterEventListener_works", "listeners.size()=%d", listeners.size()); - assertEquals(1, listeners.size()); - EventListener registeredListener = listeners.peek(); - assertNotNull(registeredListener); - assertTrue(registeredListener instanceof MockListener); - assertEquals("new.event.type", registeredListener.getEventType().getName()); - assertEquals("new.event.source", registeredListener.getEventSource().getName()); - } - - @Test - public void testRegisterEventListener_multipleListeners_works() throws Exception { - assertTrue(extensionApi.registerEventListener("new.event.type1", "new.event.source1", MockListener.class, null)); - assertTrue(extensionApi.registerEventListener("new.event.type2", "new.event.source2", MockListener.class, null)); - assertTrue(extensionApi.registerEventListener("new.event.type3", "new.event.source3", MockListener.class, null)); - assertTrue(extensionApi.registerEventListener("new.event.type4", "new.event.source4", MockListener.class, null)); - eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(MockListener.listenerInitialized); - ConcurrentLinkedQueue listeners = mockEventHub.getModuleListeners(extensionApi); - assertEquals(4, listeners.size()); - } - - @Test - public void testRegisterEventListener_nullListener_doesNot_throw_withErrorCallback() { - // test and verify does not throw and returns error - try { - assertFalse(extensionApi.registerEventListener("new.event.type", "new.event.source", null, errorCallback)); - } catch (Exception e) { - fail("On registerEventListener should not have thrown any exception when listener null"); - } - - assertEquals(ExtensionError.UNEXPECTED_ERROR, returnedError[0]); - } - - @Test - public void testRegisterEventListener_nullListener_doesNot_throw() { - // test and verify does not throw - try { - assertFalse(extensionApi.registerEventListener("new.event.type", "new.event.source", null, null)); - } catch (Exception e) { - fail("On registerEventListener should not have thrown any exception when listener null"); - } - } - - @Test - public void testRegisterEventListener_nullEventType_doesNot_throw_withErrorCallback() { - // test and verify does not throw and returns error - try { - assertFalse(extensionApi.registerEventListener(null, "new.event.source", MockListener.class, errorCallback)); - } catch (Exception e) { - fail("On registerEventListener should not have thrown any exception when event type null"); - } - - assertEquals(ExtensionError.EVENT_TYPE_NOT_SUPPORTED, returnedError[0]); - } - - @Test - public void testRegisterEventListener_nullEventType_doesNot_throw() { - // test and verify does not throw - try { - assertFalse(extensionApi.registerEventListener(null, "new.event.source", MockListener.class, null)); - } catch (Exception e) { - fail("On registerEventListener should not have thrown any exception when event type null"); - } - } - - @Test - public void testRegisterEventListener_nullEventSource_doesNot_throw_withErrorCallback() { - // test and verify does not throw and returns error - try { - assertFalse(extensionApi.registerEventListener("new.event.type", null, MockListener.class, errorCallback)); - } catch (Exception e) { - fail("On registerEventListener should not have thrown any exception when event source null"); - } - - assertEquals(ExtensionError.EVENT_SOURCE_NOT_SUPPORTED, returnedError[0]); - } - - @Test - public void testRegisterEventListener_nullEventSource_doesNot_throw() { - // test and verify does not throw - try { - assertFalse(extensionApi.registerEventListener("new.event.type", null, MockListener.class, null)); - } catch (Exception e) { - fail("On registerEventListener should not have thrown any exception when event source null"); - } - } - - @Test - public void testRegisterEventListener_constructorThrow_callsOnUnexpectedError() { - // test and verify does not throw - try { - assertTrue(extensionApi.registerEventListener("new.event.type", "new.event.source", MockListenerThatThrows.class, - null)); - eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - } catch (Exception e) { - fail("On registerEventListener should not have thrown any exception for this class"); - } - - assertTrue(MockExtension.onUnexpectedErrorWasCalled); - } - - @Test - public void testUnregisterExtension_unregisteredCalled() throws Exception { - extensionApi.unregisterExtension(); - eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(MockExtension.onUnregisteredWasCalled); - } - - @Test - public void testRegisterWildcardListener_works() throws Exception { - assertTrue(extensionApi.registerWildcardListener(MockListener.class, null)); - eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(MockListener.listenerInitialized); - ConcurrentLinkedQueue listeners = mockEventHub.getModuleListeners(extensionApi); - Log.error("testRegisterEventListener_works", "listeners.size()=%d", listeners.size()); - assertEquals(1, listeners.size()); - EventListener registeredListener = listeners.peek(); - assertNotNull(registeredListener); - assertTrue(registeredListener instanceof MockListener); - assertEquals(EventType.WILDCARD, registeredListener.getEventType()); - assertEquals(EventSource.WILDCARD, registeredListener.getEventSource()); - } - - @SuppressWarnings("all") - @Test - public void testRegisterWildcardListener_whenCalledMultipleTimes_registersOneListener_unregisterExistingOnes() throws - Exception { - assertTrue(extensionApi.registerWildcardListener(MockListener.class, null)); - eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertFalse(MockListener.onUnregisteredWasCalled); - assertTrue(MockListener.listenerInitialized); - MockListener.listenerInitialized = false; - assertTrue(extensionApi.registerWildcardListener(MockListener.class, null)); - eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(MockListener.onUnregisteredWasCalled); - assertTrue(MockListener.listenerInitialized); - MockListener.onUnregisteredWasCalled = false; - MockListener.listenerInitialized = false; - assertTrue(extensionApi.registerWildcardListener(MockListener.class, null)); - eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(MockListener.onUnregisteredWasCalled); - assertTrue(MockListener.listenerInitialized); - MockListener.onUnregisteredWasCalled = false; - MockListener.listenerInitialized = false; - ConcurrentLinkedQueue listeners = mockEventHub.getModuleListeners(extensionApi); - assertEquals(1, listeners.size()); - } - - @Test - public void testRegisterWildcardListener_nullListener_doesNot_throw_withErrorCallback() { - // test and verify does not throw and returns error - try { - assertFalse(extensionApi.registerWildcardListener(null, errorCallback)); - } catch (Exception e) { - fail("On registerWildcardListener should not have thrown any exception when listener null"); - } - - assertEquals(ExtensionError.UNEXPECTED_ERROR, returnedError[0]); - } - - @Test - public void testRegisterWildcardListener_nullListener_doesNot_throw() { - // test and verify does not throw - try { - assertFalse(extensionApi.registerWildcardListener(null, null)); - } catch (Exception e) { - fail("On registerWildcardListener should not have thrown any exception when listener null"); - } - } - - @Test - public void testUnregisterExtension_when_notRegistered_works() { - try { - ExtensionApi extensionApi = new ExtensionApi(mockEventHub); - extensionApi.unregisterExtension(); - } catch (Exception e) { - fail("On unregisterExtension should not have thrown any exception when not registered before"); - } - } - - @Test - public void testUnregisterExtension_eventListenerUnregister_Works() throws Exception { - // setup - extensionApi.registerEventListener("new.event.type", "new.event.source", MockListener.class, null); - eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - - // test - extensionApi.unregisterExtension(); - eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - - // verify - assertTrue(MockExtension.onUnregisteredWasCalled); - assertTrue(MockListener.onUnregisteredWasCalled); - ConcurrentLinkedQueue listeners = mockEventHub.getModuleListeners(extensionApi); - assertNull(listeners); - } - - @Test - public void testGetSharedEventState_works() throws Exception { - // setup - Map eventData = getMixedData(); - mockEventHub.createSharedState(TEST_SHARED_STATE, 1, new EventData().putString("first", "time")); - mockEventHub.createSharedState(TEST_SHARED_STATE, 2, - EventData.fromObjectMap(eventData)); - Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(1).build(); - Event nextEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(2).build(); - - // test&verify - Map resultSharedState = extensionApi.getSharedEventState(TEST_SHARED_STATE, event, - errorCallback); - assertEquals(new HashMap() { - { - put("first", "time"); - } - }, resultSharedState); - assertNull(returnedError[0]); - - resultSharedState = extensionApi.getSharedEventState(TEST_SHARED_STATE, nextEvent, - errorCallback); - assertEquals(eventData, resultSharedState); - assertNull(returnedError[0]); - } - - @Test - public void testGetXDMSharedEventState_works() throws Exception { - // setup - Map eventData = getMixedData(); - mockEventHub.createSharedState(TEST_SHARED_STATE, 1, new EventData().putString("first", "time"), SharedStateType.XDM); - mockEventHub.createSharedState(TEST_SHARED_STATE, 2, - EventData.fromObjectMap(eventData), SharedStateType.XDM); - Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(1).build(); - Event nextEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(2).build(); - - // test&verify - Map resultSharedState = extensionApi.getXDMSharedEventState(TEST_SHARED_STATE, event, - errorCallback); - assertEquals(new HashMap() { - { - put("first", "time"); - } - }, resultSharedState); - assertNull(returnedError[0]); - - resultSharedState = extensionApi.getXDMSharedEventState(TEST_SHARED_STATE, nextEvent, - errorCallback); - assertEquals(eventData, resultSharedState); - assertNull(returnedError[0]); - } - - @Test - public void testGetSharedEventState_returnsError_whenErrorOccurs() { - // setup - Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(100).build(); - mockEventHub.throwException = true; - - // test&verify - Map resultSharedState = extensionApi.getSharedEventState(TEST_SHARED_STATE, event, - errorCallback); - assertNull(resultSharedState); - assertEquals(ExtensionError.UNEXPECTED_ERROR, returnedError[0]); - } - - @Test - public void testGetXDMSharedEventState_returnsError_whenErrorOccurs() { - // setup - Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(100).build(); - mockEventHub.throwException = true; - - // test&verify - Map resultSharedState = extensionApi.getXDMSharedEventState(TEST_SHARED_STATE, event, - errorCallback); - assertNull(resultSharedState); - assertEquals(ExtensionError.UNEXPECTED_ERROR, returnedError[0]); - } - - @Test - public void testGetSharedEventState_returnsLatestSharedState_whenEventNull() throws Exception { - // setup - Map eventData = getMixedData(); - mockEventHub.createSharedState(TEST_SHARED_STATE, 1, EventData.fromObjectMap(eventData)); - mockEventHub.createSharedState(TEST_SHARED_STATE, 2, new EventData().putString("second", "time")); - - // test&verify - Map resultSharedState = extensionApi.getSharedEventState(TEST_SHARED_STATE, null, - errorCallback); - assertEquals(1, resultSharedState.size()); - assertEquals("time", resultSharedState.get("second")); - assertNull(returnedError[0]); - } - - @Test - public void testXDMGetSharedEventState_returnsLatestSharedState_whenEventNull() throws Exception { - // setup - Map eventData = getMixedData(); - mockEventHub.createSharedState(TEST_SHARED_STATE, 1, EventData.fromObjectMap(eventData), SharedStateType.XDM); - mockEventHub.createSharedState(TEST_SHARED_STATE, 2, new EventData().putString("second", "time"), SharedStateType.XDM); - - // test&verify - Map resultSharedState = extensionApi.getXDMSharedEventState(TEST_SHARED_STATE, null, - errorCallback); - assertEquals(1, resultSharedState.size()); - assertEquals("time", resultSharedState.get("second")); - assertNull(returnedError[0]); - } - - @Test - public void testGetSharedEventState_returnsNull_whenStateNameNull() { - // setup - Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(100).build(); - - // test&verify - Map resultSharedState = extensionApi.getSharedEventState(null, event, - errorCallback); - assertNull(resultSharedState); - assertEquals(ExtensionError.UNEXPECTED_ERROR, returnedError[0]); - } - - @Test - public void testGetXDMSharedEventState_returnsNull_whenStateNameNull() { - // setup - Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(100).build(); - - // test&verify - Map resultSharedState = extensionApi.getXDMSharedEventState(null, event, - errorCallback); - assertNull(resultSharedState); - assertEquals(ExtensionError.UNEXPECTED_ERROR, returnedError[0]); - } - - @Test - public void testGetSharedEventState_returnsNull_whenStatePending() { - mockEventHub.createSharedState(TEST_SHARED_STATE, 1, EventHub.SHARED_STATE_PENDING); - - // setup - Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(1) - .build(); - - // test&verify - Map resultSharedState = extensionApi.getSharedEventState(TEST_SHARED_STATE, event, - errorCallback); - assertNull(resultSharedState); - assertNull(returnedError[0]); - } - - @Test - public void testGetXDMSharedEventState_returnsNull_whenStatePending() { - mockEventHub.createSharedState(TEST_SHARED_STATE, 1, EventHub.SHARED_STATE_PENDING, SharedStateType.XDM); - - // setup - Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(1) - .build(); - - // test&verify - Map resultSharedState = extensionApi.getXDMSharedEventState(TEST_SHARED_STATE, event, - errorCallback); - assertNull(resultSharedState); - assertNull(returnedError[0]); - } - - @Test - public void testSetSharedEventState_works() { - // setup - Map eventData = getMixedData(); - Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(100).build(); - - // test - assertTrue(extensionApi.setSharedEventState(eventData, event, errorCallback)); - - // verify - Event nextEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(101).build(); - EventData resultSharedState = mockEventHub.getSharedEventState(extensionApi.getModuleName(), nextEvent, extensionApi); - assertEquals(eventData, resultSharedState.toObjectMap()); - assertNull(returnedError[0]); - } - - @Test - public void testSetXDMSharedEventState_works() { - // setup - Map eventData = getMixedData(); - Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(100).build(); - - // test - assertTrue(extensionApi.setXDMSharedEventState(eventData, event, errorCallback)); - - // verify - Event nextEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(101).build(); - EventData resultSharedState = mockEventHub.getSharedEventState(extensionApi.getModuleName(), nextEvent, - extensionApi, SharedStateType.XDM); - assertEquals(eventData, resultSharedState.toObjectMap()); - assertNull(returnedError[0]); - } - - @Test - public void testSetSharedEventState_WithNullEvent_works() { - // setup - Map eventData = getMixedData(); - - // test - assertTrue(extensionApi.setSharedEventState(eventData, null, errorCallback)); - - // verify - EventData resultSharedState = mockEventHub.getSharedEventState(extensionApi.getModuleName(), null, extensionApi); - assertEquals(eventData, resultSharedState.toObjectMap()); - assertNull(returnedError[0]); - } - - @Test - public void testSetXDMSharedEventState_WithNullEvent_works() { - // setup - Map eventData = getMixedData(); - - // test - assertTrue(extensionApi.setXDMSharedEventState(eventData, null, errorCallback)); - - // verify - EventData resultSharedState = mockEventHub.getSharedEventState(extensionApi.getModuleName(), null, extensionApi, - SharedStateType.XDM); - assertEquals(eventData, resultSharedState.toObjectMap()); - assertNull(returnedError[0]); - } - - @Test - public void testSetSharedEventState_eventDataIsImmutable() { - // setup - Map eventData = getMixedData(); - Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(100).build(); - - // test - assertTrue(extensionApi.setSharedEventState(eventData, event, errorCallback)); - eventData.clear(); - - // verify - EventData resultSharedState = mockEventHub.getSharedEventState(extensionApi.getModuleName(), null, extensionApi); - assertEquals(3, resultSharedState.toObjectMap().size()); - assertNull(returnedError[0]); - } - - @Test - public void testSetXDMSharedEventState_eventDataIsImmutable() { - // setup - Map eventData = getMixedData(); - Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(100).build(); - - // test - assertTrue(extensionApi.setXDMSharedEventState(eventData, event, errorCallback)); - eventData.clear(); - - // verify - EventData resultSharedState = mockEventHub.getSharedEventState(extensionApi.getModuleName(), null, extensionApi, - SharedStateType.XDM); - assertEquals(3, resultSharedState.toObjectMap().size()); - assertNull(returnedError[0]); - } - - @Test - public void testSetSharedEventState_returnsFalse_whenErrorOccurs() { - // setup - Map eventData = getMixedData(); - Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(100).build(); - mockEventHub.throwException = true; - - // test - assertFalse(extensionApi.setSharedEventState(eventData, event, errorCallback)); - assertEquals(ExtensionError.UNEXPECTED_ERROR, returnedError[0]); - } - - @Test - public void testSetXDMSharedEventState_returnsFalse_whenErrorOccurs() { - // setup - Map eventData = getMixedData(); - Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(100).build(); - mockEventHub.throwException = true; - - // test - assertFalse(extensionApi.setXDMSharedEventState(eventData, event, errorCallback)); - assertEquals(ExtensionError.UNEXPECTED_ERROR, returnedError[0]); - } - - @Test - public void testClearSharedEventStates_works() { - // setup - Map eventData = getMixedData(); - Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(100).build(); - Event nextEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(101).build(); - - extensionApi.setSharedEventState(eventData, event, null); - extensionApi.setSharedEventState(eventData, nextEvent, null); - - // test&verify - assertTrue(extensionApi.clearSharedEventStates(errorCallback)); - assertNull(returnedError[0]); - assertNull(mockEventHub.getSharedEventState(extensionApi.getModuleName(), Event.SHARED_STATE_NEWEST, extensionApi)); - } - - @Test - public void testClearXDMSharedEventStates_works() { - // setup - Map eventData = getMixedData(); - Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(100).build(); - Event nextEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(101).build(); - - extensionApi.setXDMSharedEventState(eventData, event, null); - extensionApi.setXDMSharedEventState(eventData, nextEvent, null); - - // test&verify - assertTrue(extensionApi.clearXDMSharedEventStates(errorCallback)); - assertNull(returnedError[0]); - assertNull(mockEventHub.getSharedEventState(extensionApi.getModuleName(), Event.SHARED_STATE_NEWEST, extensionApi, - SharedStateType.XDM)); - } - - @Test - public void testClearSharedEventStates_returnsFalse_whenErrorOccurs() { - // setup - Map eventData = getMixedData(); - Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(100).build(); - Event nextEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(101).build(); - - extensionApi.setSharedEventState(eventData, event, null); - extensionApi.setSharedEventState(eventData, nextEvent, null); - mockEventHub.throwException = true; - - // test&verify - assertFalse(extensionApi.clearSharedEventStates(errorCallback)); - assertEquals(ExtensionError.UNEXPECTED_ERROR, returnedError[0]); - } - - @Test - public void testClearXDMSharedEventStates_returnsFalse_whenErrorOccurs() { - // setup - Map eventData = getMixedData(); - Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(100).build(); - Event nextEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(101).build(); - - extensionApi.setXDMSharedEventState(eventData, event, null); - extensionApi.setXDMSharedEventState(eventData, nextEvent, null); - mockEventHub.throwException = true; - - // test&verify - assertFalse(extensionApi.clearXDMSharedEventStates(errorCallback)); - assertEquals(ExtensionError.UNEXPECTED_ERROR, returnedError[0]); - } - - @Test - public void testClearSharedEventStates_returnsFalse_whenErrorOccurs_noCallback() { - // setup - Map eventData = getMixedData(); - Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(100).build(); - Event nextEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(101).build(); - - extensionApi.setSharedEventState(eventData, event, null); - extensionApi.setSharedEventState(eventData, nextEvent, null); - mockEventHub.throwException = true; - - // test&verify - boolean clearStatus = false; - - try { - clearStatus = extensionApi.clearSharedEventStates(null); - } catch (Exception e) { - fail("clearSharedEventStates should not throw, but it did."); - } - - assertFalse(clearStatus); - } - - @Test - public void testClearXDMSharedEventStates_returnsFalse_whenErrorOccurs_noCallback() { - // setup - Map eventData = getMixedData(); - Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(100).build(); - Event nextEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setEventNumber(101).build(); - - extensionApi.setXDMSharedEventState(eventData, event, null); - extensionApi.setXDMSharedEventState(eventData, nextEvent, null); - mockEventHub.throwException = true; - - // test&verify - boolean clearStatus = false; - - try { - clearStatus = extensionApi.clearXDMSharedEventStates(null); - } catch (Exception e) { - fail("clearSharedEventStates should not throw, but it did."); - } - - assertFalse(clearStatus); - } - - @Test - public void testClearSharedEventStates_works_whenNoSharedStateSet() { - // test - assertTrue(extensionApi.clearSharedEventStates(errorCallback)); - assertNull(returnedError[0]); - assertNull(mockEventHub.getSharedEventState(extensionApi.getModuleName(), Event.SHARED_STATE_NEWEST, extensionApi)); - } - - @Test - public void testClearXDMSharedEventStates_works_whenNoSharedStateSet() { - // test - assertTrue(extensionApi.clearXDMSharedEventStates(errorCallback)); - assertNull(returnedError[0]); - assertNull(mockEventHub.getSharedEventState(extensionApi.getModuleName(), Event.SHARED_STATE_NEWEST, extensionApi, - SharedStateType.XDM)); - } - - private Map getMixedData() { - Map data = new HashMap(); - data.put("key1", "value1"); - data.put("key2", 1000000); - data.put("key3", getMultilevelMap(100)); - return data; - } - - private Map getMultilevelMap(final int levels) { - return getMultilevelMap(1, levels, new HashMap()); - } - - private Map getMultilevelMap(final int level, final int maxDepth, - final Map aboveLevelMap) { - if (level == maxDepth) { - aboveLevelMap.put(String.format("level %d", level), "test"); - return aboveLevelMap; - } - - Map aboveLevels = getMultilevelMap(level + 1, maxDepth, new HashMap()); - Map multilevelMap = new HashMap(); - multilevelMap.put(String.format("level %d", level), aboveLevels); - return multilevelMap; - } -} +///* +// Copyright 2022 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may obtain a copy +// of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under +// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +// OF ANY KIND, either express or implied. See the License for the specific language +// governing permissions and limitations under the License. +// */ +// +//package com.adobe.marketing.mobile; +// +//import org.junit.Before; +//import org.junit.Test; +// +//import java.util.HashMap; +//import java.util.Map; +//import java.util.concurrent.ConcurrentLinkedQueue; +//import java.util.concurrent.CountDownLatch; +//import java.util.concurrent.TimeUnit; +// +//import static junit.framework.TestCase.assertTrue; +//import static org.junit.Assert.*; +// +//class MockListener extends ExtensionListener { +// static boolean hearWasCalled; +// static boolean listenerInitialized; +// static boolean onUnregisteredWasCalled; +// +// public MockListener(final ExtensionApi module, final String type, final String source) { +// super(module, type, source); +// listenerInitialized = true; +// } +// +// @Override +// public void hear(Event e) { +// hearWasCalled = true; +// } +// +// @Override +// public void onUnregistered() { +// onUnregisteredWasCalled = true; +// } +//} +// +//class MockListenerThatThrows extends ExtensionListener { +// public MockListenerThatThrows(final ExtensionApi module, final String type, final String source) throws Exception { +// super(module, type, source); +// throw new ExtensionUnexpectedError(ExtensionError.UNEXPECTED_ERROR); +// } +// +// @Override +// public void hear(Event e) { } +// +// @Override +// public void onUnregistered() { } +//} +// +//public class ExtensionApiTests { +// private static CountDownLatch eventHubLatch = new CountDownLatch(1); +// private static int EVENTHUB_WAIT_MS = 50; +// private static String TEST_SHARED_STATE = "com.adobe.module.configuration"; +// private ExtensionApi extensionApi; +// private MockEventHubUnitTest mockEventHub; +// private ExtensionErrorCallback errorCallback; +// private final ExtensionError[] returnedError = new ExtensionError[1]; +// +// static class MockExtension extends Extension { +// static boolean onUnregisteredWasCalled; +// static boolean onUnexpectedErrorWasCalled; +// +// MockExtension(final ExtensionApi api) { +// super(api); +// } +// +// @Override +// public String getName() { +// return "testExtension"; +// } +// +// @Override +// public String getVersion() { +// return "1.0"; +// } +// +// @Override +// public void onUnregistered() { +// onUnregisteredWasCalled = true; +// } +// +// @Override +// public void onUnexpectedError(final ExtensionUnexpectedError extensionUnexpectedError) { +// onUnexpectedErrorWasCalled = true; +// } +// } +// +// static class MockExtension2 extends Extension { +// MockExtension2(final ExtensionApi api) { +// super(api); +// } +// +// @Override +// public String getName() { +// return "testExtension2"; +// } +// +// @Override +// public String getVersion() { +// return "2.0"; +// } +// +// @Override +// public void onUnregistered() {} +// } +// +// @Before +// public void setup() { +// +// // initialize static flags +// MockListener.hearWasCalled = false; +// MockListener.listenerInitialized = false; +// MockListener.onUnregisteredWasCalled = false; +// MockExtension.onUnregisteredWasCalled = false; +// +// try { +// // register the MockExtension +// mockEventHub = new MockEventHubUnitTest("hub", new MockPlatformServices()); +// mockEventHub.registerExtension(MockExtension.class); +// eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// extensionApi = (ExtensionApi)mockEventHub.getActiveModules().iterator().next(); +// assertNotNull(extensionApi); +// MockExtension mockExtension = (MockExtension)extensionApi.getExtension(); +// assertNotNull(mockExtension); +// returnedError[0] = null; +// errorCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// returnedError[0] = extensionError; +// } +// }; +// +// } catch (Exception e) { +// fail("Failure initializing test"); +// } +// } +// +// @Test +// public void testSetExtension_works() { +// ExtensionApi extensionApi = new ExtensionApi(mockEventHub); +// extensionApi.setExtension(new MockExtension(extensionApi)); +// assertEquals("testExtension", extensionApi.getModuleName()); +// } +// +// @Test +// public void testSetExtension_doesNot_updateExtensionIfInitialized() { +// extensionApi.setExtension(new MockExtension2(extensionApi)); +// assertEquals("testExtension", extensionApi.getModuleName()); +// assertEquals("1.0", extensionApi.getModuleVersion()); +// } +// +// @Test +// public void testOnUnregistered_works() throws Exception { +// extensionApi.onUnregistered(); +// eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertTrue(MockExtension.onUnregisteredWasCalled); +// } +// +// @Test +// public void testOnUnregistered_doesNot_crash_when_extensionNotInitialized() { +// // test and verify does not throw +// try { +// ExtensionApi extensionApi = new ExtensionApi(mockEventHub); +// extensionApi.onUnregistered(); +// } catch (Exception e) { +// fail("On unregistered should not have thrown any exception when extension not initialized"); +// } +// +// assertFalse(MockExtension.onUnregisteredWasCalled); +// } +// +// @Test +// public void testRegisterEventListener_works() throws Exception { +// assertTrue(extensionApi.registerEventListener("new.event.type", "new.event.source", MockListener.class, null)); +// eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertTrue(MockListener.listenerInitialized); +// ConcurrentLinkedQueue listeners = mockEventHub.getModuleListeners(extensionApi); +// eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// Log.error("testRegisterEventListener_works", "listeners.size()=%d", listeners.size()); +// assertEquals(1, listeners.size()); +// EventListener registeredListener = listeners.peek(); +// assertNotNull(registeredListener); +// assertTrue(registeredListener instanceof MockListener); +// assertEquals("new.event.type", registeredListener.getEventType().getName()); +// assertEquals("new.event.source", registeredListener.getEventSource().getName()); +// } +// +// @Test +// public void testRegisterEventListener_multipleListeners_works() throws Exception { +// assertTrue(extensionApi.registerEventListener("new.event.type1", "new.event.source1", MockListener.class, null)); +// assertTrue(extensionApi.registerEventListener("new.event.type2", "new.event.source2", MockListener.class, null)); +// assertTrue(extensionApi.registerEventListener("new.event.type3", "new.event.source3", MockListener.class, null)); +// assertTrue(extensionApi.registerEventListener("new.event.type4", "new.event.source4", MockListener.class, null)); +// eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertTrue(MockListener.listenerInitialized); +// ConcurrentLinkedQueue listeners = mockEventHub.getModuleListeners(extensionApi); +// assertEquals(4, listeners.size()); +// } +// +// @Test +// public void testRegisterEventListener_nullListener_doesNot_throw_withErrorCallback() { +// // test and verify does not throw and returns error +// try { +// assertFalse(extensionApi.registerEventListener("new.event.type", "new.event.source", null, errorCallback)); +// } catch (Exception e) { +// fail("On registerEventListener should not have thrown any exception when listener null"); +// } +// +// assertEquals(ExtensionError.UNEXPECTED_ERROR, returnedError[0]); +// } +// +// @Test +// public void testRegisterEventListener_nullListener_doesNot_throw() { +// // test and verify does not throw +// try { +// assertFalse(extensionApi.registerEventListener("new.event.type", "new.event.source", null, null)); +// } catch (Exception e) { +// fail("On registerEventListener should not have thrown any exception when listener null"); +// } +// } +// +// @Test +// public void testRegisterEventListener_nullEventType_doesNot_throw_withErrorCallback() { +// // test and verify does not throw and returns error +// try { +// assertFalse(extensionApi.registerEventListener(null, "new.event.source", MockListener.class, errorCallback)); +// } catch (Exception e) { +// fail("On registerEventListener should not have thrown any exception when event type null"); +// } +// +// assertEquals(ExtensionError.EVENT_TYPE_NOT_SUPPORTED, returnedError[0]); +// } +// +// @Test +// public void testRegisterEventListener_nullEventType_doesNot_throw() { +// // test and verify does not throw +// try { +// assertFalse(extensionApi.registerEventListener(null, "new.event.source", MockListener.class, null)); +// } catch (Exception e) { +// fail("On registerEventListener should not have thrown any exception when event type null"); +// } +// } +// +// @Test +// public void testRegisterEventListener_nullEventSource_doesNot_throw_withErrorCallback() { +// // test and verify does not throw and returns error +// try { +// assertFalse(extensionApi.registerEventListener("new.event.type", null, MockListener.class, errorCallback)); +// } catch (Exception e) { +// fail("On registerEventListener should not have thrown any exception when event source null"); +// } +// +// assertEquals(ExtensionError.EVENT_SOURCE_NOT_SUPPORTED, returnedError[0]); +// } +// +// @Test +// public void testRegisterEventListener_nullEventSource_doesNot_throw() { +// // test and verify does not throw +// try { +// assertFalse(extensionApi.registerEventListener("new.event.type", null, MockListener.class, null)); +// } catch (Exception e) { +// fail("On registerEventListener should not have thrown any exception when event source null"); +// } +// } +// +// @Test +// public void testRegisterEventListener_constructorThrow_callsOnUnexpectedError() { +// // test and verify does not throw +// try { +// assertTrue(extensionApi.registerEventListener("new.event.type", "new.event.source", MockListenerThatThrows.class, +// null)); +// eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// } catch (Exception e) { +// fail("On registerEventListener should not have thrown any exception for this class"); +// } +// +// assertTrue(MockExtension.onUnexpectedErrorWasCalled); +// } +// +// @Test +// public void testUnregisterExtension_unregisteredCalled() throws Exception { +// extensionApi.unregisterExtension(); +// eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertTrue(MockExtension.onUnregisteredWasCalled); +// } +// +// @Test +// public void testRegisterWildcardListener_works() throws Exception { +// assertTrue(extensionApi.registerWildcardListener(MockListener.class, null)); +// eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertTrue(MockListener.listenerInitialized); +// ConcurrentLinkedQueue listeners = mockEventHub.getModuleListeners(extensionApi); +// Log.error("testRegisterEventListener_works", "listeners.size()=%d", listeners.size()); +// assertEquals(1, listeners.size()); +// EventListener registeredListener = listeners.peek(); +// assertNotNull(registeredListener); +// assertTrue(registeredListener instanceof MockListener); +// assertEquals(EventType.WILDCARD, registeredListener.getEventType()); +// assertEquals(EventSource.WILDCARD, registeredListener.getEventSource()); +// } +// +// @SuppressWarnings("all") +// @Test +// public void testRegisterWildcardListener_whenCalledMultipleTimes_registersOneListener_unregisterExistingOnes() throws +// Exception { +// assertTrue(extensionApi.registerWildcardListener(MockListener.class, null)); +// eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertFalse(MockListener.onUnregisteredWasCalled); +// assertTrue(MockListener.listenerInitialized); +// MockListener.listenerInitialized = false; +// assertTrue(extensionApi.registerWildcardListener(MockListener.class, null)); +// eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertTrue(MockListener.onUnregisteredWasCalled); +// assertTrue(MockListener.listenerInitialized); +// MockListener.onUnregisteredWasCalled = false; +// MockListener.listenerInitialized = false; +// assertTrue(extensionApi.registerWildcardListener(MockListener.class, null)); +// eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertTrue(MockListener.onUnregisteredWasCalled); +// assertTrue(MockListener.listenerInitialized); +// MockListener.onUnregisteredWasCalled = false; +// MockListener.listenerInitialized = false; +// ConcurrentLinkedQueue listeners = mockEventHub.getModuleListeners(extensionApi); +// assertEquals(1, listeners.size()); +// } +// +// @Test +// public void testRegisterWildcardListener_nullListener_doesNot_throw_withErrorCallback() { +// // test and verify does not throw and returns error +// try { +// assertFalse(extensionApi.registerWildcardListener(null, errorCallback)); +// } catch (Exception e) { +// fail("On registerWildcardListener should not have thrown any exception when listener null"); +// } +// +// assertEquals(ExtensionError.UNEXPECTED_ERROR, returnedError[0]); +// } +// +// @Test +// public void testRegisterWildcardListener_nullListener_doesNot_throw() { +// // test and verify does not throw +// try { +// assertFalse(extensionApi.registerWildcardListener(null, null)); +// } catch (Exception e) { +// fail("On registerWildcardListener should not have thrown any exception when listener null"); +// } +// } +// +// @Test +// public void testUnregisterExtension_when_notRegistered_works() { +// try { +// ExtensionApi extensionApi = new ExtensionApi(mockEventHub); +// extensionApi.unregisterExtension(); +// } catch (Exception e) { +// fail("On unregisterExtension should not have thrown any exception when not registered before"); +// } +// } +// +// @Test +// public void testUnregisterExtension_eventListenerUnregister_Works() throws Exception { +// // setup +// extensionApi.registerEventListener("new.event.type", "new.event.source", MockListener.class, null); +// eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// +// // test +// extensionApi.unregisterExtension(); +// eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// +// // verify +// assertTrue(MockExtension.onUnregisteredWasCalled); +// assertTrue(MockListener.onUnregisteredWasCalled); +// ConcurrentLinkedQueue listeners = mockEventHub.getModuleListeners(extensionApi); +// assertNull(listeners); +// } +// +// @Test +// public void testGetSharedEventState_works() throws Exception { +// // setup +// Map eventData = getMixedData(); +// mockEventHub.createSharedState(TEST_SHARED_STATE, 1, new EventData().putString("first", "time")); +// mockEventHub.createSharedState(TEST_SHARED_STATE, 2, +// EventData.fromObjectMap(eventData)); +// Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(1).build(); +// Event nextEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(2).build(); +// +// // test&verify +// Map resultSharedState = extensionApi.getSharedEventState(TEST_SHARED_STATE, event, +// errorCallback); +// assertEquals(new HashMap() { +// { +// put("first", "time"); +// } +// }, resultSharedState); +// assertNull(returnedError[0]); +// +// resultSharedState = extensionApi.getSharedEventState(TEST_SHARED_STATE, nextEvent, +// errorCallback); +// assertEquals(eventData, resultSharedState); +// assertNull(returnedError[0]); +// } +// +// @Test +// public void testGetXDMSharedEventState_works() throws Exception { +// // setup +// Map eventData = getMixedData(); +// mockEventHub.createSharedState(TEST_SHARED_STATE, 1, new EventData().putString("first", "time"), SharedStateType.XDM); +// mockEventHub.createSharedState(TEST_SHARED_STATE, 2, +// EventData.fromObjectMap(eventData), SharedStateType.XDM); +// Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(1).build(); +// Event nextEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(2).build(); +// +// // test&verify +// Map resultSharedState = extensionApi.getXDMSharedEventState(TEST_SHARED_STATE, event, +// errorCallback); +// assertEquals(new HashMap() { +// { +// put("first", "time"); +// } +// }, resultSharedState); +// assertNull(returnedError[0]); +// +// resultSharedState = extensionApi.getXDMSharedEventState(TEST_SHARED_STATE, nextEvent, +// errorCallback); +// assertEquals(eventData, resultSharedState); +// assertNull(returnedError[0]); +// } +// +// @Test +// public void testGetSharedEventState_returnsError_whenErrorOccurs() { +// // setup +// Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(100).build(); +// mockEventHub.throwException = true; +// +// // test&verify +// Map resultSharedState = extensionApi.getSharedEventState(TEST_SHARED_STATE, event, +// errorCallback); +// assertNull(resultSharedState); +// assertEquals(ExtensionError.UNEXPECTED_ERROR, returnedError[0]); +// } +// +// @Test +// public void testGetXDMSharedEventState_returnsError_whenErrorOccurs() { +// // setup +// Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(100).build(); +// mockEventHub.throwException = true; +// +// // test&verify +// Map resultSharedState = extensionApi.getXDMSharedEventState(TEST_SHARED_STATE, event, +// errorCallback); +// assertNull(resultSharedState); +// assertEquals(ExtensionError.UNEXPECTED_ERROR, returnedError[0]); +// } +// +// @Test +// public void testGetSharedEventState_returnsLatestSharedState_whenEventNull() throws Exception { +// // setup +// Map eventData = getMixedData(); +// mockEventHub.createSharedState(TEST_SHARED_STATE, 1, EventData.fromObjectMap(eventData)); +// mockEventHub.createSharedState(TEST_SHARED_STATE, 2, new EventData().putString("second", "time")); +// +// // test&verify +// Map resultSharedState = extensionApi.getSharedEventState(TEST_SHARED_STATE, null, +// errorCallback); +// assertEquals(1, resultSharedState.size()); +// assertEquals("time", resultSharedState.get("second")); +// assertNull(returnedError[0]); +// } +// +// @Test +// public void testXDMGetSharedEventState_returnsLatestSharedState_whenEventNull() throws Exception { +// // setup +// Map eventData = getMixedData(); +// mockEventHub.createSharedState(TEST_SHARED_STATE, 1, EventData.fromObjectMap(eventData), SharedStateType.XDM); +// mockEventHub.createSharedState(TEST_SHARED_STATE, 2, new EventData().putString("second", "time"), SharedStateType.XDM); +// +// // test&verify +// Map resultSharedState = extensionApi.getXDMSharedEventState(TEST_SHARED_STATE, null, +// errorCallback); +// assertEquals(1, resultSharedState.size()); +// assertEquals("time", resultSharedState.get("second")); +// assertNull(returnedError[0]); +// } +// +// @Test +// public void testGetSharedEventState_returnsNull_whenStateNameNull() { +// // setup +// Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(100).build(); +// +// // test&verify +// Map resultSharedState = extensionApi.getSharedEventState(null, event, +// errorCallback); +// assertNull(resultSharedState); +// assertEquals(ExtensionError.UNEXPECTED_ERROR, returnedError[0]); +// } +// +// @Test +// public void testGetXDMSharedEventState_returnsNull_whenStateNameNull() { +// // setup +// Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(100).build(); +// +// // test&verify +// Map resultSharedState = extensionApi.getXDMSharedEventState(null, event, +// errorCallback); +// assertNull(resultSharedState); +// assertEquals(ExtensionError.UNEXPECTED_ERROR, returnedError[0]); +// } +// +// @Test +// public void testGetSharedEventState_returnsNull_whenStatePending() { +// mockEventHub.createSharedState(TEST_SHARED_STATE, 1, EventHub.SHARED_STATE_PENDING); +// +// // setup +// Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(1) +// .build(); +// +// // test&verify +// Map resultSharedState = extensionApi.getSharedEventState(TEST_SHARED_STATE, event, +// errorCallback); +// assertNull(resultSharedState); +// assertNull(returnedError[0]); +// } +// +// @Test +// public void testGetXDMSharedEventState_returnsNull_whenStatePending() { +// mockEventHub.createSharedState(TEST_SHARED_STATE, 1, EventHub.SHARED_STATE_PENDING, SharedStateType.XDM); +// +// // setup +// Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(1) +// .build(); +// +// // test&verify +// Map resultSharedState = extensionApi.getXDMSharedEventState(TEST_SHARED_STATE, event, +// errorCallback); +// assertNull(resultSharedState); +// assertNull(returnedError[0]); +// } +// +// @Test +// public void testSetSharedEventState_works() { +// // setup +// Map eventData = getMixedData(); +// Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(100).build(); +// +// // test +// assertTrue(extensionApi.setSharedEventState(eventData, event, errorCallback)); +// +// // verify +// Event nextEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(101).build(); +// EventData resultSharedState = mockEventHub.getSharedEventState(extensionApi.getModuleName(), nextEvent, extensionApi); +// assertEquals(eventData, resultSharedState.toObjectMap()); +// assertNull(returnedError[0]); +// } +// +// @Test +// public void testSetXDMSharedEventState_works() { +// // setup +// Map eventData = getMixedData(); +// Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(100).build(); +// +// // test +// assertTrue(extensionApi.setXDMSharedEventState(eventData, event, errorCallback)); +// +// // verify +// Event nextEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(101).build(); +// EventData resultSharedState = mockEventHub.getSharedEventState(extensionApi.getModuleName(), nextEvent, +// extensionApi, SharedStateType.XDM); +// assertEquals(eventData, resultSharedState.toObjectMap()); +// assertNull(returnedError[0]); +// } +// +// @Test +// public void testSetSharedEventState_WithNullEvent_works() { +// // setup +// Map eventData = getMixedData(); +// +// // test +// assertTrue(extensionApi.setSharedEventState(eventData, null, errorCallback)); +// +// // verify +// EventData resultSharedState = mockEventHub.getSharedEventState(extensionApi.getModuleName(), null, extensionApi); +// assertEquals(eventData, resultSharedState.toObjectMap()); +// assertNull(returnedError[0]); +// } +// +// @Test +// public void testSetXDMSharedEventState_WithNullEvent_works() { +// // setup +// Map eventData = getMixedData(); +// +// // test +// assertTrue(extensionApi.setXDMSharedEventState(eventData, null, errorCallback)); +// +// // verify +// EventData resultSharedState = mockEventHub.getSharedEventState(extensionApi.getModuleName(), null, extensionApi, +// SharedStateType.XDM); +// assertEquals(eventData, resultSharedState.toObjectMap()); +// assertNull(returnedError[0]); +// } +// +// @Test +// public void testSetSharedEventState_eventDataIsImmutable() { +// // setup +// Map eventData = getMixedData(); +// Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(100).build(); +// +// // test +// assertTrue(extensionApi.setSharedEventState(eventData, event, errorCallback)); +// eventData.clear(); +// +// // verify +// EventData resultSharedState = mockEventHub.getSharedEventState(extensionApi.getModuleName(), null, extensionApi); +// assertEquals(3, resultSharedState.toObjectMap().size()); +// assertNull(returnedError[0]); +// } +// +// @Test +// public void testSetXDMSharedEventState_eventDataIsImmutable() { +// // setup +// Map eventData = getMixedData(); +// Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(100).build(); +// +// // test +// assertTrue(extensionApi.setXDMSharedEventState(eventData, event, errorCallback)); +// eventData.clear(); +// +// // verify +// EventData resultSharedState = mockEventHub.getSharedEventState(extensionApi.getModuleName(), null, extensionApi, +// SharedStateType.XDM); +// assertEquals(3, resultSharedState.toObjectMap().size()); +// assertNull(returnedError[0]); +// } +// +// @Test +// public void testSetSharedEventState_returnsFalse_whenErrorOccurs() { +// // setup +// Map eventData = getMixedData(); +// Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(100).build(); +// mockEventHub.throwException = true; +// +// // test +// assertFalse(extensionApi.setSharedEventState(eventData, event, errorCallback)); +// assertEquals(ExtensionError.UNEXPECTED_ERROR, returnedError[0]); +// } +// +// @Test +// public void testSetXDMSharedEventState_returnsFalse_whenErrorOccurs() { +// // setup +// Map eventData = getMixedData(); +// Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(100).build(); +// mockEventHub.throwException = true; +// +// // test +// assertFalse(extensionApi.setXDMSharedEventState(eventData, event, errorCallback)); +// assertEquals(ExtensionError.UNEXPECTED_ERROR, returnedError[0]); +// } +// +// @Test +// public void testClearSharedEventStates_works() { +// // setup +// Map eventData = getMixedData(); +// Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(100).build(); +// Event nextEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(101).build(); +// +// extensionApi.setSharedEventState(eventData, event, null); +// extensionApi.setSharedEventState(eventData, nextEvent, null); +// +// // test&verify +// assertTrue(extensionApi.clearSharedEventStates(errorCallback)); +// assertNull(returnedError[0]); +// assertNull(mockEventHub.getSharedEventState(extensionApi.getModuleName(), Event.SHARED_STATE_NEWEST, extensionApi)); +// } +// +// @Test +// public void testClearXDMSharedEventStates_works() { +// // setup +// Map eventData = getMixedData(); +// Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(100).build(); +// Event nextEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(101).build(); +// +// extensionApi.setXDMSharedEventState(eventData, event, null); +// extensionApi.setXDMSharedEventState(eventData, nextEvent, null); +// +// // test&verify +// assertTrue(extensionApi.clearXDMSharedEventStates(errorCallback)); +// assertNull(returnedError[0]); +// assertNull(mockEventHub.getSharedEventState(extensionApi.getModuleName(), Event.SHARED_STATE_NEWEST, extensionApi, +// SharedStateType.XDM)); +// } +// +// @Test +// public void testClearSharedEventStates_returnsFalse_whenErrorOccurs() { +// // setup +// Map eventData = getMixedData(); +// Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(100).build(); +// Event nextEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(101).build(); +// +// extensionApi.setSharedEventState(eventData, event, null); +// extensionApi.setSharedEventState(eventData, nextEvent, null); +// mockEventHub.throwException = true; +// +// // test&verify +// assertFalse(extensionApi.clearSharedEventStates(errorCallback)); +// assertEquals(ExtensionError.UNEXPECTED_ERROR, returnedError[0]); +// } +// +// @Test +// public void testClearXDMSharedEventStates_returnsFalse_whenErrorOccurs() { +// // setup +// Map eventData = getMixedData(); +// Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(100).build(); +// Event nextEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(101).build(); +// +// extensionApi.setXDMSharedEventState(eventData, event, null); +// extensionApi.setXDMSharedEventState(eventData, nextEvent, null); +// mockEventHub.throwException = true; +// +// // test&verify +// assertFalse(extensionApi.clearXDMSharedEventStates(errorCallback)); +// assertEquals(ExtensionError.UNEXPECTED_ERROR, returnedError[0]); +// } +// +// @Test +// public void testClearSharedEventStates_returnsFalse_whenErrorOccurs_noCallback() { +// // setup +// Map eventData = getMixedData(); +// Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(100).build(); +// Event nextEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(101).build(); +// +// extensionApi.setSharedEventState(eventData, event, null); +// extensionApi.setSharedEventState(eventData, nextEvent, null); +// mockEventHub.throwException = true; +// +// // test&verify +// boolean clearStatus = false; +// +// try { +// clearStatus = extensionApi.clearSharedEventStates(null); +// } catch (Exception e) { +// fail("clearSharedEventStates should not throw, but it did."); +// } +// +// assertFalse(clearStatus); +// } +// +// @Test +// public void testClearXDMSharedEventStates_returnsFalse_whenErrorOccurs_noCallback() { +// // setup +// Map eventData = getMixedData(); +// Event event = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(100).build(); +// Event nextEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) +// .setEventNumber(101).build(); +// +// extensionApi.setXDMSharedEventState(eventData, event, null); +// extensionApi.setXDMSharedEventState(eventData, nextEvent, null); +// mockEventHub.throwException = true; +// +// // test&verify +// boolean clearStatus = false; +// +// try { +// clearStatus = extensionApi.clearXDMSharedEventStates(null); +// } catch (Exception e) { +// fail("clearSharedEventStates should not throw, but it did."); +// } +// +// assertFalse(clearStatus); +// } +// +// @Test +// public void testClearSharedEventStates_works_whenNoSharedStateSet() { +// // test +// assertTrue(extensionApi.clearSharedEventStates(errorCallback)); +// assertNull(returnedError[0]); +// assertNull(mockEventHub.getSharedEventState(extensionApi.getModuleName(), Event.SHARED_STATE_NEWEST, extensionApi)); +// } +// +// @Test +// public void testClearXDMSharedEventStates_works_whenNoSharedStateSet() { +// // test +// assertTrue(extensionApi.clearXDMSharedEventStates(errorCallback)); +// assertNull(returnedError[0]); +// assertNull(mockEventHub.getSharedEventState(extensionApi.getModuleName(), Event.SHARED_STATE_NEWEST, extensionApi, +// SharedStateType.XDM)); +// } +// +// private Map getMixedData() { +// Map data = new HashMap(); +// data.put("key1", "value1"); +// data.put("key2", 1000000); +// data.put("key3", getMultilevelMap(100)); +// return data; +// } +// +// private Map getMultilevelMap(final int levels) { +// return getMultilevelMap(1, levels, new HashMap()); +// } +// +// private Map getMultilevelMap(final int level, final int maxDepth, +// final Map aboveLevelMap) { +// if (level == maxDepth) { +// aboveLevelMap.put(String.format("level %d", level), "test"); +// return aboveLevelMap; +// } +// +// Map aboveLevels = getMultilevelMap(level + 1, maxDepth, new HashMap()); +// Map multilevelMap = new HashMap(); +// multilevelMap.put(String.format("level %d", level), aboveLevels); +// return multilevelMap; +// } +//} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ExtensionListenerTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ExtensionListenerTest.java index aef3d3066..ebc110246 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ExtensionListenerTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ExtensionListenerTest.java @@ -1,150 +1,150 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import org.junit.Before; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import static org.junit.Assert.assertEquals; - -public class ExtensionListenerTest { - private static CountDownLatch eventHubLatch = new CountDownLatch(1);; - private static int EVENTHUB_WAIT_MS = 50; - private MockEventHubUnitTest mockEventHub; - - static class TestableExtensionListener extends ExtensionListener { - static int listenerWasRegisteredTimes; - static int listenerWasUnegisteredTimes; - static List heardEvents = new ArrayList(); - static Extension extensionInstance; - static ExtensionApi extensionApiInstance; - - public TestableExtensionListener(ExtensionApi extension, String type, String source) { - super(extension, type, source); - TestableExtension.registerListenerTimes = 1; - TestableExtension.handleEventParam = null; - listenerWasRegisteredTimes += 1; - heardEvents.clear(); - extensionInstance = this.getParentExtension(); - extensionApiInstance = this.getParentExtension().getApi(); - - } - - @Override - public void hear(final Event e) { - heardEvents.add(e); - ((TestableExtension) getParentExtension()).handleEvent(e); - } - - @Override - public void onUnregistered() { - listenerWasUnegisteredTimes++; - } - } - - static class TestableExtension extends Extension { - static boolean onUnregisteredWasCalled; - static int registerListenerTimes = 1; - static Event handleEventParam; - - TestableExtension(final ExtensionApi api) { - super(api); - - for (int i = 0; i < registerListenerTimes; i++) { - getApi().registerEventListener("com.adobe.eventType.configuration", - "com.adobe.eventSource.requestContent", - TestableExtensionListener.class, null); - } - } - - @Override - public String getName() { - return "testExtension"; - } - - @Override - public void onUnregistered() { - onUnregisteredWasCalled = true; - } - - private void handleEvent(final Event event) { - handleEventParam = event; - } - } - - @Before - public void setup() { - mockEventHub = new MockEventHubUnitTest("hub", new MockPlatformServices()); - TestableExtensionListener.listenerWasRegisteredTimes = 0; - TestableExtensionListener.listenerWasUnegisteredTimes = 0; - } - - @Test - public void testExtensionListener_constructorCalledOnce_when_ListenerRegisteredOnce() throws Exception { - mockEventHub.registerExtension(TestableExtension.class); - eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertEquals(1, TestableExtensionListener.listenerWasRegisteredTimes); - } - - @Test - public void testExtensionListener_unregisterIsCalled_when_ListenerRegisteredMultipleTimes() throws Exception { - TestableExtension.registerListenerTimes = 3; - mockEventHub.registerExtension(TestableExtension.class); - eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertEquals(3, TestableExtensionListener.listenerWasRegisteredTimes); - assertEquals(2, TestableExtensionListener.listenerWasUnegisteredTimes); - } - - @Test - public void testExtensionListener_getExtensionAvailable_when_ListenerRegistered() throws Exception { - mockEventHub.registerExtension(TestableExtension.class); - eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - ExtensionApi extensionApi = (ExtensionApi)mockEventHub.getActiveModules().iterator().next(); - assertEquals(extensionApi.getExtension(), TestableExtensionListener.extensionInstance); - } - - @Test - public void testExtensionListener_getExtensionApiAvailable_when_ListenerRegistered() throws Exception { - mockEventHub.registerExtension(TestableExtension.class); - eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - ExtensionApi extensionApi = (ExtensionApi)mockEventHub.getActiveModules().iterator().next(); - assertEquals(extensionApi, TestableExtensionListener.extensionApiInstance); - } - - @Test - public void testExtensionListener_hearIsCalled_when_EventHubDispatchIsCalled() throws Exception { - mockEventHub.registerExtension(TestableExtension.class); - mockEventHub.finishModulesRegistration(null); - eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - Event configEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT).build(); - mockEventHub.dispatchEvent(configEvent); - eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertEquals(1, TestableExtensionListener.heardEvents.size()); - assertEquals(configEvent, TestableExtensionListener.heardEvents.get(0)); - } - - @Test - public void testExtensionListener_privateExtensionHandler_IsAccessible_fromListener() throws Exception { - mockEventHub.registerExtension(TestableExtension.class); - mockEventHub.finishModulesRegistration(null); - eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - Event configEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT).build(); - mockEventHub.dispatchEvent(configEvent); - eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertEquals(configEvent, TestableExtension.handleEventParam); - } -} +///* +// Copyright 2022 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may obtain a copy +// of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under +// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +// OF ANY KIND, either express or implied. See the License for the specific language +// governing permissions and limitations under the License. +// */ +// +//package com.adobe.marketing.mobile; +// +//import org.junit.Before; +//import org.junit.Test; +// +//import java.util.ArrayList; +//import java.util.List; +//import java.util.concurrent.CountDownLatch; +//import java.util.concurrent.TimeUnit; +// +//import static org.junit.Assert.assertEquals; +// +//public class ExtensionListenerTest { +// private static CountDownLatch eventHubLatch = new CountDownLatch(1);; +// private static int EVENTHUB_WAIT_MS = 50; +// private MockEventHubUnitTest mockEventHub; +// +// static class TestableExtensionListener extends ExtensionListener { +// static int listenerWasRegisteredTimes; +// static int listenerWasUnegisteredTimes; +// static List heardEvents = new ArrayList(); +// static Extension extensionInstance; +// static ExtensionApi extensionApiInstance; +// +// public TestableExtensionListener(ExtensionApi extension, String type, String source) { +// super(extension, type, source); +// TestableExtension.registerListenerTimes = 1; +// TestableExtension.handleEventParam = null; +// listenerWasRegisteredTimes += 1; +// heardEvents.clear(); +// extensionInstance = this.getParentExtension(); +// extensionApiInstance = this.getParentExtension().getApi(); +// +// } +// +// @Override +// public void hear(final Event e) { +// heardEvents.add(e); +// ((TestableExtension) getParentExtension()).handleEvent(e); +// } +// +// @Override +// public void onUnregistered() { +// listenerWasUnegisteredTimes++; +// } +// } +// +// static class TestableExtension extends Extension { +// static boolean onUnregisteredWasCalled; +// static int registerListenerTimes = 1; +// static Event handleEventParam; +// +// TestableExtension(final ExtensionApi api) { +// super(api); +// +// for (int i = 0; i < registerListenerTimes; i++) { +// getApi().registerEventListener("com.adobe.eventType.configuration", +// "com.adobe.eventSource.requestContent", +// TestableExtensionListener.class, null); +// } +// } +// +// @Override +// public String getName() { +// return "testExtension"; +// } +// +// @Override +// public void onUnregistered() { +// onUnregisteredWasCalled = true; +// } +// +// private void handleEvent(final Event event) { +// handleEventParam = event; +// } +// } +// +// @Before +// public void setup() { +// mockEventHub = new MockEventHubUnitTest("hub", new MockPlatformServices()); +// TestableExtensionListener.listenerWasRegisteredTimes = 0; +// TestableExtensionListener.listenerWasUnegisteredTimes = 0; +// } +// +// @Test +// public void testExtensionListener_constructorCalledOnce_when_ListenerRegisteredOnce() throws Exception { +// mockEventHub.registerExtension(TestableExtension.class); +// eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertEquals(1, TestableExtensionListener.listenerWasRegisteredTimes); +// } +// +// @Test +// public void testExtensionListener_unregisterIsCalled_when_ListenerRegisteredMultipleTimes() throws Exception { +// TestableExtension.registerListenerTimes = 3; +// mockEventHub.registerExtension(TestableExtension.class); +// eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertEquals(3, TestableExtensionListener.listenerWasRegisteredTimes); +// assertEquals(2, TestableExtensionListener.listenerWasUnegisteredTimes); +// } +// +// @Test +// public void testExtensionListener_getExtensionAvailable_when_ListenerRegistered() throws Exception { +// mockEventHub.registerExtension(TestableExtension.class); +// eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// ExtensionApi extensionApi = (ExtensionApi)mockEventHub.getActiveModules().iterator().next(); +// assertEquals(extensionApi.getExtension(), TestableExtensionListener.extensionInstance); +// } +// +// @Test +// public void testExtensionListener_getExtensionApiAvailable_when_ListenerRegistered() throws Exception { +// mockEventHub.registerExtension(TestableExtension.class); +// eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// ExtensionApi extensionApi = (ExtensionApi)mockEventHub.getActiveModules().iterator().next(); +// assertEquals(extensionApi, TestableExtensionListener.extensionApiInstance); +// } +// +// @Test +// public void testExtensionListener_hearIsCalled_when_EventHubDispatchIsCalled() throws Exception { +// mockEventHub.registerExtension(TestableExtension.class); +// mockEventHub.finishModulesRegistration(null); +// eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// Event configEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT).build(); +// mockEventHub.dispatchEvent(configEvent); +// eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertEquals(1, TestableExtensionListener.heardEvents.size()); +// assertEquals(configEvent, TestableExtensionListener.heardEvents.get(0)); +// } +// +// @Test +// public void testExtensionListener_privateExtensionHandler_IsAccessible_fromListener() throws Exception { +// mockEventHub.registerExtension(TestableExtension.class); +// mockEventHub.finishModulesRegistration(null); +// eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// Event configEvent = new Event.Builder("test", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT).build(); +// mockEventHub.dispatchEvent(configEvent); +// eventHubLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); +// assertEquals(configEvent, TestableExtension.handleEventParam); +// } +//} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ModuleTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ModuleTest.java index c3c785ddb..96846015a 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ModuleTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ModuleTest.java @@ -1,312 +1,312 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import com.adobe.marketing.mobile.EventHubTest.TestModule; -import org.junit.Before; -import org.junit.Test; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -public class ModuleTest { - - private FakePlatformServices services; - - private static Event testListenerLastHeardEvent = null; - private static CountDownLatch latch = new CountDownLatch(1); - private static int EVENTHUB_WAIT_MS = 50; - private EventHub hub = null; - private Module testModule = null; - - static class TestListener extends ModuleEventListener { - protected TestListener(final TestModule module, final EventType type, final EventSource source) { - super(module, type, source); - } - - @Override - public void hear(final Event e) { - testListenerLastHeardEvent = e; - latch.countDown(); - } - } - - private void waitForLatchWithTimeout(long milli) { - try { - latch.await(milli, TimeUnit.MILLISECONDS); - } catch (Exception e) { - fail("Timed out while waiting for listener"); - } - } - - @Before - public void beforeEach() { - testListenerLastHeardEvent = null; - latch = new CountDownLatch(1); - services = new FakePlatformServices(); - - hub = new EventHub("eventhub", services); - - try { - hub.registerModule(TestModule.class); - } catch (InvalidModuleException e) { - // this will not happen - } - - hub.finishModulesRegistration(null); - waitForLatchWithTimeout(EVENTHUB_WAIT_MS); - assertNotNull(hub.getActiveModules()); - assertEquals(1, hub.getActiveModules().size()); - testModule = hub.getActiveModules().iterator().next(); - - Log.setLoggingService(services.fakeLoggingService); - Log.setLogLevel(LoggingMode.VERBOSE); - } - - @Test - public void registerListener_NullEventType() { - testModule.registerListener(null, EventSource.NONE, TestListener.class); - - assertTrue(services.fakeLoggingService.containsErrorLog(TestModule.class.getSimpleName(), - "Failed to register listener. EventType, EventSource and listenerClass must be non-null values")); - } - - @Test - public void registerListener_NullEventSource() { - testModule.registerListener(EventType.CUSTOM, null, TestListener.class); - - assertTrue(services.fakeLoggingService.containsErrorLog(TestModule.class.getSimpleName(), - "Failed to register listener. EventType, EventSource and listenerClass must be non-null values")); - } - - @Test - public void registerListener_NullListenerClass() { - testModule.registerListener(EventType.CUSTOM, EventSource.NONE, null); - - assertTrue(services.fakeLoggingService.containsErrorLog(TestModule.class.getSimpleName(), - "Failed to register listener. EventType, EventSource and listenerClass must be non-null values")); - } - - @Test - public void createSharedState_NullSharedStateName() { - new Module(null, hub) { - } .createSharedState(0, new EventData()); - - assertTrue(services.fakeLoggingService.containsErrorLog(null, - "Unable to create shared state (com.adobe.marketing.mobile.InvalidModuleException: " + - "StateName was null)")); - } - - @Test - public void createXDMSharedState_NullSharedStateName() { - new Module(null, hub) { - } .createXDMSharedState(0, new EventData()); - - assertTrue(services.fakeLoggingService.containsErrorLog(null, - "Unable to create XDM shared state (com.adobe.marketing.mobile.InvalidModuleException: " + - "StateName was null)")); - } - - @Test - public void createSharedStateAndDispatchEvent_NullSharedStateOrEvent() { - EventData state = new EventData(); - state.putString("key", "value1"); - final EventData eventData = new EventData() - .putString("initialkey", "initialvalue"); - final Event event = new Event.Builder("Test Event", EventType.CUSTOM, EventSource.NONE).setData(eventData) - .build(); - new Module(null, hub) { - } .createSharedStateAndDispatchEvent(null, event); - assertTrue(services.fakeLoggingService.containsDebugLog(null, - "failed to create the shared state and dispatch the event ( null sharedState or null event)")); - services.fakeLoggingService.clearLog(); - new Module(null, hub) { - } .createSharedStateAndDispatchEvent(state, null); - assertTrue(services.fakeLoggingService.containsDebugLog(null, - "failed to create the shared state and dispatch the event ( null sharedState or null event)")); - } - - @Test - public void createXDMSharedStateAndDispatchEvent_NullSharedStateOrEvent() { - EventData state = new EventData(); - state.putString("key", "value1"); - final EventData eventData = new EventData() - .putString("initialkey", "initialvalue"); - final Event event = new Event.Builder("Test Event", EventType.CUSTOM, EventSource.NONE).setData(eventData) - .build(); - new Module(null, hub) { - } .createXDMSharedStateAndDispatchEvent(null, event); - assertTrue(services.fakeLoggingService.containsDebugLog(null, - "failed to create XDM shared state and dispatch the event ( null sharedState or null event)")); - services.fakeLoggingService.clearLog(); - new Module(null, hub) { - } .createXDMSharedStateAndDispatchEvent(state, null); - assertTrue(services.fakeLoggingService.containsDebugLog(null, - "failed to create XDM shared state and dispatch the event ( null sharedState or null event)")); - } - - @Test - public void updateSharedState_ModuleNotRegistered() { - new Module(null, hub) { - } .updateSharedState(0, new EventData()); - - assertTrue(services.fakeLoggingService.containsErrorLog(null, - "Unable to update shared state (com.adobe.marketing.mobile.InvalidModuleException: " + - "StateName was null)")); - } - - @Test - public void updateXDMSharedState_ModuleNotRegistered() { - new Module(null, hub) { - } .updateXDMSharedState(0, new EventData()); - - assertTrue(services.fakeLoggingService.containsErrorLog(null, - "Unable to update XDM shared state (com.adobe.marketing.mobile.InvalidModuleException: " + - "StateName was null)")); - } - - @Test - public void createOrUpdateSharedStateWithVersion_NullSharedStateName() { - new Module(null, hub) { - } .createOrUpdateSharedState(0, new EventData()); - - assertTrue(services.fakeLoggingService.containsErrorLog(null, - "Unable to create or update shared state with version (com.adobe.marketing.mobile.InvalidModuleException: " + - "StateName was null)")); - } - - @Test - public void createOrUpdateXDMSharedStateWithVersion_NullSharedStateName() { - new Module(null, hub) { - } .createOrUpdateXDMSharedState(0, new EventData()); - - assertTrue(services.fakeLoggingService.containsErrorLog(null, - "Unable to create or update XDM shared state with version (com.adobe.marketing.mobile.InvalidModuleException: " + - "StateName was null)")); - } - - @Test - public void createOrUpdateSharedState_NullSharedStateName() { - new Module(null, hub) { - } .createOrUpdateSharedState(new EventData()); - - assertTrue(services.fakeLoggingService.containsErrorLog(null, - "Unable to create or update shared state (com.adobe.marketing.mobile.InvalidModuleException: " + - "StateName was null)")); - } - - @Test - public void createOrUpdateXDMSharedState_NullSharedStateName() { - new Module(null, hub) { - } .createOrUpdateXDMSharedState(new EventData()); - - assertTrue(services.fakeLoggingService.containsErrorLog(null, - "Unable to create or update XDM shared state (com.adobe.marketing.mobile.InvalidModuleException: " + - "StateName was null)")); - } - - @Test - public void clearSharedStates_ValidState_Works() { - assertTrue(new Module("validModule", hub) {} .clearSharedStates()); - } - - @Test - public void clearXDMSharedStates_ValidState_Works() { - assertTrue(new Module("validModule", hub) {} .clearXDMSharedStates()); - } - - @Test - public void clearSharedStates_NullStateName_doesNotThrow() { - assertFalse(new Module(null, hub) {} .clearSharedStates()); - assertTrue(services.fakeLoggingService.containsErrorLog(null, - "Unable to clear the shared event states (com.adobe.marketing.mobile.InvalidModuleException: " + - "StateName was null)")); - } - - @Test - public void clearXDMSharedStates_NullStateName_doesNotThrow() { - assertFalse(new Module(null, hub) {} .clearXDMSharedStates()); - assertTrue(services.fakeLoggingService.containsErrorLog(null, - "Unable to clear the XDM shared event states (com.adobe.marketing.mobile.InvalidModuleException: " + - "StateName was null)")); - } - - @Test - public void hasSharedStates_NullStateName_doesNotThrow() { - assertFalse(testModule.hasSharedEventState(null)); - assertTrue(services.fakeLoggingService.containsErrorLog("TestModule", - "Unable to query shared event state (java.lang.IllegalArgumentException: " + - "StateName was null)")); - } - - @Test - public void hasXDMSharedStates_NullStateName_doesNotThrow() { - assertFalse(testModule.hasXDMSharedEventState(null)); - assertTrue(services.fakeLoggingService.containsErrorLog("TestModule", - "Unable to query XDM shared event state (java.lang.IllegalArgumentException: " + - "StateName was null)")); - } - - @Test - public void unregisterListener_ListenerNotRegistered() { - testModule.unregisterListener(EventType.CUSTOM, EventSource.BOOTED); - waitForLatchWithTimeout(EVENTHUB_WAIT_MS); - assertTrue(services.fakeLoggingService.containsDebugLog("EventHub(eventhub)", - "Failed to unregister listener (no registered listener)")); - } - - @Test - public void unregisterModule_Happy() { - testModule.unregisterModule(); - waitForLatchWithTimeout(EVENTHUB_WAIT_MS); - assertEquals(0, hub.getActiveModules().size()); - } - - @Test - public void unregisterModule_NotRegistered() { - Module unregisteredModule = new Module("UnRegistered", hub) {}; - unregisteredModule.unregisterModule(); - waitForLatchWithTimeout(EVENTHUB_WAIT_MS); - - assertTrue(services.fakeLoggingService.containsErrorLog("EventHub(eventhub)", - "Failed to unregister module, Module (UnRegistered) is not registered")); - } - - @Test - public void registerWildcardListener_Happy() { - testModule.registerWildcardListener(TestListener.class); - waitForLatchWithTimeout(EVENTHUB_WAIT_MS); - - Event event = new Event.Builder("testEvent", EventType.ACQUISITION, EventSource.BOOTED).build(); - hub.dispatch(event); - waitForLatchWithTimeout(EVENTHUB_WAIT_MS); - - assertEquals(EventType.ACQUISITION.getName(), testListenerLastHeardEvent.getEventType().getName()); - assertEquals(EventSource.BOOTED.getName(), testListenerLastHeardEvent.getEventSource().getName()); - } - - @Test - public void registerWildcardListener_Happy_NullListenerClass() { - testModule.registerWildcardListener(null); - waitForLatchWithTimeout(EVENTHUB_WAIT_MS); - - assertTrue(services.fakeLoggingService.containsErrorLog(TestModule.class.getSimpleName(), - "Failed to register listener. EventType, EventSource and listenerClass must be non-null values")); - } - -} +///* +// Copyright 2022 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may obtain a copy +// of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under +// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +// OF ANY KIND, either express or implied. See the License for the specific language +// governing permissions and limitations under the License. +// */ +// +//package com.adobe.marketing.mobile; +// +//import com.adobe.marketing.mobile.EventHubTest.TestModule; +//import org.junit.Before; +//import org.junit.Test; +// +//import java.util.concurrent.CountDownLatch; +//import java.util.concurrent.TimeUnit; +// +//import static org.junit.Assert.assertEquals; +//import static org.junit.Assert.assertFalse; +//import static org.junit.Assert.assertNotNull; +//import static org.junit.Assert.assertTrue; +//import static org.junit.Assert.fail; +// +//public class ModuleTest { +// +// private FakePlatformServices services; +// +// private static Event testListenerLastHeardEvent = null; +// private static CountDownLatch latch = new CountDownLatch(1); +// private static int EVENTHUB_WAIT_MS = 50; +// private EventHub hub = null; +// private Module testModule = null; +// +// static class TestListener extends ModuleEventListener { +// protected TestListener(final TestModule module, final EventType type, final EventSource source) { +// super(module, type, source); +// } +// +// @Override +// public void hear(final Event e) { +// testListenerLastHeardEvent = e; +// latch.countDown(); +// } +// } +// +// private void waitForLatchWithTimeout(long milli) { +// try { +// latch.await(milli, TimeUnit.MILLISECONDS); +// } catch (Exception e) { +// fail("Timed out while waiting for listener"); +// } +// } +// +// @Before +// public void beforeEach() { +// testListenerLastHeardEvent = null; +// latch = new CountDownLatch(1); +// services = new FakePlatformServices(); +// +// hub = new EventHub("eventhub", services); +// +// try { +// hub.registerModule(TestModule.class); +// } catch (InvalidModuleException e) { +// // this will not happen +// } +// +// hub.finishModulesRegistration(null); +// waitForLatchWithTimeout(EVENTHUB_WAIT_MS); +// assertNotNull(hub.getActiveModules()); +// assertEquals(1, hub.getActiveModules().size()); +// testModule = hub.getActiveModules().iterator().next(); +// +// Log.setLoggingService(services.fakeLoggingService); +// Log.setLogLevel(LoggingMode.VERBOSE); +// } +// +// @Test +// public void registerListener_NullEventType() { +// testModule.registerListener(null, EventSource.NONE, TestListener.class); +// +// assertTrue(services.fakeLoggingService.containsErrorLog(TestModule.class.getSimpleName(), +// "Failed to register listener. EventType, EventSource and listenerClass must be non-null values")); +// } +// +// @Test +// public void registerListener_NullEventSource() { +// testModule.registerListener(EventType.CUSTOM, null, TestListener.class); +// +// assertTrue(services.fakeLoggingService.containsErrorLog(TestModule.class.getSimpleName(), +// "Failed to register listener. EventType, EventSource and listenerClass must be non-null values")); +// } +// +// @Test +// public void registerListener_NullListenerClass() { +// testModule.registerListener(EventType.CUSTOM, EventSource.NONE, null); +// +// assertTrue(services.fakeLoggingService.containsErrorLog(TestModule.class.getSimpleName(), +// "Failed to register listener. EventType, EventSource and listenerClass must be non-null values")); +// } +// +// @Test +// public void createSharedState_NullSharedStateName() { +// new Module(null, hub) { +// } .createSharedState(0, new EventData()); +// +// assertTrue(services.fakeLoggingService.containsErrorLog(null, +// "Unable to create shared state (com.adobe.marketing.mobile.InvalidModuleException: " + +// "StateName was null)")); +// } +// +// @Test +// public void createXDMSharedState_NullSharedStateName() { +// new Module(null, hub) { +// } .createXDMSharedState(0, new EventData()); +// +// assertTrue(services.fakeLoggingService.containsErrorLog(null, +// "Unable to create XDM shared state (com.adobe.marketing.mobile.InvalidModuleException: " + +// "StateName was null)")); +// } +// +// @Test +// public void createSharedStateAndDispatchEvent_NullSharedStateOrEvent() { +// EventData state = new EventData(); +// state.putString("key", "value1"); +// final EventData eventData = new EventData() +// .putString("initialkey", "initialvalue"); +// final Event event = new Event.Builder("Test Event", EventType.CUSTOM, EventSource.NONE).setData(eventData) +// .build(); +// new Module(null, hub) { +// } .createSharedStateAndDispatchEvent(null, event); +// assertTrue(services.fakeLoggingService.containsDebugLog(null, +// "failed to create the shared state and dispatch the event ( null sharedState or null event)")); +// services.fakeLoggingService.clearLog(); +// new Module(null, hub) { +// } .createSharedStateAndDispatchEvent(state, null); +// assertTrue(services.fakeLoggingService.containsDebugLog(null, +// "failed to create the shared state and dispatch the event ( null sharedState or null event)")); +// } +// +// @Test +// public void createXDMSharedStateAndDispatchEvent_NullSharedStateOrEvent() { +// EventData state = new EventData(); +// state.putString("key", "value1"); +// final EventData eventData = new EventData() +// .putString("initialkey", "initialvalue"); +// final Event event = new Event.Builder("Test Event", EventType.CUSTOM, EventSource.NONE).setData(eventData) +// .build(); +// new Module(null, hub) { +// } .createXDMSharedStateAndDispatchEvent(null, event); +// assertTrue(services.fakeLoggingService.containsDebugLog(null, +// "failed to create XDM shared state and dispatch the event ( null sharedState or null event)")); +// services.fakeLoggingService.clearLog(); +// new Module(null, hub) { +// } .createXDMSharedStateAndDispatchEvent(state, null); +// assertTrue(services.fakeLoggingService.containsDebugLog(null, +// "failed to create XDM shared state and dispatch the event ( null sharedState or null event)")); +// } +// +// @Test +// public void updateSharedState_ModuleNotRegistered() { +// new Module(null, hub) { +// } .updateSharedState(0, new EventData()); +// +// assertTrue(services.fakeLoggingService.containsErrorLog(null, +// "Unable to update shared state (com.adobe.marketing.mobile.InvalidModuleException: " + +// "StateName was null)")); +// } +// +// @Test +// public void updateXDMSharedState_ModuleNotRegistered() { +// new Module(null, hub) { +// } .updateXDMSharedState(0, new EventData()); +// +// assertTrue(services.fakeLoggingService.containsErrorLog(null, +// "Unable to update XDM shared state (com.adobe.marketing.mobile.InvalidModuleException: " + +// "StateName was null)")); +// } +// +// @Test +// public void createOrUpdateSharedStateWithVersion_NullSharedStateName() { +// new Module(null, hub) { +// } .createOrUpdateSharedState(0, new EventData()); +// +// assertTrue(services.fakeLoggingService.containsErrorLog(null, +// "Unable to create or update shared state with version (com.adobe.marketing.mobile.InvalidModuleException: " + +// "StateName was null)")); +// } +// +// @Test +// public void createOrUpdateXDMSharedStateWithVersion_NullSharedStateName() { +// new Module(null, hub) { +// } .createOrUpdateXDMSharedState(0, new EventData()); +// +// assertTrue(services.fakeLoggingService.containsErrorLog(null, +// "Unable to create or update XDM shared state with version (com.adobe.marketing.mobile.InvalidModuleException: " + +// "StateName was null)")); +// } +// +// @Test +// public void createOrUpdateSharedState_NullSharedStateName() { +// new Module(null, hub) { +// } .createOrUpdateSharedState(new EventData()); +// +// assertTrue(services.fakeLoggingService.containsErrorLog(null, +// "Unable to create or update shared state (com.adobe.marketing.mobile.InvalidModuleException: " + +// "StateName was null)")); +// } +// +// @Test +// public void createOrUpdateXDMSharedState_NullSharedStateName() { +// new Module(null, hub) { +// } .createOrUpdateXDMSharedState(new EventData()); +// +// assertTrue(services.fakeLoggingService.containsErrorLog(null, +// "Unable to create or update XDM shared state (com.adobe.marketing.mobile.InvalidModuleException: " + +// "StateName was null)")); +// } +// +// @Test +// public void clearSharedStates_ValidState_Works() { +// assertTrue(new Module("validModule", hub) {} .clearSharedStates()); +// } +// +// @Test +// public void clearXDMSharedStates_ValidState_Works() { +// assertTrue(new Module("validModule", hub) {} .clearXDMSharedStates()); +// } +// +// @Test +// public void clearSharedStates_NullStateName_doesNotThrow() { +// assertFalse(new Module(null, hub) {} .clearSharedStates()); +// assertTrue(services.fakeLoggingService.containsErrorLog(null, +// "Unable to clear the shared event states (com.adobe.marketing.mobile.InvalidModuleException: " + +// "StateName was null)")); +// } +// +// @Test +// public void clearXDMSharedStates_NullStateName_doesNotThrow() { +// assertFalse(new Module(null, hub) {} .clearXDMSharedStates()); +// assertTrue(services.fakeLoggingService.containsErrorLog(null, +// "Unable to clear the XDM shared event states (com.adobe.marketing.mobile.InvalidModuleException: " + +// "StateName was null)")); +// } +// +// @Test +// public void hasSharedStates_NullStateName_doesNotThrow() { +// assertFalse(testModule.hasSharedEventState(null)); +// assertTrue(services.fakeLoggingService.containsErrorLog("TestModule", +// "Unable to query shared event state (java.lang.IllegalArgumentException: " + +// "StateName was null)")); +// } +// +// @Test +// public void hasXDMSharedStates_NullStateName_doesNotThrow() { +// assertFalse(testModule.hasXDMSharedEventState(null)); +// assertTrue(services.fakeLoggingService.containsErrorLog("TestModule", +// "Unable to query XDM shared event state (java.lang.IllegalArgumentException: " + +// "StateName was null)")); +// } +// +// @Test +// public void unregisterListener_ListenerNotRegistered() { +// testModule.unregisterListener(EventType.CUSTOM, EventSource.BOOTED); +// waitForLatchWithTimeout(EVENTHUB_WAIT_MS); +// assertTrue(services.fakeLoggingService.containsDebugLog("EventHub(eventhub)", +// "Failed to unregister listener (no registered listener)")); +// } +// +// @Test +// public void unregisterModule_Happy() { +// testModule.unregisterModule(); +// waitForLatchWithTimeout(EVENTHUB_WAIT_MS); +// assertEquals(0, hub.getActiveModules().size()); +// } +// +// @Test +// public void unregisterModule_NotRegistered() { +// Module unregisteredModule = new Module("UnRegistered", hub) {}; +// unregisteredModule.unregisterModule(); +// waitForLatchWithTimeout(EVENTHUB_WAIT_MS); +// +// assertTrue(services.fakeLoggingService.containsErrorLog("EventHub(eventhub)", +// "Failed to unregister module, Module (UnRegistered) is not registered")); +// } +// +// @Test +// public void registerWildcardListener_Happy() { +// testModule.registerWildcardListener(TestListener.class); +// waitForLatchWithTimeout(EVENTHUB_WAIT_MS); +// +// Event event = new Event.Builder("testEvent", EventType.ACQUISITION, EventSource.BOOTED).build(); +// hub.dispatch(event); +// waitForLatchWithTimeout(EVENTHUB_WAIT_MS); +// +// assertEquals(EventType.ACQUISITION.getName(), testListenerLastHeardEvent.getEventType().getName()); +// assertEquals(EventSource.BOOTED.getName(), testListenerLastHeardEvent.getEventSource().getName()); +// } +// +// @Test +// public void registerWildcardListener_Happy_NullListenerClass() { +// testModule.registerWildcardListener(null); +// waitForLatchWithTimeout(EVENTHUB_WAIT_MS); +// +// assertTrue(services.fakeLoggingService.containsErrorLog(TestModule.class.getSimpleName(), +// "Failed to register listener. EventType, EventSource and listenerClass must be non-null values")); +// } +// +//} diff --git a/code/android-core-library/src/test/kotlin/EventHubTests.kt b/code/android-core-library/src/test/kotlin/EventHubTests.kt deleted file mode 100644 index 303970ecb..000000000 --- a/code/android-core-library/src/test/kotlin/EventHubTests.kt +++ /dev/null @@ -1,11 +0,0 @@ -import org.junit.Test -import kotlin.test.assertEquals -import com.adobe.marketing.mobile.internal.eventhub.EventHub - -internal class EventHubTests { - - @Test - fun testVersion() { - assertEquals(EventHub.version, "2.0.0") - } -} \ No newline at end of file diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt new file mode 100644 index 000000000..38511437f --- /dev/null +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt @@ -0,0 +1,143 @@ +package com.adobe.marketing.mobile.internal.eventhub + +import com.adobe.marketing.mobile.Extension +import com.adobe.marketing.mobile.ExtensionApi +import org.junit.Before +import org.junit.Test +import java.lang.Exception +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import kotlin.test.assertEquals + +private object MockExtensions { + class MockExtensionInvalidConstructor(api: ExtensionApi, name: String?): Extension(api) { + override fun getName(): String { + return MockExtensionInvalidConstructor::javaClass.name + } + } + + class MockExtensionInitFailure(api: ExtensionApi): Extension(api) { + init { + throw Exception("Init Exception") + } + + override fun getName(): String { + return MockExtensionInitFailure::javaClass.name + } + } + + class MockExtensionNullName(api: ExtensionApi): Extension(api) { + override fun getName(): String? { + return null + } + } + + class MockExtensionNameException(api: ExtensionApi): Extension(api) { + override fun getName(): String { + throw Exception() + } + } + + class MockExtensionKotlin(api: ExtensionApi): Extension(api) { + override fun getName(): String { + return MockExtensionKotlin::javaClass.name + } + } +} + +internal class EventHubTests { + + // Helper to register extensions + fun registerExtension(extensionClass: Class): EventHubError { + var ret: EventHubError = EventHubError.unknown; + + val latch = CountDownLatch(1) + EventHub.shared.registerExtension(extensionClass) { error -> + ret = error + latch.countDown() + } + if (!latch.await(1, TimeUnit.SECONDS)) throw Exception("Timeout registering extension"); + return ret + } + + fun unregisterExtension(extensionClass: Class): EventHubError { + var ret: EventHubError = EventHubError.unknown; + + val latch = CountDownLatch(1) + EventHub.shared.unregisterExtension(extensionClass) { error -> + ret = error + latch.countDown() + } + if (!latch.await(1, TimeUnit.SECONDS)) throw Exception("Timeout unregistering extension"); + return ret + } + + @Before + fun setup() { + EventHub.shared.shutdown() + EventHub.shared = EventHub() + } + + @Test + fun testRegisterExtensionSuccess() { + + var ret = registerExtension(MockExtension::class.java) + assertEquals(EventHubError.none, ret) + + ret = registerExtension(MockExtensions.MockExtensionKotlin::class.java) + assertEquals(EventHubError.none, ret) + } + + @Test + fun testRegisterExtensionFailure_DuplicateExtension() { + registerExtension(MockExtension::class.java) + + var ret = registerExtension(MockExtension::class.java) + assertEquals(EventHubError.duplicateExtensionName, ret) + } + + @Test + fun testRegisterExtensionFailure_ExtensionInitialization() { + var ret = registerExtension(MockExtensions.MockExtensionInitFailure::class.java) + assertEquals(EventHubError.extensionInitializationFailure, ret) + + ret = registerExtension(MockExtensions.MockExtensionInvalidConstructor::class.java) + assertEquals(EventHubError.extensionInitializationFailure, ret) + } + + @Test + fun testRegisterExtensionFailure_InvalidExceptionName() { + var ret = registerExtension(MockExtensions.MockExtensionNullName::class.java) + assertEquals(EventHubError.invalidExtensionName, ret) + + ret = registerExtension(MockExtensions.MockExtensionNameException::class.java) + assertEquals(EventHubError.invalidExtensionName, ret) + } + + + @Test + fun testUnregisterExtensionSuccess() { + registerExtension(MockExtensions.MockExtensionKotlin::class.java) + + var ret = unregisterExtension(MockExtensions.MockExtensionKotlin::class.java) + assertEquals(EventHubError.none, ret) + } + + @Test + fun testUnregisterExtensionFailure() { + var ret = unregisterExtension(MockExtensions.MockExtensionKotlin::class.java) + assertEquals(EventHubError.extensionNotRegistered, ret) + } + + @Test + fun testRegisterAfterUnregister() { + registerExtension(MockExtensions.MockExtensionKotlin::class.java) + + var ret = unregisterExtension(MockExtensions.MockExtensionKotlin::class.java) + assertEquals(EventHubError.none, ret) + + ret = registerExtension(MockExtensions.MockExtensionKotlin::class.java) + assertEquals(EventHubError.none, ret) + } + +} \ No newline at end of file diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/MockExtension.java b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/MockExtension.java new file mode 100644 index 000000000..52feed700 --- /dev/null +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/MockExtension.java @@ -0,0 +1,15 @@ +package com.adobe.marketing.mobile.internal.eventhub; + +import com.adobe.marketing.mobile.Extension; +import com.adobe.marketing.mobile.ExtensionApi; + +public class MockExtension extends Extension { + MockExtension(ExtensionApi extensionApi) { + super(extensionApi); + } + + @Override + protected String getName() { + return "com.adobe.mockextension"; + } +} From 7880bd7c3e5488a7988e7125515d9ab8327ec804 Mon Sep 17 00:00:00 2001 From: praveek Date: Thu, 31 Mar 2022 14:10:16 -0700 Subject: [PATCH 013/476] Disable functional tests --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f4fc2155e..bee89b612 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -63,4 +63,4 @@ workflows: # Inside the workflow, you define the jobs you want to run. jobs: - build-and-unit-test - - functional-test + # - functional-test From 370a2059e29e43fd803bb5f34c63c7ade7582fd0 Mon Sep 17 00:00:00 2001 From: praveek Date: Thu, 31 Mar 2022 15:03:46 -0700 Subject: [PATCH 014/476] Compile Kotlin code for Javadoc --- code/android-core-library/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index 50a1853c7..115edf77d 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -88,7 +88,8 @@ task jacocoTestReport(type: JacocoReport, dependsOn: ['testPhoneDebugUnitTest',' } android.libraryVariants.all { variant -> - tasks.withType(Javadoc) { + tasks.withType(Javadoc) { task -> + dependsOn "compilePhoneReleaseKotlin" source = [android.sourceSets.main.java.sourceFiles, android.sourceSets.phone.java.sourceFiles] ext.androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar" From e4d2be9b6541496e8ee22eeec044006bd2120132 Mon Sep 17 00:00:00 2001 From: praveek Date: Thu, 31 Mar 2022 15:11:43 -0700 Subject: [PATCH 015/476] Add comments --- code/android-core-library/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index 115edf77d..41c4bbc19 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -89,6 +89,7 @@ task jacocoTestReport(type: JacocoReport, dependsOn: ['testPhoneDebugUnitTest',' android.libraryVariants.all { variant -> tasks.withType(Javadoc) { task -> + // Classpath should have classes compiled from Kotlin so that javadoc can find them. dependsOn "compilePhoneReleaseKotlin" source = [android.sourceSets.main.java.sourceFiles, android.sourceSets.phone.java.sourceFiles] ext.androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar" From 6155b4db5f5fd4804ec5555e757873f570758bb7 Mon Sep 17 00:00:00 2001 From: praveek Date: Thu, 31 Mar 2022 16:15:56 -0700 Subject: [PATCH 016/476] Disable javadoc --- .circleci/config.yml | 6 +++--- code/android-core-library/build.gradle | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bee89b612..3ff77bb14 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -28,9 +28,9 @@ jobs: - run: name: assemble-phone-release command: make assemble-phone-release - - run: - name: JavaDoc - command: make javadoc + # - run: + # name: JavaDoc + # command: make javadoc - run: name: unit-test command: make unit-test diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index 41c4bbc19..2b037adfe 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -89,8 +89,7 @@ task jacocoTestReport(type: JacocoReport, dependsOn: ['testPhoneDebugUnitTest',' android.libraryVariants.all { variant -> tasks.withType(Javadoc) { task -> - // Classpath should have classes compiled from Kotlin so that javadoc can find them. - dependsOn "compilePhoneReleaseKotlin" + source = [android.sourceSets.main.java.sourceFiles, android.sourceSets.phone.java.sourceFiles] ext.androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar" From f4e8d9a1b61a17dec3d01cd22f9f0af85c83ed89 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Fri, 1 Apr 2022 12:56:50 -0700 Subject: [PATCH 017/476] Launch token finder so far --- .../marketing/mobile/LaunchTokenFinder.kt | 162 ++++++ .../marketing/mobile/LaunchTokenFinderTest.kt | 486 ++++++++++++++++++ 2 files changed, 648 insertions(+) create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/LaunchTokenFinder.kt create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/LaunchTokenFinderTest.kt diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/LaunchTokenFinder.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/LaunchTokenFinder.kt new file mode 100644 index 000000000..55b191e17 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/LaunchTokenFinder.kt @@ -0,0 +1,162 @@ +package com.adobe.marketing.mobile + +import java.security.SecureRandom + +internal class LaunchTokenFinder(val event: Event, val module: Module, val platformServices: PlatformServices) { + + companion object { + private const val LOG_TAG = "LaunchTokenFinder" + private const val KEY_EVENT_TYPE = "~type" + private const val KEY_EVENT_SOURCE = "~source" + private const val KEY_TIMESTAMP_UNIX = "~timestampu" + private const val KEY_TIMESTAMP_ISO8601 = "~timestampz" + private const val KEY_TIMESTAMP_PLATFORM = "~timestampp" + private const val KEY_SDK_VERSION = "~sdkver" + private const val KEY_CACHEBUST = "~cachebust" + private const val KEY_ALL_URL = "~all_url" + private const val KEY_ALL_JSON = "~all_json" + private const val KEY_SHARED_STATE = "~state." + private const val EMPTY_STRING = "" + private const val RANDOM_INT_BOUNDARY = 100000000 + private const val SHARED_STATE_KEY_DELIMITER = "/" + } + + // ======================================================== + // public methods + // ======================================================== + + /** + * Returns the value for the `key` provided as input. + * + * + * If the `key` is a special key recognized by SDK, the value is determined based on incoming `Event`, + * or `EventHub#moduleSharedStates` data. Otherwise the key is searched in the current `Event`'s data + * and the corresponding value is returned. + * + * @param key `String` containing the key whose value needs to be determined + * + * @return `Object` containing value to be substituted for the `key` + */ + fun get(key: String): Any? { + if (StringUtils.isNullOrEmpty(key)) { + return null + } + + return when (key) { + KEY_EVENT_TYPE -> event.getEventType().getName() + KEY_EVENT_SOURCE -> event.getEventSource().getName() + KEY_TIMESTAMP_UNIX -> TimeUtil.getUnixTimeInSeconds().toString() + KEY_TIMESTAMP_ISO8601 -> TimeUtil.getIso8601Date() + KEY_TIMESTAMP_PLATFORM -> TimeUtil.getIso8601DateTimeZoneISO8601() + KEY_SDK_VERSION -> { + MobileCore.extensionVersion() + } + KEY_CACHEBUST -> SecureRandom().nextInt(RANDOM_INT_BOUNDARY).toString() + KEY_ALL_URL -> { + if (event.getData() == null) { + Log.debug(LOG_TAG, "Triggering event data is null, can not use it to generate an url query string") + return EMPTY_STRING + } + val eventDataAsObjectMap = EventDataFlattener.getFlattenedDataMap(event.getData()) + UrlUtilities.serializeToQueryString(eventDataAsObjectMap) + } + KEY_ALL_JSON -> { + if (event.getData() == null) { + Log.debug(LOG_TAG, "Triggering event data is null, can not use it to generate a json string") + return EMPTY_STRING + } + generateJsonString(event.getData()) + } + else -> { + if (key.startsWith(KEY_SHARED_STATE)) { + getValueFromSharedState(key) + } else getValueFromEvent(key) + } + } + } + + // ======================================================== + // private getter methods + // ======================================================== + + /** + * Returns the value for shared state key specified by the `key`. + * + * + * The key is provided in the format ~state.valid_shared_state_name/key + * For example: ~state.com.adobe.marketing.mobile.Identity/mid + * + * @param key `String` containing the key to search for in `EventHub#moduleSharedStates` + * + * @return `Object` containing the value for the shared state key if valid, null otherwise + */ + private fun getValueFromSharedState(key: String): Any? { + val sharedStateKeyString = key.substring(KEY_SHARED_STATE.length) + if (StringUtils.isNullOrEmpty(sharedStateKeyString)) { + return null + } + val index = sharedStateKeyString.indexOf(SHARED_STATE_KEY_DELIMITER) + if (index == -1) { + return null + } + val sharedStateName = sharedStateKeyString.substring(0, index) + val dataKeyName = sharedStateKeyString.substring(index + 1) + val sharedStateMap = EventDataFlattener.getFlattenedDataMap(module.getSharedEventState( + sharedStateName, event)) + if (sharedStateMap.isEmpty() || StringUtils.isNullOrEmpty(dataKeyName) || !sharedStateMap.containsKey(dataKeyName)) { + return null + } + val variant = sharedStateMap[dataKeyName] + return try { + PermissiveVariantSerializer.DEFAULT_INSTANCE.deserialize(variant) + } catch (ex: VariantException) { + null + } + } + + /** + * Returns the value for the `key` provided as input by searching in the current `Event`'s data. + * + * @param key `String` containing the key whose value needs to be determined + * + * @return `Object` containing value to be substituted for the `key` from the `Event`'s data + */ + private fun getValueFromEvent(key: String): Any? { + if (event.getData() == null) { + Log.debug(LOG_TAG, String.format("Unable to replace the token %s, triggering event data is null", key)) + return EMPTY_STRING + } + val eventDataMap = EventDataFlattener.getFlattenedDataMap(event.getData()) + if (!eventDataMap.containsKey(key)) { + return null + } + val value = eventDataMap[key] + return if (value == null || value is NullVariant) { + null + } else try { + PermissiveVariantSerializer.DEFAULT_INSTANCE.deserialize(value) + } catch (ex: VariantException) { + EMPTY_STRING + } + } + + /** + * Returns the `EventData` in json format + * + * @param eventData `EventData` which needs to be encoded + * @return `String` containing `event`'s data encoded in json format + */ + private fun generateJsonString(eventData: EventData): String { + val jsonUtilityService = platformServices.getJsonUtilityService() + ?: return EMPTY_STRING + val jsonObject = try { + val dataMap = eventData.asMapCopy() + val variant = Variant.fromVariantMap(dataMap) + variant.getTypedObject(JsonObjectVariantSerializer( + jsonUtilityService)) + } catch (exception: Exception) { + return EMPTY_STRING + } + return jsonObject?.toString() ?: EMPTY_STRING + } +} \ No newline at end of file diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/LaunchTokenFinderTest.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/LaunchTokenFinderTest.kt new file mode 100644 index 000000000..96d28df2e --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/LaunchTokenFinderTest.kt @@ -0,0 +1,486 @@ +package com.adobe.marketing.mobile + +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; + +import org.junit.Assert.* + +class LaunchTokenFinderTest: BaseTest() { + + private lateinit var configuration: TestableConfigurationExtension + + @Before + @Throws(MissingPlatformServicesException::class) + fun setup() { + super.beforeEach() + configuration = TestableConfigurationExtension(eventHub, platformServices) + } + + @Test + fun get_ReturnsNull_When_KeyIsEmpty() { + //setup + val testEvent = getDefaultEvent() + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, platformServices) + //test + val result = launchTokenFinder.get("") + //verify + assertNull("get should return null on empty input string", result) + } + + @Test + fun get_ReturnsEventType_When_KeyIsType() { + //setup + val testEvent = getDefaultEvent() + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, platformServices) + //test + val result = launchTokenFinder.get("~type") + //verify + assertEquals("get should return Event Type on valid Event", "com.adobe.eventtype.analytics", result) + } + + @Test + fun get_ReturnsEventSource_When_KeyIsSource() { + //setup + val testEvent = getDefaultEvent() + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, platformServices) + //test + val result = launchTokenFinder.get("~source") + //verify + assertEquals("get should return Event Source on valid Event", "com.adobe.eventsource.requestcontent", result) + } + + @Test + fun get_ReturnsCurrentUnixTimestamp_When_KeyPrefixIsTimestampu() { + //setup + val testEvent = getDefaultEvent()!! + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration!!, + platformServices) + //test + val result = launchTokenFinder.get("~timestampu") + //verify + assertEquals("get should return current unix timestamp on valid event", TimeUtil.getUnixTimeInSeconds().toString(), result) + } + + @Test + fun get_ReturnsCurrentISO8601Timestamp_When_KeyPrefixIsTimestampz() { + //setup + val testEvent = getDefaultEvent()!! + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration!!, + platformServices) + //test + val result = launchTokenFinder.get("~timestampz") + //verify + assertEquals("get should return current ISO8601 timestamp on valid event", TimeUtil.getIso8601Date(), result) + } + + @Test + fun get_ReturnsCurrentIso8601DateTimeZone_When_KeyPrefixIsTimestampp() { + //setup + val testEvent = getDefaultEvent()!! + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration!!, + platformServices) + //test + val result = launchTokenFinder.get("~timestampp") + //verify + assertEquals("get should return current ISO8601 date timezone on valid event", + TimeUtil.getIso8601DateTimeZoneISO8601(), result) + } + + @Test + fun expandKey_ReturnsCurrentSdkVersion_When_KeyPrefixIsSdkVersion() { + //setup + val testEvent = getDefaultEvent()!! + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration!!, + platformServices) + //test + val result = launchTokenFinder.get("~sdkver") + //verify + assertEquals("get should return current sdk version on valid event", "mockSdkVersion", result) + } + + @Test + fun get_ReturnsRandomNumber_When_KeyPrefixIsCachebust() { + //setup + val testEvent = getDefaultEvent() + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("~cachebust") as? String + //verify + try { + if (result != null) { + assertTrue("get should return random cachebust on valid event", result.toInt() < 100000000) + } + } catch (ex: VariantException) { + } + } + + @Test + fun get_ReturnsUrlEncoded_When_KeyPrefixIsAllUrlStringOrNull() { + //setup + val testEventData = EventData() + testEventData.putString("key1", "value 1") + testEventData.putNull("key8") + val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("~all_url") + //verify + assertEquals("get should return all variables on valid event encoded in url format", + "&key1=value%201", + result) + } + + @Test + fun get_ReturnsUrlEncoded_When_KeyPrefixIsAllUrlAndEventDataIsIntOrLong() { + //setup + val testEventData = EventData() + testEventData.putInteger("key3", 123) + testEventData.putLong("key4", -456L) + val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("~all_url") + //verify + assertTrue("get should return all list variables on valid event encoded in url format", + "&key3=123&key4=-456" == result || "&key4=-456&key3=123" == result) + } + + @Test + fun get_ReturnsUrlEncoded_When_KeyPrefixIsAllUrlAndEventDataIsDoubleOrBoolean() { + //setup + val testEventData = EventData() + testEventData.putBoolean("key2", true) + testEventData.putDouble("key5", -123.456) + val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("~all_url") + //verify + assertTrue("get should return all list variables on valid event encoded in url format", + "&key2=true&key5=-123.456" == result || "&key5=-123.456&key2=true" == result) + } + + @Test + fun get_ReturnsUrlEncoded_When_KeyPrefixIsAllUrlAndEventDataIsList() { + //setup + val testEventData = EventData() + val stringList: MutableList = ArrayList() + stringList.add("String1") + stringList.add("String2") + testEventData.putStringList("key6", stringList) + val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("~all_url") + //verify + assertEquals("get should return all list variables on valid event encoded in url format", + "&key6=String1%2CString2", + result) + } + + @Test + fun get_ReturnsUrlEncoded_When_KeyPrefixIsAllUrlAndEventDataIsMap() { + //setup + val testEventData = EventData() + val stringMap: MutableMap = HashMap() + stringMap["innerKey1"] = "inner val1" + stringMap["innerKey2"] = "innerVal2" + testEventData.putStringMap("key7", stringMap) + val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("~all_url") + //verify + assertTrue("get should return all map variables on valid event encoded in url format", + "&key7.innerKey1=inner%20val1&key7.innerKey2=innerVal2" == result || "&key7.innerKey2=innerVal2&key7.innerKey1=inner%20val1" == result) + } + + @Test + fun get_ReturnsEmptyString_When_KeyPrefixIsAllUrlEventDataIsNull() { + //setup + val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("~all_url") + //verify + assertEquals("get should return empty string on event with no event data", "", result) + } + + /* @Test + @Throws(JSONException::class) + fun get_ReturnsJson_When_KeyPrefixIsAllJson() { + //setup + val testEventData = EventData() + testEventData.putString("key1", "value1") + testEventData.putBoolean("key2", true) + testEventData.putInteger("key3", 123) + testEventData.putLong("key4", -456L) + testEventData.putDouble("key5", -123.456) + testEventData.putNull("key6") + val stringList: MutableList = ArrayList() + stringList.add("String1") + stringList.add("String2") + testEventData.putStringList("key7", stringList) + val stringMap: MutableMap = HashMap() + stringMap["key22"] = "22" + testEventData.putStringMap("key8", stringMap) + val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val launchTokenFinder = LaunchTokenFinder(testEvent!!, configuration!!, + platformServices) + //test + val result = launchTokenFinder.get("~all_json") + val resultObj = JSONObject(result as String) + val expectedObj = JSONObject("{\"key1\":\"value1\",\"key2\":true,\"key5\":-123.456,\"key6\":null,\"key3\":123,\"key4\":-456,\"key7\":[\"String1\",\"String2\"],\"key8\":{\"key22\":\"22\"}}") + + //verify + assertTrue("get should return all variables on valid event encoded in json format", + expectedObj.similar(resultObj)) + } */ + + @Test + fun get_ReturnsEmptyString_When_KeyPrefixIsAllJsonEventDataIsNull() { + //setup + val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("~all_json") + //verify + assertEquals("get should return empty string on event with no event data", "", result) + } + + @Test + fun get_ReturnsSharedStateKey_When_KeyPrefixIsState() { + //setup + val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) + val lcdata = EventData() + val lifecycleSharedState: MutableMap = HashMap() + lifecycleSharedState["akey"] = "avalue" + lcdata.putStringMap("analytics.contextData", lifecycleSharedState) + eventHub.setSharedState("com.adobe.marketing.mobile.Analytics", lcdata) + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("~state.com.adobe.marketing.mobile.Analytics/analytics.contextData.akey") + //verify + assertEquals("get should return shared state of the module on valid event", "avalue", result) + } + + @Test + fun get_ReturnsSharedStateList_When_KeyPrefixIsStateAndValueIsList() { + //setup + val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) + val lcdata = EventData() + val identitySharedState: MutableList = ArrayList() + identitySharedState.add("vid1") + identitySharedState.add("vid2") + lcdata.putStringList("visitoridslist", identitySharedState) + eventHub.setSharedState("com.adobe.marketing.mobile.identity", lcdata) + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("~state.com.adobe.marketing.mobile.identity/visitoridslist") + //verify + assertEquals("get should return shared state list of the module on valid event", identitySharedState, result) + } + + @Test + fun get_ReturnsSharedStateMap_When_KeyPrefixIsStateAndValueIsMap() { + //setup + val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) + val lcdata = EventData() + val lifecycleSharedState: MutableMap = HashMap() + lifecycleSharedState["akey"] = "avalue" + lcdata.putStringMap("analytics.contextData", lifecycleSharedState) + eventHub.setSharedState("com.adobe.marketing.mobile.Analytics", lcdata) + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("~state.com.adobe.marketing.mobile.Analytics/analytics.contextData") + //verify + assertEquals("get should return shared state of the module on valid event", lifecycleSharedState, result) + } + + @Test + fun get_ReturnsNull_When_KeyPrefixIsStateAndMissingSharedStateKeyName() { + //setup + val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("~state.") + //verify + assertNull("get should return null when key does not have shared state name", result) + } + + @Test + fun get_ReturnsNull_When_KeyPrefixIsStateAndMissingKeyName() { + //setup + val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("~state.com.adobe.marketing.mobile.Analytics/") + //verify + assertNull("get should return null when key does not have shared state key name", result) + } + + @Test + fun get_ReturnsNull_When_KeyPrefixIsStateAndIncorrectFormat() { + //setup + val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("~state.com.adobe/.marketing.mobile.Analytics/analytics.contextData.akey") + //verify + assertNull("get should return null when key does not have valid format", result) + } + + @Test + fun get_ReturnsNull_When_KeyPrefixIsStateAndKeyNotExist() { + //setup + val testEventData = EventData() + val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("~state.com.adobe.marketing.mobile.Analytics/analytics.contextData.akey") + //verify + assertNull("get should return null when key does not exist in shared state", result) + } + + @Test + fun get_ReturnsEventDataValue_When_KeyIsNotSpecialKey() { + //setup + val testEvent = getDefaultEvent() + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("key1") + //verify + assertEquals("get should return value of the key from event data on valid event", "value1", result) + } + + @Test + fun get_ReturnsEmptyString_When_KeyIsNotSpecialKeyAndEventDataIsNull() { + //setup + val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("key1") + //verify + assertEquals("get should return empty string when event data is null on valid event", "", result) + } + + @Test + fun get_ReturnsNull_When_KeyIsNotSpecialKeyAndDoesNotExist() { + //setup + val testEvent = getDefaultEvent() + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("abc") + //verify + assertNull("get should return null when key does not exist in event data on valid event", result) + } + + @Test + fun get_ReturnsNull_When_KeyIsNotSpecialKeyAndValueIsNull() { + //setup + val testEventData = EventData() + testEventData.putNull("key1") + val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("key1") + //verify + assertNull("get should return null when value for the key in event data is null on valid event", result) + } + + @Test + fun get_ReturnsList_When_KeyIsNotSpecialKeyAndValueIsList() { + //setup + val testEventData = EventData() + val stringList: MutableList = ArrayList() + stringList.add("String1") + stringList.add("String2") + testEventData.putStringList("key6", stringList) + val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("key6") + //verify + assertEquals("get should return empty string on list variant", stringList, result) + } + + /* @Test + fun get_ReturnsMap_When_KeyIsNotSpecialKeyAndValueIsEmptyMap() { + //setup + val testEventData = EventData() + testEventData.putVariantMap("key1", HashMap()) + val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val launchTokenFinder = LaunchTokenFinder(testEvent!!, configuration!!, + platformServices) + //test + val result = launchTokenFinder.get("key1") + //verify + assertEquals("get should return empty map on empty map variant", HashMap(), result) + } */ + + @Test + fun get_ReturnsMap_When_KeyIsNotSpecialKeyAndValueIsMap() { + //setup + val testEventData = EventData() + val stringMap: MutableMap = HashMap() + stringMap["innerKey1"] = "inner val1" + testEventData.putStringMap("key1", stringMap) + val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("key1") + //verify + assertEquals("get should return map on map variant", stringMap, result) + } + + @Test + fun get_ReturnsNestedValue_When_KeyIsFlattenedNestedKey() { + //setup + val testEventData = EventData() + val stringMap: MutableMap = HashMap() + stringMap["innerKey1"] = "inner val1" + stringMap["innerKey2"] = "innerVal2" + testEventData.putStringMap("key7", stringMap) + val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, + platformServices) + //test + val result = launchTokenFinder.get("key7.innerKey1") + //verify + assertEquals("get should return nested value for valid flattened key on a valid event", "inner val1", result) + } + + private fun getEvent(type: EventType?, source: EventSource?, eventData: EventData?): Event { + return Event.Builder("TEST", type, source) + .setData(eventData).build() + } + + private fun getDefaultEvent(): Event { + val testEventData = EventData() + testEventData.putString("key1", "value1") + return getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + } +} \ No newline at end of file From b03ba78a81b5d453788666c43b65e28879696cfe Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Mon, 11 Apr 2022 12:28:16 -0700 Subject: [PATCH 018/476] [#39] Migrate aepsdk-core-android to AndroidX [Summary] - Uses the AndroidX migration tool for preliminary migration. - Manually grep for reflection use inside the project for class mapping. Following instance needed a manual change : * android.support.v4.app.NotificationCompat -> androidx.core.app.NotificationCompat - Following instances of instrumentation tests needed change manually : * InstrumentationRegistry.getContext() -> InstrumentationRegistry.getInstrumentation().getContext() - androidx.test.ext:junit:1.1.3 was added for AndroidJUnit4 test support. [Testing] - Used the TestApp to trigger changes and ensured that change surface works. - Instrumentation and unit tests pass --- .../build.gradle | 8 +-- code/android-core-library/build.gradle | 7 +-- .../AndroidCompressedFileServiceTests.java | 6 +-- .../AndroidConfigurationFunctionalTests.java | 10 +--- .../marketing/mobile/AndroidCursorTests.java | 4 +- .../mobile/AndroidDatabaseServiceTests.java | 4 +- .../mobile/AndroidDatabaseTests.java | 4 +- .../mobile/AndroidEncodingServiceTests.java | 2 +- .../AndroidEventHistoryDatabaseTests.java | 4 +- .../mobile/AndroidEventHistoryTests.java | 4 +- .../mobile/AndroidJsonArrayTests.java | 2 +- .../mobile/AndroidJsonObjectTests.java | 2 +- .../mobile/AndroidJsonUtilityTests.java | 2 +- .../AndroidLocalStorageServiceTests.java | 4 +- .../mobile/AndroidLoggingServiceTests.java | 2 +- ...idThirdPartyExtensionsFunctionalTests.java | 2 +- .../mobile/AndroidV4ToV5MigrationTests.java | 2 +- .../marketing/mobile/DataMarshallerTests.java | 4 +- .../marketing/mobile/EventCoderTests.java | 3 +- .../mobile/ExampleInstrumentedTest.java | 6 +-- .../mobile/QueryStringBuilderTests.java | 4 +- .../services/LocalDataStoreServiceTests.java | 4 +- .../services/SqliteDatabaseHelperTests.java | 6 +-- .../marketing/mobile/AbstractE2ETest.java | 2 +- .../mobile/AndroidFullscreenMessage.java | 4 +- .../mobile/LocalNotificationHandler.java | 6 +-- .../services/ui/MessageWebViewClient.java | 4 +- code/gradle.properties | 7 +++ code/testapp/build.gradle | 20 ++++---- .../marketing/mobile/PerformanceTest.java | 8 ++- .../testapp/UIServicesInstrumentedTest.java | 50 +++++++++---------- .../com/adobe/testapp/ExtensionFragment.java | 2 +- .../java/com/adobe/testapp/MainActivity.java | 8 +-- .../com/adobe/testapp/MobileCoreFragment.java | 6 +-- .../java/com/adobe/testapp/PagerAdapter.java | 6 +-- .../testapp/PlatformServicesFragment.java | 2 +- .../java/com/adobe/testapp/RootFragment.java | 4 +- .../com/adobe/testapp/UIServicesFragment.java | 2 +- .../src/main/res/layout/activity_main.xml | 16 +++--- .../main/res/layout/fragment_extension.xml | 4 +- .../main/res/layout/fragment_mobile_core.xml | 4 +- .../res/layout/fragment_platform_services.xml | 4 +- .../main/res/layout/fragment_ui_services.xml | 4 +- 43 files changed, 128 insertions(+), 131 deletions(-) diff --git a/code/android-core-library-maven-root/build.gradle b/code/android-core-library-maven-root/build.gradle index d993133db..3568d661a 100644 --- a/code/android-core-library-maven-root/build.gradle +++ b/code/android-core-library-maven-root/build.gradle @@ -10,7 +10,7 @@ android { versionCode 1 versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } @@ -91,8 +91,8 @@ signing { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'androidx.appcompat:appcompat:1.0.0' testImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' } diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index 50a1853c7..6b6350b83 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -10,7 +10,7 @@ android { targetSdkVersion 30 //Include the Proguard rules for Core Extension in the aar consumerProguardFiles 'lib-proguard-rules.pro' - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } sourceSets { @@ -109,7 +109,7 @@ apply from: 'release.gradle' dependencies { //noinspection GradleCompatible - implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'androidx.appcompat:appcompat:1.0.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" // unit tests testImplementation "junit:junit:4.13.2" @@ -123,7 +123,8 @@ dependencies { testImplementation 'org.json:json:20160810' testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" // instrumentation tests - androidTestImplementation "com.android.support.test:rules:1.0.2" + androidTestImplementation 'androidx.test:rules:1.1.1' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' } repositories { diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCompressedFileServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCompressedFileServiceTests.java index fe47f1d17..7eaa86287 100755 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCompressedFileServiceTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCompressedFileServiceTests.java @@ -21,8 +21,8 @@ import org.junit.runner.RunWith; import android.content.res.Resources; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import java.io.File; import java.io.FileOutputStream; @@ -151,7 +151,7 @@ public void testExtract_NotCreatedDestination() throws IOException { void prepareZipFile(String filename) { try { - Resources res = InstrumentationRegistry.getContext().getResources(); + Resources res = InstrumentationRegistry.getInstrumentation().getContext().getResources(); InputStream instream = res.getAssets().open(filename); tempSource = folder.newFile(); diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidConfigurationFunctionalTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidConfigurationFunctionalTests.java index 4413cbf4f..c2a0217a9 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidConfigurationFunctionalTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidConfigurationFunctionalTests.java @@ -12,25 +12,17 @@ package com.adobe.marketing.mobile; import org.junit.After; -import org.junit.AfterClass; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import static com.adobe.marketing.mobile.E2ETestableNetworkService.NetworkRequest; import static com.adobe.marketing.mobile.E2ETestableNetworkService.NetworkResponse; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java index b34831dd6..f63263a1d 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java @@ -21,8 +21,8 @@ import org.junit.rules.TestName; import org.junit.runner.RunWith; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.HashMap; import java.util.Random; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java index fae6d1d60..d883584e5 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java @@ -17,8 +17,8 @@ import org.junit.runner.RunWith; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseTests.java index 07775dc55..723078d30 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseTests.java @@ -21,8 +21,8 @@ import org.junit.runner.RunWith; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import android.util.Log; import java.util.ArrayList; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEncodingServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEncodingServiceTests.java index 02bbf0062..23169ba82 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEncodingServiceTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEncodingServiceTests.java @@ -14,7 +14,7 @@ import org.junit.Test; import org.junit.runner.RunWith; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryDatabaseTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryDatabaseTests.java index df833e162..96d18683f 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryDatabaseTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryDatabaseTests.java @@ -19,8 +19,8 @@ import android.content.Context; import android.database.DatabaseUtils; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import com.adobe.marketing.mobile.services.ServiceProvider; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryTests.java index debb9fb57..64e693a35 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryTests.java @@ -16,8 +16,8 @@ import static org.junit.Assert.fail; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import com.adobe.marketing.mobile.services.ServiceProvider; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonArrayTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonArrayTests.java index faba89b7a..1963ba617 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonArrayTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonArrayTests.java @@ -17,7 +17,7 @@ import org.junit.Test; import org.junit.runner.RunWith; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonObjectTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonObjectTests.java index 68baf83b5..e166cdaaf 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonObjectTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonObjectTests.java @@ -17,7 +17,7 @@ import org.junit.Test; import org.junit.runner.RunWith; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonUtilityTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonUtilityTests.java index e2dabe9db..1fe37020a 100755 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonUtilityTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonUtilityTests.java @@ -18,7 +18,7 @@ import org.junit.Test; import org.junit.runner.RunWith; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.HashMap; import java.util.Iterator; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidLocalStorageServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidLocalStorageServiceTests.java index fbf6b0f43..122142836 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidLocalStorageServiceTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidLocalStorageServiceTests.java @@ -17,8 +17,8 @@ import org.junit.runner.RunWith; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.HashMap; import java.util.Map; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidLoggingServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidLoggingServiceTests.java index f4c524c41..fd4fa4400 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidLoggingServiceTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidLoggingServiceTests.java @@ -15,7 +15,7 @@ import org.junit.Test; import org.junit.runner.RunWith; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import java.io.BufferedReader; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java index 813a4afb7..732429473 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java @@ -17,7 +17,7 @@ import org.junit.Test; import org.junit.runner.RunWith; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.ArrayList; import java.util.HashMap; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidV4ToV5MigrationTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidV4ToV5MigrationTests.java index 8599ab839..dc08871b8 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidV4ToV5MigrationTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidV4ToV5MigrationTests.java @@ -13,7 +13,7 @@ import android.content.Context; import android.content.SharedPreferences; -import android.support.test.InstrumentationRegistry; +import androidx.test.platform.app.InstrumentationRegistry; import junit.framework.Assert; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/DataMarshallerTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/DataMarshallerTests.java index f1e6e296e..a44f69ee7 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/DataMarshallerTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/DataMarshallerTests.java @@ -14,8 +14,8 @@ import android.app.Activity; import android.content.Intent; import android.net.Uri; -import android.support.test.rule.ActivityTestRule; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.rule.ActivityTestRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Rule; import org.junit.Test; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/EventCoderTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/EventCoderTests.java index 48e26fe36..a85e7efa7 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/EventCoderTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/EventCoderTests.java @@ -12,12 +12,11 @@ package com.adobe.marketing.mobile; import android.app.Activity; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/ExampleInstrumentedTest.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/ExampleInstrumentedTest.java index 71e318aac..15b1e4240 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/ExampleInstrumentedTest.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/ExampleInstrumentedTest.java @@ -12,8 +12,8 @@ package com.adobe.marketing.mobile; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,7 +30,7 @@ public class ExampleInstrumentedTest { @Test public void useAppContext() { // Context of the app under test. - Context appContext = InstrumentationRegistry.getTargetContext(); + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); assertEquals("com.adobe.marketing.mobile.test", appContext.getPackageName()); } diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/QueryStringBuilderTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/QueryStringBuilderTests.java index 6785939ec..30ee19266 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/QueryStringBuilderTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/QueryStringBuilderTests.java @@ -17,8 +17,8 @@ import org.junit.runner.RunWith; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.ArrayList; import java.util.List; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/LocalDataStoreServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/LocalDataStoreServiceTests.java index 4e5552e7b..ebb9f1ef4 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/LocalDataStoreServiceTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/LocalDataStoreServiceTests.java @@ -17,8 +17,8 @@ import static junit.framework.Assert.assertTrue; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; import org.junit.Test; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDatabaseHelperTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDatabaseHelperTests.java index e0fc37cc0..9d27901f2 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDatabaseHelperTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDatabaseHelperTests.java @@ -12,8 +12,8 @@ package com.adobe.marketing.mobile.services; import android.content.ContentValues; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Assert; @@ -38,7 +38,7 @@ public class SqliteDatabaseHelperTests { @Before public void setUp() { - dbPath = new File(InstrumentationRegistry.getContext().getCacheDir(), "test.sqlite").getPath(); + dbPath = new File(InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(), "test.sqlite").getPath(); sqLiteDatabaseHelper = new SQLiteDatabaseHelper(); createTable(); } diff --git a/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/AbstractE2ETest.java b/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/AbstractE2ETest.java index 8f4957e68..63b88ea32 100755 --- a/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/AbstractE2ETest.java +++ b/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/AbstractE2ETest.java @@ -14,7 +14,7 @@ import android.app.Application; import android.app.Instrumentation; import android.content.Context; -import android.support.test.InstrumentationRegistry; +import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Rule; import org.junit.rules.TestName; diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidFullscreenMessage.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidFullscreenMessage.java index 09b98a5b7..b28e626a6 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidFullscreenMessage.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidFullscreenMessage.java @@ -22,8 +22,8 @@ import android.os.Build; import android.os.Handler; import android.os.Looper; -import android.support.annotation.Nullable; -import android.support.annotation.RequiresApi; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.TranslateAnimation; diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/LocalNotificationHandler.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/LocalNotificationHandler.java index 036fbbf9a..77971de4e 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/LocalNotificationHandler.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/LocalNotificationHandler.java @@ -183,14 +183,14 @@ public void onReceive(Context context, Intent intent) { notificationManager.createNotificationChannel((NotificationChannel)notificationChannel); // specify the notification channel id when creating the notification compat builder - notificationBuilderClass = classLoader.loadClass("android.support.v4.app.NotificationCompat$Builder"); + notificationBuilderClass = classLoader.loadClass("androidx.core.app.NotificationCompat$Builder"); Constructor notificationConstructor = notificationBuilderClass.getConstructor(Context.class, NOTIFICATION_CHANNEL_ID.getClass()); notificationConstructor.setAccessible(true); notificationBuilder = notificationConstructor.newInstance(context.getApplicationContext(), NOTIFICATION_CHANNEL_ID); final Method methodSetStyle = notificationBuilderClass.getDeclaredMethod("setStyle", - classLoader.loadClass("android.support.v4.app.NotificationCompat$Style")); + classLoader.loadClass("androidx.core.app.NotificationCompat$Style")); methodSetStyle.invoke(notificationBuilder, getBigTextStyle(buildVersion, classLoader, message)); } else { @@ -305,7 +305,7 @@ private static Object getBigTextStyle(final int buildVersion, final ClassLoader Object bigTextStyle; Class classBigTextStyle = classLoader.loadClass(buildVersion >= 26 ? - "android.support.v4.app.NotificationCompat$BigTextStyle" : "android.app.Notification$BigTextStyle"); + "androidx.core.app.NotificationCompat$BigTextStyle" : "android.app.Notification$BigTextStyle"); Constructor bigTextStyleConstructor = classBigTextStyle.getConstructor(); bigTextStyle = bigTextStyleConstructor.newInstance(); Method methodBigText = classBigTextStyle.getDeclaredMethod("bigText", CharSequence.class); diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewClient.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewClient.java index d4eb90726..7e6edeb4a 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewClient.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewClient.java @@ -14,8 +14,8 @@ import android.annotation.TargetApi; import android.net.Uri; import android.os.Build; -import android.support.annotation.Nullable; -import android.support.annotation.RequiresApi; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import android.webkit.MimeTypeMap; import android.webkit.WebResourceRequest; import android.webkit.WebResourceResponse; diff --git a/code/gradle.properties b/code/gradle.properties index f317f1686..1b3a73277 100644 --- a/code/gradle.properties +++ b/code/gradle.properties @@ -23,4 +23,11 @@ mavenLifecycleVersion=1.1.0 #android.enableUnitTestBinaryResources=false kotlin_version = 1.4.0 +android.useAndroidX=true + +# Currrently core depends on libraries that are +# compatible with AndroidX. Hence jettification is +# not nececessary. Enable the flag below incase an +# incompatible library is used. +#android.enableJetifier=true diff --git a/code/testapp/build.gradle b/code/testapp/build.gradle index 5f6d29857..dce376679 100644 --- a/code/testapp/build.gradle +++ b/code/testapp/build.gradle @@ -13,7 +13,7 @@ android { //This says that my test app only wants to use the Phone variant missingDimensionStrategy 'target', 'phone' - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } buildTypes { @@ -34,9 +34,9 @@ android { dependencies { // implementation fileTree(include: ['*.jar'], dir: 'libs') def withoutCore = { exclude group: 'com.adobe.marketing.mobile', module: 'core' } - implementation 'com.android.support:appcompat-v7:28.0.0' - implementation 'com.android.support:design:28.0.0' - implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'androidx.appcompat:appcompat:1.0.0' + implementation 'com.google.android.material:material:1.0.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation project(':android-core-library') implementation 'com.adobe.marketing.mobile:lifecycle:1.0.2', withoutCore implementation 'com.adobe.marketing.mobile:identity:1.+', withoutCore @@ -45,15 +45,15 @@ dependencies { // implementation 'com.adobe.marketing.mobile:sdk-core:1.+' testImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test:rules:1.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2', { + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test:rules:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0', { exclude group: 'com.android.support', module: 'support-annotations' } - androidTestImplementation 'com.android.support.test.espresso:espresso-intents:3.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-web:3.0.2' + androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0' + androidTestImplementation 'androidx.test.espresso:espresso-web:3.1.0' - androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3' + androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' /* Uncomment the following line when testing with local test-third-party-extension changes */ //implementation project(':test-third-party-extension') diff --git a/code/testapp/src/androidTest/java/com/adobe/marketing/mobile/PerformanceTest.java b/code/testapp/src/androidTest/java/com/adobe/marketing/mobile/PerformanceTest.java index bed598361..35e8ddeec 100644 --- a/code/testapp/src/androidTest/java/com/adobe/marketing/mobile/PerformanceTest.java +++ b/code/testapp/src/androidTest/java/com/adobe/marketing/mobile/PerformanceTest.java @@ -12,9 +12,9 @@ import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; -import android.support.v4.widget.TextViewCompat; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.core.widget.TextViewCompat; import android.util.Log; import org.junit.Test; @@ -22,8 +22,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import static org.junit.Assert.*; diff --git a/code/testapp/src/androidTest/java/com/adobe/testapp/UIServicesInstrumentedTest.java b/code/testapp/src/androidTest/java/com/adobe/testapp/UIServicesInstrumentedTest.java index 76b0608f2..e9fc05eea 100644 --- a/code/testapp/src/androidTest/java/com/adobe/testapp/UIServicesInstrumentedTest.java +++ b/code/testapp/src/androidTest/java/com/adobe/testapp/UIServicesInstrumentedTest.java @@ -11,34 +11,34 @@ package com.adobe.testapp; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist; -import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.intent.Intents.intended; -import static android.support.test.espresso.intent.matcher.IntentMatchers.hasAction; -import static android.support.test.espresso.intent.matcher.IntentMatchers.hasData; -import static android.support.test.espresso.matcher.RootMatchers.isDialog; -import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; -import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static android.support.test.espresso.matcher.ViewMatchers.withTagKey; -import static android.support.test.espresso.matcher.ViewMatchers.withTagValue; -import static android.support.test.espresso.matcher.ViewMatchers.withText; -import static android.support.test.espresso.web.assertion.WebViewAssertions.webMatches; -import static android.support.test.espresso.web.sugar.Web.onWebView; -import static android.support.test.espresso.web.webdriver.DriverAtoms.findElement; -import static android.support.test.espresso.web.webdriver.DriverAtoms.getText; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.intent.Intents.intended; +import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction; +import static androidx.test.espresso.intent.matcher.IntentMatchers.hasData; +import static androidx.test.espresso.matcher.RootMatchers.isDialog; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withTagKey; +import static androidx.test.espresso.matcher.ViewMatchers.withTagValue; +import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static androidx.test.espresso.web.assertion.WebViewAssertions.webMatches; +import static androidx.test.espresso.web.sugar.Web.onWebView; +import static androidx.test.espresso.web.webdriver.DriverAtoms.findElement; +import static androidx.test.espresso.web.webdriver.DriverAtoms.getText; import android.content.Context; import android.content.Intent; -import android.support.test.InstrumentationRegistry; -import android.support.test.espresso.intent.rule.IntentsTestRule; -import android.support.test.espresso.web.webdriver.Locator; -import android.support.test.runner.AndroidJUnit4; -import android.support.test.uiautomator.By; -import android.support.test.uiautomator.UiDevice; -import android.support.test.uiautomator.UiObject2; -import android.support.test.uiautomator.Until; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.espresso.intent.rule.IntentsTestRule; +import androidx.test.espresso.web.webdriver.Locator; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.UiObject2; +import androidx.test.uiautomator.Until; import org.junit.Before; import org.junit.Rule; diff --git a/code/testapp/src/main/java/com/adobe/testapp/ExtensionFragment.java b/code/testapp/src/main/java/com/adobe/testapp/ExtensionFragment.java index 62d965e95..9732eda1a 100644 --- a/code/testapp/src/main/java/com/adobe/testapp/ExtensionFragment.java +++ b/code/testapp/src/main/java/com/adobe/testapp/ExtensionFragment.java @@ -12,7 +12,7 @@ package com.adobe.testapp; import android.os.Bundle; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/code/testapp/src/main/java/com/adobe/testapp/MainActivity.java b/code/testapp/src/main/java/com/adobe/testapp/MainActivity.java index c8c72348d..7268dac3b 100644 --- a/code/testapp/src/main/java/com/adobe/testapp/MainActivity.java +++ b/code/testapp/src/main/java/com/adobe/testapp/MainActivity.java @@ -11,11 +11,11 @@ package com.adobe.testapp; -import android.support.design.widget.TabLayout; -import android.support.v4.view.ViewPager; -import android.support.v7.app.AppCompatActivity; +import com.google.android.material.tabs.TabLayout; +import androidx.viewpager.widget.ViewPager; +import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; -import android.support.v7.widget.Toolbar; +import androidx.appcompat.widget.Toolbar; public class MainActivity extends AppCompatActivity { private ViewPager viewPager; diff --git a/code/testapp/src/main/java/com/adobe/testapp/MobileCoreFragment.java b/code/testapp/src/main/java/com/adobe/testapp/MobileCoreFragment.java index dc8e79e75..75536afc3 100644 --- a/code/testapp/src/main/java/com/adobe/testapp/MobileCoreFragment.java +++ b/code/testapp/src/main/java/com/adobe/testapp/MobileCoreFragment.java @@ -14,9 +14,9 @@ import android.content.Context; import android.graphics.Color; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentTransaction; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; import android.util.Log; import android.view.LayoutInflater; import android.view.View; diff --git a/code/testapp/src/main/java/com/adobe/testapp/PagerAdapter.java b/code/testapp/src/main/java/com/adobe/testapp/PagerAdapter.java index 6c522bbf9..10eeda7db 100644 --- a/code/testapp/src/main/java/com/adobe/testapp/PagerAdapter.java +++ b/code/testapp/src/main/java/com/adobe/testapp/PagerAdapter.java @@ -11,9 +11,9 @@ package com.adobe.testapp; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentPagerAdapter; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; public class PagerAdapter extends FragmentPagerAdapter { diff --git a/code/testapp/src/main/java/com/adobe/testapp/PlatformServicesFragment.java b/code/testapp/src/main/java/com/adobe/testapp/PlatformServicesFragment.java index 201f4a11f..8b918c8bf 100644 --- a/code/testapp/src/main/java/com/adobe/testapp/PlatformServicesFragment.java +++ b/code/testapp/src/main/java/com/adobe/testapp/PlatformServicesFragment.java @@ -12,7 +12,7 @@ package com.adobe.testapp; import android.os.Bundle; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/code/testapp/src/main/java/com/adobe/testapp/RootFragment.java b/code/testapp/src/main/java/com/adobe/testapp/RootFragment.java index 34aca9e84..9490300ba 100644 --- a/code/testapp/src/main/java/com/adobe/testapp/RootFragment.java +++ b/code/testapp/src/main/java/com/adobe/testapp/RootFragment.java @@ -12,8 +12,8 @@ package com.adobe.testapp; import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentTransaction; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/code/testapp/src/main/java/com/adobe/testapp/UIServicesFragment.java b/code/testapp/src/main/java/com/adobe/testapp/UIServicesFragment.java index c5b11acdb..169ddba56 100644 --- a/code/testapp/src/main/java/com/adobe/testapp/UIServicesFragment.java +++ b/code/testapp/src/main/java/com/adobe/testapp/UIServicesFragment.java @@ -12,7 +12,7 @@ package com.adobe.testapp; import android.os.Bundle; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/code/testapp/src/main/res/layout/activity_main.xml b/code/testapp/src/main/res/layout/activity_main.xml index 5e318e1d8..654be6b43 100644 --- a/code/testapp/src/main/res/layout/activity_main.xml +++ b/code/testapp/src/main/res/layout/activity_main.xml @@ -7,7 +7,7 @@ android:orientation="vertical" tools:context=".MainActivity"> - - - - - - + - + tools:ignore="SpeakableTextPresentCheck"> \ No newline at end of file diff --git a/code/testapp/src/main/res/layout/fragment_extension.xml b/code/testapp/src/main/res/layout/fragment_extension.xml index 368165e23..0c57cdadb 100644 --- a/code/testapp/src/main/res/layout/fragment_extension.xml +++ b/code/testapp/src/main/res/layout/fragment_extension.xml @@ -1,5 +1,5 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/code/testapp/src/main/res/layout/fragment_mobile_core.xml b/code/testapp/src/main/res/layout/fragment_mobile_core.xml index 3c4841f35..0a9e3a840 100644 --- a/code/testapp/src/main/res/layout/fragment_mobile_core.xml +++ b/code/testapp/src/main/res/layout/fragment_mobile_core.xml @@ -1,5 +1,5 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/code/testapp/src/main/res/layout/fragment_platform_services.xml b/code/testapp/src/main/res/layout/fragment_platform_services.xml index 953cbb9da..e0eac6593 100644 --- a/code/testapp/src/main/res/layout/fragment_platform_services.xml +++ b/code/testapp/src/main/res/layout/fragment_platform_services.xml @@ -1,5 +1,5 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/code/testapp/src/main/res/layout/fragment_ui_services.xml b/code/testapp/src/main/res/layout/fragment_ui_services.xml index 25290df18..a1c6ed60b 100644 --- a/code/testapp/src/main/res/layout/fragment_ui_services.xml +++ b/code/testapp/src/main/res/layout/fragment_ui_services.xml @@ -1,5 +1,5 @@ - - \ No newline at end of file + \ No newline at end of file From 4065759ab2e6ed2bd6069905f6c6a896d898c243 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Wed, 13 Apr 2022 09:48:29 -0700 Subject: [PATCH 019/476] launch rules transformer implementation --- .../mobile/internal/utility/UrlUtilities.kt | 117 ++++++++ .../rulesengine/LaunchRuleTransformer.kt | 101 +++++++ .../rulesengine/LaunchRulesConstants.kt | 31 ++ .../rulesengine/LaunchRuleTransformerTests.kt | 268 ++++++++++++++++++ 4 files changed, 517 insertions(+) create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/UrlUtilities.kt create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformer.kt create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConstants.kt create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformerTests.kt diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/UrlUtilities.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/UrlUtilities.kt new file mode 100644 index 000000000..8acc6c613 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/UrlUtilities.kt @@ -0,0 +1,117 @@ +/* ************************************************************************ + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2022 Adobe Systems Incorporated + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe Systems Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Adobe Systems Incorporated and its + * suppliers and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe Systems Incorporated. + **************************************************************************/ + +package com.adobe.marketing.mobile.internal.utility + +import com.adobe.marketing.mobile.LoggingMode +import com.adobe.marketing.mobile.MobileCore +import java.io.UnsupportedEncodingException +import java.nio.charset.StandardCharsets + +internal object UrlUtilities { + + // lookup tables used by urlEncode + private val encodedChars = arrayOf( + "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", "%08", "%09", "%0A", "%0B", "%0C", "%0D", "%0E", "%0F", + "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17", "%18", "%19", "%1A", "%1B", "%1C", "%1D", "%1E", "%1F", + "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27", "%28", "%29", "%2A", "%2B", "%2C", "-", ".", "%2F", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "%3A", "%3B", "%3C", "%3D", "%3E", "%3F", + "%40", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", + "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "%5B", "%5C", "%5D", "%5E", "_", + "%60", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", + "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "%7B", "%7C", "%7D", "~", "%7F", + "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87", "%88", "%89", "%8A", "%8B", "%8C", "%8D", "%8E", "%8F", + "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97", "%98", "%99", "%9A", "%9B", "%9C", "%9D", "%9E", "%9F", + "%A0", "%A1", "%A2", "%A3", "%A4", "%A5", "%A6", "%A7", "%A8", "%A9", "%AA", "%AB", "%AC", "%AD", "%AE", "%AF", + "%B0", "%B1", "%B2", "%B3", "%B4", "%B5", "%B6", "%B7", "%B8", "%B9", "%BA", "%BB", "%BC", "%BD", "%BE", "%BF", + "%C0", "%C1", "%C2", "%C3", "%C4", "%C5", "%C6", "%C7", "%C8", "%C9", "%CA", "%CB", "%CC", "%CD", "%CE", "%CF", + "%D0", "%D1", "%D2", "%D3", "%D4", "%D5", "%D6", "%D7", "%D8", "%D9", "%DA", "%DB", "%DC", "%DD", "%DE", "%DF", + "%E0", "%E1", "%E2", "%E3", "%E4", "%E5", "%E6", "%E7", "%E8", "%E9", "%EA", "%EB", "%EC", "%ED", "%EE", "%EF", + "%F0", "%F1", "%F2", "%F3", "%F4", "%F5", "%F6", "%F7", "%F8", "%F9", "%FA", "%FB", "%FC", "%FD", "%FE", "%FF" + ) + + private val ALL_BITS_ENABLED = 0xFF + private val utf8Mask = booleanArrayOf( + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, true, true, false, + true, true, true, true, true, true, true, true, true, true, false, false, false, false, false, false, + false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, false, false, false, false, true, + false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, false, false, false, true, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false + ) + private val LOG_TAG = "UrlUtilities" + + /** + * Encodes an URL given as [String] + * + * @property [unencodedString] nullable [String] value to be encoded + */ + fun urlEncode(unencodedString: String?): String? { + // bail fast + return if (unencodedString == null) { + null + } else try { + val stringBytes = unencodedString.toByteArray(charset("UTF-8")) + val len = stringBytes.size + var curIndex = 0 + + // iterate looking for any characters that don't match our "safe" mask + while (curIndex < len && utf8Mask[stringBytes[curIndex].toInt() and ALL_BITS_ENABLED]) { + curIndex++ + } + + // if our iterator got all the way to the end of the string, no unsafe characters existed + // and it's safe to return the original value that was passed in + if (curIndex == len) { + return unencodedString + } + + // if we get here we know there's at least one character we need to encode + val encodedString = StringBuilder(stringBytes.size shl 1) + + // if i > than 1 then we have some characters we can just "paste" in + if (curIndex > 0) { + encodedString.append(String(stringBytes, 0, curIndex, StandardCharsets.UTF_8)) + } + + // rip through the rest of the string character by character + while (curIndex < len) { + encodedString.append(encodedChars[stringBytes[curIndex].toInt() and ALL_BITS_ENABLED]) + curIndex++ + } + + // return the completed string + encodedString.toString() + } catch (e: UnsupportedEncodingException) { + MobileCore.log( + LoggingMode.ERROR, + LOG_TAG, + "Failed to url encode string $unencodedString $e") + null + } + } +} \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformer.kt new file mode 100644 index 000000000..82fc77a7d --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformer.kt @@ -0,0 +1,101 @@ +/* ************************************************************************ + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2022 Adobe Systems Incorporated + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe Systems Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Adobe Systems Incorporated and its + * suppliers and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe Systems Incorporated. + **************************************************************************/ + +package com.adobe.marketing.mobile.launch.rulesengine + +import com.adobe.marketing.mobile.internal.utility.UrlUtilities +import com.adobe.marketing.mobile.rulesengine.Transformer +import com.adobe.marketing.mobile.rulesengine.Transforming + +/** + * Generates the [Transforming] instance used by Launch Rules Engine. + * + * @return instance of [Transforming] + **/ +internal class LaunchRuleTransformer { + + companion object { + fun createTransforming(): Transforming { + val transformer = Transformer() + addConsequenceTransform(transformer) + addTypeTransform(transformer) + return transformer + } + + /** + * Registers a [TransformerBlock] for [LaunchRulesConstants.Transform.URL_ENCODING_FUNCTION] + * to encode a `String` value to url format. + * + * @param[transformer] [Transformer] instance used to register the [TransformerBlock] + */ + private fun addConsequenceTransform(transformer: Transformer) { + transformer.register(LaunchRulesConstants.Transform.URL_ENCODING_FUNCTION) { value -> + if (value is String) { + UrlUtilities.urlEncode(value) + } else value + } + } + + /** + * Registers multiple [TransformerBlock] to transform a value into one of + * [LaunchRulesConstants.Transform] types. + * + * @param[transformer] [Transformer] instance used to register the [TransformerBlock] + */ + private fun addTypeTransform(transformer: Transformer) { + transformer.register(LaunchRulesConstants.Transform.TRANSFORM_TO_INT) { value -> + when (value) { + is String -> { + try { + value.toInt() + } catch (e: NumberFormatException) { + null + } + } + is Double -> value.toInt() + is Boolean -> if (value) 1 else 0 + else -> value + } + } + transformer.register(LaunchRulesConstants.Transform.TRANSFORM_TO_STRING) { value -> + value?.toString() + } + transformer.register(LaunchRulesConstants.Transform.TRANSFORM_TO_DOUBLE) { value -> + when (value) { + is String -> { + try { + value.toDouble() + } catch (e: NumberFormatException) { + null + } + } + is Int -> value.toDouble() + is Boolean -> if (value) 1.0 else 0.0 + else -> value + } + } + transformer.register(LaunchRulesConstants.Transform.TRANSFORM_TO_BOOL) { value -> + when (value) { + is String -> java.lang.Boolean.parseBoolean(value) + is Int -> value == 1 + is Double -> value == 1.0 + else -> value + } + } + } + } +} \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConstants.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConstants.kt new file mode 100644 index 000000000..fef197fa7 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConstants.kt @@ -0,0 +1,31 @@ +/* ************************************************************************ + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2022 Adobe Systems Incorporated + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe Systems Incorporated and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Adobe Systems Incorporated and its + * suppliers and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe Systems Incorporated. + **************************************************************************/ + +package com.adobe.marketing.mobile.launch.rulesengine + +internal object LaunchRulesConstants { + const val LOG_MODULE_PREFIX = "Launch Rules Engine" + const val DATA_STORE_PREFIX = "com.adobe.module.rulesengine" + + internal object Transform { + const val URL_ENCODING_FUNCTION = "urlenc" + const val TRANSFORM_TO_INT = "int" + const val TRANSFORM_TO_DOUBLE = "double" + const val TRANSFORM_TO_STRING = "string" + const val TRANSFORM_TO_BOOL = "bool" + } +} \ No newline at end of file diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformerTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformerTests.kt new file mode 100644 index 000000000..f67b411fa --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformerTests.kt @@ -0,0 +1,268 @@ +package com.adobe.marketing.mobile.launch.rulesengine + +import org.junit.Test +import java.util.ArrayList +import java.util.HashMap +import org.junit.Assert.* + +class LaunchRuleTransformerTests { + + @Test + fun transform_ReturnsInt_WhenTransformingIntToInt() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("int", 3) + assertEquals("transform should return int when transforming int to int", 3, result) + } + + @Test + fun transform_ReturnsInt_WhenTransformingBooleanToInt() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("int", true) + assertEquals("transform should return int when transforming boolean to int", 1, result) + } + + @Test + fun transform_ReturnsInt_WhenTransformingDoubleToInt() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("int", 3.33) + assertEquals("transform should return int when transforming double to int", 3, result) + } + + @Test + fun transform_ReturnsInt_WhenTransformingValidStringToInt() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("int", "3") + assertEquals("transform should return int when transforming valid string to int", 3, result) + } + + @Test + fun transform_ReturnsNull_WhenTransformingInvalidStringToInt() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("int", "something") + assertNull("transform should return null when transforming invalid string to int", result) + } + + @Test + fun transform_ReturnsNull_WhenTransformingNullToInt() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("int", null) + assertNull("transform should return null when transforming null to int", result) + } + + @Test + fun transform_ReturnsList_WhenTransformingListToInt() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("int", getDefaultList()) + assertEquals("transform should return list when transforming list to int", getDefaultList(), result) + } + + @Test + fun transform_ReturnsMap_WhenTransformingMapToInt() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("int", getDefaultMap()) + assertEquals("transform should return map when transforming map to int", getDefaultMap(), result) + } + + @Test + fun transform_ReturnsString_WhenTransformingIntToString() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("string", 3) + assertEquals("transform should return string when transforming int to string", "3", result) + } + + @Test + fun transform_ReturnsString_WhenTransformingBooleanToString() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("string", true) + assertEquals("transform should return string when transforming boolean to string", "true", result) + } + + @Test + fun transform_ReturnsString_WhenTransformingDoubleToString() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("string", 3.33) + assertEquals("transform should return string when transforming string to string", "3.33", result) + } + + @Test + fun transform_ReturnsNull_WhenTransformingNullToString() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("string", null) + assertNull("transform should return null when transforming null to string", result) + } + + @Test + fun transform_ReturnsString_WhenTransformingListToString() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("string", getDefaultList()) + assertEquals("transform should return string when transforming list to string", "[item1, item2]", result) + } + + @Test + fun transform_ReturnsString_WhenTransformingMapToString() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("string", getDefaultMap()) + assertEquals("transform should return string when transforming map to string", "{key1=value1, key2=value2}", result) + } + + @Test + fun transform_ReturnsDouble_WhenTransformingIntToDouble() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("double", 3) + assertEquals("transform should return double when transforming int to double", 3.0, result) + } + + @Test + fun transform_ReturnsDouble_WhenTransformingBooleanToDouble() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("double", true) + assertEquals("transform should return double when transforming boolean to double", 1.0, result) + } + + @Test + fun transform_ReturnsDouble_WhenTransformingDoubleToDouble() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("double", 3.33) + assertEquals("transform should return double when transforming double to double", 3.33, result) + } + + @Test + fun transform_ReturnsDouble_WhenTransformingValidStringToDouble() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("double", "3.33") + assertEquals("transform should return double when transforming valid string to double", 3.33, result) + } + + @Test + fun transform_ReturnsNull_WhenTransformingInvalidStringToDouble() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("double", "something") + assertNull("transform should return null when transforming invalid string to double", result) + } + + @Test + fun transform_ReturnsNull_WhenTransformingNullToDouble() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("double", null) + assertNull("transform should return null when transforming null to double", result) + } + + @Test + fun transform_ReturnsList_WhenTransformingListToDouble() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("double", getDefaultList()) + assertEquals("transform should return list when transforming list to double", getDefaultList(), result) + } + + @Test + fun transform_ReturnsMap_WhenTransformingMapToDouble() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("double", getDefaultMap()) + assertEquals("transform should return map when transforming map to double", getDefaultMap(), result) + } + + @Test + fun transform_ReturnsFalse_WhenTransformingInt0ToBoolean() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("bool", 0) + assertEquals("transform should false when transforming int 0 to boolean", false, result) + } + + @Test + fun transform_ReturnsTrue_WhenTransformingInt1ToBoolean() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("bool", 1) + assertEquals("transform should true when transforming int 1 to boolean", true, result) + } + + @Test + fun transform_ReturnsFalse_WhenTransformingIntRandomToBoolean() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("bool", 3) + assertEquals("transform should false when transforming random int to boolean", false, result) + } + + @Test + fun transform_ReturnsBoolean_WhenTransformingBooleanToBoolean() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("bool", true) + assertEquals("transform should boolean when transforming boolean to boolean", true, result) + } + + @Test + fun transform_ReturnsFalse_WhenTransformingDouble0ToBoolean() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("bool", 0.0) + assertEquals("transform should false when transforming double 0.0 to boolean", false, result) + } + + @Test + fun transform_ReturnsTrue_WhenTransformingDouble1ToBoolean() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("bool", 1.0) + assertEquals("transform should true when transforming double 1.0 to boolean", true, result) + } + + @Test + fun transform_ReturnsFalse_WhenTransformingDoubleRandomToBoolean() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("bool", 1.123) + assertEquals("transform should boolean when transforming random double to boolean", false, result) + } + + @Test + fun transform_ReturnsFalse_WhenTransformingValidStringFalseToBoolean() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("bool", "false") + assertEquals("transform should false when transforming valid string false to boolean", false, result) + } + + @Test + fun transform_ReturnsTrue_WhenTransformingValidStringTrueToBoolean() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("bool", "true") + assertEquals("transform should true when transforming valid string true to boolean", true, result) + } + + @Test + fun transform_ReturnsFalse_WhenTransformingInvalidStringToBoolean() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("bool", "something") + assertEquals("transform should false when transforming invalid string to boolean", false, result) + } + + @Test + fun transform_ReturnsNull_WhenTransformingNullToBoolean() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("bool", null) + assertNull("transform should return null when transforming null to boolean", result) + } + + @Test + fun transform_ReturnsList_WhenTransformingListToBoolean() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("bool", getDefaultList()) + assertEquals("transform should return list when transforming list to boolean", getDefaultList(), result) + } + + @Test + fun transform_ReturnsMap_WhenTransformingMapToBoolean() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("bool", getDefaultMap()) + assertEquals("transform should return map when transforming map to boolean", getDefaultMap(), result) + } + + private fun getDefaultList(): List { + val list: MutableList = ArrayList() + list.add("item1") + list.add("item2") + return list + } + + private fun getDefaultMap(): Map { + val map: MutableMap = HashMap() + map["key1"] = "value1" + map["key2"] = "value2" + return map + } +} \ No newline at end of file From 572d25924e2c222c96997dc3251d5397a1aed717 Mon Sep 17 00:00:00 2001 From: Yansong Yang Date: Wed, 13 Apr 2022 15:02:10 -0600 Subject: [PATCH 020/476] Merge AndroidX change to rules_engine branch (#46) * Run CI jobs in Parallel (#3) (#4) * Build changes to support kotlin (#7) * Add two public methods in UIService -> setURIHandler & getIntentWithURI (#15) * add 2 public APIs in UIService -> setURIHandler & getIntentWithURI * add tests * address review comments * fix tests * [#39] Migrate aepsdk-core-android to AndroidX [Summary] - Uses the AndroidX migration tool for preliminary migration. - Manually grep for reflection use inside the project for class mapping. Following instance needed a manual change : * android.support.v4.app.NotificationCompat -> androidx.core.app.NotificationCompat - Following instances of instrumentation tests needed change manually : * InstrumentationRegistry.getContext() -> InstrumentationRegistry.getInstrumentation().getContext() - androidx.test.ext:junit:1.1.3 was added for AndroidJUnit4 test support. [Testing] - Used the TestApp to trigger changes and ensured that change surface works. - Instrumentation and unit tests pass Co-authored-by: Praveen Co-authored-by: Prashanth Rudrabhat Co-authored-by: prudrabhat <96199823+prudrabhat@users.noreply.github.com> --- .../build.gradle | 8 +-- code/android-core-library/build.gradle | 13 +++-- .../AndroidCompressedFileServiceTests.java | 6 +-- .../AndroidConfigurationFunctionalTests.java | 10 +--- .../marketing/mobile/AndroidCursorTests.java | 4 +- .../mobile/AndroidDatabaseServiceTests.java | 4 +- .../mobile/AndroidDatabaseTests.java | 4 +- .../mobile/AndroidEncodingServiceTests.java | 2 +- .../AndroidEventHistoryDatabaseTests.java | 4 +- .../mobile/AndroidEventHistoryTests.java | 4 +- .../mobile/AndroidJsonArrayTests.java | 2 +- .../mobile/AndroidJsonObjectTests.java | 2 +- .../mobile/AndroidJsonUtilityTests.java | 2 +- .../AndroidLocalStorageServiceTests.java | 4 +- .../mobile/AndroidLoggingServiceTests.java | 2 +- ...idThirdPartyExtensionsFunctionalTests.java | 2 +- .../mobile/AndroidV4ToV5MigrationTests.java | 2 +- .../marketing/mobile/DataMarshallerTests.java | 4 +- .../marketing/mobile/EventCoderTests.java | 3 +- .../mobile/ExampleInstrumentedTest.java | 6 +-- .../mobile/QueryStringBuilderTests.java | 4 +- .../services/LocalDataStoreServiceTests.java | 4 +- .../services/SqliteDatabaseHelperTests.java | 6 +-- .../marketing/mobile/AbstractE2ETest.java | 2 +- .../mobile/AndroidFullscreenMessage.java | 8 +-- .../mobile/services/ui/UIService.java | 15 ++++++ .../mobile/services/ui/URIHandler.java | 27 ++++++++++ .../mobile/LocalNotificationHandler.java | 6 +-- .../mobile/services/ServiceProvider.java | 9 ++++ .../mobile/services/ui/AEPMessage.java | 3 +- .../mobile/services/ui/AndroidUIService.java | 32 ++++++++---- .../services/ui/MessageWebViewClient.java | 4 +- .../mobile/AndroidUIServiceTests.java | 21 ++++++++ .../services/ui/AndroidUIServiceTests.java | 26 +++++++++- .../src/test/kotlin/EventHubTests.kt | 11 ++++ code/gradle.properties | 7 +++ code/testapp/build.gradle | 38 +++++--------- .../marketing/mobile/PerformanceTest.java | 8 ++- .../testapp/UIServicesInstrumentedTest.java | 50 +++++++++---------- .../com/adobe/testapp/ExtensionFragment.java | 2 +- .../java/com/adobe/testapp/MainActivity.java | 8 +-- .../com/adobe/testapp/MobileCoreFragment.java | 6 +-- .../java/com/adobe/testapp/PagerAdapter.java | 6 +-- .../testapp/PlatformServicesFragment.java | 2 +- .../java/com/adobe/testapp/RootFragment.java | 4 +- .../com/adobe/testapp/UIServicesFragment.java | 2 +- .../src/main/res/layout/activity_main.xml | 16 +++--- .../main/res/layout/fragment_extension.xml | 4 +- .../main/res/layout/fragment_mobile_core.xml | 4 +- .../res/layout/fragment_platform_services.xml | 4 +- .../main/res/layout/fragment_ui_services.xml | 4 +- 51 files changed, 266 insertions(+), 165 deletions(-) create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/ui/URIHandler.java create mode 100644 code/android-core-library/src/test/kotlin/EventHubTests.kt diff --git a/code/android-core-library-maven-root/build.gradle b/code/android-core-library-maven-root/build.gradle index d993133db..3568d661a 100644 --- a/code/android-core-library-maven-root/build.gradle +++ b/code/android-core-library-maven-root/build.gradle @@ -10,7 +10,7 @@ android { versionCode 1 versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } @@ -91,8 +91,8 @@ signing { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'androidx.appcompat:appcompat:1.0.0' testImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' } diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index 2ea5c01be..6b6350b83 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -10,7 +10,7 @@ android { targetSdkVersion 30 //Include the Proguard rules for Core Extension in the aar consumerProguardFiles 'lib-proguard-rules.pro' - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } sourceSets { @@ -109,14 +109,12 @@ apply from: 'release.gradle' dependencies { //noinspection GradleCompatible - implementation 'com.android.support:appcompat-v7:27.1.1' - //noinspection GradleDependency + implementation 'androidx.appcompat:appcompat:1.0.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" // unit tests testImplementation "junit:junit:4.13.2" //noinspection GradleDependency testImplementation "org.mockito:mockito-core:2.22.0" - testImplementation "org.mockito.kotlin:mockito-kotlin:2.2.11" testImplementation 'org.powermock:powermock-api-mockito2:2.0.0' testImplementation 'org.powermock:powermock-module-junit4:2.0.0' testImplementation 'commons-codec:commons-codec:1.15' @@ -125,5 +123,10 @@ dependencies { testImplementation 'org.json:json:20160810' testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" // instrumentation tests - androidTestImplementation "com.android.support.test:rules:1.0.2" + androidTestImplementation 'androidx.test:rules:1.1.1' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' +} + +repositories { + mavenCentral() } diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCompressedFileServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCompressedFileServiceTests.java index fe47f1d17..7eaa86287 100755 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCompressedFileServiceTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCompressedFileServiceTests.java @@ -21,8 +21,8 @@ import org.junit.runner.RunWith; import android.content.res.Resources; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import java.io.File; import java.io.FileOutputStream; @@ -151,7 +151,7 @@ public void testExtract_NotCreatedDestination() throws IOException { void prepareZipFile(String filename) { try { - Resources res = InstrumentationRegistry.getContext().getResources(); + Resources res = InstrumentationRegistry.getInstrumentation().getContext().getResources(); InputStream instream = res.getAssets().open(filename); tempSource = folder.newFile(); diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidConfigurationFunctionalTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidConfigurationFunctionalTests.java index 4413cbf4f..c2a0217a9 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidConfigurationFunctionalTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidConfigurationFunctionalTests.java @@ -12,25 +12,17 @@ package com.adobe.marketing.mobile; import org.junit.After; -import org.junit.AfterClass; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import static com.adobe.marketing.mobile.E2ETestableNetworkService.NetworkRequest; import static com.adobe.marketing.mobile.E2ETestableNetworkService.NetworkResponse; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java index b34831dd6..f63263a1d 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java @@ -21,8 +21,8 @@ import org.junit.rules.TestName; import org.junit.runner.RunWith; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.HashMap; import java.util.Random; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java index fae6d1d60..d883584e5 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java @@ -17,8 +17,8 @@ import org.junit.runner.RunWith; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseTests.java index 07775dc55..723078d30 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseTests.java @@ -21,8 +21,8 @@ import org.junit.runner.RunWith; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import android.util.Log; import java.util.ArrayList; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEncodingServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEncodingServiceTests.java index 02bbf0062..23169ba82 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEncodingServiceTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEncodingServiceTests.java @@ -14,7 +14,7 @@ import org.junit.Test; import org.junit.runner.RunWith; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryDatabaseTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryDatabaseTests.java index df833e162..96d18683f 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryDatabaseTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryDatabaseTests.java @@ -19,8 +19,8 @@ import android.content.Context; import android.database.DatabaseUtils; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import com.adobe.marketing.mobile.services.ServiceProvider; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryTests.java index debb9fb57..64e693a35 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryTests.java @@ -16,8 +16,8 @@ import static org.junit.Assert.fail; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import com.adobe.marketing.mobile.services.ServiceProvider; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonArrayTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonArrayTests.java index faba89b7a..1963ba617 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonArrayTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonArrayTests.java @@ -17,7 +17,7 @@ import org.junit.Test; import org.junit.runner.RunWith; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonObjectTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonObjectTests.java index 68baf83b5..e166cdaaf 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonObjectTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonObjectTests.java @@ -17,7 +17,7 @@ import org.junit.Test; import org.junit.runner.RunWith; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonUtilityTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonUtilityTests.java index e2dabe9db..1fe37020a 100755 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonUtilityTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidJsonUtilityTests.java @@ -18,7 +18,7 @@ import org.junit.Test; import org.junit.runner.RunWith; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.HashMap; import java.util.Iterator; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidLocalStorageServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidLocalStorageServiceTests.java index fbf6b0f43..122142836 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidLocalStorageServiceTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidLocalStorageServiceTests.java @@ -17,8 +17,8 @@ import org.junit.runner.RunWith; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.HashMap; import java.util.Map; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidLoggingServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidLoggingServiceTests.java index f4c524c41..fd4fa4400 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidLoggingServiceTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidLoggingServiceTests.java @@ -15,7 +15,7 @@ import org.junit.Test; import org.junit.runner.RunWith; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import java.io.BufferedReader; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java index 813a4afb7..732429473 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java @@ -17,7 +17,7 @@ import org.junit.Test; import org.junit.runner.RunWith; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.ArrayList; import java.util.HashMap; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidV4ToV5MigrationTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidV4ToV5MigrationTests.java index 8599ab839..dc08871b8 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidV4ToV5MigrationTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidV4ToV5MigrationTests.java @@ -13,7 +13,7 @@ import android.content.Context; import android.content.SharedPreferences; -import android.support.test.InstrumentationRegistry; +import androidx.test.platform.app.InstrumentationRegistry; import junit.framework.Assert; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/DataMarshallerTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/DataMarshallerTests.java index f1e6e296e..a44f69ee7 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/DataMarshallerTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/DataMarshallerTests.java @@ -14,8 +14,8 @@ import android.app.Activity; import android.content.Intent; import android.net.Uri; -import android.support.test.rule.ActivityTestRule; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.rule.ActivityTestRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Rule; import org.junit.Test; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/EventCoderTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/EventCoderTests.java index 48e26fe36..a85e7efa7 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/EventCoderTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/EventCoderTests.java @@ -12,12 +12,11 @@ package com.adobe.marketing.mobile; import android.app.Activity; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/ExampleInstrumentedTest.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/ExampleInstrumentedTest.java index 71e318aac..15b1e4240 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/ExampleInstrumentedTest.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/ExampleInstrumentedTest.java @@ -12,8 +12,8 @@ package com.adobe.marketing.mobile; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,7 +30,7 @@ public class ExampleInstrumentedTest { @Test public void useAppContext() { // Context of the app under test. - Context appContext = InstrumentationRegistry.getTargetContext(); + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); assertEquals("com.adobe.marketing.mobile.test", appContext.getPackageName()); } diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/QueryStringBuilderTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/QueryStringBuilderTests.java index 6785939ec..30ee19266 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/QueryStringBuilderTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/QueryStringBuilderTests.java @@ -17,8 +17,8 @@ import org.junit.runner.RunWith; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.ArrayList; import java.util.List; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/LocalDataStoreServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/LocalDataStoreServiceTests.java index 4e5552e7b..ebb9f1ef4 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/LocalDataStoreServiceTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/LocalDataStoreServiceTests.java @@ -17,8 +17,8 @@ import static junit.framework.Assert.assertTrue; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Before; import org.junit.Test; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDatabaseHelperTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDatabaseHelperTests.java index e0fc37cc0..9d27901f2 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDatabaseHelperTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDatabaseHelperTests.java @@ -12,8 +12,8 @@ package com.adobe.marketing.mobile.services; import android.content.ContentValues; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; import org.junit.Assert; @@ -38,7 +38,7 @@ public class SqliteDatabaseHelperTests { @Before public void setUp() { - dbPath = new File(InstrumentationRegistry.getContext().getCacheDir(), "test.sqlite").getPath(); + dbPath = new File(InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(), "test.sqlite").getPath(); sqLiteDatabaseHelper = new SQLiteDatabaseHelper(); createTable(); } diff --git a/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/AbstractE2ETest.java b/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/AbstractE2ETest.java index 8f4957e68..63b88ea32 100755 --- a/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/AbstractE2ETest.java +++ b/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/AbstractE2ETest.java @@ -14,7 +14,7 @@ import android.app.Application; import android.app.Instrumentation; import android.content.Context; -import android.support.test.InstrumentationRegistry; +import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Rule; import org.junit.rules.TestName; diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidFullscreenMessage.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidFullscreenMessage.java index 9c90dad3c..b28e626a6 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidFullscreenMessage.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidFullscreenMessage.java @@ -22,8 +22,8 @@ import android.os.Build; import android.os.Handler; import android.os.Looper; -import android.support.annotation.Nullable; -import android.support.annotation.RequiresApi; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.TranslateAnimation; @@ -34,6 +34,7 @@ import android.webkit.WebView; import android.webkit.WebViewClient; +import com.adobe.marketing.mobile.services.ServiceProvider; import com.adobe.marketing.mobile.services.ui.internal.MessagesMonitor; import com.adobe.marketing.mobile.internal.context.App; @@ -127,8 +128,7 @@ public void show() { @Override public void openUrl(final String url) { try { - final Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); + final Intent intent = ServiceProvider.getInstance().getUIService().getIntentWithURI(url); if (messageFullScreenActivity != null) { messageFullScreenActivity.startActivity(intent); diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/ui/UIService.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/ui/UIService.java index 03efff144..c24cb30e4 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/ui/UIService.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/ui/UIService.java @@ -11,6 +11,8 @@ package com.adobe.marketing.mobile.services.ui; +import android.content.Intent; + /** * Interface for displaying alerts, local notifications, and fullscreen web views. */ @@ -33,6 +35,19 @@ public interface UIService { boolean showUrl(String url); + /** + * Provides an {@link URIHandler} to decide the destination of the given URI + * @param uriHandler An {@link URIHandler} instance used to decide the Android link's destination + */ + void setURIHandler(URIHandler uriHandler); + + /** + * Returns a destination Intent for the given URI. + * @param uri the URI to open + * @return an {@link Intent} instance + */ + Intent getIntentWithURI(String uri); + /** * Creates a floating button instance * diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/ui/URIHandler.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/ui/URIHandler.java new file mode 100644 index 000000000..71b530ff9 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/ui/URIHandler.java @@ -0,0 +1,27 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.services.ui; + +import android.content.Intent; + +/** + * Interface for handling Android links + */ +public interface URIHandler { + /** + * Returns a destination of the given URI. + * + * @param uri the URI to open + * @return an {@link Intent} instance + */ + Intent getURIDestination(String uri); +} diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/LocalNotificationHandler.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/LocalNotificationHandler.java index 036fbbf9a..77971de4e 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/LocalNotificationHandler.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/LocalNotificationHandler.java @@ -183,14 +183,14 @@ public void onReceive(Context context, Intent intent) { notificationManager.createNotificationChannel((NotificationChannel)notificationChannel); // specify the notification channel id when creating the notification compat builder - notificationBuilderClass = classLoader.loadClass("android.support.v4.app.NotificationCompat$Builder"); + notificationBuilderClass = classLoader.loadClass("androidx.core.app.NotificationCompat$Builder"); Constructor notificationConstructor = notificationBuilderClass.getConstructor(Context.class, NOTIFICATION_CHANNEL_ID.getClass()); notificationConstructor.setAccessible(true); notificationBuilder = notificationConstructor.newInstance(context.getApplicationContext(), NOTIFICATION_CHANNEL_ID); final Method methodSetStyle = notificationBuilderClass.getDeclaredMethod("setStyle", - classLoader.loadClass("android.support.v4.app.NotificationCompat$Style")); + classLoader.loadClass("androidx.core.app.NotificationCompat$Style")); methodSetStyle.invoke(notificationBuilder, getBigTextStyle(buildVersion, classLoader, message)); } else { @@ -305,7 +305,7 @@ private static Object getBigTextStyle(final int buildVersion, final ClassLoader Object bigTextStyle; Class classBigTextStyle = classLoader.loadClass(buildVersion >= 26 ? - "android.support.v4.app.NotificationCompat$BigTextStyle" : "android.app.Notification$BigTextStyle"); + "androidx.core.app.NotificationCompat$BigTextStyle" : "android.app.Notification$BigTextStyle"); Constructor bigTextStyleConstructor = classBigTextStyle.getConstructor(); bigTextStyle = bigTextStyleConstructor.newInstance(); Method methodBigText = classBigTextStyle.getDeclaredMethod("bigText", CharSequence.class); diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ServiceProvider.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ServiceProvider.java index 03e1fb6c2..fd26b24e1 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ServiceProvider.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ServiceProvider.java @@ -16,6 +16,7 @@ import com.adobe.marketing.mobile.services.ui.AndroidUIService; import com.adobe.marketing.mobile.services.ui.FullscreenMessageDelegate; import com.adobe.marketing.mobile.services.ui.UIService; +import com.adobe.marketing.mobile.services.ui.URIHandler; import java.lang.ref.WeakReference; @@ -159,6 +160,14 @@ public void setMessageDelegate(final FullscreenMessageDelegate messageDelegate) this.messageDelegate = messageDelegate; } + /** + * Provides an {@link URIHandler} to decide the destination of the given URI + * @param uriHandler An {@link URIHandler} instance used to decide the Android link's destination + */ + public void setURIHandler(URIHandler uriHandler){ + this.getUIService().setURIHandler(uriHandler); + } + /** * Reset the {@code ServiceProvider} to its default state. * Any previously set services are reset to their default state. diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java index 11e915df6..e564c183d 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPMessage.java @@ -198,8 +198,7 @@ public void openUrl(final String url) { } try { - final Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); + final Intent intent = ServiceProvider.getInstance().getUIService().getIntentWithURI(url); App.getInstance().getCurrentActivity().startActivity(intent); } catch (final NullPointerException ex) { MobileCore.log(LoggingMode.WARNING, TAG, "Could not open the url from the message " + ex.getMessage()); diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/AndroidUIService.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/AndroidUIService.java index 6f3cfffb1..1263de7d1 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/AndroidUIService.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/AndroidUIService.java @@ -48,6 +48,7 @@ public class AndroidUIService implements UIService { public static final int NOTIFICATION_SENDER_CODE = 750183; public static final String NOTIFICATION_REQUEST_CODE_KEY = "NOTIFICATION_REQUEST_CODE"; public static final String NOTIFICATION_TITLE = "NOTIFICATION_TITLE"; + private URIHandler uriHandler; MessagesMonitor messagesMonitor = MessagesMonitor.getInstance(); @@ -245,7 +246,7 @@ public boolean showUrl(final String url) { } try { - final Intent intent = getIntentWithUrl(url); + final Intent intent = getIntentWithURI(url); currentActivity.startActivity(intent); return true; } catch (Exception ex) { @@ -255,16 +256,25 @@ public boolean showUrl(final String url) { return false; } - /** - * Creates a new instance of an {@code Intent} with its {@code data} set to the {@code URI} parsed from the {@code url} passed in. - * @param url The {@link String} url that needs to be set as data. - * @return A new instance of an {@link Intent} - * - * @throws NullPointerException If the url is null - */ - protected Intent getIntentWithUrl(String url) { - final Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); + @Override + public void setURIHandler(URIHandler uriHandler){ + this.uriHandler = uriHandler; + } + + @Override + public Intent getIntentWithURI(String uri) { + URIHandler handler = this.uriHandler; + Intent intent = null; + if (handler != null){ + intent = handler.getURIDestination(uri); + if (intent == null){ + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("%s is not handled with a custom Intent, use SDK's default Intent instead.", uri)); + } + } + if (intent == null){ + intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(uri)); + } return intent; } diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewClient.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewClient.java index d4eb90726..7e6edeb4a 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewClient.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/ui/MessageWebViewClient.java @@ -14,8 +14,8 @@ import android.annotation.TargetApi; import android.net.Uri; import android.os.Build; -import android.support.annotation.Nullable; -import android.support.annotation.RequiresApi; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import android.webkit.MimeTypeMap; import android.webkit.WebResourceRequest; import android.webkit.WebResourceResponse; diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/AndroidUIServiceTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/AndroidUIServiceTests.java index e1a4fac7e..c7f37dc29 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/AndroidUIServiceTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/AndroidUIServiceTests.java @@ -32,6 +32,8 @@ import static org.mockito.Mockito.when; import com.adobe.marketing.mobile.internal.context.App; +import com.adobe.marketing.mobile.services.ServiceProvider; +import com.adobe.marketing.mobile.services.ui.URIHandler; import com.adobe.marketing.mobile.services.ui.internal.MessagesMonitor; @RunWith(MockitoJUnitRunner.Silent.class) @@ -153,4 +155,23 @@ public void messageMonitorDisplayedCalled_When_FullscreenMessageShown() { verify(mockMessagesMonitor).displayed(); } + + public void setURIHandlerUsage(){ + ServiceProvider.getInstance().setURIHandler(new URIHandler() { + @Override + public Intent getURIDestination(String uri) { + if (uri !=null && uri.startsWith("my_company_links_prefix")){ + Context applicationContext = null; + Intent intent = new Intent(applicationContext, MyCompanyLinkHandlerClass.class); + return intent; + } + return null; + } + }); + } + + +} +class MyCompanyLinkHandlerClass{ + } \ No newline at end of file diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/ui/AndroidUIServiceTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/ui/AndroidUIServiceTests.java index eac622065..29cdf5412 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/ui/AndroidUIServiceTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/ui/AndroidUIServiceTests.java @@ -31,6 +31,8 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNotSame; +import static junit.framework.Assert.assertSame; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; @@ -44,6 +46,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.adobe.marketing.mobile.internal.context.App; +import com.adobe.marketing.mobile.services.ServiceProvider; import com.adobe.marketing.mobile.services.ui.internal.MessagesMonitor; @RunWith(MockitoJUnitRunner.Silent.class) @@ -348,7 +351,7 @@ public void showUrlStartsActivity_When_ValidUrl() { doNothing().when(mockActivity).startActivity(intentArgumentCaptor.capture()); AndroidUIService spyUIService = spy(new AndroidUIService()); - doReturn(mockIntent).when(spyUIService).getIntentWithUrl("myappscheme://host"); + doReturn(mockIntent).when(spyUIService).getIntentWithURI("myappscheme://host"); //test spyUIService.showUrl("myappscheme://host"); //verify @@ -363,7 +366,7 @@ public void showUrlDoesNotStartsActivity_When_NullUrl() { appContextProvider.setContext(mockContext); AndroidUIService spyUIService = spy(new AndroidUIService()); - doReturn(mockIntent).when(spyUIService).getIntentWithUrl(anyString()); + doReturn(mockIntent).when(spyUIService).getIntentWithURI(anyString()); //test spyUIService.showUrl(null); //verify @@ -422,6 +425,25 @@ public void messageMonitorDisplayedNotCalled_When_LocalNotificationShown() { } + @Test + public void testSetURIHandler(){ + final String specialURI = "abc.com"; + Intent specialIntent = new Intent(); + ServiceProvider.getInstance().setURIHandler(new URIHandler() { + @Override + public Intent getURIDestination(String uri) { + if(specialURI.equals(uri)){ + return specialIntent; + } + return null; + } + }); + Intent intent = ServiceProvider.getInstance().getUIService().getIntentWithURI("abc.com"); + assertSame(specialIntent, intent); + Intent defaultIntent = ServiceProvider.getInstance().getUIService().getIntentWithURI("xyz.com"); + assertNotSame(specialIntent, defaultIntent); + } + private long getTriggerTimeForFireDate(long fireDate) { Calendar calendar = Calendar.getInstance(); diff --git a/code/android-core-library/src/test/kotlin/EventHubTests.kt b/code/android-core-library/src/test/kotlin/EventHubTests.kt new file mode 100644 index 000000000..303970ecb --- /dev/null +++ b/code/android-core-library/src/test/kotlin/EventHubTests.kt @@ -0,0 +1,11 @@ +import org.junit.Test +import kotlin.test.assertEquals +import com.adobe.marketing.mobile.internal.eventhub.EventHub + +internal class EventHubTests { + + @Test + fun testVersion() { + assertEquals(EventHub.version, "2.0.0") + } +} \ No newline at end of file diff --git a/code/gradle.properties b/code/gradle.properties index f317f1686..1b3a73277 100644 --- a/code/gradle.properties +++ b/code/gradle.properties @@ -23,4 +23,11 @@ mavenLifecycleVersion=1.1.0 #android.enableUnitTestBinaryResources=false kotlin_version = 1.4.0 +android.useAndroidX=true + +# Currrently core depends on libraries that are +# compatible with AndroidX. Hence jettification is +# not nececessary. Enable the flag below incase an +# incompatible library is used. +#android.enableJetifier=true diff --git a/code/testapp/build.gradle b/code/testapp/build.gradle index ee174546e..dce376679 100644 --- a/code/testapp/build.gradle +++ b/code/testapp/build.gradle @@ -13,16 +13,13 @@ android { //This says that my test app only wants to use the Phone variant missingDimensionStrategy 'target', 'phone' - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - - // Required when setting minSdkVersion to 20 or lower - multiDexEnabled true + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } buildTypes { -// debug { -// testCoverageEnabled true -// } + debug { + testCoverageEnabled true + } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' @@ -32,40 +29,31 @@ android { testOptions { animationsDisabled true } - compileOptions { - // Flag to enable support for the new language APIs - coreLibraryDesugaringEnabled true - // Sets Java compatibility to Java 8 - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } } dependencies { // implementation fileTree(include: ['*.jar'], dir: 'libs') def withoutCore = { exclude group: 'com.adobe.marketing.mobile', module: 'core' } - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - implementation 'com.android.support:appcompat-v7:28.0.0' - implementation 'com.android.support:design:28.0.0' - implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'androidx.appcompat:appcompat:1.0.0' + implementation 'com.google.android.material:material:1.0.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation project(':android-core-library') implementation 'com.adobe.marketing.mobile:lifecycle:1.0.2', withoutCore implementation 'com.adobe.marketing.mobile:identity:1.+', withoutCore implementation 'com.adobe.marketing.mobile:signal:1.0.3', withoutCore implementation 'com.adobe.marketing.mobile:analytics:1.2.4', withoutCore - implementation 'com.appdynamics:appdynamics-runtime:21.11.1' // implementation 'com.adobe.marketing.mobile:sdk-core:1.+' testImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test:rules:1.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2', { + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test:rules:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0', { exclude group: 'com.android.support', module: 'support-annotations' } - androidTestImplementation 'com.android.support.test.espresso:espresso-intents:3.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-web:3.0.2' + androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0' + androidTestImplementation 'androidx.test.espresso:espresso-web:3.1.0' - androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3' + androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' /* Uncomment the following line when testing with local test-third-party-extension changes */ //implementation project(':test-third-party-extension') diff --git a/code/testapp/src/androidTest/java/com/adobe/marketing/mobile/PerformanceTest.java b/code/testapp/src/androidTest/java/com/adobe/marketing/mobile/PerformanceTest.java index bed598361..35e8ddeec 100644 --- a/code/testapp/src/androidTest/java/com/adobe/marketing/mobile/PerformanceTest.java +++ b/code/testapp/src/androidTest/java/com/adobe/marketing/mobile/PerformanceTest.java @@ -12,9 +12,9 @@ import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; -import android.support.v4.widget.TextViewCompat; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.core.widget.TextViewCompat; import android.util.Log; import org.junit.Test; @@ -22,8 +22,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import static org.junit.Assert.*; diff --git a/code/testapp/src/androidTest/java/com/adobe/testapp/UIServicesInstrumentedTest.java b/code/testapp/src/androidTest/java/com/adobe/testapp/UIServicesInstrumentedTest.java index 76b0608f2..e9fc05eea 100644 --- a/code/testapp/src/androidTest/java/com/adobe/testapp/UIServicesInstrumentedTest.java +++ b/code/testapp/src/androidTest/java/com/adobe/testapp/UIServicesInstrumentedTest.java @@ -11,34 +11,34 @@ package com.adobe.testapp; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist; -import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.intent.Intents.intended; -import static android.support.test.espresso.intent.matcher.IntentMatchers.hasAction; -import static android.support.test.espresso.intent.matcher.IntentMatchers.hasData; -import static android.support.test.espresso.matcher.RootMatchers.isDialog; -import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; -import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static android.support.test.espresso.matcher.ViewMatchers.withTagKey; -import static android.support.test.espresso.matcher.ViewMatchers.withTagValue; -import static android.support.test.espresso.matcher.ViewMatchers.withText; -import static android.support.test.espresso.web.assertion.WebViewAssertions.webMatches; -import static android.support.test.espresso.web.sugar.Web.onWebView; -import static android.support.test.espresso.web.webdriver.DriverAtoms.findElement; -import static android.support.test.espresso.web.webdriver.DriverAtoms.getText; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.intent.Intents.intended; +import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction; +import static androidx.test.espresso.intent.matcher.IntentMatchers.hasData; +import static androidx.test.espresso.matcher.RootMatchers.isDialog; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withTagKey; +import static androidx.test.espresso.matcher.ViewMatchers.withTagValue; +import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static androidx.test.espresso.web.assertion.WebViewAssertions.webMatches; +import static androidx.test.espresso.web.sugar.Web.onWebView; +import static androidx.test.espresso.web.webdriver.DriverAtoms.findElement; +import static androidx.test.espresso.web.webdriver.DriverAtoms.getText; import android.content.Context; import android.content.Intent; -import android.support.test.InstrumentationRegistry; -import android.support.test.espresso.intent.rule.IntentsTestRule; -import android.support.test.espresso.web.webdriver.Locator; -import android.support.test.runner.AndroidJUnit4; -import android.support.test.uiautomator.By; -import android.support.test.uiautomator.UiDevice; -import android.support.test.uiautomator.UiObject2; -import android.support.test.uiautomator.Until; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.espresso.intent.rule.IntentsTestRule; +import androidx.test.espresso.web.webdriver.Locator; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.UiObject2; +import androidx.test.uiautomator.Until; import org.junit.Before; import org.junit.Rule; diff --git a/code/testapp/src/main/java/com/adobe/testapp/ExtensionFragment.java b/code/testapp/src/main/java/com/adobe/testapp/ExtensionFragment.java index 62d965e95..9732eda1a 100644 --- a/code/testapp/src/main/java/com/adobe/testapp/ExtensionFragment.java +++ b/code/testapp/src/main/java/com/adobe/testapp/ExtensionFragment.java @@ -12,7 +12,7 @@ package com.adobe.testapp; import android.os.Bundle; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/code/testapp/src/main/java/com/adobe/testapp/MainActivity.java b/code/testapp/src/main/java/com/adobe/testapp/MainActivity.java index c8c72348d..7268dac3b 100644 --- a/code/testapp/src/main/java/com/adobe/testapp/MainActivity.java +++ b/code/testapp/src/main/java/com/adobe/testapp/MainActivity.java @@ -11,11 +11,11 @@ package com.adobe.testapp; -import android.support.design.widget.TabLayout; -import android.support.v4.view.ViewPager; -import android.support.v7.app.AppCompatActivity; +import com.google.android.material.tabs.TabLayout; +import androidx.viewpager.widget.ViewPager; +import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; -import android.support.v7.widget.Toolbar; +import androidx.appcompat.widget.Toolbar; public class MainActivity extends AppCompatActivity { private ViewPager viewPager; diff --git a/code/testapp/src/main/java/com/adobe/testapp/MobileCoreFragment.java b/code/testapp/src/main/java/com/adobe/testapp/MobileCoreFragment.java index dc8e79e75..75536afc3 100644 --- a/code/testapp/src/main/java/com/adobe/testapp/MobileCoreFragment.java +++ b/code/testapp/src/main/java/com/adobe/testapp/MobileCoreFragment.java @@ -14,9 +14,9 @@ import android.content.Context; import android.graphics.Color; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentTransaction; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; import android.util.Log; import android.view.LayoutInflater; import android.view.View; diff --git a/code/testapp/src/main/java/com/adobe/testapp/PagerAdapter.java b/code/testapp/src/main/java/com/adobe/testapp/PagerAdapter.java index 6c522bbf9..10eeda7db 100644 --- a/code/testapp/src/main/java/com/adobe/testapp/PagerAdapter.java +++ b/code/testapp/src/main/java/com/adobe/testapp/PagerAdapter.java @@ -11,9 +11,9 @@ package com.adobe.testapp; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentPagerAdapter; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; public class PagerAdapter extends FragmentPagerAdapter { diff --git a/code/testapp/src/main/java/com/adobe/testapp/PlatformServicesFragment.java b/code/testapp/src/main/java/com/adobe/testapp/PlatformServicesFragment.java index 201f4a11f..8b918c8bf 100644 --- a/code/testapp/src/main/java/com/adobe/testapp/PlatformServicesFragment.java +++ b/code/testapp/src/main/java/com/adobe/testapp/PlatformServicesFragment.java @@ -12,7 +12,7 @@ package com.adobe.testapp; import android.os.Bundle; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/code/testapp/src/main/java/com/adobe/testapp/RootFragment.java b/code/testapp/src/main/java/com/adobe/testapp/RootFragment.java index 34aca9e84..9490300ba 100644 --- a/code/testapp/src/main/java/com/adobe/testapp/RootFragment.java +++ b/code/testapp/src/main/java/com/adobe/testapp/RootFragment.java @@ -12,8 +12,8 @@ package com.adobe.testapp; import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentTransaction; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/code/testapp/src/main/java/com/adobe/testapp/UIServicesFragment.java b/code/testapp/src/main/java/com/adobe/testapp/UIServicesFragment.java index c5b11acdb..169ddba56 100644 --- a/code/testapp/src/main/java/com/adobe/testapp/UIServicesFragment.java +++ b/code/testapp/src/main/java/com/adobe/testapp/UIServicesFragment.java @@ -12,7 +12,7 @@ package com.adobe.testapp; import android.os.Bundle; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/code/testapp/src/main/res/layout/activity_main.xml b/code/testapp/src/main/res/layout/activity_main.xml index 5e318e1d8..654be6b43 100644 --- a/code/testapp/src/main/res/layout/activity_main.xml +++ b/code/testapp/src/main/res/layout/activity_main.xml @@ -7,7 +7,7 @@ android:orientation="vertical" tools:context=".MainActivity"> - - - - - - + - + tools:ignore="SpeakableTextPresentCheck"> \ No newline at end of file diff --git a/code/testapp/src/main/res/layout/fragment_extension.xml b/code/testapp/src/main/res/layout/fragment_extension.xml index 368165e23..0c57cdadb 100644 --- a/code/testapp/src/main/res/layout/fragment_extension.xml +++ b/code/testapp/src/main/res/layout/fragment_extension.xml @@ -1,5 +1,5 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/code/testapp/src/main/res/layout/fragment_mobile_core.xml b/code/testapp/src/main/res/layout/fragment_mobile_core.xml index 3c4841f35..0a9e3a840 100644 --- a/code/testapp/src/main/res/layout/fragment_mobile_core.xml +++ b/code/testapp/src/main/res/layout/fragment_mobile_core.xml @@ -1,5 +1,5 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/code/testapp/src/main/res/layout/fragment_platform_services.xml b/code/testapp/src/main/res/layout/fragment_platform_services.xml index 953cbb9da..e0eac6593 100644 --- a/code/testapp/src/main/res/layout/fragment_platform_services.xml +++ b/code/testapp/src/main/res/layout/fragment_platform_services.xml @@ -1,5 +1,5 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/code/testapp/src/main/res/layout/fragment_ui_services.xml b/code/testapp/src/main/res/layout/fragment_ui_services.xml index 25290df18..a1c6ed60b 100644 --- a/code/testapp/src/main/res/layout/fragment_ui_services.xml +++ b/code/testapp/src/main/res/layout/fragment_ui_services.xml @@ -1,5 +1,5 @@ - - \ No newline at end of file + \ No newline at end of file From d81448a08db8f760a3f7370338494ddffd931a6b Mon Sep 17 00:00:00 2001 From: Yansong Yang Date: Mon, 18 Apr 2022 13:51:19 -0600 Subject: [PATCH 021/476] EventDataMerger for merging event data in RulesEngine (#13) * a draft of EventDataMerger class with Java and Kotlin versions * Update README.md * add wildcard support * address review comments * address review comments * rename a function * revert change * update code docs * update one test --- README.md | 1 + .../internal/utility/EventDataMerger.kt | 134 +++++ .../utility/EventDataMergerJavaTest.java | 53 ++ .../internal/utility/EventDataMergerTests.kt | 484 ++++++++++++++++++ 4 files changed, 672 insertions(+) create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/EventDataMerger.kt create mode 100644 code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/EventDataMergerJavaTest.java create mode 100644 code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/EventDataMergerTests.kt diff --git a/README.md b/README.md index 37628d018..a29bc0176 100644 --- a/README.md +++ b/README.md @@ -11,3 +11,4 @@ Contributions are welcomed! Read the [Contributing Guide](./.github/CONTRIBUTING ## Licensing This project is licensed under the Apache V2 License. See [LICENSE](LICENSE) for more information. + diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/EventDataMerger.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/EventDataMerger.kt new file mode 100644 index 000000000..312d13bc3 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/EventDataMerger.kt @@ -0,0 +1,134 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.internal.utility + +internal object EventDataMerger { + private const val WILD_CARD_SUFFIX_FOR_LIST = "[*]" + + /** + * Merge one [Map] into another + * + * @param from the map containing new data + * @param to the map to be merged to + * @param overwrite true, if the from map should take priority + * @return the merged [Map] + */ + @JvmStatic + fun merge( + from: Map?, + to: Map?, + overwrite: Boolean + ): Map { + return innerMerge(from, to, overwrite, fun(fromValue, toValue): Any? { + if (fromValue is Map<*, *> && toValue is Map<*, *>) { + return mergeWildcardMaps(fromValue, toValue, overwrite) + } + if (!overwrite) { + return toValue + } + if (fromValue is Collection<*> && toValue is Collection<*>) { + return mergeCollection(fromValue, toValue) + } + return fromValue + }) + } + + private fun mergeCollection(from: Collection<*>?, to: Collection<*>?): Collection { + return object : ArrayList() { + init { + from?.let { addAll(it) } + to?.let { addAll(it) } + } + } + } + + @Suppress("UNCHECKED_CAST") + private fun mergeWildcardMaps( + from: Map<*, *>?, + to: Map<*, *>?, + overwrite: Boolean + ): Map<*, *>? { + from?.let { if (!it.keys.isAllString()) return to } + to?.let { if (!it.keys.isAllString()) return to } + return try { + merge(from as? Map, to as? Map, overwrite) + } catch (e: Exception) { + to + } + } + + private fun Set<*>.isAllString(): Boolean { + this.forEach { if (it !is String) return false } + return true + } + + private fun innerMerge( + from: Map?, + to: Map?, + overwrite: Boolean, + overwriteStrategy: (fromValue: Any?, toValue: Any?) -> Any? + ): Map { + val mergedMap = HashMap() + to?.let(mergedMap::putAll) + from?.forEach { (k, v) -> + when { + mergedMap.containsKey(k) -> { + val resolvedValue = overwriteStrategy(v, mergedMap[k]) + resolvedValue?.let { mergedMap[k] = resolvedValue } ?: mergedMap.remove(k) + } + k.endsWith(WILD_CARD_SUFFIX_FOR_LIST) -> { + if (v is Map<*, *>) handleWildcardMerge(mergedMap, k, v, overwrite) + } + else -> { + mergedMap[k] = v + } + } + } + return mergedMap + } + + /** + * If the map contains the specific key and its value is a [Collection], merge `data` to each item of the [Collection] + * + * For example: + * for the wildcard key: `list[*]`, merge the following data: + * {"k":"v"} + * to the target [Map]: + * {"list":[{"k1":"v1"},{"k2":"v2"}]} + * the result is: + * {"list":[{"k1":"v1","k":"v"},{"k2":"v2","k":"v"}]} + * + * @param targetMap the [Map] to be merged with the given `data` if it contains the target key + * @param wildcardKey the target key with suffix `[*]` + * @param data the new data to be merged to the `targetMap` + * @param overwrite true, if the new data should take priority + */ + private fun handleWildcardMerge( + targetMap: HashMap, + wildcardKey: String, + data: Map<*, *>, + overwrite: Boolean + ) { + val targetKey = wildcardKey.dropLast(WILD_CARD_SUFFIX_FOR_LIST.length) + val targetValueAsList = targetMap[targetKey] + if (targetValueAsList is Collection<*>) { + val newList = ArrayList() + targetValueAsList.forEach { + val itMap = it as? Map<*, *> + itMap?.let { newList.add(mergeWildcardMaps(data, itMap, overwrite)) } + ?: run { newList.add(it) } + } + targetMap[targetKey] = newList + } + } + +} \ No newline at end of file diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/EventDataMergerJavaTest.java b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/EventDataMergerJavaTest.java new file mode 100644 index 000000000..e6898b560 --- /dev/null +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/EventDataMergerJavaTest.java @@ -0,0 +1,53 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.internal.utility; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +public class EventDataMergerJavaTest { + + @Test + public void testMerge() { + Map innerMap1 = new HashMap() { + { + put("key", "oldValue"); + put("toBeDeleted", "value"); + } + }; + Map innerMap2 = new HashMap() { + { + put("newKey", "newValue"); + put("toBeDeleted", null); + } + }; + Map toMap = new HashMap() { + { + put("nested", innerMap1); + } + }; + Map fromMap = new HashMap() { + { + put("nested", innerMap2); + } + }; + Map mergedMap = EventDataMerger.merge(fromMap, toMap, true); + Map innerMap = (Map) mergedMap.get("nested"); + assertEquals(2, innerMap.size()); + assertEquals("oldValue", innerMap.get("key")); + assertEquals("newValue", innerMap.get("newKey")); + } +} diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/EventDataMergerTests.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/EventDataMergerTests.kt new file mode 100644 index 000000000..2c9d1c8a7 --- /dev/null +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/EventDataMergerTests.kt @@ -0,0 +1,484 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.internal.utility + +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class EventDataMergerTests { + + @Test + fun testSimpleMerge() { + val fromMap = mapOf( + "key" to "oldValue" + ) + val toMap = mapOf( + "newKey" to "newValue" + ) + val expectedMap = mapOf( + "key" to "oldValue", + "newKey" to "newValue" + ) + assertEquals(expectedMap, EventDataMerger.merge(fromMap, toMap, true)) + + } + + @Test + fun testConflictAndNotOverwrite() { + val toMap = mapOf( + "key" to "oldValue", + "donotdelete" to "value" + ) + val fromMap = mapOf( + "newKey" to "newValue", + "donotdelete" to null + ) + val expectedMap = mapOf( + "key" to "oldValue", + "donotdelete" to "value", + "newKey" to "newValue" + ) + assertEquals(expectedMap, EventDataMerger.merge(fromMap, toMap, false)) + } + + @Test + fun testNestedMapSimpleMerge() { + val toMap = mapOf( + "nested" to mapOf( + "key" to "oldValue" + ) + ) + val fromMap = mapOf( + "nested" to mapOf( + "newKey" to "newValue" + ) + ) + val expectedMap = mapOf( + "nested" to mapOf( + "key" to "oldValue", + "newKey" to "newValue" + ) + ) + assertEquals(expectedMap, EventDataMerger.merge(fromMap, toMap, true)) + } + + @Test + fun testNestedMapConflictAndOverwrite() { + val toMap = mapOf( + "nested" to mapOf( + "key" to "oldValue", + "toBeDeleted" to "value" + ) + ) + val fromMap = mapOf( + "nested" to mapOf( + "newKey" to "newValue", + "toBeDeleted" to null + ) + ) + val expectedMap = mapOf( + "nested" to mapOf( + "key" to "oldValue", + "newKey" to "newValue" + ) + ) + assertEquals(expectedMap, EventDataMerger.merge(fromMap, toMap, true)) + } + + @Test + fun testListSimpleMerge() { + val fromMap = mapOf( + "key" to listOf("abc", "def") + ) + val toMap = mapOf( + "key" to listOf("0", "1") + ) + val expectedMap = mapOf( + "key" to listOf("abc", "def", "0", "1") + ) + assertEquals(expectedMap, EventDataMerger.merge(fromMap, toMap, true)) + } + + @Test + fun testListWithDuplicatedItems() { + val fromMap = mapOf( + "key" to listOf("abc", "def") + ) + val toMap = mapOf( + "key" to listOf("abc", "1") + ) + val expectedMap = mapOf( + "key" to listOf("abc", "def", "abc", "1") + ) + assertEquals(expectedMap, EventDataMerger.merge(fromMap, toMap, true)) + } + + @Test + fun testListWithDifferentTypes() { + val fromMap = mapOf( + "key" to listOf("abc", "def") + ) + val toMap = mapOf( + "key" to listOf(0, 1) + ) + val expectedMap = mapOf( + "key" to listOf("abc", "def", 0, 1) + ) + assertEquals(expectedMap, EventDataMerger.merge(fromMap, toMap, true)) + } + + @Test + fun testWildCardMergeBasic() { + val toMap = mapOf( + "list" to listOf( + mapOf( + "k1" to "v1" + ), + mapOf( + "k2" to "v2" + ) + ) + ) + val fromMap = mapOf( + "list[*]" to mapOf( + "newKey" to "newValue" + ) + ) + val expectedMap = mapOf( + "list" to listOf( + mapOf( + "k1" to "v1", + "newKey" to "newValue" + ), + mapOf( + "k2" to "v2", + "newKey" to "newValue" + ) + ) + ) + assertEquals(expectedMap, EventDataMerger.merge(fromMap, toMap, true)) + } + + @Test + fun testWildCardMergeNotAtRootLevel() { + val toMap = mapOf( + "inner" to mapOf( + "list" to listOf( + mapOf( + "k1" to "v1" + ), + mapOf( + "k2" to "v2" + ) + ) + ) + ) + val fromMap = mapOf( + "inner" to mapOf( + "list[*]" to mapOf( + "newKey" to "newValue" + ) + ) + ) + val expectedMap = mapOf( + "inner" to mapOf( + "list" to listOf( + mapOf( + "k1" to "v1", + "newKey" to "newValue" + ), + mapOf( + "k2" to "v2", + "newKey" to "newValue" + ) + ) + ) + ) + assertEquals(expectedMap, EventDataMerger.merge(fromMap, toMap, true)) + } + + @Test + fun testWildCardMergeWithoutTarget() { + val toMap = mapOf( + "list" to listOf( + mapOf( + "k1" to "v1" + ), + mapOf( + "k2" to "v2" + ) + ) + ) + val fromMap = mapOf( + "lists[*]" to mapOf( + "newKey" to "newValue" + ) + ) + val expectedMap = mapOf( + "list" to listOf( + mapOf( + "k1" to "v1" + ), + mapOf( + "k2" to "v2" + ) + ) + ) + assertEquals(expectedMap, EventDataMerger.merge(fromMap, toMap, true)) + } + + @Test + fun testWildCardMergeOverwrite() { + val toMap = mapOf( + "list" to listOf( + mapOf( + "k1" to "v1", + "key" to "oldValue" + ), + mapOf( + "k2" to "v2" + ) + ) + ) + val fromMap = mapOf( + "list[*]" to mapOf( + "key" to "newValue" + ) + ) + val expectedMap = mapOf( + "list" to listOf( + mapOf( + "k1" to "v1", + "key" to "newValue" + ), + mapOf( + "k2" to "v2", + "key" to "newValue" + ) + ) + ) + assertEquals(expectedMap, EventDataMerger.merge(fromMap, toMap, true)) + } + + @Test + fun testWildCardMergeOverwriteWithNoneMapItem() { + val toMap = mapOf( + "list" to listOf( + mapOf( + "k1" to "v1", + "key" to "oldValue" + ), + "none_map_item" + ) + ) + val fromMap = mapOf( + "list[*]" to mapOf( + "key" to "newValue" + ) + ) + val expectedMap = mapOf( + "list" to listOf( + mapOf( + "k1" to "v1", + "key" to "newValue" + ), + "none_map_item" + ) + ) + assertEquals(expectedMap, EventDataMerger.merge(fromMap, toMap, true)) + } + + @Test + fun testWildCardMergeNotOverwrite() { + val toMap = mapOf( + "list" to listOf( + mapOf( + "k1" to "v1", + "key" to "oldValue" + ), + mapOf( + "k2" to "v2" + ) + ) + ) + val fromMap = mapOf( + "list[*]" to mapOf( + "key" to "newValue" + ) + ) + val expectedMap = mapOf( + "list" to listOf( + mapOf( + "k1" to "v1", + "key" to "oldValue" + ), + mapOf( + "k2" to "v2", + "key" to "newValue" + ) + ) + ) + assertEquals(expectedMap, EventDataMerger.merge(fromMap, toMap, false)) + } + + @Test + fun testWildCardNestedMapMergeOverwrite() { + val toMap = mapOf( + "list" to listOf( + mapOf( + "k1" to "v1", + "inner" to mapOf( + "inner_k1" to "oldValue", + "key" to "oldValue" + ) + ), + mapOf( + "k2" to "v2" + ) + ) + ) + val fromMap = mapOf( + "list[*]" to mapOf( + "key" to "newValue", + "inner" to mapOf( + "inner_k1" to "newValue", + "newKey" to "newValue" + ) + ) + ) + val expectedMap = mapOf( + "list" to listOf( + mapOf( + "k1" to "v1", + "inner" to mapOf( + "inner_k1" to "newValue", + "key" to "oldValue", + "newKey" to "newValue" + ), + "key" to "newValue" + ), + mapOf( + "k2" to "v2", + "key" to "newValue", + "inner" to mapOf( + "inner_k1" to "newValue", + "newKey" to "newValue" + ) + ) + ) + ) + assertEquals(expectedMap, EventDataMerger.merge(fromMap, toMap, true)) + } + + @Test + fun testWildCardNestedMapMergeNotOverwrite() { + val toMap = mapOf( + "list" to listOf( + mapOf( + "k1" to "v1", + "inner" to mapOf( + "inner_k1" to "oldValue", + "key" to "oldValue" + ) + ), + mapOf( + "k2" to "v2" + ) + ) + ) + val fromMap = mapOf( + "list[*]" to mapOf( + "key" to "newValue", + "inner" to mapOf( + "inner_k1" to "newValue", + "newKey" to "newValue" + ) + ) + ) + val expectedMap = mapOf( + "list" to listOf( + mapOf( + "k1" to "v1", + "inner" to mapOf( + "inner_k1" to "oldValue", + "key" to "oldValue", + "newKey" to "newValue" + ), + "key" to "newValue", + ), + mapOf( + "k2" to "v2", + "key" to "newValue", + "inner" to mapOf( + "inner_k1" to "newValue", + "newKey" to "newValue" + ) + ) + ) + ) + assertEquals(expectedMap, EventDataMerger.merge(fromMap, toMap, false)) + } + + @Test + fun testFromMapWithNonStringKey() { + val toMap = mapOf( + "k1" to "v1", + "k2" to "v2", + "nested" to mapOf( + 1 to "ValueForIntKey", + "k1" to "v1" + ) + ) + val fromMap = mapOf( + "nested" to mapOf( + "k1" to "v11", + "k2" to "v2" + ) + ) + val expectedMap = mapOf( + "k1" to "v1", + "k2" to "v2", + "nested" to mapOf( + 1 to "ValueForIntKey", + "k1" to "v1" + ) + ) + assertEquals(expectedMap, EventDataMerger.merge(fromMap, toMap, true)) + } + + @Test + fun testToMapWithNonStringKey() { + val toMap = mapOf( + "k1" to "v1", + "k2" to "v2", + "nested" to mapOf( + "k1" to "v1", + "k2" to "v2" + ) + ) + val fromMap = mapOf( + "nested" to mapOf( + 1 to "ValueForIntKey", + "k1" to "v11" + ) + ) + val expectedMap = mapOf( + "k1" to "v1", + "k2" to "v2", + "nested" to mapOf( + "k1" to "v1", + "k2" to "v2" + ) + ) + assertEquals(expectedMap, EventDataMerger.merge(fromMap, toMap, true)) + } +} \ No newline at end of file From e2b2a1ed3e40bfe709d08be2d9165545a5fca823 Mon Sep 17 00:00:00 2001 From: praveek Date: Tue, 19 Apr 2022 15:19:37 -0700 Subject: [PATCH 022/476] Rename file --- .../mobile/internal/eventhub/{Extensions.kt => ExtensionExt.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/{Extensions.kt => ExtensionExt.kt} (100%) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/Extensions.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt similarity index 100% rename from code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/Extensions.kt rename to code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt From 6f800209481c34aeec06cdfd76f91f80d37fe7fc Mon Sep 17 00:00:00 2001 From: praveek Date: Tue, 19 Apr 2022 15:47:37 -0700 Subject: [PATCH 023/476] Fix enums --- .../com/adobe/marketing/mobile/EventHub.java | 6 ++--- .../mobile/internal/eventhub/EventHub.kt | 8 +++--- .../mobile/internal/eventhub/EventHubError.kt | 12 ++++----- .../internal/eventhub/ExtensionContainer.kt | 6 ++--- .../mobile/internal/eventhub/EventHubTests.kt | 26 +++++++++---------- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java index 6469183f1..c688d168f 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java @@ -442,13 +442,13 @@ void registerExtensionWithCallback(final Class extensionCla com.adobe.marketing.mobile.internal.eventhub.EventHub.Companion.getShared().registerExtension(extensionClass, new Function1() { @Override public Unit invoke(EventHubError e) { - if (errorCallback == null || EventHubError.none.equals(e)) { + if (errorCallback == null || EventHubError.None.equals(e)) { return null; } - if (EventHubError.invalidExtensionName.equals(e)) { + if (EventHubError.InvalidExtensionName.equals(e)) { errorCallback.error(ExtensionError.BAD_NAME); - } else if (EventHubError.duplicateExtensionName.equals(e)) { + } else if (EventHubError.DuplicateExtensionName.equals(e)) { errorCallback.error(ExtensionError.DUPLICATE_NAME); } else { errorCallback.error(ExtensionError.UNEXPECTED_ERROR); diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index 4315f8d8c..6e8cff8ad 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -42,13 +42,13 @@ internal class EventHub { fun registerExtension(extensionClass: Class?, completion: (error: EventHubError) -> Unit) { eventHubExecutor.submit { if (extensionClass == null) { - completion(EventHubError.extensionInitializationFailure) + completion(EventHubError.ExtensionInitializationFailure) return@submit } val extensionName = extensionClass.extensionTypeName if (registeredExtensions.containsKey(extensionName)) { - completion(EventHubError.duplicateExtensionName) + completion(EventHubError.DuplicateExtensionName) return@submit } @@ -70,9 +70,9 @@ internal class EventHub { if (container != null) { container?.shutdown() shareEventHubSharedState() - completion(EventHubError.none) + completion(EventHubError.None) } else { - completion(EventHubError.extensionNotRegistered) + completion(EventHubError.ExtensionNotRegistered) } } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubError.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubError.kt index 5a87fe607..ecff7f018 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubError.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubError.kt @@ -1,10 +1,10 @@ package com.adobe.marketing.mobile.internal.eventhub internal enum class EventHubError { - invalidExtensionName, - duplicateExtensionName, - extensionInitializationFailure, - extensionNotRegistered, - unknown, - none + InvalidExtensionName, + DuplicateExtensionName, + ExtensionInitializationFailure, + ExtensionNotRegistered, + Unknown, + None } \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt index 9d5e77824..e24b44453 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt @@ -105,18 +105,18 @@ internal class ExtensionContainer constructor( taskExecutor.submit { val extension = extensionClass.initWith(extensionRuntime) if (extension == null) { - callback(EventHubError.extensionInitializationFailure) + callback(EventHubError.ExtensionInitializationFailure) return@submit } if (extension.name == null) { - callback(EventHubError.invalidExtensionName) + callback(EventHubError.InvalidExtensionName) return@submit } // We set this circular reference because ExtensionApi exposes an API to get the underlying extension. extensionRuntime.extension = extension - callback(EventHubError.none) + callback(EventHubError.None) } } diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt index 38511437f..bc1bb1345 100644 --- a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt @@ -49,7 +49,7 @@ internal class EventHubTests { // Helper to register extensions fun registerExtension(extensionClass: Class): EventHubError { - var ret: EventHubError = EventHubError.unknown; + var ret: EventHubError = EventHubError.Unknown; val latch = CountDownLatch(1) EventHub.shared.registerExtension(extensionClass) { error -> @@ -61,7 +61,7 @@ internal class EventHubTests { } fun unregisterExtension(extensionClass: Class): EventHubError { - var ret: EventHubError = EventHubError.unknown; + var ret: EventHubError = EventHubError.Unknown; val latch = CountDownLatch(1) EventHub.shared.unregisterExtension(extensionClass) { error -> @@ -82,10 +82,10 @@ internal class EventHubTests { fun testRegisterExtensionSuccess() { var ret = registerExtension(MockExtension::class.java) - assertEquals(EventHubError.none, ret) + assertEquals(EventHubError.None, ret) ret = registerExtension(MockExtensions.MockExtensionKotlin::class.java) - assertEquals(EventHubError.none, ret) + assertEquals(EventHubError.None, ret) } @Test @@ -93,25 +93,25 @@ internal class EventHubTests { registerExtension(MockExtension::class.java) var ret = registerExtension(MockExtension::class.java) - assertEquals(EventHubError.duplicateExtensionName, ret) + assertEquals(EventHubError.DuplicateExtensionName, ret) } @Test fun testRegisterExtensionFailure_ExtensionInitialization() { var ret = registerExtension(MockExtensions.MockExtensionInitFailure::class.java) - assertEquals(EventHubError.extensionInitializationFailure, ret) + assertEquals(EventHubError.ExtensionInitializationFailure, ret) ret = registerExtension(MockExtensions.MockExtensionInvalidConstructor::class.java) - assertEquals(EventHubError.extensionInitializationFailure, ret) + assertEquals(EventHubError.ExtensionInitializationFailure, ret) } @Test fun testRegisterExtensionFailure_InvalidExceptionName() { var ret = registerExtension(MockExtensions.MockExtensionNullName::class.java) - assertEquals(EventHubError.invalidExtensionName, ret) + assertEquals(EventHubError.InvalidExtensionName, ret) ret = registerExtension(MockExtensions.MockExtensionNameException::class.java) - assertEquals(EventHubError.invalidExtensionName, ret) + assertEquals(EventHubError.InvalidExtensionName, ret) } @@ -120,13 +120,13 @@ internal class EventHubTests { registerExtension(MockExtensions.MockExtensionKotlin::class.java) var ret = unregisterExtension(MockExtensions.MockExtensionKotlin::class.java) - assertEquals(EventHubError.none, ret) + assertEquals(EventHubError.None, ret) } @Test fun testUnregisterExtensionFailure() { var ret = unregisterExtension(MockExtensions.MockExtensionKotlin::class.java) - assertEquals(EventHubError.extensionNotRegistered, ret) + assertEquals(EventHubError.ExtensionNotRegistered, ret) } @Test @@ -134,10 +134,10 @@ internal class EventHubTests { registerExtension(MockExtensions.MockExtensionKotlin::class.java) var ret = unregisterExtension(MockExtensions.MockExtensionKotlin::class.java) - assertEquals(EventHubError.none, ret) + assertEquals(EventHubError.None, ret) ret = registerExtension(MockExtensions.MockExtensionKotlin::class.java) - assertEquals(EventHubError.none, ret) + assertEquals(EventHubError.None, ret) } } \ No newline at end of file From 06ad5d8e1803636c5549922a3baa2ec69d97af1d Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Thu, 21 Apr 2022 10:06:02 -0700 Subject: [PATCH 024/476] refactor database migration to use databaseDir instead of cacheDir --- ...oidCacheToFilesDatabaseMigrationTests.java | 62 -------------- .../marketing/mobile/AndroidCursorTests.java | 11 +-- .../AndroidDatabaseMigrationTestHelper.java | 58 -------------- .../mobile/AndroidDatabaseServiceTests.java | 57 +++---------- .../mobile/AndroidDatabaseTests.java | 11 +-- .../com/adobe/marketing/mobile/TestUtils.java | 16 ++-- .../services/SqliteDatabaseHelperTests.java | 2 +- .../mobile/E2EAndroidSystemInfoService.java | 8 -- .../mobile/MockSystemInfoService.java | 6 -- .../mobile/AndroidDatabaseService.java | 69 +++------------- .../mobile/CacheToFilesDatabaseMigration.java | 42 ---------- .../adobe/marketing/mobile/CoreConstants.java | 15 ---- .../com/adobe/marketing/mobile/FileUtil.java | 24 +++--- .../marketing/mobile/SystemInfoService.java | 9 --- .../mobile/services/DeviceInforming.java | 9 --- .../mobile/AndroidPlatformServices.java | 2 +- .../mobile/AndroidSystemInfoService.java | 11 --- .../adobe/marketing/mobile/MobileCore.java | 3 - .../mobile/services/DataQueueService.java | 80 +++++++++++++++++-- .../mobile/services/DeviceInfoService.java | 11 --- .../mobile/services/SQLiteDataQueue.java | 27 +------ .../mobile/AndroidSystemInfoServiceTests.java | 16 ---- .../mobile/services/SqliteDataQueueTests.java | 2 +- .../testapp/PlatformServicesFragment.java | 1 - 24 files changed, 125 insertions(+), 427 deletions(-) delete mode 100644 code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCacheToFilesDatabaseMigrationTests.java delete mode 100644 code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseMigrationTestHelper.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/CacheToFilesDatabaseMigration.java diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCacheToFilesDatabaseMigrationTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCacheToFilesDatabaseMigrationTests.java deleted file mode 100644 index ce943e835..000000000 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCacheToFilesDatabaseMigrationTests.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import static com.adobe.marketing.mobile.AndroidDatabaseMigrationTestHelper.MOCK_FILE_CONTENT; -import static junit.framework.Assert.assertEquals; -import static junit.framework.TestCase.assertNull; - -import android.content.Context; -import android.support.test.InstrumentationRegistry; - -import com.adobe.marketing.mobile.services.ServiceProvider; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.io.File; - -public class AndroidCacheToFilesDatabaseMigrationTests { - - private CacheToFilesDatabaseMigration databaseMigrationTool; - private AndroidDatabaseMigrationTestHelper migrationTestHelper; - - @Before - public void setup() { - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - ServiceProvider.getInstance().setContext(appContext); - databaseMigrationTool = new CacheToFilesDatabaseMigration(); - migrationTestHelper = new AndroidDatabaseMigrationTestHelper(); - } - - @After - public void tearDown() { - migrationTestHelper.deleteDirectory(ServiceProvider.getInstance().getDeviceInfoService().getApplicationCacheDir()); - migrationTestHelper.deleteDirectory(ServiceProvider.getInstance().getDeviceInfoService().getApplicationFilesDir()); - } - - @Test - public void testDatabaseMigration_ExistingDatabase() { - migrationTestHelper.createMockEdgeDatabaseInCacheDir(); - databaseMigrationTool.migrate(); - assertEquals(MOCK_FILE_CONTENT, FileUtil.readStringFromFile(new File(ServiceProvider.getInstance().getDeviceInfoService().getApplicationFilesDir(), - CoreConstants.ExtensionNames.Edge.EDGE_EXTENSION_NAME))); - } - - @Test - public void testDatabaseMigration_NoDatabase() { - databaseMigrationTool.migrate(); - assertNull(FileUtil.readStringFromFile(new File(ServiceProvider.getInstance().getDeviceInfoService().getApplicationFilesDir(), - CoreConstants.ExtensionNames.Edge.EDGE_EXTENSION_NAME))); - } -} diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java index 2f289568a..c58ab37f8 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java @@ -50,10 +50,10 @@ public class AndroidCursorTests { @Before public void beforeEach() { - androidDatabaseService = new AndroidDatabaseService(null); - androidDatabase = androidDatabaseService.openDatabase(TestUtils.getFilesDir( - InstrumentationRegistry.getInstrumentation().getTargetContext()) + "/" + - name.getMethodName()); + androidDatabaseService = new AndroidDatabaseService(); + androidDatabase = androidDatabaseService.openDatabase(TestUtils.getDatabasePathInDatabaseDir( + InstrumentationRegistry.getInstrumentation().getTargetContext(), + name.getMethodName())); } @After @@ -62,7 +62,8 @@ public void afterEach() { queryResult.close(); } - TestUtils.deleteAllFilesInFilesDir(InstrumentationRegistry.getInstrumentation().getTargetContext()); + TestUtils.deleteDatabaseInDatabaseDir(InstrumentationRegistry.getInstrumentation().getTargetContext(), + name.getMethodName()); } @Test diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseMigrationTestHelper.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseMigrationTestHelper.java deleted file mode 100644 index e9fc4d046..000000000 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseMigrationTestHelper.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import com.adobe.marketing.mobile.services.ServiceProvider; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; - -class AndroidDatabaseMigrationTestHelper { - - static final String MOCK_FILE_CONTENT = "Sample database file contents"; - - void createMockEdgeDatabaseInCacheDir() { - File cacheFile = new File(ServiceProvider.getInstance().getDeviceInfoService().getApplicationCacheDir(), - CoreConstants.ExtensionNames.Edge.EDGE_EXTENSION_NAME); - - try { - FileWriter fileWriter = new FileWriter(cacheFile); - fileWriter.write(MOCK_FILE_CONTENT); - fileWriter.flush(); - fileWriter.close(); - } catch (IOException ex) {} - } - - /** - * Removes the cache directory recursively - */ - void deleteDirectory(final File directory) { - if (directory != null) { - String[] files = directory.list(); - - if (files != null) { - for (String file : files) { - File currentFile = new File(directory.getPath(), file); - - if (currentFile.isDirectory()) { - deleteDirectory(currentFile); - } else { - currentFile.delete(); - } - } - } - - directory.delete(); - } - } -} diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java index 9e4de01c4..980208b79 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java @@ -30,14 +30,12 @@ public class AndroidDatabaseServiceTests { private AndroidDatabaseService androidDatabaseService; - private String filesDir; private Context appContext; @Before public void beforeEach() { appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - filesDir = appContext.getFilesDir().getPath(); - androidDatabaseService = new AndroidDatabaseService(null); + androidDatabaseService = new AndroidDatabaseService(); App.setAppContext(appContext); } @@ -48,13 +46,14 @@ public void afterEach() { @Test public void testOpenDatabase_Happy() throws Exception { - assertNotNull(androidDatabaseService.openDatabase(filesDir + "/testOpenDatabase_Happy")); + assertNotNull(androidDatabaseService.openDatabase(appContext.getDatabasePath("testOpenDatabase_Happy").getPath())); + appContext.getDatabasePath("testOpenDatabase_Happy").delete(); } @Test public void testOpenDatabase_Exists() throws Exception { - assertNotNull(androidDatabaseService.openDatabase(filesDir + "/testOpenDatabase_Happy")); - assertNotNull(androidDatabaseService.openDatabase(filesDir + "/testOpenDatabase_Happy")); + assertNotNull(androidDatabaseService.openDatabase(appContext.getDatabasePath("testOpenDatabase_Happy").getPath())); + appContext.getDatabasePath("testOpenDatabase_Happy").delete(); } @Test @@ -69,13 +68,13 @@ public void testOpenDatabase_EmptyFilePath() throws Exception { @Test public void testDeleteDatabase_Happy() { - assertNotNull(androidDatabaseService.openDatabase(filesDir + "/testDeleteDatabase_Happy")); - assertTrue(androidDatabaseService.deleteDatabase(filesDir + "/testDeleteDatabase_Happy")); + assertNotNull(androidDatabaseService.openDatabase(appContext.getDatabasePath("testOpenDatabase_Happy").getPath())); + assertTrue(androidDatabaseService.deleteDatabase(appContext.getDatabasePath("testOpenDatabase_Happy").getPath())); } @Test public void testDeleteDatabase_DoesNotExist() { - assertFalse(androidDatabaseService.deleteDatabase(filesDir + "/testDeleteDatabase_DoesNotExist")); + assertFalse(androidDatabaseService.deleteDatabase(appContext.getDatabasePath("testDeleteDatabase_DoesNotExist").getPath())); } @Test @@ -88,48 +87,12 @@ public void testDeleteDatabase_EmptyFilePath() { assertFalse(androidDatabaseService.deleteDatabase("")); } - @Test - public void testDeleteDatabase_RelativePathBackslashClearnedUp() { - assertNotNull(androidDatabaseService.openDatabase(filesDir + "/mydatabase\\..\\..\\database1")); - assertTrue(androidDatabaseService.deleteDatabase(filesDir + "/mydatabase\\..\\..\\database1")); - } - - @Test - public void testDeleteDatabase_RelativePathForwardslashClearnedUp() { - assertNotNull(androidDatabaseService.openDatabase(filesDir + "/mydatabase/../../database1")); - assertTrue(androidDatabaseService.deleteDatabase(filesDir + "/mydatabase/../../database1")); - } - - @Test - public void testDeleteDatabase_RelativePathBackslashDoesNotChangeDir() { - assertNotNull(androidDatabaseService.openDatabase(filesDir + "/mydatabase\\..\\database1")); - assertFalse(androidDatabaseService.deleteDatabase(filesDir + "/database1")); - } - - @Test - public void testDeleteDatabase_RelativePathForwardslashDoesNotChangeDir() { - assertNotNull(androidDatabaseService.openDatabase(filesDir + "/mydatabase/../database1")); - assertFalse(androidDatabaseService.deleteDatabase(filesDir + "/database1")); - } - - @Test - public void testDeleteDatabase_RelativePathMixedWorkTheSameWhenNotMatch() { - assertNotNull(androidDatabaseService.openDatabase(filesDir + "/mydatabase\\..\\database1")); - assertTrue(androidDatabaseService.deleteDatabase(filesDir + "/mydatabase/../../database1")); - } - - @Test - public void testDeleteDatabase_RelativePathMixedWorkTheSameWhenMatch() { - assertNotNull(androidDatabaseService.openDatabase(filesDir + "/mydatabase\\..\\database1")); - assertTrue(androidDatabaseService.deleteDatabase(filesDir + "/mydatabase/../database1")); - } - @Test public void testDeleteDatabase_RelativePathMixedWorkTheSameWhenMatch1() { AndroidSystemInfoService systemInfoService = new AndroidSystemInfoService(); - androidDatabaseService = new AndroidDatabaseService(systemInfoService); - assertNull(androidDatabaseService.openDatabase("/invalid/file/path")); + androidDatabaseService = new AndroidDatabaseService(); + assertNull(androidDatabaseService.openDatabase(appContext.getDatabasePath("/invalid/file/path").getPath())); } // diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseTests.java index 8ca49dea6..94c3cdef0 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseTests.java @@ -59,15 +59,16 @@ public class AndroidDatabaseTests { @Before public void beforeEach() { Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); - androidDatabaseService = new AndroidDatabaseService(null); - androidDatabase = androidDatabaseService.openDatabase(TestUtils.getFilesDir( - InstrumentationRegistry.getInstrumentation().getTargetContext()) + "/" + - name.getMethodName()); + androidDatabaseService = new AndroidDatabaseService(); + androidDatabase = androidDatabaseService.openDatabase(TestUtils.getDatabasePathInDatabaseDir( + InstrumentationRegistry.getInstrumentation().getTargetContext(), + name.getMethodName())); } @After public void afterEach() { - TestUtils.deleteAllFilesInFilesDir(InstrumentationRegistry.getInstrumentation().getTargetContext()); + TestUtils.deleteDatabaseInDatabaseDir(InstrumentationRegistry.getInstrumentation().getTargetContext(), + name.getMethodName()); if (queryResult != null) { queryResult.close(); diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/TestUtils.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/TestUtils.java index e9178c8e0..b9655d3e5 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/TestUtils.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/TestUtils.java @@ -33,19 +33,13 @@ public static void deleteAllFilesInCacheDir(Context appContext) { } } - public static void deleteAllFilesInFilesDir(Context appContext) { + public static void deleteDatabaseInDatabaseDir(Context appContext, String databaseName) { if (appContext == null) { return; } - File filesDir = appContext.getFilesDir(); - File[] files = filesDir.listFiles(); - - if (files != null) { - for (File file : files) { - file.delete(); - } - } + File databasePath = appContext.getDatabasePath(databaseName); + databasePath.delete(); } public static String getCacheDir(Context appContext) { @@ -56,12 +50,12 @@ public static String getCacheDir(Context appContext) { return appContext.getCacheDir().getPath(); } - public static String getFilesDir(Context appContext) { + public static String getDatabasePathInDatabaseDir(Context appContext, String databaseName) { if (appContext == null) { return null; } - return appContext.getFilesDir().getPath(); + return appContext.getDatabasePath(databaseName).getPath(); } public static boolean almostEqual(long actual, long expected, long tolerance) { diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDatabaseHelperTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDatabaseHelperTests.java index 8b442c4e3..1fc6210d1 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDatabaseHelperTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDatabaseHelperTests.java @@ -38,7 +38,7 @@ public class SqliteDatabaseHelperTests { @Before public void setUp() { - dbPath = new File(InstrumentationRegistry.getContext().getFilesDir(), "test.sqlite").getPath(); + dbPath = InstrumentationRegistry.getContext().getDatabasePath( "test.sqlite").getPath(); sqLiteDatabaseHelper = new SQLiteDatabaseHelper(); createTable(); } diff --git a/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/E2EAndroidSystemInfoService.java b/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/E2EAndroidSystemInfoService.java index 9c2d85975..addd3b0c8 100644 --- a/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/E2EAndroidSystemInfoService.java +++ b/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/E2EAndroidSystemInfoService.java @@ -12,12 +12,4 @@ public File getApplicationCacheDir() { tempDir.mkdir(); return tempDir; } - - @Override - public File getApplicationFilesDir() { - File systemFilesDir = super.getApplicationFilesDir(); - File tempDir = new File(systemFilesDir, String.valueOf(UUID.randomUUID()).replaceAll("-", "")); - tempDir.mkdir(); - return tempDir; - } } \ No newline at end of file diff --git a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockSystemInfoService.java b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockSystemInfoService.java index 9303ce927..f3e13640d 100755 --- a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockSystemInfoService.java +++ b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockSystemInfoService.java @@ -54,12 +54,6 @@ public File getApplicationCacheDir() { return applicationCacheDir; } - public File applicationFilesDir; - @Override - public File getApplicationFilesDir() { - return applicationFilesDir; - } - public InputStream assetStream; public Map assetStreams = new HashMap<>(); @Override diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidDatabaseService.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidDatabaseService.java index 2f01da5ca..c1f6f4c3d 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidDatabaseService.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidDatabaseService.java @@ -22,77 +22,32 @@ * AndroidDatabaseService class implements DatabaseService Interface to manage a SQLite database. */ class AndroidDatabaseService implements DatabaseService { - private final SystemInfoService systemInfoService; private final Object dbServiceMutex = new Object(); private static final String TAG = AndroidDatabaseService.class.getSimpleName(); private Map map = new HashMap<>(); - AndroidDatabaseService(final SystemInfoService systemInfoService) { - this.systemInfoService = systemInfoService; - - if (systemInfoService == null) { - Log.warning(TAG, "Unable to access system info service while creating the database service"); - } - } - - private String removeRelativePath(final String filePath) { - try { - // we don't want to leave any extra "/" or "\" but also can't touch existing slashes - // first use a regex to find all of the ".\" and "./" and turn them into "." - // (\\. is escape for ., \\\\ is escape for \\, which means \ in a String) - // for example: /data/user/0/com.adobe.marketing.mobile.test/cache/mydatabase/../../database1 - // will become /data/user/0/com.adobe.marketing.mobile.test/cache/mydatabase/....database1 - String result = filePath.replaceAll("\\.[/\\\\]", "\\."); - // now use a regex to find all of the "/.." and "\.." and turn into "_", any "." occurs more than 2 times will be counted - // for example : /data/user/0/com.adobe.marketing.mobile.test/cache/mydatabase/....database1 - // will become /data/user/0/com.adobe.marketing.mobile.test/cache/mydatabase_database1 - result = result.replaceAll("[/\\\\](\\.{2,})", "_"); - return result; - } catch (IllegalArgumentException e) { - return filePath; - } - } - /** * Opens a database if it exists, otherwise creates a new one at the specified path. * - * @param filePath {@link String} containing the database file path + * @param databasePath {@link String} containing the database file path * * @return {@link Database} instance, or null if error occurs */ @Override - public Database openDatabase(final String filePath) { - if (StringUtils.isNullOrEmpty(filePath)) { + public Database openDatabase(final String databasePath) { + if (StringUtils.isNullOrEmpty(databasePath)) { Log.debug(TAG, "Failed to open database - filepath is null or empty"); return null; } - final String cleanedPath = removeRelativePath(filePath); - - if (this.systemInfoService != null && this.systemInfoService.getApplicationFilesDir() != null) { - try { - final String filesDirCanonicalPath = this.systemInfoService.getApplicationFilesDir().getCanonicalPath(); - final File file = new File(cleanedPath); - final String dbFileCanonicalPath = file.getCanonicalPath(); - - if (!dbFileCanonicalPath.startsWith(filesDirCanonicalPath)) { - Log.warning(TAG, "Invalid database file path (%s)", cleanedPath); - return null; - } - } catch (Exception e) { - Log.warning(TAG, "Failed to read database file (%s)", e); - return null; - } - } - synchronized (dbServiceMutex) { try { - SQLiteDatabase database = SQLiteDatabase.openDatabase(cleanedPath, + SQLiteDatabase database = SQLiteDatabase.openDatabase(databasePath, null, SQLiteDatabase.NO_LOCALIZED_COLLATORS | SQLiteDatabase.CREATE_IF_NECESSARY); AndroidDatabase androidDatabase = new AndroidDatabase(database); - map.put(cleanedPath, androidDatabase); + map.put(databasePath, androidDatabase); return androidDatabase; } catch (Exception e) { Log.error(TAG, "Failed to open database (%s)", e); @@ -104,24 +59,22 @@ public Database openDatabase(final String filePath) { /** * Delete database at the specified path, if it exists. * - * @param filePath {@link String} containing the database file path + * @param databasePath {@link String} containing the database file path * * @return {@code boolean} indicating whether the database file delete operation was successful */ @Override - public boolean deleteDatabase(final String filePath) { - if (StringUtils.isNullOrEmpty(filePath)) { + public boolean deleteDatabase(final String databasePath) { + if (StringUtils.isNullOrEmpty(databasePath)) { Log.debug(TAG, "Failed to delete database - filepath is null or empty"); return false; } - String cleanedPath = removeRelativePath(filePath); - synchronized (dbServiceMutex) { - if (map.containsKey(cleanedPath)) { + if (map.containsKey(databasePath)) { try { - File databaseFile = new File(cleanedPath); - map.remove(cleanedPath); + File databaseFile = new File(databasePath); + map.remove(databasePath); return databaseFile.delete(); } catch (SecurityException e) { Log.error(TAG, "Failed to delete database (%s)", e); diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/CacheToFilesDatabaseMigration.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/CacheToFilesDatabaseMigration.java deleted file mode 100644 index eebfa8595..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/CacheToFilesDatabaseMigration.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import com.adobe.marketing.mobile.services.ServiceProvider; - -import java.io.File; - -class CacheToFilesDatabaseMigration { - private static final String LOG_TAG = "CacheToFilesDatabaseMigration"; - - protected void migrate() { - final File applicationCacheDir = ServiceProvider.getInstance().getDeviceInfoService().getApplicationCacheDir(); - if (applicationCacheDir != null) { - final File cacheDirEdgeDataQueue = new File(applicationCacheDir, CoreConstants.ExtensionNames.Edge.EDGE_EXTENSION_NAME); - - if (cacheDirEdgeDataQueue.exists()) { - final File applicationFilesDir = ServiceProvider.getInstance().getDeviceInfoService().getApplicationFilesDir(); - if (applicationFilesDir != null) { - final File filesDirEdgeDataQueue = new File(applicationFilesDir, CoreConstants.ExtensionNames.Edge.EDGE_EXTENSION_NAME); - try { - FileUtil.copyFile(cacheDirEdgeDataQueue, filesDirEdgeDataQueue); - Log.debug(LOG_TAG, String.format("Successfully moved DataQueue for database (%s) from cache directory to files directory", - CoreConstants.ExtensionNames.Edge.EDGE_EXTENSION_NAME)); - } catch (Exception e) { - Log.warning(LOG_TAG, String.format("Failed in moving DataQueue for database (%s), Files dir is null.", - CoreConstants.ExtensionNames.Edge.EDGE_EXTENSION_NAME)); - } - } - } - } - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/CoreConstants.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/CoreConstants.java index 8cc8753e2..d70ae7080 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/CoreConstants.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/CoreConstants.java @@ -100,19 +100,4 @@ private Signal() {} } } - static class ExtensionNames { - - private ExtensionNames() {} - - /** - * Hold extension name for the {@code Edge} module. - */ - static final class Edge { - static final String EDGE_EXTENSION_NAME = "com.adobe.edge"; - - private Edge() { - } - } - } - } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/FileUtil.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/FileUtil.java index 44e1dcb31..54361d544 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/FileUtil.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/FileUtil.java @@ -15,6 +15,8 @@ final class FileUtil { + private final static String LOG_TAG = "com.adobe.marketing.mobile.FileUtil"; + private FileUtil() {} /** @@ -25,15 +27,14 @@ private FileUtil() {} * or if the file do not have read permission */ static String readStringFromFile(final File file) { - final String logPrefix = "File Reader"; try { if (file == null || !file.exists() || !file.canRead() || !file.isFile()) { - Log.warning(logPrefix, "Write to file - File does not exist or don't have read permission (%s)", file); + Log.warning(LOG_TAG, "Write to file - File does not exist or don't have read permission (%s)", file); return null; } } catch (SecurityException e) { - Log.debug(logPrefix, "Failed to read file (%s)", e); + Log.debug(LOG_TAG, "Failed to read file (%s)", e); return null; } @@ -54,7 +55,7 @@ static String readStringFromFile(final File file) { return builder.toString(); } catch (IOException e) { - Log.debug(logPrefix, "Failed to close file (%s)", e); + Log.debug(LOG_TAG, "Failed to close file (%s)", e); return null; } finally { try { @@ -66,7 +67,7 @@ static String readStringFromFile(final File file) { bufferedReader.close(); } } catch (IOException e) { - Log.debug(logPrefix, "Failed to close file (%s)", e); + Log.debug(LOG_TAG, "Failed to close file (%s)", e); } } } @@ -89,24 +90,17 @@ static boolean isValidDirectory(final File directory) { * @throws IOException if {@code src} or {@code dest} is not present or it does not have read permissions */ static void copyFile(final File src, final File dest) throws IOException, NullPointerException{ - final String logPrefix = "File Copy"; final int STREAM_READ_BUFFER_SIZE = 1024; - InputStream input = new FileInputStream(src); - try { - OutputStream output = new FileOutputStream(dest); - try { + try (InputStream input = new FileInputStream(src)) { + try (OutputStream output = new FileOutputStream(dest)) { byte[] buffer = new byte[STREAM_READ_BUFFER_SIZE]; int length; while ((length = input.read(buffer)) != -1) { output.write(buffer, 0, length); } - Log.debug(logPrefix, "Successfully copied (%s) to (%s)", src.getCanonicalPath(), dest.getCanonicalPath()); - } finally { - output.close(); + Log.debug(LOG_TAG, "Successfully copied (%s) to (%s)", src.getCanonicalPath(), dest.getCanonicalPath()); } - } finally { - input.close(); } } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SystemInfoService.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SystemInfoService.java index a1076fa60..f879a7602 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SystemInfoService.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SystemInfoService.java @@ -94,15 +94,6 @@ interface DisplayInformation { */ File getApplicationCacheDir(); - /** - * Returns the application specific directory where files are created and stored. - * The application will be able to read and write to the directory and - * the files stored in the directory are persisted (it is not deleted by the system). - * - * @return A {@link File} representing the application file directory, or null if not available on the platform. - */ - File getApplicationFilesDir(); - /** * Open the requested asset returns an InputStream to read its contents. * diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/DeviceInforming.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/DeviceInforming.java index 504fa610c..2bd2fd8ea 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/DeviceInforming.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/DeviceInforming.java @@ -109,15 +109,6 @@ interface DisplayInformation { */ File getApplicationCacheDir(); - /** - * Returns the application specific directory where files are created and stored. - * The application will be able to read and write to the directory and - * the files stored in the directory are persisted (it is not deleted by the system). - * - * @return A {@link File} representing the application file directory, or null if not available on the platform. - */ - File getApplicationFilesDir(); - /** * Open the requested asset returns an InputStream to read its contents. * diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/AndroidPlatformServices.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/AndroidPlatformServices.java index 67c5bc41b..5ff65e1bc 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/AndroidPlatformServices.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/AndroidPlatformServices.java @@ -40,7 +40,7 @@ class AndroidPlatformServices implements PlatformServices { systemInfoService = new AndroidSystemInfoService(); networkService = new AndroidNetworkService(ServiceProvider.getInstance().getNetworkService()); loggingService = new AndroidLoggingService(); - databaseService = new AndroidDatabaseService(systemInfoService); + databaseService = new AndroidDatabaseService(); uiService = new AndroidUIService(); localStorageService = new AndroidLocalStorageService(); deepLinkService = new AndroidDeepLinkService(); diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/AndroidSystemInfoService.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/AndroidSystemInfoService.java index 8e88bb9d7..9feb24ff2 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/AndroidSystemInfoService.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/AndroidSystemInfoService.java @@ -70,17 +70,6 @@ public File getApplicationCacheDir() { return context.getCacheDir(); } - @Override - public File getApplicationFilesDir() { - Context context = App.getAppContext(); - - if (context == null) { - return null; - } - - return context.getFilesDir(); - } - @Override public InputStream getAsset(String fileName) { Context context = App.getAppContext(); diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java index 98daca09f..7f2fbe0fe 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java @@ -91,9 +91,6 @@ public static void setApplication(final Application app) { V4ToV5Migration migrationTool = new V4ToV5Migration(); migrationTool.migrate(); - CacheToFilesDatabaseMigration dbMigration = new CacheToFilesDatabaseMigration(); - dbMigration.migrate(); - if (core == null) { synchronized (mutex) { if (platformServices == null) { diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java index 47e8caa5d..2955a9a54 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java @@ -15,6 +15,11 @@ import com.adobe.marketing.mobile.MobileCore; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.HashMap; import java.util.Map; @@ -43,15 +48,29 @@ public DataQueue getDataQueue(final String databaseName) { dataQueue = dataQueueCache.get(databaseName); if (dataQueue == null) { - final File cacheDir = ServiceProvider.getInstance().getDeviceInfoService().getApplicationFilesDir(); + final String cleanedDatabasePath = removeRelativePath(databaseName); + final File databaseDirDataQueue = ServiceProvider.getInstance().getApplicationContext().getDatabasePath(cleanedDatabasePath); - if (cacheDir == null) { - MobileCore.log(LoggingMode.WARNING, LOG_TAG, - String.format("Failed in creating DataQueue for database (%s), Cache dir is null.", databaseName)); - return null; + final File cacheDir = ServiceProvider.getInstance().getDeviceInfoService().getApplicationCacheDir(); + if (cacheDir != null) { + final File cacheDirDataQueue = new File(cacheDir, cleanedDatabasePath); + if (cacheDirDataQueue.exists()) { + try { + if(databaseDirDataQueue.createNewFile()) { + copyFile(cacheDirDataQueue, databaseDirDataQueue); + } + } catch (Exception e) { + MobileCore.log(LoggingMode.WARNING, + LOG_TAG, + String.format("Failed in moving DataQueue for database (%s), could not create new file in database directory.", databaseName)); + return null; + } + } } - - dataQueue = new SQLiteDataQueue(cacheDir, databaseName, databaseHelper); + dataQueue = new SQLiteDataQueue(databaseDirDataQueue.getPath(), databaseHelper); + MobileCore.log(LoggingMode.DEBUG, + LOG_TAG, + String.format("Successfully moved DataQueue for database (%s) from cache directory to database directory", databaseName)); dataQueueCache.put(databaseName, dataQueue); } } @@ -59,4 +78,51 @@ public DataQueue getDataQueue(final String databaseName) { return dataQueue; } + + /** + * Removes the relative part of the file name(if exists). + *

+ * for ex: File name `/mydatabase/../../database1` will be converted to `mydatabase_database1` + *

+ * + * @param filePath the file name + * @return file name without relative path + */ + private String removeRelativePath(final String filePath) { + if (filePath == null || filePath.isEmpty()) { + return filePath; + } + + try { + String result = filePath.replaceAll("\\.[/\\\\]", "\\."); + result = result.replaceAll("[/\\\\](\\.{2,})", "_"); + return result; + } catch (IllegalArgumentException e) { + return filePath; + } + } + + /** + * Copies the contents from {@code src} to {@code dest}. + * + * @param src {@link File} from which the contents are read + * @param dest {@link File} to which contents are written to + * @throws IOException if {@code src} or {@code dest} is not present or it does not have read permissions + */ + private void copyFile(final File src, final File dest) throws IOException, NullPointerException{ + final int STREAM_READ_BUFFER_SIZE = 1024; + + try (InputStream input = new FileInputStream(src)) { + try (OutputStream output = new FileOutputStream(dest)) { + byte[] buffer = new byte[STREAM_READ_BUFFER_SIZE]; + int length; + while ((length = input.read(buffer)) != -1) { + output.write(buffer, 0, length); + } + MobileCore.log(LoggingMode.DEBUG, + LOG_TAG, + String.format("Successfully copied (%s) to (%s)", src.getCanonicalPath(), dest.getCanonicalPath())); + } + } + } } diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DeviceInfoService.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DeviceInfoService.java index c8c79052d..0722efa29 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DeviceInfoService.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DeviceInfoService.java @@ -285,17 +285,6 @@ public File getApplicationCacheDir() { return context.getCacheDir(); } - @Override - public File getApplicationFilesDir() { - final Context context = getApplicationContext(); - - if (context == null) { - return null; - } - - return context.getFilesDir(); - } - @Override public InputStream getAsset(String fileName) { final Context context = getApplicationContext(); diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java index d50721f08..0bffe4642 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java @@ -39,9 +39,9 @@ final class SQLiteDataQueue implements DataQueue { private boolean isClose = false; private final Object dbMutex = new Object(); - SQLiteDataQueue(final File filesDir, final String databaseName, final SQLiteDatabaseHelper databaseHelper) { + SQLiteDataQueue(final String databasePath, final SQLiteDatabaseHelper databaseHelper) { this.databaseHelper = databaseHelper; - this.databasePath = new File(filesDir, removeRelativePath(databaseName)).getPath(); + this.databasePath = databasePath; createTableIfNotExists(); } @@ -221,27 +221,4 @@ private void createTableIfNotExists() { MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, String.format("createTableIfNotExists - Error creating/accessing table (%s) ", TABLE_NAME)); } - - /** - * Removes the relative part of the file name(if exists). - *

- * for ex: File name `/mydatabase/../../database1` will be converted to `mydatabase_database1` - *

- * - * @param filePath the file name - * @return file name without relative path - */ - private String removeRelativePath(final String filePath) { - if (filePath == null || filePath.isEmpty()) { - return filePath; - } - - try { - String result = filePath.replaceAll("\\.[/\\\\]", "\\."); - result = result.replaceAll("[/\\\\](\\.{2,})", "_"); - return result; - } catch (IllegalArgumentException e) { - return filePath; - } - } } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/AndroidSystemInfoServiceTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/AndroidSystemInfoServiceTests.java index 09b1661c3..e736f36fa 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/AndroidSystemInfoServiceTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/AndroidSystemInfoServiceTests.java @@ -146,22 +146,6 @@ public void testGetApplicationCacheDir_NullContext() throws Exception { assertNull(systemInfoService.getApplicationCacheDir()); } - @Test - public void testGetApplicationFilesDir_Happy() throws Exception { - AndroidSystemInfoService systemInfoService = new AndroidSystemInfoService(); - File testFilesDir = new File("testFilesDir"); - when(mockContext.getFilesDir()).thenReturn(testFilesDir); - assertEquals(testFilesDir, systemInfoService.getApplicationFilesDir()); - } - - @Test - public void testGetApplicationFilesDir_NullContext() throws Exception { - AndroidSystemInfoService systemInfoService = new AndroidSystemInfoService(); - App.setAppContext(null); - Runtime.getRuntime().gc(); - assertNull(systemInfoService.getApplicationFilesDir()); - } - @Test public void testGetApplicationName_Happy() throws Exception { AndroidSystemInfoService systemInfoService = new AndroidSystemInfoService(); diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java index df109a05b..25dfeb84a 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java @@ -56,7 +56,7 @@ public SqliteDataQueueTests() { @Before public void setUp() { - dataQueue = new SQLiteDataQueue(null, DATABASE_NAME, database); + dataQueue = new SQLiteDataQueue(DATABASE_NAME, database); } diff --git a/code/testapp/src/main/java/com/adobe/testapp/PlatformServicesFragment.java b/code/testapp/src/main/java/com/adobe/testapp/PlatformServicesFragment.java index 0a83185b4..201f4a11f 100644 --- a/code/testapp/src/main/java/com/adobe/testapp/PlatformServicesFragment.java +++ b/code/testapp/src/main/java/com/adobe/testapp/PlatformServicesFragment.java @@ -56,7 +56,6 @@ public void onClick(View view) { stringBuffer.append("\ngetApplicationVersionCode() - " + deviceInforming.getApplicationVersionCode()); stringBuffer.append("\ngetApplicationBaseDir() - " + deviceInforming.getApplicationBaseDir()); stringBuffer.append("\ngetApplicationCacheDir() - " + deviceInforming.getApplicationCacheDir()); - stringBuffer.append("\ngetApplicationFilesDir() - " + deviceInforming.getApplicationFilesDir()); stringBuffer.append("\ngetActiveLocale() - " + deviceInforming.getActiveLocale()); stringBuffer.append("\ngetCanonicalPlatformName() - " + deviceInforming.getCanonicalPlatformName()); // stringBuffer.append("\ngetCoreVersion() - " + deviceInforming.getCoreVersion()); From 3f0b1bf6c939404f5c343e40a024ec184499ac0e Mon Sep 17 00:00:00 2001 From: praveek Date: Thu, 21 Apr 2022 14:11:41 -0700 Subject: [PATCH 025/476] Add javadoc and lint support for Kotlin --- .circleci/config.yml | 3 +++ Makefile | 8 +++++++- code/android-core-library/build.gradle | 2 ++ code/build.gradle | 2 ++ code/gradle.properties | 1 + 5 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f4fc2155e..62f24d67c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -25,6 +25,9 @@ jobs: steps: # Checkout the code as the first step. - checkout + - run: + name: check-format + command: make check-format - run: name: assemble-phone-release command: make assemble-phone-release diff --git a/Makefile b/Makefile index 328716026..0509a88d4 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,12 @@ clean: checkstyle: (./code/gradlew -p code/android-core-library checkstyle) +check-format: + (./code/gradlew -p code/android-core-library ktlintCheck) + +format: + (./code/gradlew -p code/android-core-library ktlintFormat) + assemble-phone: (./code/gradlew -p code/android-core-library assemblePhone) @@ -31,7 +37,7 @@ functional-test: (./code/gradlew -p code/android-core-library connectedPhoneDebugAndroidTest) javadoc: - (./code/gradlew -p code/android-core-library Javadoc) + (./code/gradlew -p code/android-core-library dokkaJavadoc) build-third-party-extension: diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index 6b6350b83..5c7216e2b 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -1,5 +1,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' +apply plugin: 'org.jlleitschuh.gradle.ktlint' +apply plugin: 'org.jetbrains.dokka' android { compileSdkVersion 30 diff --git a/code/build.gradle b/code/build.gradle index 75bea9caf..6016ae009 100644 --- a/code/build.gradle +++ b/code/build.gradle @@ -11,6 +11,8 @@ buildscript { classpath 'com.android.tools.build:gradle:4.0.1' classpath "org.jacoco:org.jacoco.core:0.8.7" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}" + classpath "org.jlleitschuh.gradle:ktlint-gradle:9.2.1" } } diff --git a/code/gradle.properties b/code/gradle.properties index 1b3a73277..c21eed4a4 100644 --- a/code/gradle.properties +++ b/code/gradle.properties @@ -31,3 +31,4 @@ android.useAndroidX=true # incompatible library is used. #android.enableJetifier=true +dokka_version = 1.6.20 From ace97345f40efdb45b8b91d8d012316834f846c6 Mon Sep 17 00:00:00 2001 From: praveek Date: Thu, 21 Apr 2022 14:11:41 -0700 Subject: [PATCH 026/476] Add javadoc and lint support for Kotlin --- .circleci/config.yml | 3 +++ Makefile | 8 +++++++- code/android-core-library/build.gradle | 2 ++ code/build.gradle | 2 ++ code/gradle.properties | 1 + 5 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3ff77bb14..a536ffc63 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -25,6 +25,9 @@ jobs: steps: # Checkout the code as the first step. - checkout + - run: + name: check-format + command: make check-format - run: name: assemble-phone-release command: make assemble-phone-release diff --git a/Makefile b/Makefile index 328716026..0509a88d4 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,12 @@ clean: checkstyle: (./code/gradlew -p code/android-core-library checkstyle) +check-format: + (./code/gradlew -p code/android-core-library ktlintCheck) + +format: + (./code/gradlew -p code/android-core-library ktlintFormat) + assemble-phone: (./code/gradlew -p code/android-core-library assemblePhone) @@ -31,7 +37,7 @@ functional-test: (./code/gradlew -p code/android-core-library connectedPhoneDebugAndroidTest) javadoc: - (./code/gradlew -p code/android-core-library Javadoc) + (./code/gradlew -p code/android-core-library dokkaJavadoc) build-third-party-extension: diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index 2b037adfe..fb3b21d71 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -1,5 +1,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' +apply plugin: 'org.jlleitschuh.gradle.ktlint' +apply plugin: 'org.jetbrains.dokka' android { compileSdkVersion 30 diff --git a/code/build.gradle b/code/build.gradle index 75bea9caf..6016ae009 100644 --- a/code/build.gradle +++ b/code/build.gradle @@ -11,6 +11,8 @@ buildscript { classpath 'com.android.tools.build:gradle:4.0.1' classpath "org.jacoco:org.jacoco.core:0.8.7" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}" + classpath "org.jlleitschuh.gradle:ktlint-gradle:9.2.1" } } diff --git a/code/gradle.properties b/code/gradle.properties index f317f1686..2aaac7786 100644 --- a/code/gradle.properties +++ b/code/gradle.properties @@ -24,3 +24,4 @@ mavenLifecycleVersion=1.1.0 #android.enableUnitTestBinaryResources=false kotlin_version = 1.4.0 +dokka_version = 1.6.20 From 91b4d3443e6ba51b7e83d5da303889a65d48e338 Mon Sep 17 00:00:00 2001 From: praveek Date: Thu, 21 Apr 2022 14:47:03 -0700 Subject: [PATCH 027/476] Run code format and add headers --- .../mobile/internal/eventhub/EventHub.kt | 30 ++++++++++--- .../internal/eventhub/EventHubConstants.kt | 13 +++++- .../mobile/internal/eventhub/EventHubError.kt | 13 +++++- .../eventhub/EventHubPlaceholderExtension.kt | 14 +++++- .../internal/eventhub/ExtensionContainer.kt | 20 ++++++++- .../mobile/internal/eventhub/ExtensionExt.kt | 43 +++++++++++++------ .../mobile/internal/eventhub/EventHubTests.kt | 37 ++++++++++------ .../internal/eventhub/MockExtension.java | 11 +++++ 8 files changed, 140 insertions(+), 41 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index 6e8cff8ad..c9a78faed 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -1,21 +1,37 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + package com.adobe.marketing.mobile.internal.eventhub -import com.adobe.marketing.mobile.* -import java.util.concurrent.* +import com.adobe.marketing.mobile.Extension +import com.adobe.marketing.mobile.LoggingMode +import com.adobe.marketing.mobile.MobileCore +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors /** * EventHub class is responsible for delivering events to listeners and maintaining registered extension's lifecycle. */ internal class EventHub { + constructor() + companion object { - val LOG_TAG = "EventHub" + val LOG_TAG = "EventHub" public var shared = EventHub() } private val eventHubExecutor: ExecutorService by lazy { Executors.newSingleThreadExecutor() } private val registeredExtensions: ConcurrentHashMap = ConcurrentHashMap() - private var hubStarted = false; - + private var hubStarted = false init { registerExtension(EventHubPlaceholderExtension::class.java) {} @@ -27,7 +43,7 @@ internal class EventHub { fun start() { eventHubExecutor.submit { this.hubStarted = true - + this.shareEventHubSharedState() MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Event Hub successfully started") } @@ -100,6 +116,6 @@ internal class EventHub { } } -/// Helper to get extension type name +// / Helper to get extension type name private val Class.extensionTypeName get() = this.name diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubConstants.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubConstants.kt index 394010ba2..b34aeaa74 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubConstants.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubConstants.kt @@ -1,3 +1,14 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + package com.adobe.marketing.mobile.internal.eventhub internal object EventHubConstants { @@ -13,4 +24,4 @@ internal object EventHubConstants { const val METADATA = "metadata" const val FRIENDLY_NAME = "friendlyName" } -} \ No newline at end of file +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubError.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubError.kt index ecff7f018..a6e5b79bf 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubError.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubError.kt @@ -1,3 +1,14 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + package com.adobe.marketing.mobile.internal.eventhub internal enum class EventHubError { @@ -7,4 +18,4 @@ internal enum class EventHubError { ExtensionNotRegistered, Unknown, None -} \ No newline at end of file +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubPlaceholderExtension.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubPlaceholderExtension.kt index 8dac986a2..c4bee3bc2 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubPlaceholderExtension.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHubPlaceholderExtension.kt @@ -1,3 +1,14 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + package com.adobe.marketing.mobile.internal.eventhub import com.adobe.marketing.mobile.Extension @@ -7,9 +18,8 @@ import com.adobe.marketing.mobile.ExtensionApi * An `Extension` for `EventHub`. This serves no purpose other than to allow `EventHub` to store share state and manage event listeners. */ -internal class EventHubPlaceholderExtension(val extensionApi: ExtensionApi): Extension(extensionApi) { +internal class EventHubPlaceholderExtension(val extensionApi: ExtensionApi) : Extension(extensionApi) { override fun getName() = EventHubConstants.NAME override fun getFriendlyName() = EventHubConstants.FRIENDLY_NAME override fun getVersion() = EventHubConstants.VERSION_NUMBER } - diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt index e24b44453..092108097 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt @@ -1,9 +1,25 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + package com.adobe.marketing.mobile.internal.eventhub -import com.adobe.marketing.mobile.* +import com.adobe.marketing.mobile.Event +import com.adobe.marketing.mobile.Extension +import com.adobe.marketing.mobile.ExtensionApi +import com.adobe.marketing.mobile.ExtensionError +import com.adobe.marketing.mobile.ExtensionErrorCallback +import com.adobe.marketing.mobile.ExtensionListener import java.util.concurrent.ExecutorService -internal class ExtensionRuntime(): ExtensionApi() { +internal class ExtensionRuntime() : ExtensionApi() { var extension: Extension? = null set(value) { field = value diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt index 15098d9e0..05d1eeb2d 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt @@ -1,52 +1,67 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + package com.adobe.marketing.mobile.internal.eventhub -import com.adobe.marketing.mobile.* +import com.adobe.marketing.mobile.Extension +import com.adobe.marketing.mobile.ExtensionApi +import com.adobe.marketing.mobile.ExtensionHelper +import com.adobe.marketing.mobile.ExtensionListener +import com.adobe.marketing.mobile.LoggingMode +import com.adobe.marketing.mobile.MobileCore import java.lang.Exception -/// Type extensions for [Extension] to allow for easier usage +// / Type extensions for [Extension] to allow for easier usage -/// Function to initialize Extension with [ExtensionApi] -internal fun Class.initWith(extensionApi: ExtensionApi): Extension? { +// / Function to initialize Extension with [ExtensionApi] +internal fun Class.initWith(extensionApi: ExtensionApi): Extension? { try { val extensionConstructor = this.getDeclaredConstructor(ExtensionApi::class.java) extensionConstructor.setAccessible(true) return extensionConstructor.newInstance(extensionApi) - } catch(ex: Exception) { - MobileCore.log(LoggingMode.DEBUG,"Extension", "Initializing Extension $this failed with $ex") + } catch (ex: Exception) { + MobileCore.log(LoggingMode.DEBUG, "Extension", "Initializing Extension $this failed with $ex") } return null } -/// Property to get Extension name +// / Property to get Extension name internal val Extension.name: String? get() = ExtensionHelper.getName(this) -/// Property to get Extension version +// / Property to get Extension version internal val Extension.version: String? get() = ExtensionHelper.getVersion(this) -/// Property to get Extension friendly name +// / Property to get Extension friendly name internal val Extension.friendlyName: String? get() = ExtensionHelper.getFriendlyName(this) -/// Function to notify that the Extension has been unregistered +// / Function to notify that the Extension has been unregistered internal fun Extension.onUnregistered() { ExtensionHelper.onUnregistered(this) } -/// Type extensions for [ExtensionListener] to allow for easier usage +// / Type extensions for [ExtensionListener] to allow for easier usage -/// Function to initialize ExtensionListener with [ExtensionApi], type and source. +// / Function to initialize ExtensionListener with [ExtensionApi], type and source. internal fun Class.initWith(extensionApi: ExtensionApi, type: String, source: String): ExtensionListener? { try { val extensionListenerConstructor = this.getDeclaredConstructor(ExtensionApi::class.java, String::class.java, String::class.java) extensionListenerConstructor.setAccessible(true) return extensionListenerConstructor.newInstance(extensionApi, type, source) } catch (ex: Exception) { - MobileCore.log(LoggingMode.DEBUG,"Extension", "Initializing Extension $this failed with $ex") + MobileCore.log(LoggingMode.DEBUG, "Extension", "Initializing Extension $this failed with $ex") } return null } - diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt index bc1bb1345..d7f2804b2 100644 --- a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt @@ -1,22 +1,33 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + package com.adobe.marketing.mobile.internal.eventhub import com.adobe.marketing.mobile.Extension import com.adobe.marketing.mobile.ExtensionApi -import org.junit.Before -import org.junit.Test import java.lang.Exception import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import kotlin.test.assertEquals +import org.junit.Before +import org.junit.Test private object MockExtensions { - class MockExtensionInvalidConstructor(api: ExtensionApi, name: String?): Extension(api) { + class MockExtensionInvalidConstructor(api: ExtensionApi, name: String?) : Extension(api) { override fun getName(): String { return MockExtensionInvalidConstructor::javaClass.name } } - class MockExtensionInitFailure(api: ExtensionApi): Extension(api) { + class MockExtensionInitFailure(api: ExtensionApi) : Extension(api) { init { throw Exception("Init Exception") } @@ -26,19 +37,19 @@ private object MockExtensions { } } - class MockExtensionNullName(api: ExtensionApi): Extension(api) { + class MockExtensionNullName(api: ExtensionApi) : Extension(api) { override fun getName(): String? { return null } } - class MockExtensionNameException(api: ExtensionApi): Extension(api) { + class MockExtensionNameException(api: ExtensionApi) : Extension(api) { override fun getName(): String { throw Exception() } } - class MockExtensionKotlin(api: ExtensionApi): Extension(api) { + class MockExtensionKotlin(api: ExtensionApi) : Extension(api) { override fun getName(): String { return MockExtensionKotlin::javaClass.name } @@ -49,26 +60,26 @@ internal class EventHubTests { // Helper to register extensions fun registerExtension(extensionClass: Class): EventHubError { - var ret: EventHubError = EventHubError.Unknown; + var ret: EventHubError = EventHubError.Unknown val latch = CountDownLatch(1) EventHub.shared.registerExtension(extensionClass) { error -> ret = error latch.countDown() } - if (!latch.await(1, TimeUnit.SECONDS)) throw Exception("Timeout registering extension"); + if (!latch.await(1, TimeUnit.SECONDS)) throw Exception("Timeout registering extension") return ret } fun unregisterExtension(extensionClass: Class): EventHubError { - var ret: EventHubError = EventHubError.Unknown; + var ret: EventHubError = EventHubError.Unknown val latch = CountDownLatch(1) EventHub.shared.unregisterExtension(extensionClass) { error -> ret = error latch.countDown() } - if (!latch.await(1, TimeUnit.SECONDS)) throw Exception("Timeout unregistering extension"); + if (!latch.await(1, TimeUnit.SECONDS)) throw Exception("Timeout unregistering extension") return ret } @@ -114,7 +125,6 @@ internal class EventHubTests { assertEquals(EventHubError.InvalidExtensionName, ret) } - @Test fun testUnregisterExtensionSuccess() { registerExtension(MockExtensions.MockExtensionKotlin::class.java) @@ -139,5 +149,4 @@ internal class EventHubTests { ret = registerExtension(MockExtensions.MockExtensionKotlin::class.java) assertEquals(EventHubError.None, ret) } - -} \ No newline at end of file +} diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/MockExtension.java b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/MockExtension.java index 52feed700..beb5324c8 100644 --- a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/MockExtension.java +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/MockExtension.java @@ -1,3 +1,14 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + package com.adobe.marketing.mobile.internal.eventhub; import com.adobe.marketing.mobile.Extension; From 18f5e9d26b6cd557c2b8407c373a9c4d2b151f5c Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Thu, 21 Apr 2022 15:38:36 -0700 Subject: [PATCH 028/476] DataQueueService tests --- .../marketing/mobile/AndroidCursorTests.java | 1 - .../mobile/AndroidDatabaseServiceTests.java | 5 - .../services/DataQueueServiceTests.java | 135 ++++++++++++++++++ .../mobile/services/DataQueueService.java | 24 +++- .../adobe/marketing/mobile/FileUtilTests.java | 1 - 5 files changed, 153 insertions(+), 13 deletions(-) create mode 100644 code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/DataQueueServiceTests.java diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java index c58ab37f8..3b345bf11 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java @@ -44,7 +44,6 @@ public class AndroidCursorTests { private DatabaseService androidDatabaseService; private DatabaseService.Database androidDatabase; - private String filesDir; private DatabaseService.QueryResult queryResult; private Query testQuery; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java index 980208b79..e725e5900 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java @@ -39,11 +39,6 @@ public void beforeEach() { App.setAppContext(appContext); } - @After - public void afterEach() { - appContext.getFilesDir().delete(); - } - @Test public void testOpenDatabase_Happy() throws Exception { assertNotNull(androidDatabaseService.openDatabase(appContext.getDatabasePath("testOpenDatabase_Happy").getPath())); diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/DataQueueServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/DataQueueServiceTests.java new file mode 100644 index 000000000..d702a44f1 --- /dev/null +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/DataQueueServiceTests.java @@ -0,0 +1,135 @@ +package com.adobe.marketing.mobile.services; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; + +@RunWith(AndroidJUnit4.class) +public class DataQueueServiceTests { + + private static final String DATABASE_NAME = "testDatabase"; + + @Before + public void beforeEach() { + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + ServiceProvider.getInstance().setContext(context); + } + + @After + public void tearDown() { + if(ServiceProvider.getInstance().getApplicationContext() != null) { + new File(ServiceProvider.getInstance().getDeviceInfoService().getApplicationCacheDir(), DATABASE_NAME).delete(); + ServiceProvider.getInstance().getApplicationContext().getDatabasePath(DATABASE_NAME).delete(); + } + } + + @Test + public void testApplicationContextIsNotSet() { + ServiceProvider.getInstance().setContext(null); + DataQueue dataQueue = new DataQueueService().getDataQueue(DATABASE_NAME); + assertNull(dataQueue); + } + + @Test + public void testGetDataQueue_NewQueue() { + DataQueue dataQueue = new DataQueueService().getDataQueue(DATABASE_NAME); + assertNotNull(dataQueue); + } + + @Test + public void testGetDataQueue_MigrateFromCacheDir() { + if(createDataQueue(new File(ServiceProvider.getInstance().getDeviceInfoService().getApplicationCacheDir(), DATABASE_NAME), 1)) { + DataQueue dataQueue = new DataQueueService().getDataQueue(DATABASE_NAME); + + assertNotNull(dataQueue); + assertEquals(dataQueue.count(), 1); + } + } + + @Test + public void testGetDataQueue_DataQueueInCacheDirAndDatabaseDir() { + if(createDataQueue(new File(ServiceProvider.getInstance().getDeviceInfoService().getApplicationCacheDir(), DATABASE_NAME),1) + && createDataQueue(ServiceProvider.getInstance().getApplicationContext().getDatabasePath(DATABASE_NAME), 2)){ + DataQueue dataQueue = new DataQueueService().getDataQueue(DATABASE_NAME); + + assertNotNull(dataQueue); + assertEquals(dataQueue.count(), 2); + } + } + + @Test + public void testGetDataQueue_NonEmptyDataQueueCache() { + DataQueueService dataQueueService = new DataQueueService(); + DataQueue dataQueue = dataQueueService.getDataQueue(DATABASE_NAME); + dataQueue.add(new DataEntity("{}")); + + assertEquals(dataQueueService.getDataQueue(DATABASE_NAME).count(), 1); + } + + @Test + public void testGetDataQueue_RelativePathBackslashCleanedUp() { + DataQueueService dataQueueService = new DataQueueService(); + DataQueue dataQueue = dataQueueService.getDataQueue("/mydatabase\\..\\..\\database1"); + dataQueue.add(new DataEntity("{}")); + + assertEquals(dataQueueService.getDataQueue("/mydatabase\\..\\..\\database1").count(), 1); + ServiceProvider.getInstance().getApplicationContext().getDatabasePath(dataQueueService.removeRelativePath("/mydatabase\\..\\..\\database1")).delete(); + } + + @Test + public void testGetDataQueue_RelativePathForwardslashCleanedUp() { + DataQueueService dataQueueService = new DataQueueService(); + DataQueue dataQueue = dataQueueService.getDataQueue("/mydatabase/../../database1"); + dataQueue.add(new DataEntity("{}")); + + assertEquals(dataQueueService.getDataQueue("/mydatabase/../../database1").count(), 1); + ServiceProvider.getInstance().getApplicationContext().getDatabasePath(dataQueueService.removeRelativePath("/mydatabase/../../database1")).delete(); + } + + @Test + public void testGetDataQueue_RelativePathMixedWorkTheSameWhenNotMatch() { + DataQueueService dataQueueService = new DataQueueService(); + DataQueue dataQueue = dataQueueService.getDataQueue("/mydatabase\\..\\database1"); + dataQueue.add(new DataEntity("{}")); + + assertEquals(dataQueueService.getDataQueue("/mydatabase/../../database1").count(), 1); + ServiceProvider.getInstance().getApplicationContext().getDatabasePath(dataQueueService.removeRelativePath("/mydatabase/../../database1")).delete(); + } + + @Test + public void testGetDataQueue_RelativePathMixedWorkTheSameWhenMatch() { + DataQueueService dataQueueService = new DataQueueService(); + DataQueue dataQueue = dataQueueService.getDataQueue("/mydatabase\\..\\database1"); + dataQueue.add(new DataEntity("{}")); + + assertEquals(dataQueueService.getDataQueue("/mydatabase/../database1").count(), 1); + ServiceProvider.getInstance().getApplicationContext().getDatabasePath(dataQueueService.removeRelativePath("/mydatabase/../database1")).delete(); + } + + private boolean createDataQueue(File dataQueueFile, int numberOfEntities) { + try { + if(!dataQueueFile.exists()) { + dataQueueFile.createNewFile(); + } + DataQueue dataQueue = new SQLiteDataQueue(dataQueueFile.getPath(), new SQLiteDatabaseHelper()); + while(numberOfEntities > 0) { + dataQueue.add(new DataEntity("{}")); + numberOfEntities--; + } + } catch (Exception e){ + return false; + } + return true; + } +} diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java index 2955a9a54..5175ff827 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java @@ -11,6 +11,8 @@ package com.adobe.marketing.mobile.services; +import android.content.Context; + import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; @@ -48,29 +50,38 @@ public DataQueue getDataQueue(final String databaseName) { dataQueue = dataQueueCache.get(databaseName); if (dataQueue == null) { + Context appContext = ServiceProvider.getInstance().getApplicationContext(); + + if(appContext == null) { + MobileCore.log(LoggingMode.WARNING, + LOG_TAG, + String.format("Failed to create DataQueue for database (%s), the ApplicationContext is null", databaseName)); + return null; + } + final String cleanedDatabasePath = removeRelativePath(databaseName); final File databaseDirDataQueue = ServiceProvider.getInstance().getApplicationContext().getDatabasePath(cleanedDatabasePath); final File cacheDir = ServiceProvider.getInstance().getDeviceInfoService().getApplicationCacheDir(); - if (cacheDir != null) { + if (!databaseDirDataQueue.exists() && cacheDir != null) { final File cacheDirDataQueue = new File(cacheDir, cleanedDatabasePath); if (cacheDirDataQueue.exists()) { try { if(databaseDirDataQueue.createNewFile()) { copyFile(cacheDirDataQueue, databaseDirDataQueue); + MobileCore.log(LoggingMode.DEBUG, + LOG_TAG, + String.format("Successfully moved DataQueue for database (%s) from cache directory to database directory", databaseName)); } } catch (Exception e) { MobileCore.log(LoggingMode.WARNING, LOG_TAG, - String.format("Failed in moving DataQueue for database (%s), could not create new file in database directory.", databaseName)); + String.format("Failed to move DataQueue for database (%s), could not create new file in database directory", databaseName)); return null; } } } dataQueue = new SQLiteDataQueue(databaseDirDataQueue.getPath(), databaseHelper); - MobileCore.log(LoggingMode.DEBUG, - LOG_TAG, - String.format("Successfully moved DataQueue for database (%s) from cache directory to database directory", databaseName)); dataQueueCache.put(databaseName, dataQueue); } } @@ -88,7 +99,7 @@ public DataQueue getDataQueue(final String databaseName) { * @param filePath the file name * @return file name without relative path */ - private String removeRelativePath(final String filePath) { + String removeRelativePath(final String filePath) { if (filePath == null || filePath.isEmpty()) { return filePath; } @@ -96,6 +107,7 @@ private String removeRelativePath(final String filePath) { try { String result = filePath.replaceAll("\\.[/\\\\]", "\\."); result = result.replaceAll("[/\\\\](\\.{2,})", "_"); + result = result.replaceAll("/",""); return result; } catch (IllegalArgumentException e) { return filePath; diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileUtilTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileUtilTests.java index 1c83e0e8c..b2a1a3356 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileUtilTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileUtilTests.java @@ -36,7 +36,6 @@ public void setup() { @After public void tearDown() { fileTestHelper.deleteTempCacheDirectory(); - fileTestHelper.deleteTempCacheDirectory(FILE_DIRECTORY); } @Test From 0974b973503dbbd2cb3b81a007ade70a427407d1 Mon Sep 17 00:00:00 2001 From: praveek Date: Thu, 21 Apr 2022 23:57:34 -0700 Subject: [PATCH 029/476] Fix doc comments --- .../mobile/internal/eventhub/EventHub.kt | 4 ++- .../mobile/internal/eventhub/ExtensionExt.kt | 28 +++++++++++++------ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index c9a78faed..c6703f48e 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -116,6 +116,8 @@ internal class EventHub { } } -// / Helper to get extension type name +/** + * Helper to get extension type name + */ private val Class.extensionTypeName get() = this.name diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt index 05d1eeb2d..feb6967ea 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt @@ -19,9 +19,11 @@ import com.adobe.marketing.mobile.LoggingMode import com.adobe.marketing.mobile.MobileCore import java.lang.Exception -// / Type extensions for [Extension] to allow for easier usage +// Type extensions for [Extension] to allow for easier usage -// / Function to initialize Extension with [ExtensionApi] +/** + * Function to initialize Extension with [ExtensionApi] + */ internal fun Class.initWith(extensionApi: ExtensionApi): Extension? { try { val extensionConstructor = this.getDeclaredConstructor(ExtensionApi::class.java) @@ -34,26 +36,36 @@ internal fun Class.initWith(extensionApi: ExtensionApi): Extensio return null } -// / Property to get Extension name +/** + * Property to get Extension name + */ internal val Extension.name: String? get() = ExtensionHelper.getName(this) -// / Property to get Extension version +/** + * Property to get Extension version + */ internal val Extension.version: String? get() = ExtensionHelper.getVersion(this) -// / Property to get Extension friendly name +/** + * Property to get Extension friendly name + */ internal val Extension.friendlyName: String? get() = ExtensionHelper.getFriendlyName(this) -// / Function to notify that the Extension has been unregistered +/** + * Function to notify that the Extension has been unregistered + */ internal fun Extension.onUnregistered() { ExtensionHelper.onUnregistered(this) } -// / Type extensions for [ExtensionListener] to allow for easier usage +// Type extensions for [ExtensionListener] to allow for easier usage -// / Function to initialize ExtensionListener with [ExtensionApi], type and source. +/** + * Function to initialize ExtensionListener with [ExtensionApi], type and source. + */ internal fun Class.initWith(extensionApi: ExtensionApi, type: String, source: String): ExtensionListener? { try { val extensionListenerConstructor = this.getDeclaredConstructor(ExtensionApi::class.java, String::class.java, String::class.java) From de05f597ae62f8d67e6ab73948d9fe8cd151a627 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Fri, 22 Apr 2022 09:35:50 -0700 Subject: [PATCH 030/476] launch rules transformer review changes --- .../adobe/marketing/mobile/UrlUtilities.java | 41 +---- .../mobile/internal/utility/UrlUtilities.kt | 33 ++-- .../rulesengine/LaunchRuleTransformer.kt | 153 +++++++++--------- .../rulesengine/LaunchRulesConstants.kt | 27 ++-- .../marketing/mobile/UrlUtilitiesTest.java | 46 ------ .../rulesengine/LaunchRuleTransformerTests.kt | 47 +++++- .../internal/utility/UrlUtilitiesTest.java | 67 ++++++++ 7 files changed, 210 insertions(+), 204 deletions(-) create mode 100644 code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/UrlUtilitiesTest.java diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/UrlUtilities.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/UrlUtilities.java index 0379429cc..f58b75410 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/UrlUtilities.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/UrlUtilities.java @@ -109,46 +109,7 @@ public static String serializeToQueryString(final Map parameter * @return the encoded {@code String} */ public static String urlEncode(final String unencodedString) { - // bail fast - if (unencodedString == null) { - return null; - } - - try { - final byte[] stringBytes = unencodedString.getBytes("UTF-8"); - final int len = stringBytes.length; - int curIndex = 0; - - // iterate looking for any characters that don't match our "safe" mask - while (curIndex < len && utf8Mask[stringBytes[curIndex] & ALL_BITS_ENABLED]) { - curIndex++; - } - - // if our iterator got all the way to the end of the string, no unsafe characters existed - // and it's safe to return the original value that was passed in - if (curIndex == len) { - return unencodedString; - } - - // if we get here we know there's at least one character we need to encode - final StringBuilder encodedString = new StringBuilder(stringBytes.length << 1); - - // if i > than 1 then we have some characters we can just "paste" in - if (curIndex > 0) { - encodedString.append(new String(stringBytes, 0, curIndex, "UTF-8")); - } - - // rip through the rest of the string character by character - for (; curIndex < len; curIndex++) { - encodedString.append(encodedChars[stringBytes[curIndex] & ALL_BITS_ENABLED]); - } - - // return the completed string - return encodedString.toString(); - } catch (UnsupportedEncodingException e) { - Log.debug(LOG_TAG, "Failed to url encode string %s (%s)", unencodedString, e); - return null; - } + return com.adobe.marketing.mobile.internal.utility.UrlUtilities.urlEncode(unencodedString); } /** diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/UrlUtilities.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/UrlUtilities.kt index 8acc6c613..90279c0fe 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/UrlUtilities.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/UrlUtilities.kt @@ -1,19 +1,13 @@ -/* ************************************************************************ - * ADOBE CONFIDENTIAL - * ___________________ - * - * Copyright 2022 Adobe Systems Incorporated - * All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Adobe Systems Incorporated and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to Adobe Systems Incorporated and its - * suppliers and are protected by trade secret or copyright law. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from Adobe Systems Incorporated. - **************************************************************************/ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ package com.adobe.marketing.mobile.internal.utility @@ -24,6 +18,8 @@ import java.nio.charset.StandardCharsets internal object UrlUtilities { + private const val LOG_TAG = "UrlUtilities" + // lookup tables used by urlEncode private val encodedChars = arrayOf( "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", "%08", "%09", "%0A", "%0B", "%0C", "%0D", "%0E", "%0F", @@ -63,13 +59,14 @@ internal object UrlUtilities { false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false ) - private val LOG_TAG = "UrlUtilities" /** * Encodes an URL given as [String] * - * @property [unencodedString] nullable [String] value to be encoded + * @param [unencodedString] nullable [String] value to be encoded + * @return [String] which is URL encoded or null if encoding failed */ + @JvmStatic fun urlEncode(unencodedString: String?): String? { // bail fast return if (unencodedString == null) { diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformer.kt index 82fc77a7d..83e0ba0e8 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformer.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformer.kt @@ -1,19 +1,13 @@ -/* ************************************************************************ - * ADOBE CONFIDENTIAL - * ___________________ - * - * Copyright 2022 Adobe Systems Incorporated - * All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Adobe Systems Incorporated and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to Adobe Systems Incorporated and its - * suppliers and are protected by trade secret or copyright law. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from Adobe Systems Incorporated. - **************************************************************************/ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ package com.adobe.marketing.mobile.launch.rulesengine @@ -21,80 +15,79 @@ import com.adobe.marketing.mobile.internal.utility.UrlUtilities import com.adobe.marketing.mobile.rulesengine.Transformer import com.adobe.marketing.mobile.rulesengine.Transforming -/** - * Generates the [Transforming] instance used by Launch Rules Engine. - * - * @return instance of [Transforming] - **/ -internal class LaunchRuleTransformer { +internal object LaunchRuleTransformer { - companion object { - fun createTransforming(): Transforming { - val transformer = Transformer() - addConsequenceTransform(transformer) - addTypeTransform(transformer) - return transformer - } + /** + * Generates the [Transforming] instance used by Launch Rules Engine. + * + * @return instance of [Transforming] + **/ + @JvmStatic + fun createTransforming(): Transforming { + val transformer = Transformer() + addConsequenceTransform(transformer) + addTypeTransform(transformer) + return transformer + } - /** - * Registers a [TransformerBlock] for [LaunchRulesConstants.Transform.URL_ENCODING_FUNCTION] - * to encode a `String` value to url format. - * - * @param[transformer] [Transformer] instance used to register the [TransformerBlock] - */ - private fun addConsequenceTransform(transformer: Transformer) { - transformer.register(LaunchRulesConstants.Transform.URL_ENCODING_FUNCTION) { value -> - if (value is String) { - UrlUtilities.urlEncode(value) - } else value - } + /** + * Registers a [TransformerBlock] for [LaunchRulesConstants.Transform.URL_ENCODING_FUNCTION] + * to encode a `String` value to url format. + * + * @param[transformer] [Transformer] instance used to register the [TransformerBlock] + */ + private fun addConsequenceTransform(transformer: Transformer) { + transformer.register(LaunchRulesConstants.Transform.URL_ENCODING_FUNCTION) { value -> + if (value is String) { + UrlUtilities.urlEncode(value) + } else value } + } - /** - * Registers multiple [TransformerBlock] to transform a value into one of - * [LaunchRulesConstants.Transform] types. - * - * @param[transformer] [Transformer] instance used to register the [TransformerBlock] - */ - private fun addTypeTransform(transformer: Transformer) { - transformer.register(LaunchRulesConstants.Transform.TRANSFORM_TO_INT) { value -> - when (value) { - is String -> { - try { - value.toInt() - } catch (e: NumberFormatException) { - null - } + /** + * Registers multiple [TransformerBlock] to transform a value into one of + * [LaunchRulesConstants.Transform] types. + * + * @param[transformer] [Transformer] instance used to register the [TransformerBlock] + */ + private fun addTypeTransform(transformer: Transformer) { + transformer.register(LaunchRulesConstants.Transform.TRANSFORM_TO_INT) { value -> + when (value) { + is String -> { + try { + value.toInt() + } catch (e: NumberFormatException) { + value } - is Double -> value.toInt() - is Boolean -> if (value) 1 else 0 - else -> value } + is Number -> value.toInt() + is Boolean -> if (value) 1 else 0 + else -> value } - transformer.register(LaunchRulesConstants.Transform.TRANSFORM_TO_STRING) { value -> - value?.toString() - } - transformer.register(LaunchRulesConstants.Transform.TRANSFORM_TO_DOUBLE) { value -> - when (value) { - is String -> { - try { - value.toDouble() - } catch (e: NumberFormatException) { - null - } + } + transformer.register(LaunchRulesConstants.Transform.TRANSFORM_TO_STRING) { value -> + value?.toString() + } + transformer.register(LaunchRulesConstants.Transform.TRANSFORM_TO_DOUBLE) { value -> + when (value) { + is String -> { + try { + value.toDouble() + } catch (e: NumberFormatException) { + value } - is Int -> value.toDouble() - is Boolean -> if (value) 1.0 else 0.0 - else -> value } + is Int -> value.toDouble() + is Boolean -> if (value) 1.0 else 0.0 + else -> value } - transformer.register(LaunchRulesConstants.Transform.TRANSFORM_TO_BOOL) { value -> - when (value) { - is String -> java.lang.Boolean.parseBoolean(value) - is Int -> value == 1 - is Double -> value == 1.0 - else -> value - } + } + transformer.register(LaunchRulesConstants.Transform.TRANSFORM_TO_BOOL) { value -> + when (value) { + is String -> java.lang.Boolean.parseBoolean(value) + is Int -> value == 1 + is Number -> value == 1.0 + else -> value } } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConstants.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConstants.kt index fef197fa7..02a30caa0 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConstants.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConstants.kt @@ -1,19 +1,14 @@ -/* ************************************************************************ - * ADOBE CONFIDENTIAL - * ___________________ - * - * Copyright 2022 Adobe Systems Incorporated - * All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Adobe Systems Incorporated and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to Adobe Systems Incorporated and its - * suppliers and are protected by trade secret or copyright law. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from Adobe Systems Incorporated. - **************************************************************************/ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + package com.adobe.marketing.mobile.launch.rulesengine diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/UrlUtilitiesTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/UrlUtilitiesTest.java index c46c86188..aa6342705 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/UrlUtilitiesTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/UrlUtilitiesTest.java @@ -152,52 +152,6 @@ public void testSerializeToQueryStringArrayListEmptyObject() { assertEquals("&key1=TestArrayList1%2CTestArrayList2%2C%2CTestArrayList4", valueUnderTest); } - @Test - public void urlEncodeWithNoEncodedNeeded() { - assertEquals(UrlUtilities.urlEncode("thisisateststring"), "thisisateststring"); - } - - @Test - public void urlEncodeWithSpaces() { - assertEquals(UrlUtilities.urlEncode("this is a test string"), "this%20is%20a%20test%20string"); - } - - @Test - public void urlEncodeStartsWithSpace() { - assertEquals(UrlUtilities.urlEncode(" afterspace"), "%20afterspace"); - } - - @Test - public void urlEncodeOnlyUnicode() { - assertEquals(UrlUtilities.urlEncode("网"), "%E7%BD%91"); - } - - @Test - public void urlEncodeStartsWithUnicode() { - assertEquals(UrlUtilities.urlEncode("网test"), "%E7%BD%91test"); - } - - @Test - public void urlEncodeEndsWithUnicode() { - assertEquals(UrlUtilities.urlEncode("test网"), "test%E7%BD%91"); - } - - @Test - public void urlEncodeBlankString() { - assertEquals(UrlUtilities.urlEncode(""), ""); - } - - @Test - public void urlEncodeDeathString() { - assertEquals(UrlUtilities.urlEncode("~!@#$%^&*()-+=|}{][\\/.<,>"), - "~%21%40%23%24%25%5E%26%2A%28%29-%2B%3D%7C%7D%7B%5D%5B%5C%2F.%3C%2C%3E"); - } - - @Test - public void testURLEncodeNull() { - Assert.assertNull(UrlUtilities.urlEncode(null)); - } - @Test public void testJoin_when_validDelimiterAndTokens_happy() throws Exception { List tokens = new ArrayList(); diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformerTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformerTests.kt index f67b411fa..c0222a06b 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformerTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformerTests.kt @@ -1,3 +1,14 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + package com.adobe.marketing.mobile.launch.rulesengine import org.junit.Test @@ -7,6 +18,34 @@ import org.junit.Assert.* class LaunchRuleTransformerTests { + @Test + fun transform_ReturnsEncodedURL_WhenTransformingStringToUrlEnc() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("urlenc", "this is a test string") + assertEquals("transform should return url encoded string when url encoding string", "this%20is%20a%20test%20string", result) + } + + @Test + fun transform_ReturnsInt_WhenTransformingIntToUrlEnc() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("urlenc", 3) + assertEquals("transform should return int when url encoding int", 3, result) + } + + @Test + fun transform_ReturnsDouble_WhenTransformingDoubleToUrlEnc() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("urlenc", 3.33) + assertEquals("transform should return double when url encoding double", 3.33, result) + } + + @Test + fun transform_ReturnsBoolean_WhenTransformingBooleanToUrlEnc() { + val transformer = LaunchRuleTransformer.createTransforming() + val result = transformer.transform("urlenc", true) + assertEquals("transform should return boolean when url encoding boolean", true, result) + } + @Test fun transform_ReturnsInt_WhenTransformingIntToInt() { val transformer = LaunchRuleTransformer.createTransforming() @@ -36,10 +75,10 @@ class LaunchRuleTransformerTests { } @Test - fun transform_ReturnsNull_WhenTransformingInvalidStringToInt() { + fun transform_ReturnsInt_WhenTransformingInvalidStringToInt() { val transformer = LaunchRuleTransformer.createTransforming() val result = transformer.transform("int", "something") - assertNull("transform should return null when transforming invalid string to int", result) + assertEquals("transform should return value when transforming invalid string to int", "something", result) } @Test @@ -134,10 +173,10 @@ class LaunchRuleTransformerTests { } @Test - fun transform_ReturnsNull_WhenTransformingInvalidStringToDouble() { + fun transform_ReturnsDouble_WhenTransformingInvalidStringToDouble() { val transformer = LaunchRuleTransformer.createTransforming() val result = transformer.transform("double", "something") - assertNull("transform should return null when transforming invalid string to double", result) + assertEquals("transform should return value when transforming invalid string to double", "something", result) } @Test diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/UrlUtilitiesTest.java b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/UrlUtilitiesTest.java new file mode 100644 index 000000000..cdb2743ee --- /dev/null +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/UrlUtilitiesTest.java @@ -0,0 +1,67 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.internal.utility; + +import static org.junit.Assert.assertEquals; + +import org.junit.Assert; +import org.junit.Test; + +public class UrlUtilitiesTest { + + @Test + public void urlEncodeWithNoEncodedNeeded() { + assertEquals(UrlUtilities.urlEncode("thisisateststring"), "thisisateststring"); + } + + @Test + public void urlEncodeWithSpaces() { + assertEquals(UrlUtilities.urlEncode("this is a test string"), "this%20is%20a%20test%20string"); + } + + @Test + public void urlEncodeStartsWithSpace() { + assertEquals(UrlUtilities.urlEncode(" afterspace"), "%20afterspace"); + } + + @Test + public void urlEncodeOnlyUnicode() { + assertEquals(UrlUtilities.urlEncode("网"), "%E7%BD%91"); + } + + @Test + public void urlEncodeStartsWithUnicode() { + assertEquals(UrlUtilities.urlEncode("网test"), "%E7%BD%91test"); + } + + @Test + public void urlEncodeEndsWithUnicode() { + assertEquals(UrlUtilities.urlEncode("test网"), "test%E7%BD%91"); + } + + @Test + public void urlEncodeBlankString() { + assertEquals(UrlUtilities.urlEncode(""), ""); + } + + @Test + public void urlEncodeDeathString() { + assertEquals(UrlUtilities.urlEncode("~!@#$%^&*()-+=|}{][\\/.<,>"), + "~%21%40%23%24%25%5E%26%2A%28%29-%2B%3D%7C%7D%7B%5D%5B%5C%2F.%3C%2C%3E"); + } + + @Test + public void testURLEncodeNull() { + Assert.assertNull(UrlUtilities.urlEncode(null)); + } + +} From 910909819782c57a1763d7150e54f5e38ae95c02 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Mon, 25 Apr 2022 09:20:09 -0700 Subject: [PATCH 031/476] refactor to use internal urlEncode method --- .../marketing/mobile/ContextDataUtil.java | 2 ++ .../marketing/mobile/RuleTokenParser.java | 4 +++- .../adobe/marketing/mobile/URLBuilder.java | 2 ++ .../adobe/marketing/mobile/UrlUtilities.java | 13 ++---------- .../rulesengine/LaunchRuleTransformer.kt | 6 ++---- .../rulesengine/LaunchRuleTransformerTests.kt | 20 +++++++++---------- 6 files changed, 21 insertions(+), 26 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ContextDataUtil.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ContextDataUtil.java index 4710609ce..2cc9d9ac0 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ContextDataUtil.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ContextDataUtil.java @@ -11,6 +11,8 @@ package com.adobe.marketing.mobile; +import com.adobe.marketing.mobile.internal.utility.UrlUtilities; + import java.io.UnsupportedEncodingException; import java.util.*; import java.util.regex.Matcher; diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleTokenParser.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleTokenParser.java index fcd8d1534..325d5e401 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleTokenParser.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleTokenParser.java @@ -11,6 +11,8 @@ package com.adobe.marketing.mobile; +import com.adobe.marketing.mobile.internal.utility.UrlUtilities; + import java.lang.reflect.Method; import java.security.SecureRandom; import java.util.*; @@ -386,7 +388,7 @@ public String find(final Event e) { } final Map eventDataAsObjectMap = EventDataFlattener.getFlattenedDataMap(e.getData()); - return UrlUtilities.serializeToQueryString(eventDataAsObjectMap); + return com.adobe.marketing.mobile.UrlUtilities.serializeToQueryString(eventDataAsObjectMap); } }); diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/URLBuilder.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/URLBuilder.java index f005c3cc1..f742167c9 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/URLBuilder.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/URLBuilder.java @@ -14,6 +14,8 @@ import java.net.URL; import java.util.Map; +import com.adobe.marketing.mobile.internal.utility.UrlUtilities; + /** * A class providing a better way to construct a url. */ diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/UrlUtilities.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/UrlUtilities.java index f58b75410..03a8758b5 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/UrlUtilities.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/UrlUtilities.java @@ -11,7 +11,8 @@ package com.adobe.marketing.mobile; -import java.io.UnsupportedEncodingException; +import static com.adobe.marketing.mobile.internal.utility.UrlUtilities.urlEncode; + import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -102,16 +103,6 @@ public static String serializeToQueryString(final Map parameter return sBuilder.toString(); } - /** - * Encodes an URL given as {@code String}. - * - * @param unencodedString nullable {@link String} value to be encoded - * @return the encoded {@code String} - */ - public static String urlEncode(final String unencodedString) { - return com.adobe.marketing.mobile.internal.utility.UrlUtilities.urlEncode(unencodedString); - } - /** * Serializes a key/value pair for URL consumption, e.g. {@code &key=value}. *

diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformer.kt index 83e0ba0e8..1e5cc9f8f 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformer.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformer.kt @@ -22,7 +22,6 @@ internal object LaunchRuleTransformer { * * @return instance of [Transforming] **/ - @JvmStatic fun createTransforming(): Transforming { val transformer = Transformer() addConsequenceTransform(transformer) @@ -77,7 +76,7 @@ internal object LaunchRuleTransformer { value } } - is Int -> value.toDouble() + is Number -> value.toDouble() is Boolean -> if (value) 1.0 else 0.0 else -> value } @@ -85,8 +84,7 @@ internal object LaunchRuleTransformer { transformer.register(LaunchRulesConstants.Transform.TRANSFORM_TO_BOOL) { value -> when (value) { is String -> java.lang.Boolean.parseBoolean(value) - is Int -> value == 1 - is Number -> value == 1.0 + is Number -> (value.toLong() == 1L && value.toDouble() == 1.0) else -> value } } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformerTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformerTests.kt index c0222a06b..0489dabc5 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformerTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformerTests.kt @@ -204,70 +204,70 @@ class LaunchRuleTransformerTests { fun transform_ReturnsFalse_WhenTransformingInt0ToBoolean() { val transformer = LaunchRuleTransformer.createTransforming() val result = transformer.transform("bool", 0) - assertEquals("transform should false when transforming int 0 to boolean", false, result) + assertEquals("transform should return false when transforming int 0 to boolean", false, result) } @Test fun transform_ReturnsTrue_WhenTransformingInt1ToBoolean() { val transformer = LaunchRuleTransformer.createTransforming() val result = transformer.transform("bool", 1) - assertEquals("transform should true when transforming int 1 to boolean", true, result) + assertEquals("transform should return true when transforming int 1 to boolean", true, result) } @Test fun transform_ReturnsFalse_WhenTransformingIntRandomToBoolean() { val transformer = LaunchRuleTransformer.createTransforming() val result = transformer.transform("bool", 3) - assertEquals("transform should false when transforming random int to boolean", false, result) + assertEquals("transform should return false when transforming random int to boolean", false, result) } @Test fun transform_ReturnsBoolean_WhenTransformingBooleanToBoolean() { val transformer = LaunchRuleTransformer.createTransforming() val result = transformer.transform("bool", true) - assertEquals("transform should boolean when transforming boolean to boolean", true, result) + assertEquals("transform should return boolean when transforming boolean to boolean", true, result) } @Test fun transform_ReturnsFalse_WhenTransformingDouble0ToBoolean() { val transformer = LaunchRuleTransformer.createTransforming() val result = transformer.transform("bool", 0.0) - assertEquals("transform should false when transforming double 0.0 to boolean", false, result) + assertEquals("transform should return false when transforming double 0.0 to boolean", false, result) } @Test fun transform_ReturnsTrue_WhenTransformingDouble1ToBoolean() { val transformer = LaunchRuleTransformer.createTransforming() val result = transformer.transform("bool", 1.0) - assertEquals("transform should true when transforming double 1.0 to boolean", true, result) + assertEquals("transform should return true when transforming double 1.0 to boolean", true, result) } @Test fun transform_ReturnsFalse_WhenTransformingDoubleRandomToBoolean() { val transformer = LaunchRuleTransformer.createTransforming() val result = transformer.transform("bool", 1.123) - assertEquals("transform should boolean when transforming random double to boolean", false, result) + assertEquals("transform should return boolean when transforming random double to boolean", false, result) } @Test fun transform_ReturnsFalse_WhenTransformingValidStringFalseToBoolean() { val transformer = LaunchRuleTransformer.createTransforming() val result = transformer.transform("bool", "false") - assertEquals("transform should false when transforming valid string false to boolean", false, result) + assertEquals("transform should return false when transforming valid string false to boolean", false, result) } @Test fun transform_ReturnsTrue_WhenTransformingValidStringTrueToBoolean() { val transformer = LaunchRuleTransformer.createTransforming() val result = transformer.transform("bool", "true") - assertEquals("transform should true when transforming valid string true to boolean", true, result) + assertEquals("transform should return true when transforming valid string true to boolean", true, result) } @Test fun transform_ReturnsFalse_WhenTransformingInvalidStringToBoolean() { val transformer = LaunchRuleTransformer.createTransforming() val result = transformer.transform("bool", "something") - assertEquals("transform should false when transforming invalid string to boolean", false, result) + assertEquals("transform should return false when transforming invalid string to boolean", false, result) } @Test From e47435c317804fa24347c4707bd7ee9f77ff2f3f Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Mon, 25 Apr 2022 09:34:38 -0700 Subject: [PATCH 032/476] disable javadocs temporarily: --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f4fc2155e..7dcba7a20 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -28,9 +28,9 @@ jobs: - run: name: assemble-phone-release command: make assemble-phone-release - - run: - name: JavaDoc - command: make javadoc +# - run: +# name: JavaDoc +# command: make javadoc - run: name: unit-test command: make unit-test From 42947ae44e44478bb7a6af193bbc342006c14fcf Mon Sep 17 00:00:00 2001 From: praveek Date: Thu, 21 Apr 2022 14:11:41 -0700 Subject: [PATCH 033/476] Add javadoc and lint support for Kotlin --- .circleci/config.yml | 3 +++ Makefile | 8 +++++++- code/android-core-library/build.gradle | 2 ++ code/build.gradle | 2 ++ code/gradle.properties | 1 + 5 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7dcba7a20..ddeb1b158 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -25,6 +25,9 @@ jobs: steps: # Checkout the code as the first step. - checkout + - run: + name: check-format + command: make check-format - run: name: assemble-phone-release command: make assemble-phone-release diff --git a/Makefile b/Makefile index 328716026..0509a88d4 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,12 @@ clean: checkstyle: (./code/gradlew -p code/android-core-library checkstyle) +check-format: + (./code/gradlew -p code/android-core-library ktlintCheck) + +format: + (./code/gradlew -p code/android-core-library ktlintFormat) + assemble-phone: (./code/gradlew -p code/android-core-library assemblePhone) @@ -31,7 +37,7 @@ functional-test: (./code/gradlew -p code/android-core-library connectedPhoneDebugAndroidTest) javadoc: - (./code/gradlew -p code/android-core-library Javadoc) + (./code/gradlew -p code/android-core-library dokkaJavadoc) build-third-party-extension: diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index 6b6350b83..5c7216e2b 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -1,5 +1,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' +apply plugin: 'org.jlleitschuh.gradle.ktlint' +apply plugin: 'org.jetbrains.dokka' android { compileSdkVersion 30 diff --git a/code/build.gradle b/code/build.gradle index 75bea9caf..6016ae009 100644 --- a/code/build.gradle +++ b/code/build.gradle @@ -11,6 +11,8 @@ buildscript { classpath 'com.android.tools.build:gradle:4.0.1' classpath "org.jacoco:org.jacoco.core:0.8.7" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}" + classpath "org.jlleitschuh.gradle:ktlint-gradle:9.2.1" } } diff --git a/code/gradle.properties b/code/gradle.properties index 1b3a73277..c21eed4a4 100644 --- a/code/gradle.properties +++ b/code/gradle.properties @@ -31,3 +31,4 @@ android.useAndroidX=true # incompatible library is used. #android.enableJetifier=true +dokka_version = 1.6.20 From 0f4d47f4d55f37c4049ea0b5088355e792e29aec Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Wed, 27 Apr 2022 09:49:45 -0700 Subject: [PATCH 034/476] fix linting, add back javadocs step to CI/CD --- .circleci/config.yml | 6 +++--- .../adobe/marketing/mobile/internal/eventhub/EventHub.kt | 2 +- .../marketing/mobile/internal/utility/EventDataMerger.kt | 3 +-- .../adobe/marketing/mobile/internal/utility/JSONUtils.kt | 1 - .../marketing/mobile/internal/utility/UrlUtilities.kt | 2 +- .../marketing/mobile/launch/rulesengine/LaunchRule.kt | 2 +- .../mobile/launch/rulesengine/LaunchRuleTransformer.kt | 2 +- .../mobile/launch/rulesengine/LaunchRulesConstants.kt | 3 +-- .../mobile/launch/rulesengine/RuleConsequence.kt | 2 +- .../mobile/launch/rulesengine/json/GroupCondition.kt | 3 +-- .../mobile/launch/rulesengine/json/HistoricalCondition.kt | 2 +- .../mobile/launch/rulesengine/json/JSONCondition.kt | 3 --- .../mobile/launch/rulesengine/json/JSONConsequence.kt | 2 +- .../mobile/launch/rulesengine/json/JSONDefinition.kt | 4 +--- .../marketing/mobile/launch/rulesengine/json/JSONRule.kt | 4 +--- .../mobile/launch/rulesengine/json/JSONRuleRoot.kt | 2 +- .../mobile/launch/rulesengine/json/JSONRulesParser.kt | 3 --- .../mobile/launch/rulesengine/json/MatcherCondition.kt | 8 ++++++-- .../launch/rulesengine/LaunchRuleTransformerTests.kt | 7 ++++--- .../mobile/launch/rulesengine/json/JSONConditionTests.kt | 4 ++-- .../launch/rulesengine/json/JSONConsequenceTests.kt | 4 ++-- .../mobile/launch/rulesengine/json/JSONRuleRootTests.kt | 4 ++-- .../mobile/launch/rulesengine/json/JSONRuleTests.kt | 6 +++--- .../mobile/launch/rulesengine/json/JSONRuleUtilities.kt | 2 +- .../launch/rulesengine/json/JSONRulesParserTests.kt | 5 ++--- .../android-core-library/src/test/kotlin/EventHubTests.kt | 6 +++--- .../marketing/mobile/internal/eventhub/EventHubTests.kt | 4 ++-- .../mobile/internal/utility/EventDataMergerTests.kt | 8 +++----- 28 files changed, 46 insertions(+), 58 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ddeb1b158..62f24d67c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,9 +31,9 @@ jobs: - run: name: assemble-phone-release command: make assemble-phone-release -# - run: -# name: JavaDoc -# command: make javadoc + - run: + name: JavaDoc + command: make javadoc - run: name: unit-test command: make unit-test diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index dfb4f6db4..6b8c2d6f0 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -4,4 +4,4 @@ class EventHub { companion object { val version = "2.0.0" } -} \ No newline at end of file +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/EventDataMerger.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/EventDataMerger.kt index 312d13bc3..2d81b2dbf 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/EventDataMerger.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/EventDataMerger.kt @@ -130,5 +130,4 @@ internal object EventDataMerger { targetMap[targetKey] = newList } } - -} \ No newline at end of file +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/JSONUtils.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/JSONUtils.kt index a82b38441..5de9e1d5b 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/JSONUtils.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/JSONUtils.kt @@ -66,4 +66,3 @@ internal fun JSONArray.toList(): List { } return list } - diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/UrlUtilities.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/UrlUtilities.kt index 90279c0fe..66f92c3bf 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/UrlUtilities.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/UrlUtilities.kt @@ -111,4 +111,4 @@ internal object UrlUtilities { null } } -} \ No newline at end of file +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRule.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRule.kt index 87e082286..3a077e248 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRule.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRule.kt @@ -20,4 +20,4 @@ import com.adobe.marketing.mobile.rulesengine.Evaluable * @property consequenceList a list of [RuleConsequence] objects * @constructor Constructs a new [LaunchRule] */ -data class LaunchRule(val condition: Evaluable, val consequenceList: List) \ No newline at end of file +data class LaunchRule(val condition: Evaluable, val consequenceList: List) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformer.kt index 1e5cc9f8f..9c0eb7d1d 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformer.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformer.kt @@ -89,4 +89,4 @@ internal object LaunchRuleTransformer { } } } -} \ No newline at end of file +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConstants.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConstants.kt index 02a30caa0..fb21ad985 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConstants.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConstants.kt @@ -9,7 +9,6 @@ governing permissions and limitations under the License. */ - package com.adobe.marketing.mobile.launch.rulesengine internal object LaunchRulesConstants { @@ -23,4 +22,4 @@ internal object LaunchRulesConstants { const val TRANSFORM_TO_STRING = "string" const val TRANSFORM_TO_BOOL = "bool" } -} \ No newline at end of file +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence.kt index 1c1ee2d86..82d679378 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence.kt @@ -23,4 +23,4 @@ data class RuleConsequence( val id: String, val type: String, var detail: Map? = null -) \ No newline at end of file +) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/GroupCondition.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/GroupCondition.kt index 34a3a19c5..b5de36249 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/GroupCondition.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/GroupCondition.kt @@ -15,7 +15,7 @@ import com.adobe.marketing.mobile.LoggingMode import com.adobe.marketing.mobile.MobileCore import com.adobe.marketing.mobile.rulesengine.Evaluable import com.adobe.marketing.mobile.rulesengine.LogicalExpression -import java.util.* +import java.util.Locale /** * The class representing a group of [JSONCondition]s @@ -45,5 +45,4 @@ internal class GroupCondition(val definition: JSONDefinition) : JSONCondition() val evaluableList = definition.conditions.map { it.toEvaluable() } return LogicalExpression(evaluableList, logicalOperator) } - } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt index d1d6770cc..43b09cc1b 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt @@ -17,4 +17,4 @@ internal class HistoricalCondition(val definition: JSONDefinition) : JSONConditi override fun toEvaluable(): Evaluable? { TODO("Not yet implemented") } -} \ No newline at end of file +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONCondition.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONCondition.kt index aa295ce1f..158d0d9f4 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONCondition.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONCondition.kt @@ -91,6 +91,3 @@ internal abstract class JSONCondition { @JvmSynthetic abstract fun toEvaluable(): Evaluable? } - - - diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequence.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequence.kt index cde57a838..712693a77 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequence.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequence.kt @@ -46,4 +46,4 @@ internal class JSONConsequence private constructor( internal fun toRuleConsequence(): RuleConsequence { return RuleConsequence(this.id, this.type, this.detail) } -} \ No newline at end of file +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONDefinition.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONDefinition.kt index 4046eab84..7a32bffd2 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONDefinition.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONDefinition.kt @@ -41,7 +41,7 @@ internal data class JSONDefinition( val value: Any?, val from: Int?, val to: Int?, - val searchType: String?, + val searchType: String? ) { companion object { private const val DEFINITION_KEY_LOGIC = "logic" @@ -107,7 +107,5 @@ internal data class JSONDefinition( ?: throw JSONException("Unsupported [rule.condition.historical.events] JSON format: $it ") } } - } } - diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRule.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRule.kt index b3d94b1a2..c3d54468f 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRule.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRule.kt @@ -52,7 +52,6 @@ internal class JSONRule private constructor( } return JSONRule(condition, consequences) } - } /** @@ -76,5 +75,4 @@ internal class JSONRule private constructor( } return LaunchRule(evaluable, consequenceList) } - -} \ No newline at end of file +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRoot.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRoot.kt index 6a9528859..c6918d722 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRoot.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRoot.kt @@ -52,4 +52,4 @@ internal class JSONRuleRoot private constructor(val version: String, val jsonArr JSONRule(it as? JSONObject)?.toLaunchRule() ?: throw Exception() } } -} \ No newline at end of file +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParser.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParser.kt index a9c131272..6fe7df5e8 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParser.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParser.kt @@ -46,6 +46,3 @@ object JSONRulesParser { return null } } - - - diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/MatcherCondition.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/MatcherCondition.kt index ab9e220d5..4361d566e 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/MatcherCondition.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/MatcherCondition.kt @@ -13,7 +13,11 @@ package com.adobe.marketing.mobile.launch.rulesengine.json import com.adobe.marketing.mobile.LoggingMode import com.adobe.marketing.mobile.MobileCore -import com.adobe.marketing.mobile.rulesengine.* +import com.adobe.marketing.mobile.rulesengine.ComparisonExpression +import com.adobe.marketing.mobile.rulesengine.Evaluable +import com.adobe.marketing.mobile.rulesengine.LogicalExpression +import com.adobe.marketing.mobile.rulesengine.OperandLiteral +import com.adobe.marketing.mobile.rulesengine.OperandMustacheToken /** * The class representing a matcher condition @@ -75,7 +79,7 @@ internal class MatcherCondition(val definition: JSONDefinition) : JSONCondition( is String -> String::class.java is Int -> Number::class.java is Double -> Number::class.java - //note: Kotlin.Boolean is not mapped to java.lang.Boolean correctly + // note: Kotlin.Boolean is not mapped to java.lang.Boolean correctly is Boolean -> java.lang.Boolean::class.java is Float -> Number::class.java else -> null diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformerTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformerTests.kt index 0489dabc5..67310c38b 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformerTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformerTests.kt @@ -11,10 +11,11 @@ package com.adobe.marketing.mobile.launch.rulesengine -import org.junit.Test import java.util.ArrayList import java.util.HashMap -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test class LaunchRuleTransformerTests { @@ -304,4 +305,4 @@ class LaunchRuleTransformerTests { map["key2"] = "value2" return map } -} \ No newline at end of file +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConditionTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConditionTests.kt index 533996d98..a9ee4b6a9 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConditionTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConditionTests.kt @@ -13,8 +13,8 @@ package com.adobe.marketing.mobile.launch.rulesengine.json import com.adobe.marketing.mobile.rulesengine.ComparisonExpression import com.adobe.marketing.mobile.rulesengine.Evaluable import com.adobe.marketing.mobile.rulesengine.LogicalExpression -import org.junit.Test import kotlin.test.assertTrue +import org.junit.Test class JSONConditionTests { @Test @@ -156,4 +156,4 @@ class JSONConditionTests { assertTrue(evaluable is Evaluable) assertTrue(evaluable is LogicalExpression) } -} \ No newline at end of file +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequenceTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequenceTests.kt index ad2c5572a..a1e503a66 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequenceTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequenceTests.kt @@ -10,9 +10,9 @@ */ package com.adobe.marketing.mobile.launch.rulesengine.json -import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull +import org.junit.Test class JSONConsequenceTests { @Test @@ -48,4 +48,4 @@ class JSONConsequenceTests { assertEquals("value1", attachedData["key1"] as? String) assertEquals("{%~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches%}", attachedData["launches"] as? String) } -} \ No newline at end of file +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRootTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRootTests.kt index f1efe4aab..51dd4231e 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRootTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRootTests.kt @@ -11,9 +11,9 @@ package com.adobe.marketing.mobile.launch.rulesengine.json -import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertTrue +import org.junit.Test class JSONRuleRootTests { @Test @@ -86,4 +86,4 @@ class JSONRuleRootTests { assertTrue(jsonRuleRoot is JSONRuleRoot) assertEquals("0", jsonRuleRoot.version) } -} \ No newline at end of file +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleTests.kt index ff140cdc5..3ba631894 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleTests.kt @@ -13,11 +13,11 @@ package com.adobe.marketing.mobile.launch.rulesengine.json import com.adobe.marketing.mobile.launch.rulesengine.LaunchRule import com.adobe.marketing.mobile.rulesengine.ComparisonExpression -import org.json.JSONException -import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertNull import kotlin.test.assertTrue +import org.json.JSONException +import org.junit.Test class JSONRuleTests { @Test @@ -78,4 +78,4 @@ class JSONRuleTests { """.trimIndent() JSONRule(buildJSONObject(jsonString)) } -} \ No newline at end of file +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleUtilities.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleUtilities.kt index a91d2eb51..f61fdea42 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleUtilities.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleUtilities.kt @@ -24,4 +24,4 @@ internal fun buildJSONArray(jsonArray: String): JSONArray { val jsonAry = JSONTokener(jsonArray).nextValue() as? JSONArray if (jsonAry !is JSONArray) throw IllegalArgumentException() return jsonAry -} \ No newline at end of file +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParserTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParserTests.kt index 3f93c60d3..ef2d71507 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParserTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParserTests.kt @@ -11,10 +11,10 @@ package com.adobe.marketing.mobile.launch.rulesengine.json -import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull +import org.junit.Test class JSONRulesParserTests { @Test @@ -31,5 +31,4 @@ class JSONRulesParserTests { assertNotNull(result) assertEquals(1, result.size) } - -} \ No newline at end of file +} diff --git a/code/android-core-library/src/test/kotlin/EventHubTests.kt b/code/android-core-library/src/test/kotlin/EventHubTests.kt index 303970ecb..897d50a94 100644 --- a/code/android-core-library/src/test/kotlin/EventHubTests.kt +++ b/code/android-core-library/src/test/kotlin/EventHubTests.kt @@ -1,6 +1,6 @@ -import org.junit.Test -import kotlin.test.assertEquals import com.adobe.marketing.mobile.internal.eventhub.EventHub +import kotlin.test.assertEquals +import org.junit.Test internal class EventHubTests { @@ -8,4 +8,4 @@ internal class EventHubTests { fun testVersion() { assertEquals(EventHub.version, "2.0.0") } -} \ No newline at end of file +} diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt index 6592a385f..1aca86d85 100644 --- a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt @@ -10,8 +10,8 @@ */ package com.adobe.marketing.mobile.internal.eventhub -import org.junit.Test import kotlin.test.assertEquals +import org.junit.Test internal class EventHubTests { @@ -19,4 +19,4 @@ internal class EventHubTests { fun testVersion() { assertEquals(EventHub.version, "2.0.0") } -} \ No newline at end of file +} diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/EventDataMergerTests.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/EventDataMergerTests.kt index 2c9d1c8a7..638f97db8 100644 --- a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/EventDataMergerTests.kt +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/EventDataMergerTests.kt @@ -10,9 +10,8 @@ */ package com.adobe.marketing.mobile.internal.utility -import org.junit.Test import kotlin.test.assertEquals -import kotlin.test.assertTrue +import org.junit.Test class EventDataMergerTests { @@ -29,7 +28,6 @@ class EventDataMergerTests { "newKey" to "newValue" ) assertEquals(expectedMap, EventDataMerger.merge(fromMap, toMap, true)) - } @Test @@ -413,7 +411,7 @@ class EventDataMergerTests { "key" to "oldValue", "newKey" to "newValue" ), - "key" to "newValue", + "key" to "newValue" ), mapOf( "k2" to "v2", @@ -481,4 +479,4 @@ class EventDataMergerTests { ) assertEquals(expectedMap, EventDataMerger.merge(fromMap, toMap, true)) } -} \ No newline at end of file +} From fbcd150620343540c597ec316eb6d5ff6c07b4dc Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Mon, 25 Apr 2022 19:45:36 -0700 Subject: [PATCH 035/476] [#29] Implement shared state management [Summary] - Add SharedStateManager.kt that manages CRUD operations on event data. - SharedStateManager is agnostic of pending rule and type of shared state - ExtensionContainer holds both XDM and STANDARD StateManagers to relay operations to SharedStateManager - Plumb the SharedStateManager through EventContainter and EventHub [Tests] - Added tests for SharedStateManager - Tests for EventContainer will be updated in the next commit as a followup. - EventHub tests are deferred to when dispatching is implemented. --- .../marketing/mobile/ExtensionError.java | 16 +- .../mobile/internal/eventhub/EventHub.kt | 189 +++++++++++++++++- .../internal/eventhub/ExtensionContainer.kt | 134 +++++++++++-- .../internal/eventhub/SharedStateManager.kt | 183 +++++++++++++++++ .../eventhub/SharedStateManagerTest.kt | 187 +++++++++++++++++ 5 files changed, 687 insertions(+), 22 deletions(-) create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedStateManager.kt create mode 100644 code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/SharedStateManagerTest.kt diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionError.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionError.java index b4a7b11c6..3d93ed1bc 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionError.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionError.java @@ -23,37 +23,37 @@ public class ExtensionError extends AdobeError { /** * Unexpected error is returned when something happened internally while processing an extension. */ - static final ExtensionError UNEXPECTED_ERROR = new ExtensionError("extension.unexpected", + public static final ExtensionError UNEXPECTED_ERROR = new ExtensionError("extension.unexpected", 0); /** * Extension bad name error is returned when the extension name is invalid (null, empty). */ - static final ExtensionError BAD_NAME = new + public static final ExtensionError BAD_NAME = new ExtensionError("extension.bad_extension_name", 1); /** * Extension duplicated name error is returned when an extension with the same name is already registered. */ - static final ExtensionError DUPLICATE_NAME = new + public static final ExtensionError DUPLICATE_NAME = new ExtensionError("extension.dup_extension_name", 2); /** * Event type not supported is returned when a new listener is registered for an invalid event type (null/empty). */ - static final ExtensionError EVENT_TYPE_NOT_SUPPORTED = new + public static final ExtensionError EVENT_TYPE_NOT_SUPPORTED = new ExtensionError("extension.event_type_not_supported", 3); /** * Event source not supported is returned when a new listener is registered for an invalid event source (null/empty). */ - static final ExtensionError EVENT_SOURCE_NOT_SUPPORTED = new + public static final ExtensionError EVENT_SOURCE_NOT_SUPPORTED = new ExtensionError("extension.event_source_not_supported", 4); /** * Event data not supported is returned when the event data cannot be converted to the supported JSON format. */ - static final ExtensionError EVENT_DATA_NOT_SUPPORTED = new + public static final ExtensionError EVENT_DATA_NOT_SUPPORTED = new ExtensionError("extension.event_data_not_supported", 5); /** @@ -65,13 +65,13 @@ public class ExtensionError extends AdobeError { /** * Listener timeout error is returned when the registered extension listener takes more than the accepted timeout (~100ms). */ - static final ExtensionError LISTENER_TIMEOUT = new + public static final ExtensionError LISTENER_TIMEOUT = new ExtensionError("extension.listener_timeout_exception", 8); /** * This error is returned when a null callback is provided for a required parameter. */ - static final ExtensionError CALLBACK_NULL = new + public static final ExtensionError CALLBACK_NULL = new ExtensionError("extension.callback_null", 9); private ExtensionError(final String errorName, final int errorCode) { diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index c6703f48e..4d181eaa5 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -11,12 +11,17 @@ package com.adobe.marketing.mobile.internal.eventhub +import com.adobe.marketing.mobile.Event import com.adobe.marketing.mobile.Extension +import com.adobe.marketing.mobile.ExtensionError +import com.adobe.marketing.mobile.ExtensionErrorCallback import com.adobe.marketing.mobile.LoggingMode import com.adobe.marketing.mobile.MobileCore +import java.util.concurrent.Callable import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ExecutorService import java.util.concurrent.Executors +import java.util.concurrent.atomic.AtomicInteger /** * EventHub class is responsible for delivering events to listeners and maintaining registered extension's lifecycle. @@ -25,14 +30,20 @@ internal class EventHub { constructor() companion object { - val LOG_TAG = "EventHub" + const val LOG_TAG = "EventHub" public var shared = EventHub() } private val eventHubExecutor: ExecutorService by lazy { Executors.newSingleThreadExecutor() } private val registeredExtensions: ConcurrentHashMap = ConcurrentHashMap() + private val lastEventNumber: AtomicInteger = AtomicInteger(0) private var hubStarted = false + /** + * A cache that maps UUID of an Event to an internal sequence of its dispatch. + */ + private val eventNumberMap: ConcurrentHashMap = ConcurrentHashMap() + init { registerExtension(EventHubPlaceholderExtension::class.java) {} } @@ -69,7 +80,12 @@ internal class EventHub { } val executor = Executors.newSingleThreadExecutor() - val container = ExtensionContainer(extensionClass, executor, completion) + val stateManagers: Map = mapOf( + SharedStateType.STANDARD to SharedStateManager(), + SharedStateType.XDM to SharedStateManager() + ) + + val container = ExtensionContainer(extensionClass, ExtensionRuntime(), stateManagers, executor, completion) registeredExtensions[extensionName] = container } } @@ -93,6 +109,163 @@ internal class EventHub { } } + /** + * Sets the shared state for the extension - [extensionName] with [data] + * + * @param sharedStateType the type of shared state that needs to be set. + * @param extensionName the name of the extension for which the state is being set + * @param data a map representing state of [extensionName] extension. Passing null will set the extension's + * shared state on pending until it is resolved. Another call with non-null state is expected in order + * to resolve this share state. + * @param event The [Event] for which the state is being set. Passing null will set the state for the next shared + * state version. + * @param errorCallback the callback which will be notified in the event of an error + * + * @return true if the state was successfully set, false otherwise + */ + fun setSharedState( + sharedStateType: SharedStateType, + extensionName: String?, + data: MutableMap?, + event: Event?, + errorCallback: ExtensionErrorCallback? + ): Boolean { + + val setSharedStateCallable: Callable = Callable { + + if (extensionName.isNullOrEmpty() || extensionName.isBlank()) { + MobileCore.log(LoggingMode.ERROR, LOG_TAG, + String.format("Unable to set SharedState for extension: [%s]. ExtensionName is invalid.", extensionName)) + + errorCallback?.error(ExtensionError.BAD_NAME) + return@Callable false + } + + val extensionContainer: ExtensionContainer? = registeredExtensions[extensionName] + + if (extensionContainer == null) { + MobileCore.log(LoggingMode.ERROR, LOG_TAG, + String.format("Error seting SharedState for extension: [%s]. Extension may not have been registered.", extensionName)) + + errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) + return@Callable false + } + + // Find the version where this state needs to be set + val version: Int = if (event == null) { + // Use the next available version if event is null + lastEventNumber.incrementAndGet() + } else { + // Fetch the event number for the event if it has been dispatched. + // If no such event exists, use the next available sequence number + getEventNumber(event) ?: lastEventNumber.incrementAndGet() + } + + val wasSet: Boolean = extensionContainer.setSharedState(sharedStateType, data, version) + + // Check if the new state can be dispatched as a state change event(currently implies a + // non null/non pending state according to the ExtensionAPI) + val shouldDispatch: Boolean = (data == null) + + if (shouldDispatch && wasSet) { + // If the new state can be dispatched and was successfully + // set (via a new state being created or a state being updated), + // dispatch a shared state notification. + // TODO: dispatch() + } + return@Callable wasSet + } + + return eventHubExecutor.submit(setSharedStateCallable).get() + } + + /** + * Retrieves the shared state for the extension [extensionName] at the [event] + * + * @param sharedStateType the type of shared state that needs to be retrieved. + * @param extensionName the name of the extension for which the state is being retrieved + * @param event The [Event] for which the state is being retrieved. Passing null will retrieve latest state available. + * state version. + * @param errorCallback the callback which will be notified in the event of an error + * @return a [Map] containing the shared state data at [event], + * null if the state is pending, not yet set or, in case of an error + */ + fun getSharedState( + sharedStateType: SharedStateType, + extensionName: String?, + event: Event?, + errorCallback: ExtensionErrorCallback? + ): Map? { + + val getSharedStateCallable: Callable?> = Callable { + if (extensionName.isNullOrEmpty() || extensionName.isBlank()) { + MobileCore.log(LoggingMode.ERROR, LOG_TAG, String.format("Unable to get SharedState. State name [%s] is invalid.", extensionName)) + + errorCallback?.error(ExtensionError.BAD_NAME) + return@Callable null + } + + val extensionContainer: ExtensionContainer? = registeredExtensions[extensionName] + + if (extensionContainer == null) { + MobileCore.log(LoggingMode.ERROR, LOG_TAG, + String.format("Error retrieving SharedState for extension: [%s]." + + "Extension may not have been registered.", extensionName)) + errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) + return@Callable null + } + + val version: Int = if (event == null) { + // Get the most recent number if event is not specified + SharedStateManager.VERSION_LATEST + } else { + // Fetch event number from the provided event. + // If not such event was dispatched, return the most recent state. + getEventNumber(event) ?: SharedStateManager.VERSION_LATEST + } + + return@Callable extensionContainer.getSharedState(sharedStateType, version) + } + + return eventHubExecutor.submit(getSharedStateCallable).get() + } + + /** + * Clears all shared state previously set by [extensionName]. + * + * @param sharedStateType the type of shared state that needs to be cleared. + * @param extensionName the name of the extension for which the state is being cleared + * @param errorCallback the callback which will be notified in the event of an error + */ + fun clearSharedState( + sharedStateType: SharedStateType, + extensionName: String?, + errorCallback: ExtensionErrorCallback? + ): Boolean { + val clearSharedStateCallable: Callable = Callable { + if (extensionName.isNullOrEmpty() || extensionName.isBlank()) { + MobileCore.log(LoggingMode.ERROR, LOG_TAG, String.format("Unable to clear SharedState. State name [%s] is invalid.", extensionName)) + + errorCallback?.error(ExtensionError.BAD_NAME) + return@Callable null + } + + val extensionContainer: ExtensionContainer? = registeredExtensions[extensionName] + + if (extensionContainer == null) { + MobileCore.log(LoggingMode.ERROR, + LOG_TAG, + String.format("Error clearing SharedState for extension: [%s]. Extension may not have been registered.")) + errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) + return@Callable false + } + + return@Callable extensionContainer.clearSharedState(sharedStateType) + } + + return eventHubExecutor.submit(clearSharedStateCallable).get() + } + /** * Stops processing events and shuts down all registered extensions. */ @@ -114,6 +287,18 @@ internal class EventHub { if (!hubStarted) return // Update shared state with registered extensions } + + /** + * Retrieve the event number for the Event from the [eventNumberMap] + * + * @param [event] the Event for which the event number should be resolved + * @return the event number for the event if it exists (if it has been recorded/dispatched), + * null otherwise + */ + private fun getEventNumber(event: Event?): Int? { + val eventUUID = event?.uniqueIdentifier + return eventNumberMap[eventUUID] + } } /** diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt index 092108097..604cbd4ff 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt @@ -17,9 +17,17 @@ import com.adobe.marketing.mobile.ExtensionApi import com.adobe.marketing.mobile.ExtensionError import com.adobe.marketing.mobile.ExtensionErrorCallback import com.adobe.marketing.mobile.ExtensionListener +import com.adobe.marketing.mobile.LoggingMode +import com.adobe.marketing.mobile.MobileCore import java.util.concurrent.ExecutorService +import kotlin.Exception internal class ExtensionRuntime() : ExtensionApi() { + + companion object { + const val LOG_TAG = "ExtensionApi" + } + var extension: Extension? = null set(value) { field = value @@ -53,43 +61,88 @@ internal class ExtensionRuntime() : ExtensionApi() { } override fun setSharedEventState( - state: MutableMap?, + state: MutableMap?, event: Event?, errorCallback: ExtensionErrorCallback? ): Boolean { - TODO("Not yet implemented") + try { + return EventHub.shared.setSharedState(SharedStateType.XDM, extensionName, state, event, errorCallback) + } catch (ex: Exception) { + MobileCore.log(LoggingMode.ERROR, getTag(), + String.format("Failed to set shared state at EventID: %s. %s", event?.uniqueIdentifier, ex)) + errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) + } + + return false } override fun setXDMSharedEventState( - state: MutableMap?, + state: MutableMap?, event: Event?, errorCallback: ExtensionErrorCallback? ): Boolean { - TODO("Not yet implemented") + try { + return EventHub.shared.setSharedState(SharedStateType.XDM, extensionName, state, event, errorCallback) + } catch (ex: Exception) { + MobileCore.log(LoggingMode.ERROR, getTag(), + String.format("Failed to set XDM shared state at EventID: %s. %s", event?.uniqueIdentifier, ex)) + errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) + } + + return false } override fun clearSharedEventStates(errorCallback: ExtensionErrorCallback?): Boolean { - TODO("Not yet implemented") + try { + EventHub.shared.clearSharedState(SharedStateType.STANDARD, extensionName, errorCallback) + } catch (ex: Exception) { + MobileCore.log(LoggingMode.ERROR, getTag(), String.format("Failed to clear shared state. %s", ex)) + errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) + } + + return false } override fun clearXDMSharedEventStates(errorCallback: ExtensionErrorCallback?): Boolean { - TODO("Not yet implemented") + try { + EventHub.shared.clearSharedState(SharedStateType.XDM, extensionName, errorCallback) + } catch (ex: Exception) { + MobileCore.log(LoggingMode.ERROR, getTag(), String.format("Failed to clear XDM shared state. %s", ex)) + errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) + } + + return false } override fun getSharedEventState( stateName: String?, event: Event?, errorCallback: ExtensionErrorCallback? - ): MutableMap { - TODO("Not yet implemented") + ): Map? { + try { + return EventHub.shared.getSharedState(SharedStateType.STANDARD, stateName, event, errorCallback) + } catch (ex: Exception) { + MobileCore.log(LoggingMode.ERROR, getTag(), + String.format("Failed to get shared state at EventID: %s. %s", event?.uniqueIdentifier, ex)) + errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) + } + return null } override fun getXDMSharedEventState( stateName: String?, event: Event?, errorCallback: ExtensionErrorCallback? - ): MutableMap { - TODO("Not yet implemented") + ): Map? { + try { + return EventHub.shared.getSharedState(SharedStateType.STANDARD, stateName, event, errorCallback) + } catch (ex: Exception) { + MobileCore.log(LoggingMode.ERROR, getTag(), + String.format("Failed to get XDM shared state at EventID: %s. %s", event?.uniqueIdentifier, ex)) + errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) + } + + return null } override fun unregisterExtension() { @@ -98,16 +151,23 @@ internal class ExtensionRuntime() : ExtensionApi() { } EventHub.shared.unregisterExtension(extension?.javaClass) {} } + + private fun getTag(): String { + if (extension == null) { + return LOG_TAG + } + return String.format("%s-%s", extensionName, extensionVersion) + } } internal class ExtensionContainer constructor( private val extensionClass: Class, + private val extensionRuntime: ExtensionRuntime, + private val sharedStateManagers: Map, private val taskExecutor: ExecutorService, callback: (EventHubError) -> Unit ) { - private val extensionRuntime = ExtensionRuntime() - val sharedStateName: String? get() = extensionRuntime.extensionName @@ -142,4 +202,54 @@ internal class ExtensionContainer constructor( } taskExecutor.shutdown() } + + /** + * Sets the shared state for the extension at [version] as [data] and type [sharedStateType] + * If [data] is null, the shared state being set is regarded as pending. + * If a pending shared state at [version] already exists, an attempt will be made to update it. + * + * @param sharedStateType the type of the shared state that need to be set + * @param [data] the content that the shared state needs to be populated with + * @param [version] the version of the shared state to be set + * @return true - if a new shared state has been created or updated at [version], false otherwise + */ + fun setSharedState( + sharedStateType: SharedStateType, + data: MutableMap?, + version: Int + ): Boolean { + val stateManager: SharedStateManager = sharedStateManagers[sharedStateType] ?: return false + val isPending = (data == null) + + // Attempt to create the state first and then attempt to update it if creation fails. + return stateManager.createSharedState(data, version, isPending) || + stateManager.updateSharedState(data, version, isPending) + } + + /** + * Clears the shares states of type [sharedStateType] for this extension. + * + * @return true always unless an exception occurs clearing the state + */ + fun clearSharedState(sharedStateType: SharedStateType): Boolean { + sharedStateManagers[sharedStateType]?.clearSharedState() + return true + } + + /** + * Gets the shared state of type [sharedStateType] at [version] or the most recent one before [version] + * if it is unavailable. + * + * @param sharedStateType the type of the shared state that need to be retrieved + * @param [version] the version of the pending shared state to be retrieved + * @return shared state at [version] if it exists or the most recent shared state before [version]. + * null If no state at or before [version] is found, or if the state fetched above is pending. + */ + fun getSharedState( + sharedStateType: SharedStateType, + version: Int + ): Map? { + + return sharedStateManagers[sharedStateType]?.getSharedState(version) + } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedStateManager.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedStateManager.kt new file mode 100644 index 000000000..5b188bcf7 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedStateManager.kt @@ -0,0 +1,183 @@ +package com.adobe.marketing.mobile.internal.eventhub + +import android.util.LruCache +import com.adobe.marketing.mobile.LoggingMode +import com.adobe.marketing.mobile.MobileCore +import java.util.TreeMap + +/** + * Responsible for managing the shared state operations for an extension via [ExtensionRuntime]. + * Employs a red-black tree to store the state data and their versions while also using a cache + * to make a O(1) best effort retrieval. + * It is intentionally agnostic of the type ([SharedStateType]) of the state it deals with. + * The knowledge of whether or not a state is pending is deferred to the caller to ensure this class + * is decoupled from the rules for a pending state. + * + * Note that the methods in this class fall on the public ExtensionApi path and, changes to method + * behaviors may impact the shared state API behavior. + */ +internal class SharedStateManager { + + /** + * A mapping between the version of the state to the state. + */ + private val states: TreeMap = TreeMap() + + /** + * Responsible for caching items that are most recently used. Useful for making + * a best attempt O(1) retrieval. + */ + private val cache: LruCache = LruCache(10) + + companion object { + const val LOG_TAG = "SharedStateManager" + const val VERSION_LATEST: Int = Int.MAX_VALUE + } + + /** + * Records the shared state for the extension at [version] as [data] if it does not already exist. + * + * @param [data] the content that the shared state needs to be populated with + * @param [version] the version of the shared state to be created + * @param [isPending] a boolean to indicate if the state content is not final (i.e will be updated later) + * @return true - if a new shared state has been created at [version], false otherwise + */ + @Synchronized + fun createSharedState( + data: Map?, + version: Int, + isPending: Boolean + ): Boolean { + + // Check if there exists a state at a version equal to, or higher than the one provided. + if (states.ceilingEntry(version) != null) { + MobileCore.log(LoggingMode.VERBOSE, LOG_TAG, String.format("Cannot create state st version %d. More recent state exists.", version)) + // If such a state exists a new state cannot be created, it can be updated, if pending, + // via SharedStateManager#updateSharedState(..) + return false + } + + // At this point, there does not exist a state at the provided version. Create one and add it to cache + // TODO: USE EventDataUtils.cloneMap to do an immutable clone when available + val sharedState = SharedState(data?.toMap(), version, isPending) + states[version] = sharedState + cache.put(version, sharedState) + + // Only update the VERSION_LATEST to the cache. Not to the state store! + cache.put(VERSION_LATEST, sharedState) + + return true + } + + /** + * Updates a previously existing pending shared state for the extension at + * [version] as [data]. If such a pending state does not exists, no operation is done. + * + * @param [data] the content that the shared state needs to be updated with + * @param [version] the version of the pending shared state to be updated + * @param [isPending] a boolean to indicate if the new state content is not final + * @return true - if a new shared state has been created at [version], false otherwise + */ + @Synchronized + fun updateSharedState( + data: Map?, + version: Int, + isPending: Boolean + ): Boolean { + + // Check if new state is pending. A state cannot be overwritten by another pending state + if (isPending) { + MobileCore.log(LoggingMode.VERBOSE, LOG_TAG, + String.format("Cannot update pending state at version %d. With a pending state.", version)) + return false + } + + // Check if state exists at the exact version provided for updating. + val stateAtVersion = states[version] ?: return false + + // Check there is a valid pending state for updating. + if (!stateAtVersion.isPending) { + MobileCore.log(LoggingMode.WARNING, LOG_TAG, + String.format("Cannot update a non pending state state version %d.", version)) + return false + } + + // At this point, there exists a previously recorded state at the version provided. + // Overwrite its value with a confirmed state. + // TODO: USE EventDataUtils.cloneMap to do an immutable clone when available + val sharedState = SharedState(data?.toMap(), version, false) + states[version] = sharedState + cache.put(version, sharedState) + + // There is no need to update the VERSION_LATEST in the cache because update operation + // should only happen on a state that exists in the tree. If we reach a point where + // VERSION_LATEST should be updated, it means it is already in the cache + + return true + } + + /** + * Retrieves data for shared state at [version]. If such a version does not exist, + * retrieves the most recent version of the shared state available. + * + * @param [version] the version of the shared state to be retrieved + * @return shared state at [version] if it exists, or the most recent shared state before [version]. + * null - If no state at or before [version] is found, or if the state fetched above is pending. + */ + @Synchronized + fun getSharedState(version: Int): Map? { + + if (states.isEmpty()) { + // No states have been added to the state store yet. + return null + } + + // Check if a state exists exactly at the version specified. + // Find cache first to get in O(1) + val stateAtVersion = cache.get(version) ?: states[version] + + if (stateAtVersion != null) { + return if (stateAtVersion.isPending) { + // State is yet to be set (is pending) + null + } else { + // If shared state at exact version exists and it is not pending, use it + stateAtVersion.data + } + } + + // Otherwise, find state at the highest version less than the version being queried for + val resolvedSharedState: SharedState? = states.floorEntry(version)?.value + + // If the resolved state is not set or is pending, return null. Otherwise return the state + return if (resolvedSharedState == null || resolvedSharedState.isPending) { + null + } else { + cache.put(resolvedSharedState.version, resolvedSharedState) + resolvedSharedState.data + } + } + + /** + * Removes all the states being tracked from [states] and [cache] + */ + @Synchronized + fun clearSharedState() { + states.clear() + cache.evictAll() + } +} + +/** + * Internal representation of a shared event state. + * Allows associating version and pending behavior with the state data in a queryable way. + */ +private data class SharedState constructor(val data: Map?, val version: Int, val isPending: Boolean) + +/** + * Represents the types of shared state that are supported. + */ +internal enum class SharedStateType { + STANDARD, + XDM +} diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/SharedStateManagerTest.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/SharedStateManagerTest.kt new file mode 100644 index 000000000..ec4626dc7 --- /dev/null +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/SharedStateManagerTest.kt @@ -0,0 +1,187 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.internal.eventhub + +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue +import org.junit.Before +import org.junit.Test + +internal class SharedStateManagerTest { + + private val sharedStateManager: SharedStateManager = SharedStateManager() + + @Before + fun setUp() { + sharedStateManager.clearSharedState() + } + + @Test + fun testCreateSharedState_NonNullData() { + val data = mutableMapOf ("One" to 1, "Yes" to true) + assertTrue { sharedStateManager.createSharedState(data, 1, false) } + } + + @Test + fun testCreateSharedState_NullData() { + assertTrue { sharedStateManager.createSharedState(null, 1, false) } + } + + @Test + fun testCreateSharedState_PendingDataAssumptions() { + val data = mutableMapOf ("One" to 1, "Yes" to true) + + // Verify that SharedStateManager does not make assumptions of pending based on data + assertTrue { sharedStateManager.createSharedState(data, 1, true) } + assertTrue { sharedStateManager.createSharedState(null, 3, true) } + } + + @Test + fun testCreateSharedState_PendingDataOverwrite_WithPending() { + // Create pending state at version 1 + sharedStateManager.createSharedState(null, 1, true) + + // Overwrite with another pending data + assertFalse { sharedStateManager.createSharedState(mapOf(), 1, true) } + } + + @Test + fun testCreateSharedState_PendingDataOverwrite_WithNonPending() { + // Create pending state at version 1 + sharedStateManager.createSharedState(null, 1, true) + + // Overwrite with another pending data + assertFalse { sharedStateManager.createSharedState(mapOf(), 1, false) } + } + + @Test + fun testCreateSharedState_OlderStateDoesNotExist() { + // Create state at version 3 + sharedStateManager.createSharedState(mapOf(), 3, false) + // Create state at version 5 + sharedStateManager.createSharedState(mapOf(), 5, false) + + // Verify that state greater than 5 can be created irrespective of pending status + assertTrue { + sharedStateManager.createSharedState(mapOf(), 10, false) + sharedStateManager.createSharedState(mapOf(), 11, true) + } + } + + @Test + fun testCreateSharedState_OlderStateExists() { + // Create state at version 5 + sharedStateManager.createSharedState(mapOf(), 5, false) + + // Verify that no state less than or equal to 5 can be created irrespective of pending status + assertFalse { + sharedStateManager.createSharedState(mapOf(), 3, false) + sharedStateManager.createSharedState(mapOf(), 5, true) + } + } + + @Test + fun testUpdateSharedState_WithPendingState() { + // Create a pending state at version 1 + sharedStateManager.updateSharedState(null, 1, true) + + // Verify that pending state cannot be updated with another pending state + assertFalse { + sharedStateManager.updateSharedState(null, 1, true) + sharedStateManager.updateSharedState(mapOf(), 1, true) + } + } + + @Test + fun testUpdateSharedState_NoStateAtVersion() { + // Verify that pending state cannot be updated when no state exists at the version + assertFalse { sharedStateManager.updateSharedState(mapOf(), 7, false) } + } + + @Test + fun testUpdateSharedState_NoPendingStateAtVersion() { + // Create a non pending state at version 7 + sharedStateManager.createSharedState(mapOf(), 7, false) + + // Verify that pending state cannot be updated when no pending state exists at the version + assertFalse { sharedStateManager.updateSharedState(mapOf("One" to 1, "Yes" to true), 7, false) } + } + + @Test + fun testUpdateSharedState_ValidPendingStateAtVersion() { + // Create a non pending state at version 7 + sharedStateManager.createSharedState(null, 7, true) + + // Verify that pending state cannot be updated when no pending state exists at the version + assertTrue { sharedStateManager.updateSharedState(mapOf("One" to 1, "Yes" to true), 7, false) } + } + + @Test + fun testGetSharedState_NoStatesYet() { + assertNull(sharedStateManager.getSharedState(0)) + } + + @Test + fun testGetSharedState_StateExistsAtQueriedVersion() { + val data = mapOf("One" to 1, "Yes" to true) + sharedStateManager.createSharedState(mapOf("One" to 1, "Yes" to true), 3, false) + + assertEquals(data, sharedStateManager.getSharedState(3)) + } + + @Test + fun testGetSharedState_PendingStateExistsAtQueriedVersion() { + sharedStateManager.createSharedState(null, 3, true) + + assertNull(sharedStateManager.getSharedState(3)) + } + + @Test + fun testGetSharedState_StateExistsAtOlderVersion() { + val dataAtV3 = mapOf("One" to 1, "Yes" to true) + val dataAtV4 = mapOf("Two" to 2, "No" to false) + sharedStateManager.createSharedState(dataAtV3, 3, false) + sharedStateManager.createSharedState(dataAtV4, 4, false) + + assertEquals(dataAtV4, sharedStateManager.getSharedState(8)) + } + + @Test + fun testGetSharedState_PendingStateExistsAtOlderVersion() { + // Create shared states at Version 3 and Version 4 + val dataAtV3 = mapOf("One" to 1, "Yes" to true) + val dataAtV4 = mapOf("Two" to 2, "No" to false) + sharedStateManager.createSharedState(dataAtV3, 3, false) + sharedStateManager.createSharedState(dataAtV4, 4, true) + + assertNull(sharedStateManager.getSharedState(8)) + } + + @Test + fun testClearSharedState() { + // Create shared states at Version 3 and Version 4 + val dataAtV3 = mapOf("One" to 1, "Yes" to true) + val dataAtV4 = mapOf("Two" to 2, "No" to false) + sharedStateManager.createSharedState(dataAtV3, 3, false) + sharedStateManager.createSharedState(dataAtV4, 4, false) + assertNotNull(sharedStateManager.getSharedState(4)) + + sharedStateManager.clearSharedState() + + // Verify that previously set states are removed + assertNull(sharedStateManager.getSharedState(4)) + assertNull(sharedStateManager.getSharedState(3)) + } +} From 77e6510b90498cb6fc547034bb132b8f2eb37e33 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Thu, 28 Apr 2022 10:33:22 -0700 Subject: [PATCH 036/476] [#29] Fix KDoc --- .../mobile/internal/eventhub/ExtensionContainer.kt | 6 +++--- .../mobile/internal/eventhub/SharedStateManager.kt | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt index 604cbd4ff..84606113e 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt @@ -209,8 +209,8 @@ internal class ExtensionContainer constructor( * If a pending shared state at [version] already exists, an attempt will be made to update it. * * @param sharedStateType the type of the shared state that need to be set - * @param [data] the content that the shared state needs to be populated with - * @param [version] the version of the shared state to be set + * @param data the content that the shared state needs to be populated with + * @param version the version of the shared state to be set * @return true - if a new shared state has been created or updated at [version], false otherwise */ fun setSharedState( @@ -241,7 +241,7 @@ internal class ExtensionContainer constructor( * if it is unavailable. * * @param sharedStateType the type of the shared state that need to be retrieved - * @param [version] the version of the pending shared state to be retrieved + * @param version the version of the pending shared state to be retrieved * @return shared state at [version] if it exists or the most recent shared state before [version]. * null If no state at or before [version] is found, or if the state fetched above is pending. */ diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedStateManager.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedStateManager.kt index 5b188bcf7..4b80dbf9f 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedStateManager.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedStateManager.kt @@ -37,9 +37,9 @@ internal class SharedStateManager { /** * Records the shared state for the extension at [version] as [data] if it does not already exist. * - * @param [data] the content that the shared state needs to be populated with - * @param [version] the version of the shared state to be created - * @param [isPending] a boolean to indicate if the state content is not final (i.e will be updated later) + * @param data the content that the shared state needs to be populated with + * @param version the version of the shared state to be created + * @param isPending a boolean to indicate if the state content is not final (i.e will be updated later) * @return true - if a new shared state has been created at [version], false otherwise */ @Synchronized @@ -73,9 +73,9 @@ internal class SharedStateManager { * Updates a previously existing pending shared state for the extension at * [version] as [data]. If such a pending state does not exists, no operation is done. * - * @param [data] the content that the shared state needs to be updated with - * @param [version] the version of the pending shared state to be updated - * @param [isPending] a boolean to indicate if the new state content is not final + * @param data the content that the shared state needs to be updated with + * @param version the version of the pending shared state to be updated + * @param isPending a boolean to indicate if the new state content is not final * @return true - if a new shared state has been created at [version], false otherwise */ @Synchronized @@ -120,7 +120,7 @@ internal class SharedStateManager { * Retrieves data for shared state at [version]. If such a version does not exist, * retrieves the most recent version of the shared state available. * - * @param [version] the version of the shared state to be retrieved + * @param version the version of the shared state to be retrieved * @return shared state at [version] if it exists, or the most recent shared state before [version]. * null - If no state at or before [version] is found, or if the state fetched above is pending. */ From 83b44359f045d933c07e3ed60b1fae43dfee93ca Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Thu, 28 Apr 2022 14:27:47 -0700 Subject: [PATCH 037/476] [#29] Relay EventContainer operations via ExecutorService --- .../internal/eventhub/ExtensionContainer.kt | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt index 84606113e..888c1cf11 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt @@ -19,6 +19,7 @@ import com.adobe.marketing.mobile.ExtensionErrorCallback import com.adobe.marketing.mobile.ExtensionListener import com.adobe.marketing.mobile.LoggingMode import com.adobe.marketing.mobile.MobileCore +import java.util.concurrent.Callable import java.util.concurrent.ExecutorService import kotlin.Exception @@ -218,22 +219,30 @@ internal class ExtensionContainer constructor( data: MutableMap?, version: Int ): Boolean { - val stateManager: SharedStateManager = sharedStateManagers[sharedStateType] ?: return false - val isPending = (data == null) + if (taskExecutor.isShutdown) return false - // Attempt to create the state first and then attempt to update it if creation fails. - return stateManager.createSharedState(data, version, isPending) || - stateManager.updateSharedState(data, version, isPending) + return taskExecutor.submit(Callable { + val stateManager: SharedStateManager = sharedStateManagers[sharedStateType] ?: return@Callable false + val isPending = (data == null) + + // Attempt to create the state first and then attempt to update it if creation fails. + return@Callable stateManager.createSharedState(data, version, isPending) || + stateManager.updateSharedState(data, version, isPending) + }).get() } /** * Clears the shares states of type [sharedStateType] for this extension. * - * @return true always unless an exception occurs clearing the state + * @return true unless an exception occurs clearing the state or if the extension is unregistered */ fun clearSharedState(sharedStateType: SharedStateType): Boolean { - sharedStateManagers[sharedStateType]?.clearSharedState() - return true + if (taskExecutor.isShutdown) return false + + return taskExecutor.submit(Callable { + sharedStateManagers[sharedStateType]?.clearSharedState() + return@Callable true + }).get() } /** @@ -243,13 +252,17 @@ internal class ExtensionContainer constructor( * @param sharedStateType the type of the shared state that need to be retrieved * @param version the version of the pending shared state to be retrieved * @return shared state at [version] if it exists or the most recent shared state before [version]. - * null If no state at or before [version] is found, or if the state fetched above is pending. + * null If no state at or before [version] is found, or if the state fetched above is pending, + * or if the extension is unregistered */ fun getSharedState( sharedStateType: SharedStateType, version: Int ): Map? { + if (taskExecutor.isShutdown) return null - return sharedStateManagers[sharedStateType]?.getSharedState(version) + return taskExecutor.submit(Callable { + return@Callable sharedStateManagers[sharedStateType]?.getSharedState(version) + }).get() } } From d10198de14e4b3d7be5c6f480122880cb161fec2 Mon Sep 17 00:00:00 2001 From: praveek Date: Thu, 21 Apr 2022 14:11:41 -0700 Subject: [PATCH 038/476] db migration PR review comments --- .circleci/config.yml | 3 + Makefile | 8 +- code/android-core-library/build.gradle | 3 + .../adobe/marketing/mobile/MockHitQueue.java | 4 +- .../mobile/AbstractHitsDatabase.java | 32 +++--- .../com/adobe/marketing/mobile/FileUtil.java | 23 +--- .../com/adobe/marketing/mobile/HitQueue.java | 52 ++++++++- .../mobile/services/DataQueueService.java | 2 +- .../adobe/marketing/mobile/FileUtilTests.java | 50 --------- .../adobe/marketing/mobile/HitQueueTest.java | 103 ++++++++++++++++++ code/build.gradle | 3 + code/gradle.properties | 10 +- 12 files changed, 195 insertions(+), 98 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 36638abc2..4fb9676ff 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -26,6 +26,9 @@ jobs: steps: # Checkout the code as the first step. - checkout + - run: + name: check-format + command: make check-format - run: name: assemble-phone-release command: make assemble-phone-release diff --git a/Makefile b/Makefile index 5a1e74e5f..1147a3850 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,12 @@ clean: checkstyle: (./code/gradlew -p code/android-core-library checkstyle) +check-format: + (./code/gradlew -p code/android-core-library ktlintCheck) + +format: + (./code/gradlew -p code/android-core-library ktlintFormat) + assemble-phone: (./code/gradlew -p code/android-core-library assemblePhone) @@ -31,7 +37,7 @@ functional-test: (./code/gradlew -p code/android-core-library connectedPhoneDebugAndroidTest) javadoc: - (./code/gradlew -p code/android-core-library Javadoc) + (./code/gradlew -p code/android-core-library dokkaJavadoc) build-third-party-extension: diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index 65cb9eed9..93f4e94f2 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -1,4 +1,7 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'org.jlleitschuh.gradle.ktlint' +apply plugin: 'org.jetbrains.dokka' android { compileSdkVersion 30 diff --git a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockHitQueue.java b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockHitQueue.java index 8597ecf1a..fc65adc41 100755 --- a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockHitQueue.java +++ b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockHitQueue.java @@ -8,9 +8,9 @@ class MockHitQueue> extend this(services, null, null, null, null); } - MockHitQueue(PlatformServices services, File dbFile, String tableName, E hitSchema, + MockHitQueue(PlatformServices services, String dbFilePath, String tableName, E hitSchema, IHitProcessor hitProcessor) { - super(services, dbFile, tableName, hitSchema, hitProcessor); + super(services, dbFilePath, tableName, hitSchema, hitProcessor); } boolean selectOldestHitWasCalled; diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AbstractHitsDatabase.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AbstractHitsDatabase.java index 8f4a690ba..e216cbc4a 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AbstractHitsDatabase.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AbstractHitsDatabase.java @@ -25,7 +25,7 @@ abstract class AbstractHitsDatabase { // ======================================================================================== private static final String LOG_TAG = "HitsDatabase"; private DatabaseService structuredDataService; - private File databaseFile; + private String databaseFilePath; // ======================================================================================== // package-private fields @@ -53,15 +53,15 @@ enum DatabaseStatus { // Constructor // ======================================================================================== /** - * Constructor which sets internal {@link #structuredDataService}, {@link #databaseFile}, and {@link #tableName} properties. + * Constructor which sets internal {@link #structuredDataService}, {@link #databaseFilePath}, and {@link #tableName} properties. * * @param databaseService {@code DatabaseService} instance used for interfacing with a native database - * @param databaseFile {@code File} of the underlying database + * @param databaseFilePath {@code String} path of the underlying database * @param tableName {@code String} containing the name of the table in the database */ - AbstractHitsDatabase(final DatabaseService databaseService, final File databaseFile, final String tableName) { + AbstractHitsDatabase(final DatabaseService databaseService, final String databaseFilePath, final String tableName) { this.structuredDataService = databaseService; - this.databaseFile = databaseFile; + this.databaseFilePath = databaseFilePath; this.tableName = tableName; } @@ -88,7 +88,7 @@ void deleteAllHits() { synchronized (dbMutex) { if (database == null) { Log.warning(LOG_TAG, "%s (Database), couldn't delete hits, db file path: %s", Log.UNEXPECTED_NULL_VALUE, - databaseFile.getAbsolutePath()); + databaseFilePath); return; } @@ -116,7 +116,7 @@ boolean deleteHitWithIdentifier(final String identifier) { synchronized (dbMutex) { if (database == null) { Log.warning(LOG_TAG, "Couldn't delete hit, %s (Database) - Path to db: %s", Log.UNEXPECTED_NULL_VALUE, - databaseFile.getAbsolutePath()); + databaseFilePath); return false; } @@ -130,7 +130,7 @@ boolean deleteHitWithIdentifier(final String identifier) { } /** - * Opens the existing database at the location represented by {@link #databaseFile}, or creates a new one. + * Opens the existing database at the location represented by {@link #databaseFilePath}, or creates a new one. *

* Logs an error if create or open operation failed. */ @@ -138,7 +138,7 @@ void openOrCreateDatabase() { synchronized (dbMutex) { closeDatabase(); - if (databaseFile == null) { + if (StringUtils.isNullOrEmpty(databaseFilePath)) { Log.debug(LOG_TAG, "Database creation failed, %s - database file", Log.UNEXPECTED_NULL_VALUE); return; } @@ -148,11 +148,11 @@ void openOrCreateDatabase() { return; } - Log.trace(LOG_TAG, "Trying to open database file located at %s", databaseFile.getAbsolutePath()); - database = structuredDataService.openDatabase(databaseFile.getPath()); + Log.trace(LOG_TAG, "Trying to open database file located at %s", databaseFilePath); + database = structuredDataService.openDatabase(databaseFilePath); if (database == null) { - Log.debug(LOG_TAG, "Database creation failed for %s", databaseFile.getPath()); + Log.debug(LOG_TAG, "Database creation failed for %s", databaseFilePath); } else { initializeDatabase(); } @@ -185,7 +185,7 @@ protected long getSize(final Query query) { synchronized (dbMutex) { if (database == null) { Log.debug(LOG_TAG, "Couldn't get size, %s (database) - Filepath: %s", Log.UNEXPECTED_NULL_VALUE, - databaseFile.getAbsolutePath()); + databaseFilePath); return 0; } @@ -214,14 +214,14 @@ protected long getSize(final Query query) { /** * Resets the underlying {@link #database}, usually as a result of a {@link DatabaseStatus#FATAL_ERROR}. *

- * This method removes the existing database and creates a new one with the same {@link #databaseFile} and structure. + * This method removes the existing database and creates a new one with the same {@link #databaseFilePath} and structure. */ protected final void reset() { Log.error(LOG_TAG, "Database in unrecoverable state, resetting."); synchronized (dbMutex) { - if (databaseFile != null && databaseFile.exists() && !structuredDataService.deleteDatabase(databaseFile.getPath())) { - Log.debug(LOG_TAG, String.format("Failed to delete database file(%s).", databaseFile.getAbsolutePath())); + if (databaseFilePath != null && new File(databaseFilePath).exists() && !structuredDataService.deleteDatabase(databaseFilePath)) { + Log.debug(LOG_TAG, String.format("Failed to delete database file(%s).", databaseFilePath)); databaseStatus = DatabaseStatus.FATAL_ERROR; return; } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/FileUtil.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/FileUtil.java index 54361d544..8fb581be2 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/FileUtil.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/FileUtil.java @@ -15,7 +15,7 @@ final class FileUtil { - private final static String LOG_TAG = "com.adobe.marketing.mobile.FileUtil"; + private final static String LOG_TAG = "FileUtil"; private FileUtil() {} @@ -82,25 +82,4 @@ static boolean isValidDirectory(final File directory) { return directory != null && directory.isDirectory() && directory.canWrite(); } - /** - * Copies the contents from {@code src} to {@code dest}. - * - * @param src {@link File} from which the contents are read - * @param dest {@link File} to which contents are written to - * @throws IOException if {@code src} or {@code dest} is not present or it does not have read permissions - */ - static void copyFile(final File src, final File dest) throws IOException, NullPointerException{ - final int STREAM_READ_BUFFER_SIZE = 1024; - - try (InputStream input = new FileInputStream(src)) { - try (OutputStream output = new FileOutputStream(dest)) { - byte[] buffer = new byte[STREAM_READ_BUFFER_SIZE]; - int length; - while ((length = input.read(buffer)) != -1) { - output.write(buffer, 0, length); - } - Log.debug(LOG_TAG, "Successfully copied (%s) to (%s)", src.getCanonicalPath(), dest.getCanonicalPath()); - } - } - } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/HitQueue.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/HitQueue.java index ea6c38377..c8c365389 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/HitQueue.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/HitQueue.java @@ -12,8 +12,14 @@ package com.adobe.marketing.mobile; import com.adobe.marketing.mobile.DatabaseService.QueryResult; +import com.adobe.marketing.mobile.internal.context.App; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.Map; /** @@ -44,7 +50,7 @@ enum RetryType { } /** - * Constructor calls {@link AbstractHitsDatabase#AbstractHitsDatabase(DatabaseService, File, String)} then calls + * Constructor calls {@link AbstractHitsDatabase#AbstractHitsDatabase(DatabaseService, String, String)} then calls * {@link #openOrCreateDatabase()} after initializing the following fields: *

    *
  • {@link #bgThreadActive}
  • @@ -55,14 +61,14 @@ enum RetryType { *
* * @param services the {@code PlatformServices} instance - * @param dbFile the {@code File} representing the underlying database + * @param dbFilePath the {@code String} representing the path of the underlying database * @param tableName {@code String} containing the database table name * @param hitSchema {@code AbstractHitSchema} containing the database table definition * @param hitProcessor object implementing the {@code IHitProcessor} responsible for processing hits */ - HitQueue(final PlatformServices services, final File dbFile, final String tableName, + HitQueue(final PlatformServices services, final String dbFilePath, final String tableName, final E hitSchema, final IHitProcessor hitProcessor) { - super(services.getDatabaseService(), dbFile, tableName); + super(services.getDatabaseService(), dbFilePath, tableName); bgThreadActive = false; isSuspended = false; this.systemInfoService = services.getSystemInfoService(); @@ -336,4 +342,42 @@ interface IHitProcessor { */ RetryType process(T hit); } + + static boolean migrate(final File src, final String newDBName){ + if(src == null) { + Log.warning(LOG_TAG, "Failed to copy database to database folder. Source file is null"); + return false; + } + if(!src.exists()){ + Log.warning(LOG_TAG, "Failed to copy database to database folder. Source file (%s) does not exist", src.getAbsolutePath()); + return false; + } + if(StringUtils.isNullOrEmpty(newDBName)) { + Log.warning(LOG_TAG, "Failed to copy database to database folder. New database name is null or empty"); + return false; + } + + final int STREAM_READ_BUFFER_SIZE = 1024; + final File newHitQueueFile = App.getInstance().getAppContext().getDatabasePath(newDBName); + + + try (InputStream input = new FileInputStream(src)) { + newHitQueueFile.createNewFile(); + try (OutputStream output = new FileOutputStream(newHitQueueFile)) { + byte[] buffer = new byte[STREAM_READ_BUFFER_SIZE]; + int length; + while ((length = input.read(buffer)) != -1) { + output.write(buffer, 0, length); + } + Log.debug(LOG_TAG, "Successfully copied (%s) to database folder", src.getAbsolutePath()); + return true; + } catch (Exception e) { + Log.debug(LOG_TAG, "Failed to copy database (%s) to database folder", src.getAbsolutePath()); + return false; + } + } catch (Exception e) { + Log.debug(LOG_TAG, "Failed to copy database (%s) to database folder", src.getAbsolutePath()); + return false; + } + } } \ No newline at end of file diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java index 5175ff827..a2395a5e7 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java @@ -133,7 +133,7 @@ private void copyFile(final File src, final File dest) throws IOException, NullP } MobileCore.log(LoggingMode.DEBUG, LOG_TAG, - String.format("Successfully copied (%s) to (%s)", src.getCanonicalPath(), dest.getCanonicalPath())); + String.format("Successfully copied (%s) to (%s)", src.getAbsolutePath(), dest.getAbsolutePath())); } } } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileUtilTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileUtilTests.java index b2a1a3356..368d73a8b 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileUtilTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileUtilTests.java @@ -98,54 +98,4 @@ public void testReadJsonStringFromFile_When_DirectoryInsteadOfFile_Then_ReturnsN assertNull(content); } - // ================================================================================================================= - // protected void copyFile(final File src, final File dest) - // ================================================================================================================= - @Test - public void testCopyFile_When_ValidSrcFile_And_ValidEmptyDestFile() throws Exception { - File src = fileTestHelper.placeSampleCacheFile(); - File dest = fileTestHelper.createEmptyFile(FILE_DIRECTORY, "testFileName"); - assertTrue(StringUtils.isNullOrEmpty(FileUtil.readStringFromFile(dest))); - FileUtil.copyFile(src, dest); - assertEquals(MOCK_CONFIG_JSON, FileUtil.readStringFromFile(dest)); - } - - @Test - public void testCopyFile_When_ValidSrcFile_And_ValidNonEmptyDestFile() throws Exception{ - File src = fileTestHelper.placeSampleCacheFile(); - File dest = fileTestHelper.createEmptyFile(FILE_DIRECTORY, "testFileName"); - fileTestHelper.writeToFile(dest, "testContent"); - assertEquals("testContent", FileUtil.readStringFromFile(dest)); - FileUtil.copyFile(src, dest); - assertEquals(MOCK_CONFIG_JSON, FileUtil.readStringFromFile(dest)); - } - - @Test(expected = NullPointerException.class) - public void testCopyFile_When_ValidSrcFile_And_NullDestFile() throws Exception { - File src = fileTestHelper.placeSampleCacheFile(); - FileUtil.copyFile(src, null); - } - - @Test(expected = IOException.class) - public void testCopyFile_When_ValidSrcFile_And_InvalidDestFile() throws Exception { - File src = fileTestHelper.placeSampleCacheFile(); - File dest = fileTestHelper.createEmptyFile(FILE_DIRECTORY, "testFileName"); - fileTestHelper.deleteTempCacheDirectory(FILE_DIRECTORY); - FileUtil.copyFile(src, dest); - } - - @Test(expected = NullPointerException.class) - public void testCopyFile_When_NullSrcFile() throws Exception { - File src = fileTestHelper.placeSampleCacheFile(); - File dest = fileTestHelper.createEmptyFile(FILE_DIRECTORY, "testFileName"); - FileUtil.copyFile(null, dest); - } - - @Test(expected = IOException.class) - public void testCopyFile_When_InvalidSrcFile() throws Exception { - File src = fileTestHelper.placeSampleCacheFile(); - File dest = fileTestHelper.createEmptyFile(FILE_DIRECTORY, "testFileName"); - fileTestHelper.deleteTempCacheDirectory(); - FileUtil.copyFile(src, dest); - } } \ No newline at end of file diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/HitQueueTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/HitQueueTest.java index 0ee41e964..f86040cee 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/HitQueueTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/HitQueueTest.java @@ -10,19 +10,71 @@ */ package com.adobe.marketing.mobile; +import static com.adobe.marketing.mobile.FileTestHelper.FILE_DIRECTORY; +import static com.adobe.marketing.mobile.FileTestHelper.MOCK_CONFIG_JSON; + import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.junit.Assert.*; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import android.content.Context; + +import com.adobe.marketing.mobile.internal.context.App; +@RunWith(MockitoJUnitRunner.Silent.class) public class HitQueueTest { + @Mock + private Context mockContext; + + private static AppContextProvider appContextProvider = new AppContextProvider(); + + private static class AppContextProvider implements App.AppContextProvider { + + private Context context; + + private Activity currentActivity; + public void setContext(Context context) { + this.context = context; + } + + @Override + public Context getAppContext() { + return this.context; + } + + @Override + public Activity getCurrentActivity() { + return this.currentActivity; + } + } + + private FileTestHelper fileTestHelper; + + @Before + public void setup() { + fileTestHelper = new FileTestHelper(); + App.getInstance().initializeApp(appContextProvider); + } + + @After + public void tearDown() { + fileTestHelper.deleteTempCacheDirectory(); + } + // private static final String TEST_TABLE = "test_table"; // private static final String TEST_DATABASE_FILE = "test_file.sqlite"; // private static final String COL_ID = "ID"; @@ -377,4 +429,55 @@ public class HitQueueTest { // Thread.sleep(1000); // assertFalse(fakeHitDatabase.hitWasProcessed); // } + + // ================================================================================================================= + // static void migrate(final File src, final String newDBName) + // ================================================================================================================= + @Test + public void testMigrate_When_ValidSrcFile_And_DestFileDoesNotExist() { + when(mockContext.getDatabasePath("testFileName")).thenReturn(fileTestHelper.createEmptyFile(FILE_DIRECTORY, "testFileName")); + appContextProvider.setContext(mockContext); + File src = fileTestHelper.placeSampleCacheFile(); + assertTrue(HitQueue.migrate(src, "testFileName")); + assertEquals(MOCK_CONFIG_JSON, FileUtil.readStringFromFile(appContextProvider.getAppContext().getDatabasePath("testFileName"))); + } + + @Test + public void testMigrate_When_ValidSrcFile_And_ValidNonEmptyDestFile() { + when(mockContext.getDatabasePath("testFileName")).thenReturn(fileTestHelper.createEmptyFile(FILE_DIRECTORY, "testFileName")); + appContextProvider.setContext(mockContext); + File src = fileTestHelper.placeSampleCacheFile(); File dest = fileTestHelper.createEmptyFile(FILE_DIRECTORY, "testFileName"); + fileTestHelper.writeToFile(dest, "testContent"); + assertEquals("testContent", FileUtil.readStringFromFile(dest)); + assertTrue(HitQueue.migrate(src, "testFileName")); + assertEquals(MOCK_CONFIG_JSON, FileUtil.readStringFromFile(dest)); + } + + @Test + public void testMigrate_When_ValidSrcFile_And_NullDestFile() { + when(mockContext.getDatabasePath("testFileName")).thenReturn(fileTestHelper.createEmptyFile(FILE_DIRECTORY, "testFileName")); + appContextProvider.setContext(mockContext); + File src = fileTestHelper.placeSampleCacheFile(); + assertFalse(HitQueue.migrate(src, null)); + } + + @Test + public void testMigrate_When_ValidSrcFile_And_EmptyDestFile() { + when(mockContext.getDatabasePath("testFileName")).thenReturn(fileTestHelper.createEmptyFile(FILE_DIRECTORY, "testFileName")); + appContextProvider.setContext(mockContext); + File src = fileTestHelper.placeSampleCacheFile(); + assertFalse(HitQueue.migrate(src, "")); + } + + @Test + public void testCopyFile_When_NullSrcFile() { + assertFalse(HitQueue.migrate(null, "testFileName")); + } + + @Test + public void testCopyFile_When_InvalidSrcFile() { + File src = fileTestHelper.placeSampleCacheFile(); + fileTestHelper.deleteTempCacheDirectory(); + assertFalse(HitQueue.migrate(src, "testFileName")); + } } diff --git a/code/build.gradle b/code/build.gradle index 3a393264f..6016ae009 100644 --- a/code/build.gradle +++ b/code/build.gradle @@ -10,6 +10,9 @@ buildscript { //noinspection AndroidGradlePluginVersion classpath 'com.android.tools.build:gradle:4.0.1' classpath "org.jacoco:org.jacoco.core:0.8.7" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}" + classpath "org.jlleitschuh.gradle:ktlint-gradle:9.2.1" } } diff --git a/code/gradle.properties b/code/gradle.properties index 98d80cbd2..d62126092 100644 --- a/code/gradle.properties +++ b/code/gradle.properties @@ -2,7 +2,6 @@ org.gradle.jvmargs=-Xmx2048m # org.gradle.java.home=/Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home android.injected.testOnly=false org.gradle.configureondemand=false - #bourbon-platform-android-root.gradle coreExtensionVersion=1.9.0 coreExtensionName=core @@ -22,5 +21,12 @@ mavenIdentityVersion=1.2.2 mavenLifecycleVersion=1.1.0 #android.enableUnitTestBinaryResources=false +kotlin_version = 1.4.0 +android.useAndroidX=true +# Currrently core depends on libraries that are +# compatible with AndroidX. Hence jettification is +# not nececessary. Enable the flag below incase an +# incompatible library is used. +#android.enableJetifier=true - +dokka_version = 1.6.20 \ No newline at end of file From 0dd42e76708111e87e79ff170dc1065590a3df34 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Fri, 29 Apr 2022 19:47:51 -0700 Subject: [PATCH 039/476] db migration refactor file utility methods --- .../services/DataQueueServiceTests.java | 10 +- .../marketing/mobile/utility/FileUtilTests.kt | 28 +++++ .../com/adobe/marketing/mobile/HitQueue.java | 25 +---- .../mobile/services/DataQueueService.java | 100 ++++++------------ .../mobile/services/utility/FileUtil.kt | 39 +++++++ 5 files changed, 112 insertions(+), 90 deletions(-) create mode 100644 code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/utility/FileUtilTests.kt create mode 100644 code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/utility/FileUtil.kt diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/DataQueueServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/DataQueueServiceTests.java index d702a44f1..be6221834 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/DataQueueServiceTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/DataQueueServiceTests.java @@ -8,6 +8,8 @@ import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import com.adobe.marketing.mobile.services.utility.FileUtil; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -84,7 +86,7 @@ public void testGetDataQueue_RelativePathBackslashCleanedUp() { dataQueue.add(new DataEntity("{}")); assertEquals(dataQueueService.getDataQueue("/mydatabase\\..\\..\\database1").count(), 1); - ServiceProvider.getInstance().getApplicationContext().getDatabasePath(dataQueueService.removeRelativePath("/mydatabase\\..\\..\\database1")).delete(); + ServiceProvider.getInstance().getApplicationContext().getDatabasePath(FileUtil.removeRelativePath("/mydatabase\\..\\..\\database1")).delete(); } @Test @@ -94,7 +96,7 @@ public void testGetDataQueue_RelativePathForwardslashCleanedUp() { dataQueue.add(new DataEntity("{}")); assertEquals(dataQueueService.getDataQueue("/mydatabase/../../database1").count(), 1); - ServiceProvider.getInstance().getApplicationContext().getDatabasePath(dataQueueService.removeRelativePath("/mydatabase/../../database1")).delete(); + ServiceProvider.getInstance().getApplicationContext().getDatabasePath(FileUtil.removeRelativePath("/mydatabase/../../database1")).delete(); } @Test @@ -104,7 +106,7 @@ public void testGetDataQueue_RelativePathMixedWorkTheSameWhenNotMatch() { dataQueue.add(new DataEntity("{}")); assertEquals(dataQueueService.getDataQueue("/mydatabase/../../database1").count(), 1); - ServiceProvider.getInstance().getApplicationContext().getDatabasePath(dataQueueService.removeRelativePath("/mydatabase/../../database1")).delete(); + ServiceProvider.getInstance().getApplicationContext().getDatabasePath(FileUtil.removeRelativePath("/mydatabase/../../database1")).delete(); } @Test @@ -114,7 +116,7 @@ public void testGetDataQueue_RelativePathMixedWorkTheSameWhenMatch() { dataQueue.add(new DataEntity("{}")); assertEquals(dataQueueService.getDataQueue("/mydatabase/../database1").count(), 1); - ServiceProvider.getInstance().getApplicationContext().getDatabasePath(dataQueueService.removeRelativePath("/mydatabase/../database1")).delete(); + ServiceProvider.getInstance().getApplicationContext().getDatabasePath(FileUtil.removeRelativePath("/mydatabase/../database1")).delete(); } private boolean createDataQueue(File dataQueueFile, int numberOfEntities) { diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/utility/FileUtilTests.kt b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/utility/FileUtilTests.kt new file mode 100644 index 000000000..87524081c --- /dev/null +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/utility/FileUtilTests.kt @@ -0,0 +1,28 @@ +package com.adobe.marketing.mobile.utility + +import com.adobe.marketing.mobile.services.utility.FileUtil +import org.junit.Assert.assertEquals +import org.junit.Test + +class FileUtilTests { + + @Test + fun testRemoveRelativePath_RelativePathBackslashClearnedUp() { + assertEquals(FileUtil.removeRelativePath("/mydatabase\\..\\..\\database1"), "mydatabase_database1") + } + + @Test + fun testRemoveRelativePath_RelativePathForwardslashClearnedUp() { + assertEquals(FileUtil.removeRelativePath("/mydatabase/../../database1"), "mydatabase_database1") + } + + @Test + fun testRemoveRelativePath_RelativePathBackslashDoesNotChangeDir() { + assertEquals(FileUtil.removeRelativePath("/mydatabase\\..\\database1"), "mydatabase_database1") + } + + @Test + fun testRemoveRelativePath_RelativePathForwardslashDoesNotChangeDir() { + assertEquals(FileUtil.removeRelativePath("/mydatabase/../database1"), "mydatabase_database1") + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/HitQueue.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/HitQueue.java index c8c365389..9b37c8b2e 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/HitQueue.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/HitQueue.java @@ -13,13 +13,9 @@ import com.adobe.marketing.mobile.DatabaseService.QueryResult; import com.adobe.marketing.mobile.internal.context.App; +import com.adobe.marketing.mobile.services.utility.FileUtil; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.util.Map; /** @@ -360,22 +356,11 @@ static boolean migrate(final File src, final String newDBName){ final int STREAM_READ_BUFFER_SIZE = 1024; final File newHitQueueFile = App.getInstance().getAppContext().getDatabasePath(newDBName); - - try (InputStream input = new FileInputStream(src)) { + try { newHitQueueFile.createNewFile(); - try (OutputStream output = new FileOutputStream(newHitQueueFile)) { - byte[] buffer = new byte[STREAM_READ_BUFFER_SIZE]; - int length; - while ((length = input.read(buffer)) != -1) { - output.write(buffer, 0, length); - } - Log.debug(LOG_TAG, "Successfully copied (%s) to database folder", src.getAbsolutePath()); - return true; - } catch (Exception e) { - Log.debug(LOG_TAG, "Failed to copy database (%s) to database folder", src.getAbsolutePath()); - return false; - } - } catch (Exception e) { + FileUtil.copyFile(src, newHitQueueFile); + return true; + } catch (Exception e) { Log.debug(LOG_TAG, "Failed to copy database (%s) to database folder", src.getAbsolutePath()); return false; } diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java index a2395a5e7..557bc649b 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java @@ -15,13 +15,10 @@ import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; +import com.adobe.marketing.mobile.internal.utility.StringUtils; +import com.adobe.marketing.mobile.services.utility.FileUtil; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.util.HashMap; import java.util.Map; @@ -41,6 +38,7 @@ class DataQueueService implements DataQueuing { databaseHelper = new SQLiteDatabaseHelper(); } + //TODO add @NonNUll annotation after merging with dev-v2.0.0 branch @Override public DataQueue getDataQueue(final String databaseName) { DataQueue dataQueue = dataQueueCache.get(databaseName); @@ -59,28 +57,12 @@ public DataQueue getDataQueue(final String databaseName) { return null; } - final String cleanedDatabasePath = removeRelativePath(databaseName); - final File databaseDirDataQueue = ServiceProvider.getInstance().getApplicationContext().getDatabasePath(cleanedDatabasePath); - - final File cacheDir = ServiceProvider.getInstance().getDeviceInfoService().getApplicationCacheDir(); - if (!databaseDirDataQueue.exists() && cacheDir != null) { - final File cacheDirDataQueue = new File(cacheDir, cleanedDatabasePath); - if (cacheDirDataQueue.exists()) { - try { - if(databaseDirDataQueue.createNewFile()) { - copyFile(cacheDirDataQueue, databaseDirDataQueue); - MobileCore.log(LoggingMode.DEBUG, - LOG_TAG, - String.format("Successfully moved DataQueue for database (%s) from cache directory to database directory", databaseName)); - } - } catch (Exception e) { - MobileCore.log(LoggingMode.WARNING, - LOG_TAG, - String.format("Failed to move DataQueue for database (%s), could not create new file in database directory", databaseName)); - return null; - } - } + final File databaseDirDataQueue = migrateDataQueueService(databaseName); + + if(databaseDirDataQueue == null){ + return null; } + dataQueue = new SQLiteDataQueue(databaseDirDataQueue.getPath(), databaseHelper); dataQueueCache.put(databaseName, dataQueue); } @@ -90,51 +72,37 @@ public DataQueue getDataQueue(final String databaseName) { return dataQueue; } - /** - * Removes the relative part of the file name(if exists). - *

- * for ex: File name `/mydatabase/../../database1` will be converted to `mydatabase_database1` - *

- * - * @param filePath the file name - * @return file name without relative path - */ - String removeRelativePath(final String filePath) { - if (filePath == null || filePath.isEmpty()) { - return filePath; - } + private File migrateDataQueueService(String databaseName) { + final String cleanedDatabaseName = FileUtil.removeRelativePath(databaseName); - try { - String result = filePath.replaceAll("\\.[/\\\\]", "\\."); - result = result.replaceAll("[/\\\\](\\.{2,})", "_"); - result = result.replaceAll("/",""); - return result; - } catch (IllegalArgumentException e) { - return filePath; + if(StringUtils.isNullOrEmpty(databaseName)) { + MobileCore.log(LoggingMode.WARNING, + LOG_TAG, + "Failed to create DataQueue, database name is null"); + return null; } - } - /** - * Copies the contents from {@code src} to {@code dest}. - * - * @param src {@link File} from which the contents are read - * @param dest {@link File} to which contents are written to - * @throws IOException if {@code src} or {@code dest} is not present or it does not have read permissions - */ - private void copyFile(final File src, final File dest) throws IOException, NullPointerException{ - final int STREAM_READ_BUFFER_SIZE = 1024; - - try (InputStream input = new FileInputStream(src)) { - try (OutputStream output = new FileOutputStream(dest)) { - byte[] buffer = new byte[STREAM_READ_BUFFER_SIZE]; - int length; - while ((length = input.read(buffer)) != -1) { - output.write(buffer, 0, length); + final File databaseDirDataQueue = ServiceProvider.getInstance().getApplicationContext().getDatabasePath(cleanedDatabaseName); + + final File cacheDir = ServiceProvider.getInstance().getDeviceInfoService().getApplicationCacheDir(); + if (!databaseDirDataQueue.exists() && cacheDir != null) { + final File cacheDirDataQueue = new File(cacheDir, cleanedDatabaseName); + if (cacheDirDataQueue.exists()) { + try { + if(databaseDirDataQueue.createNewFile()) { + FileUtil.copyFile(cacheDirDataQueue, databaseDirDataQueue); + MobileCore.log(LoggingMode.DEBUG, + LOG_TAG, + String.format("Successfully moved DataQueue for database (%s) from cache directory to database directory", databaseName)); + } + } catch (Exception e) { + MobileCore.log(LoggingMode.WARNING, + LOG_TAG, + String.format("Failed to move DataQueue for database (%s), could not create new file in database directory", databaseName)); + return null; } - MobileCore.log(LoggingMode.DEBUG, - LOG_TAG, - String.format("Successfully copied (%s) to (%s)", src.getAbsolutePath(), dest.getAbsolutePath())); } } + return databaseDirDataQueue; } } diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/utility/FileUtil.kt b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/utility/FileUtil.kt new file mode 100644 index 000000000..bf56e6739 --- /dev/null +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/utility/FileUtil.kt @@ -0,0 +1,39 @@ +package com.adobe.marketing.mobile.services.utility + +import java.io.File +import kotlin.jvm.Throws + +internal object FileUtil { + /** + * Removes the relative part of the file name(if exists). + * + * for ex: File name `/mydatabase/../../database1` will be converted to `mydatabase_database1` + * + * @param filePath the file name + * @return file name without relative path + */ + @JvmStatic + fun removeRelativePath(filePath: String): String { + return if (filePath.isEmpty()) { + filePath + } else { + var result = filePath.replace("\\.[/\\\\]".toRegex(), "\\.") + result = result.replace("[/\\\\](\\.{2,})".toRegex(), "_") + result = result.replace("/".toRegex(), "") + result + } + } + + /** + * Copies the contents from `src` to `dest`. + * + * @param src [File] from which the contents are read + * @param dest [File] to which contents are written to + * @throws Exception if `src` or `dest` is not present or it does not have read permissions + */ + @JvmStatic + @Throws(Exception::class) + fun copyFile(src: File, dest: File) { + src.copyTo(dest, true) + } +} From 0856ec47cf01a3650e48e7c170dd96ba3e80c2bd Mon Sep 17 00:00:00 2001 From: Yansong Yang Date: Fri, 29 Apr 2022 21:01:09 -0600 Subject: [PATCH 040/476] JSONRulesParser Part II (historical event rule) (#41) * part I * fix TODOs * add more tests & refine code * rename tests * code reordering * remove none used optional return * address review comments and add more kotlin docs * add more docs * WIP: potential solution without tests * Move historical event logic to a sub package and decouple code from internal classes * cleanup & add fnv1a32 implementation in Kotlin * cleanup unused import * commit local temporary change * add more tests * add android tests * fix tests * address review comments * fix test failure * format --- .circleci/config.yml | 6 +- .../mobile/AndroidEventHistoryTests.java | 186 -- .../AndroidEventHistoryDatabaseTests.java | 62 +- .../history/AndroidEventHistoryTests.java | 189 ++ .../utility/SQLiteDatabaseHelperTests.java} | 73 +- .../marketing/mobile/FakeEventHistory.java | 192 +- .../mobile/FakeEventHistoryDatabase.java | 643 +++---- .../marketing/mobile/AndroidEventHistory.java | 159 -- .../mobile/AndroidEventHistoryDatabase.java | 320 ---- .../java/com/adobe/marketing/mobile/Core.java | 1191 +++++++------ .../com/adobe/marketing/mobile/Event.java | 4 +- .../com/adobe/marketing/mobile/EventData.java | 45 - .../mobile/EventHistoryProvider.java | 37 - .../com/adobe/marketing/mobile/EventHub.java | 5 +- .../adobe/marketing/mobile/RuleCondition.java | 3 - .../mobile/RuleConditionHistorical.java | 222 --- .../adobe/marketing/mobile/StringEncoder.java | 123 -- .../eventhub/history/AndroidEventHistory.java | 155 ++ .../history/AndroidEventHistoryDatabase.java | 194 ++ .../eventhub/history}/EventHistory.java | 6 +- .../history}/EventHistoryDatabase.java | 49 +- ...EventHistoryDatabaseCreationException.java | 4 +- .../eventhub/history/EventHistoryRequest.kt | 38 + .../history}/EventHistoryResultHandler.java | 5 +- .../{JSONUtils.kt => JSONExtensions.kt} | 0 .../mobile/internal/utility/MapExtensions.kt | 73 + .../utility/MapUtils.kt} | 23 +- .../utility/SQLiteDatabaseHelper.java | 315 ++++ .../internal/utility/StringEncoder.java | 99 + .../rulesengine/HistoricalEventsQuerying.kt | 54 + .../rulesengine/json/HistoricalCondition.kt | 44 +- .../launch/rulesengine/json/JSONDefinition.kt | 8 +- .../rulesengine/json/MatcherCondition.kt | 2 +- .../adobe/marketing/mobile/MobileCore.java | 1587 ++++++++--------- .../mobile/services/DataQueueService.java | 5 +- .../mobile/services/SQLiteDataQueue.java | 23 +- .../mobile/services/SQLiteDatabaseHelper.java | 317 ---- .../adobe/marketing/mobile/EventDataTest.java | 305 ---- .../marketing/mobile/StringUtilsTest.java | 5 +- .../adobe/marketing/mobile/TestHelper.java | 4 +- .../internal/utility/JSONExtensionsTests.kt | 179 ++ .../internal/utility/MapExtensionsTests.kt | 123 ++ .../internal/utility/MapUtilsTests.java | 320 ++++ .../utility}/StringEncoderTest.java | 5 +- .../mobile/services/SqliteDataQueueTests.java | 329 ++-- 45 files changed, 3879 insertions(+), 3852 deletions(-) delete mode 100644 code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryTests.java rename code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/{ => internal/eventhub/history}/AndroidEventHistoryDatabaseTests.java (60%) create mode 100644 code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/internal/eventhub/history/AndroidEventHistoryTests.java rename code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/{services/SqliteDatabaseHelperTests.java => internal/utility/SQLiteDatabaseHelperTests.java} (76%) delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidEventHistory.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidEventHistoryDatabase.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHistoryProvider.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConditionHistorical.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/StringEncoder.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/AndroidEventHistory.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/AndroidEventHistoryDatabase.java rename code/android-core-library/src/main/java/com/adobe/marketing/mobile/{ => internal/eventhub/history}/EventHistory.java (95%) rename code/android-core-library/src/main/java/com/adobe/marketing/mobile/{ => internal/eventhub/history}/EventHistoryDatabase.java (61%) rename code/android-core-library/src/main/java/com/adobe/marketing/mobile/{ => internal/eventhub/history}/EventHistoryDatabaseCreationException.java (86%) create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/EventHistoryRequest.kt rename code/android-core-library/src/main/java/com/adobe/marketing/mobile/{ => internal/eventhub/history}/EventHistoryResultHandler.java (87%) rename code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/{JSONUtils.kt => JSONExtensions.kt} (100%) create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt rename code/android-core-library/src/main/java/com/adobe/marketing/mobile/{EventHistoryRequest.java => internal/utility/MapUtils.kt} (57%) create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/SQLiteDatabaseHelper.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/StringEncoder.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/HistoricalEventsQuerying.kt delete mode 100644 code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDatabaseHelper.java create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/JSONExtensionsTests.kt create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapUtilsTests.java rename code/android-core-library/src/test/java/com/adobe/marketing/mobile/{ => internal/utility}/StringEncoderTest.java (93%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 62f24d67c..cf988e6cf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,9 +31,9 @@ jobs: - run: name: assemble-phone-release command: make assemble-phone-release - - run: - name: JavaDoc - command: make javadoc + # - run: + # name: JavaDoc + # command: make javadoc - run: name: unit-test command: make unit-test diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryTests.java deleted file mode 100644 index 64e693a35..000000000 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryTests.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertTrue; -import static org.junit.Assert.fail; - -import android.content.Context; -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import com.adobe.marketing.mobile.services.ServiceProvider; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.CountDownLatch; - -@RunWith(AndroidJUnit4.class) -public class AndroidEventHistoryTests { - private AndroidEventHistory androidEventHistory; - private HashMap data; - - @Before - public void beforeEach() { - Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); - ServiceProvider.getInstance().setContext(context); - TestUtils.deleteAllFilesInCacheDir(context); - - try { - androidEventHistory = new AndroidEventHistory(); - } catch (EventHistoryDatabaseCreationException e) { - fail(e.getLocalizedMessage()); - } - - data = new HashMap() { - { - put("key", "value"); - } - }; - } - - @Test - public void testRecordEvent() throws InterruptedException { - // setup - final CountDownLatch latch = new CountDownLatch(1); - final boolean[] result = new boolean[1]; - EventHistoryResultHandler handler = new EventHistoryResultHandler() { - @Override - public void call(Boolean value) { - result[0] = value; - latch.countDown(); - } - }; - - - Event event = new Event.Builder("name", "type", "source") - .setEventData(data) - .build(); - // test - androidEventHistory.recordEvent(event, handler); - latch.await(); - // verify record event successful - assertTrue(result[0]); - } - - @Test - public void testGetEvents() throws Exception { - // setup - final CountDownLatch latch = new CountDownLatch(5); - final CountDownLatch latch2 = new CountDownLatch(1); - final int[] result = new int[1]; - EventHistoryResultHandler handler = new - EventHistoryResultHandler() { - @Override - public void call(Boolean result) { - assertTrue(result); - latch.countDown(); - } - }; - EventHistoryResultHandler handler2 = new EventHistoryResultHandler() { - @Override - public void call(Integer value) { - result[0] = value; - latch2.countDown(); - } - }; - - for (int i = 0; i < 5; i++) { - Event event = new Event.Builder("name", "type", "source") - .setEventData(data) - .build(); - androidEventHistory.recordEvent(event, handler); - } - - latch.await(); - // test - EventHistoryRequest[] requests = new EventHistoryRequest[1]; - HashMap mask = new HashMap<>(); - mask.put("key", Variant.fromString("value")); - EventHistoryRequest request = new EventHistoryRequest(mask, 0, System.currentTimeMillis()); - requests[0] = request; - androidEventHistory.getEvents(requests, false, handler2); - latch2.await(); - // verify get events returns 5 events - assertEquals(5, result[0]); - } - - @Test - public void testDeleteEvents() throws Exception { - // setup - final CountDownLatch latch = new CountDownLatch(5); - final CountDownLatch latch2 = new CountDownLatch(10); - final CountDownLatch latch3 = new CountDownLatch(1); - final int[] result = new int[1]; - EventHistoryResultHandler handler = new - EventHistoryResultHandler() { - @Override - public void call(Boolean result) { - assertTrue(result); - latch.countDown(); - } - }; - EventHistoryResultHandler handler2 = new - EventHistoryResultHandler() { - @Override - public void call(Boolean result) { - assertTrue(result); - latch2.countDown(); - } - }; - EventHistoryResultHandler handler3 = new EventHistoryResultHandler() { - @Override - public void call(Integer value) { - result[0] = value; - latch3.countDown(); - } - }; - - for (int i = 0; i < 5; i++) { - Event event = new Event.Builder("name", "type", "source") - .setEventData(data) - .build(); - androidEventHistory.recordEvent(event, handler); - } - - latch.await(); - Map data2 = new HashMap() { - { - put("key2", "value2"); - } - }; - - for (int i = 0; i < 10; i++) { - Event event = new Event.Builder("name", "type", "source") - .setEventData(data2) - .build(); - androidEventHistory.recordEvent(event, handler2); - } - - latch2.await(); - // test - EventHistoryRequest[] requests = new EventHistoryRequest[1]; - HashMap mask = new HashMap<>(); - mask.put("key2", Variant.fromString("value2")); - EventHistoryRequest request = new EventHistoryRequest(mask, 0, 0); - requests[0] = request; - androidEventHistory.deleteEvents(requests, handler3); - latch3.await(); - // verify 10 events deleted - assertEquals(10, result[0]); - } -} diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryDatabaseTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/internal/eventhub/history/AndroidEventHistoryDatabaseTests.java similarity index 60% rename from code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryDatabaseTests.java rename to code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/internal/eventhub/history/AndroidEventHistoryDatabaseTests.java index 96d18683f..ca71b5885 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidEventHistoryDatabaseTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/internal/eventhub/history/AndroidEventHistoryDatabaseTests.java @@ -9,28 +9,24 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile; +package com.adobe.marketing.mobile.internal.eventhub.history; -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnConstraint.PRIMARY_KEY; -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnDataType.INTEGER; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; - import android.content.Context; +import android.database.Cursor; import android.database.DatabaseUtils; -import androidx.test.platform.app.InstrumentationRegistry; +import android.database.sqlite.SQLiteDatabase; import androidx.test.ext.junit.runners.AndroidJUnit4; - +import androidx.test.platform.app.InstrumentationRegistry; +import com.adobe.marketing.mobile.TestUtils; +import com.adobe.marketing.mobile.internal.utility.SQLiteDatabaseHelper; import com.adobe.marketing.mobile.services.ServiceProvider; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.ArrayList; -import java.util.List; - @RunWith(AndroidJUnit4.class) public class AndroidEventHistoryDatabaseTests { private EventHistoryDatabase androidEventHistoryDatabase; @@ -49,29 +45,9 @@ public void beforeEach() { } @Test - public void testCreateTable_Happy() { - boolean success = androidEventHistoryDatabase.createTable(new String[] {"eventHash", "timestamp"}, - new DatabaseService.Database.ColumnDataType[] {INTEGER, INTEGER}, - new ArrayList>() { - { - add(new ArrayList() { - { - add(PRIMARY_KEY); - } - }); - add(null); - } - }); - assertTrue(success); - } - - @Test - public void testInsertThenSelect_Happy() throws Exception { + public void testInsertThenSelect_Happy(){ // test insert long startTimestamp = System.currentTimeMillis(); - assertTrue(androidEventHistoryDatabase.createTable(new String[] {"eventHash", "timestamp"}, new - DatabaseService.Database.ColumnDataType[] {INTEGER, INTEGER}, - null)); for (int i = 0; i < 10; i++) { assertTrue(androidEventHistoryDatabase.insert(1234567890 + i)); @@ -80,24 +56,21 @@ public void testInsertThenSelect_Happy() throws Exception { long endTimestamp = System.currentTimeMillis(); // test select - final DatabaseService.QueryResult result = androidEventHistoryDatabase.select(1234567890 + 5, 0, + final Cursor cursor = androidEventHistoryDatabase.select(1234567890 + 5, 0, System.currentTimeMillis()); // verify - String count = result.getString(0); - String oldest = result.getString(1); - String newest = result.getString(2); + String count = cursor.getString(0); + String oldest = cursor.getString(1); + String newest = cursor.getString(2); assertEquals("1", count); assertTrue(TestUtils.almostEqual(Long.parseLong(oldest), startTimestamp, 1000)); assertTrue(TestUtils.almostEqual(Long.parseLong(newest), endTimestamp, 1000)); } @Test - public void testInsertThenDelete_Happy() throws Exception { + public void testInsertThenDelete_Happy(){ // test insert long startTimestamp = System.currentTimeMillis(); - assertTrue(androidEventHistoryDatabase.createTable(new String[] {"eventHash", "timestamp"}, new - DatabaseService.Database.ColumnDataType[] {INTEGER, INTEGER}, - null)); for (int i = 0; i < 15; i++) { assertTrue(androidEventHistoryDatabase.insert(1111111111)); @@ -107,13 +80,16 @@ public void testInsertThenDelete_Happy() throws Exception { assertTrue(androidEventHistoryDatabase.insert(222222222)); } - long dbSize = DatabaseUtils.queryNumEntries(((AndroidEventHistoryDatabase) androidEventHistoryDatabase).getDatabase(), - "Events"); + String dbPath = "/data/data/com.adobe.marketing.mobile.test/cache/com.adobe.marketing.db.eventhistory"; + SQLiteDatabase database = SQLiteDatabaseHelper.openDatabase(dbPath, SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE); + long dbSize = DatabaseUtils.queryNumEntries(database,"Events"); + SQLiteDatabaseHelper.closeDatabase(database); assertEquals(25, dbSize); // test delete int deleteCount = androidEventHistoryDatabase.delete(1111111111, startTimestamp, System.currentTimeMillis()); - dbSize = DatabaseUtils.queryNumEntries(((AndroidEventHistoryDatabase) androidEventHistoryDatabase).getDatabase(), - "Events"); + database = SQLiteDatabaseHelper.openDatabase(dbPath, SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE); + dbSize = DatabaseUtils.queryNumEntries(database, "Events"); + SQLiteDatabaseHelper.closeDatabase(database); assertEquals(15, deleteCount); assertEquals(10, dbSize); } diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/internal/eventhub/history/AndroidEventHistoryTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/internal/eventhub/history/AndroidEventHistoryTests.java new file mode 100644 index 000000000..2a70c50e5 --- /dev/null +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/internal/eventhub/history/AndroidEventHistoryTests.java @@ -0,0 +1,189 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.internal.eventhub.history; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.content.Context; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.TestUtils; +import com.adobe.marketing.mobile.services.ServiceProvider; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +@RunWith(AndroidJUnit4.class) +public class AndroidEventHistoryTests { + private AndroidEventHistory androidEventHistory; + private HashMap data; + + @Before + public void beforeEach() { + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + ServiceProvider.getInstance().setContext(context); + TestUtils.deleteAllFilesInCacheDir(context); + + try { + androidEventHistory = new AndroidEventHistory(); + } catch (EventHistoryDatabaseCreationException e) { + fail(e.getLocalizedMessage()); + } + + data = new HashMap() { + { + put("key", "value"); + } + }; + } + + @Test + public void testRecordEvent() throws InterruptedException { + // setup + final CountDownLatch latch = new CountDownLatch(1); + final boolean[] result = new boolean[1]; + EventHistoryResultHandler handler = new EventHistoryResultHandler() { + @Override + public void call(Boolean value) { + result[0] = value; + latch.countDown(); + } + }; + + + Event event = new Event.Builder("name", "type", "source") + .setEventData(data) + .build(); + // test + androidEventHistory.recordEvent(event, handler); + latch.await(); + // verify record event successful + assertTrue(result[0]); + } + + @Test + public void testGetEvents() throws Exception { + // setup + final CountDownLatch latch = new CountDownLatch(5); + final CountDownLatch latch2 = new CountDownLatch(1); + final int[] result = new int[1]; + EventHistoryResultHandler handler = new + EventHistoryResultHandler() { + @Override + public void call(Boolean result) { + assertTrue(result); + latch.countDown(); + } + }; + EventHistoryResultHandler handler2 = new EventHistoryResultHandler() { + @Override + public void call(Integer value) { + result[0] = value; + latch2.countDown(); + } + }; + + for (int i = 0; i < 5; i++) { + Event event = new Event.Builder("name", "type", "source") + .setEventData(data) + .build(); + androidEventHistory.recordEvent(event, handler); + } + + latch.await(); + // test + EventHistoryRequest[] requests = new EventHistoryRequest[1]; + HashMap mask = new HashMap<>(); + mask.put("key", "value"); + EventHistoryRequest request = new EventHistoryRequest(mask, 0, System.currentTimeMillis()); + requests[0] = request; + androidEventHistory.getEvents(requests, false, handler2); + latch2.await(); + // verify get events returns 5 events + assertEquals(5, result[0]); + } + + @Test + public void testDeleteEvents() throws Exception { + // setup + final CountDownLatch latch = new CountDownLatch(5); + final CountDownLatch latch2 = new CountDownLatch(10); + final CountDownLatch latch3 = new CountDownLatch(1); + final int[] result = new int[1]; + EventHistoryResultHandler handler = new + EventHistoryResultHandler() { + @Override + public void call(Boolean result) { + assertTrue(result); + latch.countDown(); + } + }; + EventHistoryResultHandler handler2 = new + EventHistoryResultHandler() { + @Override + public void call(Boolean result) { + assertTrue(result); + latch2.countDown(); + } + }; + EventHistoryResultHandler handler3 = new EventHistoryResultHandler() { + @Override + public void call(Integer value) { + result[0] = value; + latch3.countDown(); + } + }; + + for (int i = 0; i < 5; i++) { + Event event = new Event.Builder("name", "type", "source") + .setEventData(data) + .build(); + androidEventHistory.recordEvent(event, handler); + } + + latch.await(); + Map data2 = new HashMap() { + { + put("key2", "value2"); + } + }; + + for (int i = 0; i < 10; i++) { + Event event = new Event.Builder("name", "type", "source") + .setEventData(data2) + .build(); + androidEventHistory.recordEvent(event, handler2); + } + + latch2.await(); + // test + EventHistoryRequest[] requests = new EventHistoryRequest[1]; + HashMap mask = new HashMap<>(); + mask.put("key2", "value2"); + EventHistoryRequest request = new EventHistoryRequest(mask, 0, 0); + requests[0] = request; + androidEventHistory.deleteEvents(requests, handler3); + latch3.await(); + // verify 10 events deleted + assertEquals(10, result[0]); + } +} diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDatabaseHelperTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/internal/utility/SQLiteDatabaseHelperTests.java similarity index 76% rename from code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDatabaseHelperTests.java rename to code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/internal/utility/SQLiteDatabaseHelperTests.java index 9d27901f2..8b1e890de 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDatabaseHelperTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/internal/utility/SQLiteDatabaseHelperTests.java @@ -9,43 +9,40 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.services; +package com.adobe.marketing.mobile.internal.utility; import android.content.ContentValues; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; - +import com.adobe.marketing.mobile.services.DataEntity; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; - import java.io.File; import java.util.HashMap; import java.util.List; import java.util.Map; @RunWith(AndroidJUnit4.class) -public class SqliteDatabaseHelperTests { +public class SQLiteDatabaseHelperTests { private static final String TABLE_NAME = "TB_AEP_DATA_ENTITY"; private static final String TB_KEY_UNIQUE_IDENTIFIER = "uniqueIdentifier"; private static final String TB_KEY_TIMESTAMP = "timestamp"; private static final String TB_KEY_DATA = "data"; - private SQLiteDatabaseHelper sqLiteDatabaseHelper; private String dbPath; @Before public void setUp() { dbPath = new File(InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(), "test.sqlite").getPath(); - sqLiteDatabaseHelper = new SQLiteDatabaseHelper(); createTable(); } @After public void dispose() { - sqLiteDatabaseHelper.clearTable(dbPath, TABLE_NAME); + SQLiteDatabaseHelper.clearTable(dbPath, TABLE_NAME); } private void createTable() { @@ -55,13 +52,13 @@ private void createTable() { "timestamp INTEGER NOT NULL, " + "data TEXT);"; - sqLiteDatabaseHelper.createTableIfNotExist(dbPath, tableCreationQuery); + SQLiteDatabaseHelper.createTableIfNotExist(dbPath, tableCreationQuery); } @Test public void testTableIsEmptyInitially() { //Action - int size = sqLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME); + int size = SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME); //Assert Assert.assertEquals(size, 0); @@ -77,10 +74,10 @@ public void testAddData_Success() { row.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); //Action - Assert.assertTrue(sqLiteDatabaseHelper.insertRow(dbPath, TABLE_NAME, row)); + Assert.assertTrue(SQLiteDatabaseHelper.insertRow(dbPath, TABLE_NAME, row)); //Assert - Assert.assertEquals(sqLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME), 1); + Assert.assertEquals(SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME), 1); } @Test @@ -95,10 +92,10 @@ public void testAddData_Failure() { String incorrectDbPath = "incorrect_db_path"; //Action - Assert.assertFalse(sqLiteDatabaseHelper.insertRow(incorrectDbPath, TABLE_NAME, row)); + Assert.assertFalse(SQLiteDatabaseHelper.insertRow(incorrectDbPath, TABLE_NAME, row)); //Assert - Assert.assertEquals(sqLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME), 0); + Assert.assertEquals(SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME), 0); } @Test @@ -112,9 +109,9 @@ public void testQueryDb_Success() { row.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); //Action - Assert.assertTrue(sqLiteDatabaseHelper.insertRow(dbPath, TABLE_NAME, row)); + Assert.assertTrue(SQLiteDatabaseHelper.insertRow(dbPath, TABLE_NAME, row)); - List contentValues = sqLiteDatabaseHelper.query(dbPath, TABLE_NAME, new String[] {TB_KEY_UNIQUE_IDENTIFIER, TB_KEY_TIMESTAMP, TB_KEY_DATA}, + List contentValues = SQLiteDatabaseHelper.query(dbPath, TABLE_NAME, new String[] {TB_KEY_UNIQUE_IDENTIFIER, TB_KEY_TIMESTAMP, TB_KEY_DATA}, 1); //Assert @@ -137,9 +134,9 @@ public void testQueryDb_Failure() { String incorrectDbPath = "incorrect_db_path"; //Action - Assert.assertFalse(sqLiteDatabaseHelper.insertRow(incorrectDbPath, TABLE_NAME, row)); + Assert.assertFalse(SQLiteDatabaseHelper.insertRow(incorrectDbPath, TABLE_NAME, row)); - List contentValues = sqLiteDatabaseHelper.query(incorrectDbPath, TABLE_NAME, new String[] {TB_KEY_UNIQUE_IDENTIFIER, TB_KEY_TIMESTAMP, TB_KEY_DATA}, + List contentValues = SQLiteDatabaseHelper.query(incorrectDbPath, TABLE_NAME, new String[] {TB_KEY_UNIQUE_IDENTIFIER, TB_KEY_TIMESTAMP, TB_KEY_DATA}, 1); //Assert @@ -157,9 +154,9 @@ public void testGetTableSize_Success() { row.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); //Action - Assert.assertTrue(sqLiteDatabaseHelper.insertRow(dbPath, TABLE_NAME, row)); + Assert.assertTrue(SQLiteDatabaseHelper.insertRow(dbPath, TABLE_NAME, row)); - int tableSize = sqLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME); + int tableSize = SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME); //Assert Assert.assertEquals(tableSize, 1); @@ -179,10 +176,10 @@ public void testGetTableSize_Failure() { final String incorrectDbPath = "incorrect_database_path"; //Action - Assert.assertFalse(sqLiteDatabaseHelper.insertRow(incorrectDbPath, TABLE_NAME, row)); + Assert.assertFalse(SQLiteDatabaseHelper.insertRow(incorrectDbPath, TABLE_NAME, row)); //Action - Assert.assertEquals(sqLiteDatabaseHelper.getTableSize(incorrectDbPath, TABLE_NAME), 0); + Assert.assertEquals(SQLiteDatabaseHelper.getTableSize(incorrectDbPath, TABLE_NAME), 0); } @Test @@ -196,12 +193,12 @@ public void testRemoveRows_Success() { row.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); //Action - Assert.assertTrue(sqLiteDatabaseHelper.insertRow(dbPath, TABLE_NAME, row)); - Assert.assertEquals(1, sqLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); + Assert.assertTrue(SQLiteDatabaseHelper.insertRow(dbPath, TABLE_NAME, row)); + Assert.assertEquals(1, SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); - sqLiteDatabaseHelper.removeRows(dbPath, TABLE_NAME, "id", 1); + SQLiteDatabaseHelper.removeRows(dbPath, TABLE_NAME, "id", 1); - int tableSize = sqLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME); + int tableSize = SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME); //Assert Assert.assertEquals(tableSize, 0); @@ -218,13 +215,13 @@ public void testRemoveRows_Failure() { row.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); //Action - Assert.assertTrue(sqLiteDatabaseHelper.insertRow(dbPath, TABLE_NAME, row)); - Assert.assertEquals(1, sqLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); + Assert.assertTrue(SQLiteDatabaseHelper.insertRow(dbPath, TABLE_NAME, row)); + Assert.assertEquals(1, SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); final String incorrectDbPath = "incorrect_db_path"; - Assert.assertEquals(-1, sqLiteDatabaseHelper.removeRows(incorrectDbPath, TABLE_NAME, "id", 1)); - Assert.assertEquals(1, sqLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); + Assert.assertEquals(-1, SQLiteDatabaseHelper.removeRows(incorrectDbPath, TABLE_NAME, "id", 1)); + Assert.assertEquals(1, SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); } @Test @@ -238,14 +235,14 @@ public void testClearTable_Success() { row.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); //Action - Assert.assertTrue(sqLiteDatabaseHelper.insertRow(dbPath, TABLE_NAME, row)); - Assert.assertEquals(1, sqLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); + Assert.assertTrue(SQLiteDatabaseHelper.insertRow(dbPath, TABLE_NAME, row)); + Assert.assertEquals(1, SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); - sqLiteDatabaseHelper.clearTable(dbPath, TABLE_NAME); + SQLiteDatabaseHelper.clearTable(dbPath, TABLE_NAME); //Assert - Assert.assertTrue(sqLiteDatabaseHelper.clearTable(dbPath, TABLE_NAME)); - Assert.assertEquals(0, sqLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); + Assert.assertTrue(SQLiteDatabaseHelper.clearTable(dbPath, TABLE_NAME)); + Assert.assertEquals(0, SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); } @Test @@ -259,14 +256,14 @@ public void testClearTable_Failure() { row.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); //Action - Assert.assertTrue(sqLiteDatabaseHelper.insertRow(dbPath, TABLE_NAME, row)); - Assert.assertEquals(1, sqLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); + Assert.assertTrue(SQLiteDatabaseHelper.insertRow(dbPath, TABLE_NAME, row)); + Assert.assertEquals(1, SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); String incorrectDBPath = "incorrect_database_path"; //Assert - Assert.assertFalse(sqLiteDatabaseHelper.clearTable(incorrectDBPath, TABLE_NAME)); - Assert.assertEquals(1, sqLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); + Assert.assertFalse(SQLiteDatabaseHelper.clearTable(incorrectDBPath, TABLE_NAME)); + Assert.assertEquals(1, SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); } } diff --git a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakeEventHistory.java b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakeEventHistory.java index 9a8f7d628..8e3559f8b 100644 --- a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakeEventHistory.java +++ b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakeEventHistory.java @@ -1,94 +1,98 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import java.util.*; - -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnDataType.INTEGER; - -public class FakeEventHistory implements EventHistory { - FakeEventHistoryDatabase fakeEventHistoryDatabase = new FakeEventHistoryDatabase(); - - FakeEventHistory() { - fakeEventHistoryDatabase.openDatabase(); - fakeEventHistoryDatabase.createTable(new String[] {"eventHash", "timestamp"}, new - DatabaseService.Database.ColumnDataType[] {INTEGER, INTEGER}, - null); - } - - @Override - public void recordEvent(Event event, EventHistoryResultHandler handler) { - handler.call(fakeEventHistoryDatabase.insert(event.getData().toFnv1aHash(event.getMask()))); - } - - @Override - public void getEvents(EventHistoryRequest[] eventHistoryRequests, boolean enforceOrder, - EventHistoryResultHandler handler) { - long[] previousEventOldestOccurrence = {0L}; - final int[] foundEventCount = {0}; - - for (EventHistoryRequest request : eventHistoryRequests) { - long from = (enforceOrder - && previousEventOldestOccurrence[0] != 0) ? previousEventOldestOccurrence[0] : request.fromDate; - long to = request.toDate == 0 ? System.currentTimeMillis() : request.toDate; - Map flattenedMask = EventDataFlattener.getFlattenedEventDataMask(request.mask); - SortedMap sortedMap = new TreeMap<>(flattenedMask); - long eventHash = StringEncoder.convertMapToDecimalHash(sortedMap); - DatabaseService.QueryResult result = fakeEventHistoryDatabase.select(eventHash, from, to); - - try { - result.moveToFirst(); - - if (result.getInt(0) != 0) { - previousEventOldestOccurrence[0] = result.getLong(1); - - if (enforceOrder) { - foundEventCount[0]++; - } else { - foundEventCount[0] += result.getInt(0); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - // for ordered searches, if found event count matches the total number of requests, then all requests were found. return 1 / true. - if (enforceOrder) { - if (foundEventCount[0] == eventHistoryRequests.length) { - handler.call(1); - } else { - handler.call(0); - } - } else { // for "any" search, return total number of matching events - handler.call(foundEventCount[0]); - } - } - - @Override - public void deleteEvents(EventHistoryRequest[] eventHistoryRequests, EventHistoryResultHandler handler) { - final int[] deleteCount = {0}; - - for (EventHistoryRequest request : eventHistoryRequests) { - // if no "from" date is provided, delete from the beginning of the database - long from = request.fromDate == 0 ? 0 : request.fromDate; - // if no "to" date is provided, delete until the end of the database - long to = request.toDate == 0 ? System.currentTimeMillis() : request.toDate; - Map flattenedMask = EventDataFlattener.getFlattenedEventDataMask(request.mask); - SortedMap sortedMap = new TreeMap<>(flattenedMask); - long eventHash = StringEncoder.convertMapToDecimalHash(sortedMap); - deleteCount[0] += fakeEventHistoryDatabase.delete(eventHash, from, to); - } - - handler.call(deleteCount[0]); - } -} \ No newline at end of file +///* +// Copyright 2022 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may obtain a copy +// of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under +// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +// OF ANY KIND, either express or implied. See the License for the specific language +// governing permissions and limitations under the License. +// */ +// +//package com.adobe.marketing.mobile; +// +//import java.util.*; +// +//import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnDataType.INTEGER; +// +//import com.adobe.marketing.mobile.internal.eventhub.history.EventHistory; +//import com.adobe.marketing.mobile.internal.eventhub.history.EventHistoryResultHandler; +//import com.adobe.marketing.mobile.internal.utility.StringEncoder; +// +//public class FakeEventHistory implements EventHistory { +// FakeEventHistoryDatabase fakeEventHistoryDatabase = new FakeEventHistoryDatabase(); +// +// FakeEventHistory() { +// fakeEventHistoryDatabase.openDatabase(); +// fakeEventHistoryDatabase.createTable(new String[] {"eventHash", "timestamp"}, new +// DatabaseService.Database.ColumnDataType[] {INTEGER, INTEGER}, +// null); +// } +// +// @Override +// public void recordEvent(Event event, EventHistoryResultHandler handler) { +// handler.call(fakeEventHistoryDatabase.insert(event.getData().toFnv1aHash(event.getMask()))); +// } +// +// @Override +// public void getEvents(EventHistoryRequest[] eventHistoryRequests, boolean enforceOrder, +// EventHistoryResultHandler handler) { +// long[] previousEventOldestOccurrence = {0L}; +// final int[] foundEventCount = {0}; +// +// for (EventHistoryRequest request : eventHistoryRequests) { +// long from = (enforceOrder +// && previousEventOldestOccurrence[0] != 0) ? previousEventOldestOccurrence[0] : request.fromDate; +// long to = request.toDate == 0 ? System.currentTimeMillis() : request.toDate; +// Map flattenedMask = EventDataFlattener.getFlattenedEventDataMask(request.mask); +// SortedMap sortedMap = new TreeMap<>(flattenedMask); +// long eventHash = StringEncoder.convertMapToDecimalHash(sortedMap); +// DatabaseService.QueryResult result = fakeEventHistoryDatabase.select(eventHash, from, to); +// +// try { +// result.moveToFirst(); +// +// if (result.getInt(0) != 0) { +// previousEventOldestOccurrence[0] = result.getLong(1); +// +// if (enforceOrder) { +// foundEventCount[0]++; +// } else { +// foundEventCount[0] += result.getInt(0); +// } +// } +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } +// +// // for ordered searches, if found event count matches the total number of requests, then all requests were found. return 1 / true. +// if (enforceOrder) { +// if (foundEventCount[0] == eventHistoryRequests.length) { +// handler.call(1); +// } else { +// handler.call(0); +// } +// } else { // for "any" search, return total number of matching events +// handler.call(foundEventCount[0]); +// } +// } +// +// @Override +// public void deleteEvents(EventHistoryRequest[] eventHistoryRequests, EventHistoryResultHandler handler) { +// final int[] deleteCount = {0}; +// +// for (EventHistoryRequest request : eventHistoryRequests) { +// // if no "from" date is provided, delete from the beginning of the database +// long from = request.fromDate == 0 ? 0 : request.fromDate; +// // if no "to" date is provided, delete until the end of the database +// long to = request.toDate == 0 ? System.currentTimeMillis() : request.toDate; +// Map flattenedMask = EventDataFlattener.getFlattenedEventDataMask(request.mask); +// SortedMap sortedMap = new TreeMap<>(flattenedMask); +// long eventHash = StringEncoder.convertMapToDecimalHash(sortedMap); +// deleteCount[0] += fakeEventHistoryDatabase.delete(eventHash, from, to); +// } +// +// handler.call(deleteCount[0]); +// } +//} \ No newline at end of file diff --git a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakeEventHistoryDatabase.java b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakeEventHistoryDatabase.java index 885ca1de7..f155cadaa 100644 --- a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakeEventHistoryDatabase.java +++ b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakeEventHistoryDatabase.java @@ -1,320 +1,323 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import java.sql.*; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnDataType; -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnConstraint; - -public class FakeEventHistoryDatabase implements EventHistoryDatabase { - private Connection connection; - private final Object dbMutex = new Object(); - List databases; - private final static String TABLE_NAME = "TestEvents"; - private static final String COLUMN_HASH = "eventHash"; - private static final String COLUMN_TIMESTAMP = "timestamp"; - private static final String COUNT = "count"; - private static final String OLDEST = "oldest"; - private static final String NEWEST = "newest"; - - public FakeEventHistoryDatabase() { - synchronized (dbMutex) { - initializeConnection(); - databases = new ArrayList<>(); - } - } - - @Override - public boolean openDatabase() { - synchronized (dbMutex) { - if (databases.size() == 0) { - databases.add(new FakeEventHistoryDatabase()); - return true; - } - - // database wasn't opened as one already exists - return false; - } - } - - @Override - public boolean deleteDatabase() { - synchronized (dbMutex) { - if (databases.size() > 0) { - databases.remove(0); - return true; - } - - return false; - } - } - - // dataTypes is unused as the created table will use BIGINT for its data types - @Override - public boolean createTable(final String[] columnNames, final ColumnDataType[] dataTypes, - final List> columnConstraints) { - PreparedStatement stmt = null; - - synchronized (dbMutex) { - initializeConnection(); - - try { - String createTableQuery = QueryStringBuilder.getCreateTableQueryString(TABLE_NAME, columnNames, columnConstraints); - stmt = connection.prepareStatement(createTableQuery); - stmt.executeUpdate(); - return true; - } catch (SQLException e) { - return false; - } finally { - if (stmt != null) { - try { - stmt.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - } - } - } - - @Override - public FakeQueryResult select(final long hash, final long from, final long to) { - PreparedStatement stmt; - - synchronized (dbMutex) { - try { - StringBuilder sb = new StringBuilder(); - sb.append("SELECT "); - sb.append(COUNT + "(*) as " + COUNT + ", "); - sb.append("min(" + COLUMN_TIMESTAMP + ") as " + OLDEST + ", "); - sb.append("max(" + COLUMN_TIMESTAMP + ") as " + NEWEST); - sb.append(" FROM " + TABLE_NAME + " "); - sb.append(" WHERE " + COLUMN_HASH + "=" + hash); - sb.append(" AND " + COLUMN_TIMESTAMP + " >= " + from); - sb.append(" AND " + COLUMN_TIMESTAMP + " <= " + to); - - stmt = connection.prepareStatement(sb.toString(), ResultSet.TYPE_SCROLL_SENSITIVE, - ResultSet.CONCUR_READ_ONLY); - ResultSet resultSet = stmt.executeQuery(); - - return new FakeQueryResult(resultSet); - } catch (SQLException e) { - return null; - } - } - } - - private void sqlBindStatement(final String sb, final Map values) throws SQLException { - PreparedStatement stmt = (PreparedStatement) connection.prepareStatement(sb); - - int index = 0; - - for (Map.Entry entry : values.entrySet()) { - Object ob = entry.getValue(); - index += 1; - - if (ob instanceof String) { - stmt.setString(index, ob.toString()); - } else if (ob instanceof Integer) { - stmt.setInt(index, ((Integer) ob).intValue()); - } else if (ob instanceof Long) { - stmt.setLong(index, ((Long) ob).longValue()); - } else if (ob instanceof Double) { - stmt.setDouble(index, ((Double) ob).doubleValue()); - } else if (ob instanceof Boolean) { - stmt.setBoolean(index, ((Boolean) ob).booleanValue()); - } else if (ob instanceof Float) { - stmt.setFloat(index, ((Float) ob).floatValue()); - } - } - - stmt.executeUpdate(); - } - - public int rowCount = 0; - @Override - public boolean insert(final long hash) { - synchronized (dbMutex) { - HashMap values = new HashMap<>(); - values.put(COLUMN_HASH, hash); - values.put(COLUMN_TIMESTAMP, System.currentTimeMillis()); - - try { - StringBuilder sb = new StringBuilder(); - sb.append("INSERT INTO ").append(TABLE_NAME); - sb.append("("); - - for (Map.Entry entry : values.entrySet()) { - sb.append(entry.getKey()).append(", "); - } - - if (values.size() > 0) { - sb.delete(sb.length() - 2, sb.length()); - } - - sb.append(") VALUES ("); - - for (Map.Entry entry : values.entrySet()) { - sb.append("?, "); - } - - if (values.size() > 0) { - sb.delete(sb.length() - 2, sb.length()); - } - - sb.append(")"); - sqlBindStatement(sb.toString(), values); - rowCount++; - - return true; - } catch (SQLException e) { - e.printStackTrace(); - return false; - } - } - } - - @Override - public int delete (final long hash, final long from, final long to) { - String newWhereClause = COLUMN_HASH + " = " + hash - + " AND " + COLUMN_TIMESTAMP + " >= " + from - + " AND " + COLUMN_TIMESTAMP + " <= " + to; - - PreparedStatement stmt; - - synchronized (dbMutex) { - try { - String query = "DELETE FROM " + TABLE_NAME; - - if (newWhereClause != null) { - query += " WHERE " + newWhereClause; - } - - stmt = connection.prepareStatement(query.toString()); - int deleteCount = stmt.executeUpdate(); - rowCount -= deleteCount; - - return deleteCount; - } catch (SQLException e) { - return 0; - } - } - } - - public boolean closeWasCalled = false; - @Override - public void close() { - synchronized (dbMutex) { - closeWasCalled = true; - - try { - this.connection.close(); - } catch (Exception e) { - } - } - } - - private void initializeConnection() { - try { - if (connection == null || connection.isClosed()) { - this.connection = DriverManager.getConnection("jdbc:h2:mem:"); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - // copied from Android code - private static class QueryStringBuilder { - - private static final Map COLUMN_CONSTRAINT_STRING_MAP = new - HashMap() { - { - put(ColumnConstraint.PRIMARY_KEY, "PRIMARY KEY"); - put(ColumnConstraint.AUTOINCREMENT, "AUTO_INCREMENT"); - put(ColumnConstraint.NOT_NULL, "NOT NULL"); - put(ColumnConstraint.UNIQUE, "UNIQUE"); - } - }; - - static String getCreateTableQueryString(final String name, - final String[] columnNames, - final List> columnConstraints) throws SQLException { - - if (StringUtils.isNullOrEmpty(name) - || columnNames == null || columnNames.length == 0 - || (columnConstraints != null && !columnConstraints.isEmpty() && columnConstraints.size() != columnNames.length)) { - return null; - } - - List columnConstraintsList = getColumnConstraints(columnConstraints); - - StringBuilder createTableQueryBuilder = new StringBuilder(); - createTableQueryBuilder.append("CREATE TABLE IF NOT EXISTS "); - createTableQueryBuilder.append(name); - createTableQueryBuilder.append("("); - - for (int columnIndex = 0; columnIndex < columnNames.length; columnIndex++) { - createTableQueryBuilder.append(columnNames[columnIndex]); - createTableQueryBuilder.append(" BIGINT"); - - if (columnConstraintsList != null) { - String constraints = columnConstraintsList.get(columnIndex); - - if (constraints != null) { - createTableQueryBuilder.append(" "); - createTableQueryBuilder.append(columnConstraintsList.get(columnIndex)); - } - } - - if (columnIndex < columnNames.length - 1) { - createTableQueryBuilder.append(", "); - } - } - - createTableQueryBuilder.append(")"); - return createTableQueryBuilder.toString(); - } - - private static List getColumnConstraints(final List> - columnConstraints) throws SQLException { - if (columnConstraints == null || columnConstraints.isEmpty()) { - return null; - } - - List constraints = new ArrayList(); - - for (List constraintList : columnConstraints) { - if (constraintList == null || constraintList.isEmpty()) { - constraints.add(null); - continue; - } - - StringBuilder columnConstraintBuilder = new StringBuilder(); - - for (ColumnConstraint constraint : constraintList) { - columnConstraintBuilder.append(COLUMN_CONSTRAINT_STRING_MAP.get(constraint)); - columnConstraintBuilder.append(" "); - } - - constraints.add(columnConstraintBuilder.toString().trim()); - } - - return constraints; - } - - } -} \ No newline at end of file +///* +// Copyright 2022 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may obtain a copy +// of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under +// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +// OF ANY KIND, either express or implied. See the License for the specific language +// governing permissions and limitations under the License. +// */ +// +//package com.adobe.marketing.mobile; +// +//import java.sql.*; +//import java.util.ArrayList; +//import java.util.HashMap; +//import java.util.List; +//import java.util.Map; +//import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnDataType; +//import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnConstraint; +// +//import com.adobe.marketing.mobile.internal.eventhub.EventHistoryDatabase; +//import com.adobe.marketing.mobile.internal.eventhub.history.EventHistoryDatabase; +// +//public class FakeEventHistoryDatabase implements EventHistoryDatabase { +// private Connection connection; +// private final Object dbMutex = new Object(); +// List databases; +// private final static String TABLE_NAME = "TestEvents"; +// private static final String COLUMN_HASH = "eventHash"; +// private static final String COLUMN_TIMESTAMP = "timestamp"; +// private static final String COUNT = "count"; +// private static final String OLDEST = "oldest"; +// private static final String NEWEST = "newest"; +// +// public FakeEventHistoryDatabase() { +// synchronized (dbMutex) { +// initializeConnection(); +// databases = new ArrayList<>(); +// } +// } +// +// @Override +// public boolean openDatabase() { +// synchronized (dbMutex) { +// if (databases.size() == 0) { +// databases.add(new FakeEventHistoryDatabase()); +// return true; +// } +// +// // database wasn't opened as one already exists +// return false; +// } +// } +// +// @Override +// public boolean deleteDatabase() { +// synchronized (dbMutex) { +// if (databases.size() > 0) { +// databases.remove(0); +// return true; +// } +// +// return false; +// } +// } +// +// // dataTypes is unused as the created table will use BIGINT for its data types +// @Override +// public boolean createTable(final String[] columnNames, final ColumnDataType[] dataTypes, +// final List> columnConstraints) { +// PreparedStatement stmt = null; +// +// synchronized (dbMutex) { +// initializeConnection(); +// +// try { +// String createTableQuery = QueryStringBuilder.getCreateTableQueryString(TABLE_NAME, columnNames, columnConstraints); +// stmt = connection.prepareStatement(createTableQuery); +// stmt.executeUpdate(); +// return true; +// } catch (SQLException e) { +// return false; +// } finally { +// if (stmt != null) { +// try { +// stmt.close(); +// } catch (SQLException e) { +// e.printStackTrace(); +// } +// } +// } +// } +// } +// +// @Override +// public FakeQueryResult select(final long hash, final long from, final long to) { +// PreparedStatement stmt; +// +// synchronized (dbMutex) { +// try { +// StringBuilder sb = new StringBuilder(); +// sb.append("SELECT "); +// sb.append(COUNT + "(*) as " + COUNT + ", "); +// sb.append("min(" + COLUMN_TIMESTAMP + ") as " + OLDEST + ", "); +// sb.append("max(" + COLUMN_TIMESTAMP + ") as " + NEWEST); +// sb.append(" FROM " + TABLE_NAME + " "); +// sb.append(" WHERE " + COLUMN_HASH + "=" + hash); +// sb.append(" AND " + COLUMN_TIMESTAMP + " >= " + from); +// sb.append(" AND " + COLUMN_TIMESTAMP + " <= " + to); +// +// stmt = connection.prepareStatement(sb.toString(), ResultSet.TYPE_SCROLL_SENSITIVE, +// ResultSet.CONCUR_READ_ONLY); +// ResultSet resultSet = stmt.executeQuery(); +// +// return new FakeQueryResult(resultSet); +// } catch (SQLException e) { +// return null; +// } +// } +// } +// +// private void sqlBindStatement(final String sb, final Map values) throws SQLException { +// PreparedStatement stmt = (PreparedStatement) connection.prepareStatement(sb); +// +// int index = 0; +// +// for (Map.Entry entry : values.entrySet()) { +// Object ob = entry.getValue(); +// index += 1; +// +// if (ob instanceof String) { +// stmt.setString(index, ob.toString()); +// } else if (ob instanceof Integer) { +// stmt.setInt(index, ((Integer) ob).intValue()); +// } else if (ob instanceof Long) { +// stmt.setLong(index, ((Long) ob).longValue()); +// } else if (ob instanceof Double) { +// stmt.setDouble(index, ((Double) ob).doubleValue()); +// } else if (ob instanceof Boolean) { +// stmt.setBoolean(index, ((Boolean) ob).booleanValue()); +// } else if (ob instanceof Float) { +// stmt.setFloat(index, ((Float) ob).floatValue()); +// } +// } +// +// stmt.executeUpdate(); +// } +// +// public int rowCount = 0; +// @Override +// public boolean insert(final long hash) { +// synchronized (dbMutex) { +// HashMap values = new HashMap<>(); +// values.put(COLUMN_HASH, hash); +// values.put(COLUMN_TIMESTAMP, System.currentTimeMillis()); +// +// try { +// StringBuilder sb = new StringBuilder(); +// sb.append("INSERT INTO ").append(TABLE_NAME); +// sb.append("("); +// +// for (Map.Entry entry : values.entrySet()) { +// sb.append(entry.getKey()).append(", "); +// } +// +// if (values.size() > 0) { +// sb.delete(sb.length() - 2, sb.length()); +// } +// +// sb.append(") VALUES ("); +// +// for (Map.Entry entry : values.entrySet()) { +// sb.append("?, "); +// } +// +// if (values.size() > 0) { +// sb.delete(sb.length() - 2, sb.length()); +// } +// +// sb.append(")"); +// sqlBindStatement(sb.toString(), values); +// rowCount++; +// +// return true; +// } catch (SQLException e) { +// e.printStackTrace(); +// return false; +// } +// } +// } +// +// @Override +// public int delete (final long hash, final long from, final long to) { +// String newWhereClause = COLUMN_HASH + " = " + hash +// + " AND " + COLUMN_TIMESTAMP + " >= " + from +// + " AND " + COLUMN_TIMESTAMP + " <= " + to; +// +// PreparedStatement stmt; +// +// synchronized (dbMutex) { +// try { +// String query = "DELETE FROM " + TABLE_NAME; +// +// if (newWhereClause != null) { +// query += " WHERE " + newWhereClause; +// } +// +// stmt = connection.prepareStatement(query.toString()); +// int deleteCount = stmt.executeUpdate(); +// rowCount -= deleteCount; +// +// return deleteCount; +// } catch (SQLException e) { +// return 0; +// } +// } +// } +// +// public boolean closeWasCalled = false; +// @Override +// public void close() { +// synchronized (dbMutex) { +// closeWasCalled = true; +// +// try { +// this.connection.close(); +// } catch (Exception e) { +// } +// } +// } +// +// private void initializeConnection() { +// try { +// if (connection == null || connection.isClosed()) { +// this.connection = DriverManager.getConnection("jdbc:h2:mem:"); +// } +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } +// +// // copied from Android code +// private static class QueryStringBuilder { +// +// private static final Map COLUMN_CONSTRAINT_STRING_MAP = new +// HashMap() { +// { +// put(ColumnConstraint.PRIMARY_KEY, "PRIMARY KEY"); +// put(ColumnConstraint.AUTOINCREMENT, "AUTO_INCREMENT"); +// put(ColumnConstraint.NOT_NULL, "NOT NULL"); +// put(ColumnConstraint.UNIQUE, "UNIQUE"); +// } +// }; +// +// static String getCreateTableQueryString(final String name, +// final String[] columnNames, +// final List> columnConstraints) throws SQLException { +// +// if (StringUtils.isNullOrEmpty(name) +// || columnNames == null || columnNames.length == 0 +// || (columnConstraints != null && !columnConstraints.isEmpty() && columnConstraints.size() != columnNames.length)) { +// return null; +// } +// +// List columnConstraintsList = getColumnConstraints(columnConstraints); +// +// StringBuilder createTableQueryBuilder = new StringBuilder(); +// createTableQueryBuilder.append("CREATE TABLE IF NOT EXISTS "); +// createTableQueryBuilder.append(name); +// createTableQueryBuilder.append("("); +// +// for (int columnIndex = 0; columnIndex < columnNames.length; columnIndex++) { +// createTableQueryBuilder.append(columnNames[columnIndex]); +// createTableQueryBuilder.append(" BIGINT"); +// +// if (columnConstraintsList != null) { +// String constraints = columnConstraintsList.get(columnIndex); +// +// if (constraints != null) { +// createTableQueryBuilder.append(" "); +// createTableQueryBuilder.append(columnConstraintsList.get(columnIndex)); +// } +// } +// +// if (columnIndex < columnNames.length - 1) { +// createTableQueryBuilder.append(", "); +// } +// } +// +// createTableQueryBuilder.append(")"); +// return createTableQueryBuilder.toString(); +// } +// +// private static List getColumnConstraints(final List> +// columnConstraints) throws SQLException { +// if (columnConstraints == null || columnConstraints.isEmpty()) { +// return null; +// } +// +// List constraints = new ArrayList(); +// +// for (List constraintList : columnConstraints) { +// if (constraintList == null || constraintList.isEmpty()) { +// constraints.add(null); +// continue; +// } +// +// StringBuilder columnConstraintBuilder = new StringBuilder(); +// +// for (ColumnConstraint constraint : constraintList) { +// columnConstraintBuilder.append(COLUMN_CONSTRAINT_STRING_MAP.get(constraint)); +// columnConstraintBuilder.append(" "); +// } +// +// constraints.add(columnConstraintBuilder.toString().trim()); +// } +// +// return constraints; +// } +// +// } +//} \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidEventHistory.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidEventHistory.java deleted file mode 100644 index 7611ef0f5..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidEventHistory.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * The Android implementation of {@link EventHistory} which provides functionality for performing database - * operations on an {@link AndroidEventHistoryDatabase}. - */ -class AndroidEventHistory implements EventHistory { - private static final String LOG_TAG = "AndroidEventHistory"; - private static final int THREAD_POOL_SIZE = 1; - private static final int COUNT_INDEX = 0; - private static final int OLDEST_INDEX = 1; - private final AndroidEventHistoryDatabase androidEventHistoryDatabase; - private final ExecutorService executorService; - - /** - * Constructor. - */ - AndroidEventHistory() throws EventHistoryDatabaseCreationException { - androidEventHistoryDatabase = new AndroidEventHistoryDatabase(); - executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE); - } - - /** - * Record an event in the {@link AndroidEventHistoryDatabase}. - * - * @param event the {@link Event} to be recorded - * @param handler {@link EventHistoryResultHandler} a callback which will contain a {@code boolean} indicating if - * the database operation was successful - */ - public void recordEvent(final Event event, final EventHistoryResultHandler handler) { - final long fnv1aHash = event.getData().toFnv1aHash(event.getMask()); - - if (fnv1aHash == 0) { - Log.debug(LOG_TAG, " The event with name \"%s\" has a fnv1a hash equal to 0. The event will not be recorded.", - event.getName()); - return; - } - - executorService.submit(new Runnable() { - @Override - public void run() { - handler.call(androidEventHistoryDatabase.insert(fnv1aHash)); - } - }); - } - - /** - * Query the {@link AndroidEventHistoryDatabase} for {@link Event}s which match the contents of the - * {@link EventHistoryRequest} array. - * - * @param eventHistoryRequests an array of {@code EventHistoryRequest}s to be matched - * @param enforceOrder {@code boolean} if true, consecutive lookups will use the oldest timestamp from the previous event - * as their from date - * @param handler {@link EventHistoryResultHandler} containing the the total number of matching events in the {@code AndroidEventHistoryDatabase} - * if an "any" search was done. If an "ordered" search was done, the handler will contain a "1" - * if the event history requests were found in the order specified in the eventHistoryRequests array - * and a "0" if the events were not found in the order specified. - */ - @Override - public void getEvents(final EventHistoryRequest[] eventHistoryRequests, - final boolean enforceOrder, - final EventHistoryResultHandler handler) { - executorService.submit(new Runnable() { - @Override - public void run() { - long previousEventOldestOccurrence = 0L; - int foundEventCount = 0; - - for (final EventHistoryRequest request : eventHistoryRequests) { - final long from = (enforceOrder - && previousEventOldestOccurrence != 0) ? previousEventOldestOccurrence : request.fromDate; - final long to = request.toDate == 0 ? System.currentTimeMillis() : request.toDate; - final Map flattenedMask = EventDataFlattener.getFlattenedEventDataMask(request.mask); - final SortedMap sortedMap = new TreeMap<>(flattenedMask); - final long eventHash = StringEncoder.convertMapToDecimalHash(sortedMap); - final DatabaseService.QueryResult result = androidEventHistoryDatabase.select(eventHash, from, to); - - try { // columns are index 0: count, index 1: oldest, index 2: newest - result.moveToFirst(); - - if (result.getInt(COUNT_INDEX) != 0) { - previousEventOldestOccurrence = result.getLong(OLDEST_INDEX); - - if (enforceOrder) { - foundEventCount++; - } else { - foundEventCount += result.getInt(COUNT_INDEX); - } - } - } catch (final Exception exception) { - Log.debug(LOG_TAG, - "Exception occurred when attempting to retrieve events with eventHash %s from the EventHistoryDatabase: %s", - eventHash, exception.getMessage()); - } - } - - // for ordered searches, if found event count matches the total number of requests, then all requests were found. return 1 / true. - if (enforceOrder) { - if (foundEventCount == eventHistoryRequests.length) { - handler.call(1); - } else { // otherwise return 0 / false - handler.call(0); - } - } else { // for "any" search, return total number of matching events - handler.call(foundEventCount); - } - } - }); - } - - /** - * Delete rows from the {@link AndroidEventHistoryDatabase} that contain {@link Event}s which match the contents - * of the {@link EventHistoryRequest} array. - * - * @param eventHistoryRequests an array of {@code EventHistoryRequest}s to be deleted - * @param handler a callback which will be called with a {@code int} containing the total number of rows deleted from the - * {@code AndroidEventHistoryDatabase} - */ - @Override - public void deleteEvents(final EventHistoryRequest[] eventHistoryRequests, - final EventHistoryResultHandler handler) { - executorService.submit(new Runnable() { - @Override - public void run() { - int deletedRows = 0; - - for (final EventHistoryRequest request : eventHistoryRequests) { - // if no "from" date is provided, delete from the beginning of the database - final long from = request.fromDate == 0 ? 0 : request.fromDate; - // if no "to" date is provided, delete until the end of the database - final long to = request.toDate == 0 ? System.currentTimeMillis() : request.toDate; - final Map flattenedMask = EventDataFlattener.getFlattenedEventDataMask(request.mask); - final SortedMap sortedMap = new TreeMap<>(flattenedMask); - final long eventHash = StringEncoder.convertMapToDecimalHash(sortedMap); - deletedRows += androidEventHistoryDatabase.delete(eventHash, from, to); - } - - handler.call(deletedRows); - } - }); - } -} \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidEventHistoryDatabase.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidEventHistoryDatabase.java deleted file mode 100644 index a362a0b69..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidEventHistoryDatabase.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnDataType.INTEGER; - -import android.content.ContentValues; -import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteStatement; - -import com.adobe.marketing.mobile.DatabaseService.Database.ColumnConstraint; -import com.adobe.marketing.mobile.DatabaseService.Database.ColumnDataType; -import com.adobe.marketing.mobile.DatabaseService.QueryResult; -import com.adobe.marketing.mobile.services.ServiceProvider; - -import java.io.File; -import java.io.IOException; -import java.util.List; - -class AndroidEventHistoryDatabase implements EventHistoryDatabase { - private static final String LOG_TAG = "AndroidEventHistoryDatabase"; - private static final String DATABASE_NAME = "EventHistory"; - private static final String TABLE_NAME = "Events"; - private static final String COLUMN_HASH = "eventHash"; - private static final String COLUMN_TIMESTAMP = "timestamp"; - private static final String COUNT = "count"; - private static final String OLDEST = "oldest"; - private static final String NEWEST = "newest"; - - private final Object dbMutex = new Object(); - private SQLiteDatabase database; - private File databaseFile = null; - - /** - * Constructor. - * - * @throws {@link EventHistoryDatabaseCreationException} if any error occurred while creating the database - * or database table. - */ - AndroidEventHistoryDatabase() throws EventHistoryDatabaseCreationException { - // create the database file in the device cache directory - if (!openDatabase()) { - throw new EventHistoryDatabaseCreationException("An error occurred while opening the Android Event History database."); - } - - // create the "Events" table in the database - if (!createTable(new String[] {COLUMN_HASH, COLUMN_TIMESTAMP}, new - DatabaseService.Database.ColumnDataType[] {INTEGER, INTEGER}, - null)) { - throw new EventHistoryDatabaseCreationException("An error occurred while creating the \"Events\" table in the Android Event History database."); - } - } - - /** - * Opens an {@link EventHistoryDatabase} database file if it exists, otherwise creates a new one in the cache directory. - * - * @return {@code boolean} indicating whether the table already exists or the create table operation was successful - */ - @Override - public boolean openDatabase() { - final File applicationCacheDir = ServiceProvider.getInstance().getDeviceInfoService().getApplicationCacheDir(); - - if (applicationCacheDir != null) { - try { - final String cacheDirCanonicalPath = applicationCacheDir.getCanonicalPath(); - databaseFile = new File(cacheDirCanonicalPath + "/" + DATABASE_NAME); - } catch (final IOException e) { - Log.warning(LOG_TAG, "Failed to read %s database file (%s)", DATABASE_NAME, - (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getMessage())); - return false; - } - } - - synchronized (dbMutex) { - try { - database = SQLiteDatabase.openDatabase(databaseFile.getCanonicalPath(), - null, - SQLiteDatabase.NO_LOCALIZED_COLLATORS | SQLiteDatabase.CREATE_IF_NECESSARY); - return true; - } catch (final IOException e) { - Log.error(LOG_TAG, "Failed to open %s database (%s)", DATABASE_NAME, - (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getMessage())); - return false; - } - } - } - - /** - * Deletes an {@link EventHistoryDatabase} previously created in the cache directory, if it exists. - * - * @return {@code boolean} indicating whether the database file delete operation was successful - */ - @Override - public boolean deleteDatabase() { - // close the database before deleting it - close(); - - synchronized (dbMutex) { - if (databaseFile != null) { - try { - return databaseFile.delete(); - } catch (final SecurityException e) { - Log.error(LOG_TAG, "Failed to delete %s database (%s)", DATABASE_NAME, - (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getMessage())); - return false; - } - } - - return false; - } - } - - /** - * Create a table if it doesn't exist. - * - * @param columnNames {@code String[]} array containing column names - * @param columnDataTypes {@link ColumnDataType[]} array containing data types for each column - * @param columnConstraints {@link List} a list of lists containing column constraints - * for each table column - * @return {@code boolean} indicating whether the create table operation was successful - * @see ColumnConstraint - * @see ColumnDataType - */ - @Override - public boolean createTable(final String[] columnNames, final ColumnDataType[] columnDataTypes, - final List> columnConstraints) { - return createTable(columnNames, columnDataTypes, columnConstraints, false); - - } - - public boolean createTable(final String[] columnNames, final ColumnDataType[] columnDataTypes, - final List> columnConstraints, final boolean setColumnsDefault) { - if (columnNames == null || columnNames.length == 0 - || columnDataTypes == null || columnDataTypes.length == 0 - || columnDataTypes.length != columnNames.length || (columnConstraints != null - && columnConstraints.size() != columnNames.length)) { - Log.warning(LOG_TAG, "Failed to create table, one or more input parameters is invalid."); - return false; - } - - if (!databaseIsWritable()) { - Log.warning(LOG_TAG, "Failed to create table, database is not writeable."); - return false; - } - - synchronized (dbMutex) { - final String createTableQuery = QueryStringBuilder.getCreateTableQueryString(TABLE_NAME, columnNames, - columnDataTypes, - columnConstraints, setColumnsDefault); - final SQLiteStatement stmt = database.compileStatement(createTableQuery); - - try { - stmt.execute(); - stmt.close(); - Log.debug(LOG_TAG, "Table with name %s created.", TABLE_NAME); - return true; - } catch (final SQLException e) { - Log.warning(LOG_TAG, "Failed to create table (%s)", - (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getMessage())); - return false; - } finally { - stmt.close(); - } - } - } - - /** - * Insert a row into the database. Each row will contain a hash and a timestamp. - * - * @param hash {@code long} containing the 32-bit FNV-1a hashed representation of an Event's data - * @return a {@code boolean} which will contain the status of the database insert operation - */ - @Override - public boolean insert(final long hash) { - if (!databaseIsWritable()) { - return false; - } - - synchronized (dbMutex) { - try { - final ContentValues contentValues = new ContentValues(); - contentValues.put(COLUMN_HASH, hash); - contentValues.put(COLUMN_TIMESTAMP, System.currentTimeMillis()); - return database.insert(TABLE_NAME, null, contentValues) != -1; - } catch (final SQLException e) { - Log.warning(LOG_TAG, "Failed to insert rows into the table (%s)", - (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getMessage())); - return false; - } - } - } - - /** - * Queries the event history database to search for the existence of an event. - *

- * This method will count all records in the event history database that match the provided hash and are within - * the bounds of the provided from and to timestamps. - * If the "from" date is equal to 0, the search will use the beginning of event history as the lower bounds of the date range. - * If the "to" date is equal to 0, the search will use the current system timestamp as the upper bounds of the date range. - * The {@link EventHistoryResultHandler} will be called with a {@link DatabaseService.QueryResult} which contains the number of matching records, - * the oldest timestamp, and the newest timestamp for a matching event. - * If no database connection is available, the handler will be called with a null {@code DatabaseService.QueryResult}. - * - * @param hash {@code long} containing the 32-bit FNV-1a hashed representation of an Event's data - * @param from {@code long} a timestamp representing the lower bounds of the date range to use when searching for the hash - * @param to {@code long} a timestamp representing the upper bounds of the date range to use when searching for the hash - * @return a {@code DatabaseService.QueryResult} which will contain the matching events - */ - @Override - public QueryResult select(final long hash, final long from, final long to) { - // if the provided "to" date is equal to 0, use the current date - final long toValue = to == 0 ? System.currentTimeMillis() : to; - - synchronized (dbMutex) { - try { - final String[] whereArgs = new String[] {String.valueOf(hash), String.valueOf(from), String.valueOf(toValue)}; - final Cursor cursor = database.rawQuery( - "SELECT " + COUNT + "(*) as " + COUNT + ", " + - "min(" + COLUMN_TIMESTAMP + ") as " + OLDEST + ", " + - "max(" + COLUMN_TIMESTAMP + ") as " + NEWEST - + " FROM " + TABLE_NAME + " " - + " WHERE " + COLUMN_HASH + " = ?" - + " AND " + COLUMN_TIMESTAMP + " >= ?" - + " AND " + COLUMN_TIMESTAMP + " <= ?", - whereArgs); - cursor.moveToFirst(); - - return new AndroidCursor(cursor); - } catch (final SQLException e) { - Log.warning(LOG_TAG, "Failed to execute query (%s)", - (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getMessage())); - return null; - } - } - } - - /** - * Delete entries from the event history database. - * - * @param hash {@code long} containing the 32-bit FNV-1a hashed representation of an Event's data - * @param from {@code long} representing the lower bounds of the date range to use when searching for the hash - * @param to {@code long} representing the upper bounds of the date range to use when searching for the hash - * @return {@code int} containing the number of entries deleted for the given hash. - */ - @Override - public int delete (final long hash, final long from, final long to) { - if (!databaseIsWritable()) { - Log.debug("Event history database is not writeable. Delete failed for hash %s", Long.toString(hash)); - return 0; - } - - // if the provided "to" date is equal to 0, use the current date - final long toValue = to == 0 ? System.currentTimeMillis() : to; - - synchronized (dbMutex) { - try { - final String[] whereArgs = new String[] {String.valueOf(hash), String.valueOf(from), String.valueOf(toValue)}; - final int affectedRowsCount = database.delete(TABLE_NAME, - COLUMN_HASH + " = ?" - + " AND " + COLUMN_TIMESTAMP + " >= ?" - + " AND " + COLUMN_TIMESTAMP + " <= ?", - whereArgs); - Log.trace(LOG_TAG, "Count of rows deleted in table %s are %d", TABLE_NAME, affectedRowsCount); - - return affectedRowsCount; - } catch (final SQLException e) { - Log.debug(LOG_TAG, "Failed to delete table rows (%s)", - (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getMessage())); - return 0; - } - } - } - - /** - * Close this database. - */ - @Override - public void close() { - synchronized (dbMutex) { - database.close(); - } - } - - private boolean databaseIsWritable() { - synchronized (dbMutex) { - if (database == null) { - Log.debug(LOG_TAG, "%s (Database), unable to write", Log.UNEXPECTED_NULL_VALUE); - return false; - } - - if (!database.isOpen()) { - Log.debug(LOG_TAG, "Unable to write to database, it is not open"); - return false; - } - - if (database.isReadOnly()) { - Log.debug(LOG_TAG, "Unable to write to database, it is read-only"); - return false; - } - - return true; - } - } - - // for testing - SQLiteDatabase getDatabase() { - return database; - } -} \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Core.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Core.java index 30337c5a4..68dcd6b38 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Core.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Core.java @@ -11,596 +11,615 @@ package com.adobe.marketing.mobile; +import com.adobe.marketing.mobile.internal.eventhub.history.AndroidEventHistory; +import com.adobe.marketing.mobile.internal.eventhub.history.EventHistory; + import java.util.HashMap; import java.util.Map; class Core { - private static final String LOG_TAG = Core.class.getSimpleName(); - private boolean startActionCalled; - - EventHub eventHub; - - Core(final PlatformServices platformServices) { - this(platformServices, "undefined"); - } - - Core(final PlatformServices platformServices, final String coreVersion) { - Log.setLoggingService(platformServices.getLoggingService()); - eventHub = new EventHub("AMSEventHub", platformServices, coreVersion); - - try { - eventHub.registerModule(ConfigurationExtension.class, new ConfigurationModuleDetails(coreVersion)); - } catch (InvalidModuleException e) { - Log.error(LOG_TAG, "Failed to register Configuration extension (%s)", e); - } - - Log.trace(LOG_TAG, "Core initialization was successful"); - } - - Core(final PlatformServices platformServices, final EventHub eventHub) { - Log.setLoggingService(platformServices.getLoggingService()); - this.eventHub = eventHub; - Log.trace(LOG_TAG, "Core initialization was successful"); - } - - - // ------------------------------------ 3rd party extensions -------------------------------------- - - /** - * Registers an extension class which has {@code Extension} as parent. - * - * @param extensionClass a class whose parent is {@link Extension} - * @param errorCallback an optional {@link ExtensionErrorCallback} for the eventuality of an error, - * called when this method returns false - */ - void registerExtension(final Class extensionClass, - final ExtensionErrorCallback errorCallback) { - try { - eventHub.registerExtension(extensionClass); - } catch (InvalidModuleException e) { - Log.debug(LOG_TAG, "Core.registerExtension - Failed to register extension class %s (%s)", - extensionClass.getSimpleName(), e); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.UNEXPECTED_ERROR); - } - } - } - - /** - * Clones the provided {@code event} and dispatches it through the {@code EventHub}. - * Passes an {@link ExtensionError} to {@code errorCallback} if the event is null. - * - * @param event provided {@link Event} from the public API - * @param errorCallback callback to return {@link ExtensionError} if an error occurs - * @return {@code boolean} indicating if the event was dispatched or not - */ - boolean dispatchEvent(final Event event, final ExtensionErrorCallback errorCallback) { - if (event == null) { - Log.debug(LOG_TAG, "%s (Core.dispatchEvent) - The event was not dispatched", Log.UNEXPECTED_NULL_VALUE); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.EVENT_NULL); - } - - return false; - } - - eventHub.dispatch(event); - return true; - } - - /** - * This method will be used when the provided {@code Event} is used as a trigger and a response event is expected in return. - * The returned event needs to be sent using {@link #dispatchResponseEvent(Event, Event, ExtensionErrorCallback)} method. - *

- * Clones the provided {@code event}, registers a {@link OneTimeListener} for the response and dispatches it through the - * {@code EventHub}. Passes an {@link ExtensionError} to {@code errorCallback} if the event is null. - * - * @param event provided {@link Event} from the public API - * @param responseCallback {@link AdobeCallback} to be called with the response event received in the {@link OneTimeListener} - * @param errorCallback callback to return {@link ExtensionError} if an error occurs - * @return {@code boolean} indicating if the paired event was dispatched or not - * @see #dispatchResponseEvent - */ - boolean dispatchEventWithResponseCallback(final Event event, - final AdobeCallback responseCallback, - final ExtensionErrorCallback errorCallback) { - if (responseCallback == null) { - Log.debug(LOG_TAG, - "%s (Core.dispatchEventWithResponseCallback) - The event was not dispatched", Log.UNEXPECTED_NULL_VALUE); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.CALLBACK_NULL); - } - - return false; - } - - if (event == null) { - Log.debug(LOG_TAG, "%s (Core.dispatchEventWithResponseCallback) - The event was not dispatched", - Log.UNEXPECTED_NULL_VALUE); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.EVENT_NULL); - } - - return false; - } - - eventHub.registerOneTimeListener(event.getResponsePairID(), new Module.OneTimeListenerBlock() { - @Override - public void call(final Event e) { - responseCallback.call(e); - } - }); - eventHub.dispatch(event); - return true; - } - - /** - * This method will be used when the provided {@code Event} is used as a trigger and a response event is expected in return. - * The returned event needs to be sent using {@link #dispatchResponseEvent(Event, Event, ExtensionErrorCallback)} method. - *

- * Clones the provided {@code event}, registers a {@link OneTimeListener} for the response and dispatches it through the - * {@code EventHub} if the given {@link Event} and {@link AdobeCallbackWithError} are not null. - * The one-time listener will be unregistered by the event hub after the response event is received, or when event processing timeout (5000ms) occurs. - * - * @param event provided {@link Event} from the public API - * @param responseCallback {@link AdobeCallback} to be called with the response event received in the {@link OneTimeListener} - * @see #dispatchResponseEvent - */ - void dispatchEventWithResponseCallback(final Event event, - final AdobeCallbackWithError responseCallback) { - if (event == null || responseCallback == null) { - Log.debug(LOG_TAG, - "(Core.dispatchEventWithResponseCallback) - The event was not dispatched, the given Event or AdobeCallbackWithError is null"); - return; - } - - eventHub.registerOneTimeListener(event.getResponsePairID(), new Module.OneTimeListenerBlock() { - @Override - public void call(final Event e) { - responseCallback.call(e); - } - }, responseCallback); - eventHub.dispatch(event); - } - - /** - * This method will be used when a response event should be dispatched for a paired event that was previously sent - * using {@code dispatchEventWithResponseCallback} - *

- * Clones the provided {@code event}, sets the pair id associated with the request event's pair id and dispatches - * it through the {@link EventHub}. Passes an {@link ExtensionError} to {@code errorCallback} if the event is null. - * - * @param responseEvent provided response {@link Event} from the public API - * @param requestEvent the trigger {@link Event} for the dispatched event - * @param errorCallback callback to return {@link ExtensionError} if an error occurs - * @return {@code boolean} indicating if the response event was dispatched or not - * @see #dispatchEventWithResponseCallback - */ - boolean dispatchResponseEvent(final Event responseEvent, final Event requestEvent, - final ExtensionErrorCallback errorCallback) { - if (requestEvent == null) { - Log.debug(LOG_TAG, - "%s (Core.dispatchResponseEvent) - The response event was not dispatched", Log.UNEXPECTED_NULL_VALUE); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.EVENT_NULL); - } - - return false; - } - - if (responseEvent == null) { - Log.warning(LOG_TAG, "%s (Core.dispatchResponseEvent) - The response event was not dispatched", - Log.UNEXPECTED_NULL_VALUE); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.EVENT_NULL); - } - - return false; - } - - responseEvent.setPairId(requestEvent.getResponsePairID()); - eventHub.dispatch(responseEvent); - return true; - } - - /** - * Registers an event listener for the provided event type and source. - * - * @param eventType required parameter, the event type as a valid string (not null or empty) - * @param eventSource required parameter, the event source as a valid string (not null or empty) - * @param callback required parameter, {@link AdobeCallbackWithError#call(Object)} will be called when the event is heard - */ - void registerEventListener(final String eventType, final String eventSource, - final AdobeCallbackWithError callback) { - eventHub.registerEventListener(EventType.get(eventType), EventSource.get(eventSource), callback); - } - - - // ------------------------------ Configuration methods ------------------------------ - - /** - * Load remote configuration specified by the given application ID. - *

- * Configure the SDK by downloading the remote configuration file hosted on Adobe servers - * specified by the given application ID. The configuration file is cached once downloaded - * and used in subsequent calls to this API. If the remote file is updated after the first - * download, the updated file is downloaded and replaces the cached file. - *

- * The {@code appId} is preserved, and on application restarts, the remote configuration file specified by {@code appId} - * is downloaded and applied to the SDK. - *

- * On failure to download the remote configuration file, the SDK is configured using the cached - * file if it exists, or if no cache file exists then the existing configuration remains unchanged. - *

- * Calls to this API will replace any existing SDK configuration except those set using - * {@link #updateConfiguration(Map)} or {@link #setPrivacyStatus(MobilePrivacyStatus)}. Configuration updates - * made using {@link #updateConfiguration(Map)} and {@link #setPrivacyStatus(MobilePrivacyStatus)} - * are always applied on top of configuration changes made using this API. - * - * @param appId a unique identifier assigned to the app instance by the Adobe Mobile Services. It is automatically - * added to the Mobile configuration JSON file when downloaded from the Adobe Mobile Services UI and can be - * found in Manage App Settings. A value of {@code null} or empty {@code String} will clear the preserved value. - */ - void configureWithAppID(final String appId) { - EventData eventData = new EventData(); - eventData.putString(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID, appId); - final Event event = new Event.Builder("Configure with AppID", EventType.CONFIGURATION, - EventSource.REQUEST_CONTENT).setData(eventData).build(); - eventHub.dispatch(event); - } - - /** - * Load configuration from local file. - *

- * Configure the SDK by reading a local file containing the JSON configuration. On application relaunch, - * the configuration from the file at {@code filepath} is not preserved and this method must be called again if desired. - *

- * On failure to read the file or parse the JSON contents, the existing configuration remains unchanged. - *

- * Calls to this API will replace any existing SDK configuration except those set using - * {@link #updateConfiguration(Map)} or {@link #setPrivacyStatus(MobilePrivacyStatus)}. - * Configuration updates made using {@link #updateConfiguration(Map)} and {@link #setPrivacyStatus(MobilePrivacyStatus)} - * are always applied on top of configuration changes made using this API. - * - * @param filepath absolute path to a local configuration file. A value of {@code null} has no effect. - */ - void configureWithFileInPath(final String filepath) { - if (StringUtils.isNullOrEmpty(filepath)) { - Log.warning("Configuration", "Unable to configure with null or empty remoteURL"); - return; - } - - EventData eventData = new EventData(); - eventData.putString(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_FILE_PATH, filepath); - final Event event = new Event.Builder("Configure with FilePath", EventType.CONFIGURATION, - EventSource.REQUEST_CONTENT).setData(eventData).build(); - eventHub.dispatch(event); - } - - /** - * Load configuration from an asset file. - * @param fileName the name of the configure file in the assets folder. A value of {@code null} has no effect. - */ - void configureWithFileInAssets(final String fileName) { - if (StringUtils.isNullOrEmpty(fileName)) { - Log.warning("Configuration", "Unable to configure with null or empty file name"); - return; - } - - EventData eventData = new EventData(); - eventData.putString(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_ASSET_FILE, fileName); - final Event event = new Event.Builder("Configure with FilePath", EventType.CONFIGURATION, - EventSource.REQUEST_CONTENT).setData(eventData).build(); - eventHub.dispatch(event); - } - - /** - * Update specific configuration parameters. - *

- * Update the current SDK configuration with specific key/value pairs. Keys not found in the current - * configuration are added. Configuration updates are preserved and applied over existing or new - * configurations set by calling {@link #configureWithAppID(String)} or {@link #configureWithFileInPath(String)}, - * even across application restarts. - *

- * Using {@code null} values is allowed and effectively removes the configuration parameter from the current configuration. - * - * @param configMap configuration key/value pairs to be updated or added. A value of {@code null} has no effect. - */ - void updateConfiguration(final Map configMap) { - // Create a EventData Map - HashMap eventDataMap = new HashMap(); - Variant configMapVariant = Variant.fromTypedMap(configMap, PermissiveVariantSerializer.DEFAULT_INSTANCE); - eventDataMap.put(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_UPDATE_CONFIG, - configMapVariant); - EventData eventData = new EventData(eventDataMap); - final Event event = new Event.Builder("Configuration Update", EventType.CONFIGURATION, - EventSource.REQUEST_CONTENT).setData(eventData).build(); - eventHub.dispatch(event); - } - - /** - * Clear the changes made by {@link #updateConfiguration(Map)} to the initial configuration provided either by - * {@link #configureWithAppID(String)} or {@link #configureWithFileInPath(String)} or {@link #configureWithFileInAssets(String)} - */ - void clearUpdatedConfiguration() { - EventData eventData = new EventData(); - eventData.putBoolean(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_CLEAR_UPDATED_CONFIG, - true); - final Event event = new Event.Builder("Clear updated configuration", EventType.CONFIGURATION, - EventSource.REQUEST_CONTENT).setData(eventData).build(); - eventHub.dispatch(event); - } - /** - * Gets the SDK's current version with wrapper type. - */ - String getSdkVersion() { - return eventHub.getSdkVersion(); - } - - /** - * Sets the SDK's current wrapper type. This API should only be used if - * being developed on platforms such as React Native. - * - * @param wrapperType the type of wrapper being used. - */ - void setWrapperType(final WrapperType wrapperType) { - eventHub.setWrapperType(wrapperType); - } - - /** - * Set the Adobe Mobile Privacy status. - *

- * Sets the {@link MobilePrivacyStatus} for this SDK. The set privacy status is preserved and applied over any new - * configuration changes from calls to {@link #configureWithAppID(String)} or {@link #configureWithFileInPath(String)}, - * even across application restarts. - * - * @param privacyStatus {@link MobilePrivacyStatus} to be set to the SDK - * @see MobilePrivacyStatus - */ - void setPrivacyStatus(final MobilePrivacyStatus privacyStatus) { - final Map privacyStatusUpdateConfig = new HashMap(); - final String privacyStatusString = (privacyStatus == null ? null : privacyStatus.getValue()); - privacyStatusUpdateConfig.put(CoreConstants.EventDataKeys.Configuration.GLOBAL_CONFIG_PRIVACY, privacyStatusString); - updateConfiguration(privacyStatusUpdateConfig); - } - - /** - * Get the current Adobe Mobile Privacy Status. - *

- * Gets the currently configured {@link MobilePrivacyStatus} and passes it as a parameter to the given - * {@link AdobeCallback#call(Object)} function. - * - * @param callback {@link AdobeCallback} instance which is invoked with the configured privacy status as a parameter - */ - void getPrivacyStatus(final AdobeCallback callback) { - if (callback == null) { - return; - } - - EventData eventData = new EventData(); - eventData.putBoolean(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_RETRIEVE_CONFIG, true); - Event event = new Event.Builder("PrivacyStatusRequest", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setData(eventData).build(); - - - final AdobeCallbackWithError adobeCallbackWithError = callback instanceof AdobeCallbackWithError ? - (AdobeCallbackWithError) callback : null; - - eventHub.registerOneTimeListener(event.getResponsePairID(), new Module.OneTimeListenerBlock() { - @Override - public void call(final Event e) { - EventData eventData = e.getData(); - callback.call(MobilePrivacyStatus.fromString(eventData.getString(ConfigurationConstants.EventDataKeys - .Configuration.GLOBAL_CONFIG_PRIVACY))); - } - }, adobeCallbackWithError); - - eventHub.dispatch(event); - - } - - /** - * Retrieve all identities stored by/known to the SDK in a JSON {@code String} format. - *

- * Dispatches an {@link EventType#CONFIGURATION} - {@link EventSource#REQUEST_IDENTITY} {@code Event}. - *

- * Returns an empty string if the SDK is unable to retrieve any identifiers. - * - * @param callback {@link AdobeCallback} instance which is invoked with all the known identifier in JSON {@link String} format - * @see AdobeCallback - */ - void getSdkIdentities(final AdobeCallback callback) { - if (callback == null) { - return; - } - - final AdobeCallbackWithError adobeCallbackWithError = callback instanceof AdobeCallbackWithError ? - (AdobeCallbackWithError) callback : null; - - Event event = new Event.Builder("getSdkIdentities", EventType.CONFIGURATION, EventSource.REQUEST_IDENTITY).build(); - eventHub.registerOneTimeListener(event.getResponsePairID(), new Module.OneTimeListenerBlock() { - @Override - public void call(final Event e) { - EventData eventData = e.getData(); - callback.call(eventData.optString( - ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_RESPONSE_IDENTITY_ALL_IDENTIFIERS, "{}")); - } - }, adobeCallbackWithError); - - eventHub.dispatch(event); - } - - /** - * Dispatches a track action request event. - * - * @param action The action string - * @param contextData Any context data that needs to be associated with the {@code action} or {@code state} - */ - void trackAction(final String action, final Map contextData) { - EventData trackData = new EventData(); - trackData.putString(CoreConstants.EventDataKeys.Analytics.TRACK_ACTION, action); - trackData.putStringMap(CoreConstants.EventDataKeys.Analytics.CONTEXT_DATA, - contextData == null ? new HashMap() : contextData); - Event event = new Event.Builder("Analytics Track", EventType.GENERIC_TRACK, EventSource.REQUEST_CONTENT) - .setData(trackData).build(); - - eventHub.dispatch(event); - } - - /** - * Dispatches a track state request event. - * - * @param state The state string - * @param contextData Any context data that needs to be associated with the {@code action} or {@code state} - */ - void trackState(final String state, final Map contextData) { - EventData trackData = new EventData(); - trackData.putString(CoreConstants.EventDataKeys.Analytics.TRACK_STATE, state); - trackData.putStringMap(CoreConstants.EventDataKeys.Analytics.CONTEXT_DATA, - contextData == null ? new HashMap() : contextData); - Event event = new Event.Builder("Analytics Track", EventType.GENERIC_TRACK, EventSource.REQUEST_CONTENT) - .setData(trackData).build(); - - eventHub.dispatch(event); - } - - /** - * Dispatches an event with the Advertising Identifier - * - * @param adid the advertising idenifier string. - */ - void setAdvertisingIdentifier(final String adid) { - EventData eventData = new EventData(); - eventData.putString(CoreConstants.EventDataKeys.Identity.ADVERTISING_IDENTIFIER, adid); - - Event event = new Event.Builder("SetAdvertisingIdentifier", EventType.GENERIC_IDENTITY, EventSource.REQUEST_CONTENT) - .setData(eventData) - .build(); - - eventHub.dispatch(event); - - } - - /** - * Dispatches an event with the push token - * - * @param registrationID push token that needs to be set. - */ - void setPushIdentifier(final String registrationID) { - EventData eventData = new EventData(); - eventData.putString(CoreConstants.EventDataKeys.Identity.PUSH_IDENTIFIER, registrationID); - - Event event = new Event.Builder("SetPushIdentifier", EventType.GENERIC_IDENTITY, EventSource.REQUEST_CONTENT) - .setData(eventData) - .build(); - - eventHub.dispatch(event); - } - - /** - * Dispatches an event to resume/start a lifecycle session - * - * @param additionalContextData {@code Map} context data - */ - void lifecycleStart(final Map additionalContextData) { - EventData eventData = new EventData(); - eventData.putString(CoreConstants.EventDataKeys.Lifecycle.LIFECYCLE_ACTION_KEY, - CoreConstants.EventDataKeys.Lifecycle.LIFECYCLE_START); - - eventData.putStringMap(CoreConstants.EventDataKeys.Lifecycle.ADDITIONAL_CONTEXT_DATA, additionalContextData); - Event event = new Event.Builder("LifecycleResume", EventType.GENERIC_LIFECYLE, EventSource.REQUEST_CONTENT) - .setData(eventData) - .build(); - - eventHub.dispatch(event); - } - - /** - * Dispatches an event to pause/stop a lifecycle session - */ - void lifecyclePause() { - EventData eventData = new EventData(); - eventData.putString(CoreConstants.EventDataKeys.Lifecycle.LIFECYCLE_ACTION_KEY, - CoreConstants.EventDataKeys.Lifecycle.LIFECYCLE_PAUSE); - - Event event = new Event.Builder("LifecyclePause", EventType.GENERIC_LIFECYLE, EventSource.REQUEST_CONTENT) - .setData(eventData) - .build(); - - eventHub.dispatch(event); - } - - /** - * Create collect PII event, which is listened by Rules Engine module to determine if the data matches any PII request. - * - * @param data the PII data to be collected, which will be used in Rules Engine comparison and request token replacement. - */ - void collectPii(final Map data) { - if (data == null || data.isEmpty()) { - Log.debug(LOG_TAG, "Could not trigger PII, the data is null or empty."); - return; - } - - final EventData eventData = new EventData() - .putStringMap(CoreConstants.EventDataKeys.Signal.SIGNAL_CONTEXT_DATA, data); - eventHub.dispatch(new Event.Builder("CollectPII", EventType.GENERIC_PII, - EventSource.REQUEST_CONTENT).setData(eventData).build()); - Log.trace(LOG_TAG, "Collect PII event was sent"); - } - - /** - * Create collect data event, which may contain deep link information, messages info or referrer data. - * Dispatches an {@link EventType#GENERIC_DATA} {@link EventSource#OS} event to the {@link EventHub}. - * - * @param marshalledData OS launch data marshalled as {@code Map} - */ - void collectData(final Map marshalledData) { - if (marshalledData == null || marshalledData.isEmpty()) { - Log.debug(LOG_TAG, "collectData: Could not dispatch generic data event, data is null or empty."); - return; - } - - Event event = new Event.Builder("CollectData", EventType.GENERIC_DATA, EventSource.OS) - .setEventData(marshalledData) - .build(); - eventHub.dispatch(event); - Log.trace(LOG_TAG, "collectData: generic data OS event dispatched."); - } - - /** - * Dispatches a generic identity event to notify extensions to reset their stored identities - */ - void resetIdentities() { - Event event = new Event.Builder("Reset Identities Request", EventType.GENERIC_IDENTITY, EventSource.REQUEST_RESET) - .build(); - - eventHub.dispatch(event); - } - - /** - * Start the Core processing. This should be called after the initial set of extensions have been registered. - *

- * This call will wait for any outstanding registrations to complete and then start event processing. - * You can use the callback to kickoff additional operations immediately after any operations kicked off during registration. - * - * @param completionCallback An optional {@link AdobeCallback} invoked after registrations are completed - */ - void start(final AdobeCallback completionCallback) { - if (startActionCalled) { - Log.debug(LOG_TAG, "Can't start Core more than once."); - return; - } - - startActionCalled = true; - eventHub.finishModulesRegistration(completionCallback); - } - + private static final String LOG_TAG = Core.class.getSimpleName(); + private boolean startActionCalled; + + EventHub eventHub; + private EventHistory eventHistory; + + Core(final PlatformServices platformServices) { + this(platformServices, "undefined"); + } + + Core(final PlatformServices platformServices, final String coreVersion) { + Log.setLoggingService(platformServices.getLoggingService()); + eventHub = new EventHub("AMSEventHub", platformServices, coreVersion); + + try { + eventHub.registerModule(ConfigurationExtension.class, new ConfigurationModuleDetails(coreVersion)); + } catch (InvalidModuleException e) { + Log.error(LOG_TAG, "Failed to register Configuration extension (%s)", e); + } + + try { + eventHistory = new AndroidEventHistory(); + Log.trace(LOG_TAG, "Android EventHistory created and set in the EventHistoryProvider"); + } catch (Exception e) { + Log.warning(LOG_TAG, "Failed to create the android event history service: %s", + e.getMessage()); + eventHistory = null; + } + + + Log.trace(LOG_TAG, "Core initialization was successful"); + } + + Core(final PlatformServices platformServices, final EventHub eventHub) { + Log.setLoggingService(platformServices.getLoggingService()); + this.eventHub = eventHub; + Log.trace(LOG_TAG, "Core initialization was successful"); + } + + EventHistory getEventHistory() { + return this.eventHistory; + } + + + // ------------------------------------ 3rd party extensions -------------------------------------- + + /** + * Registers an extension class which has {@code Extension} as parent. + * + * @param extensionClass a class whose parent is {@link Extension} + * @param errorCallback an optional {@link ExtensionErrorCallback} for the eventuality of an error, + * called when this method returns false + */ + void registerExtension(final Class extensionClass, + final ExtensionErrorCallback errorCallback) { + try { + eventHub.registerExtension(extensionClass); + } catch (InvalidModuleException e) { + Log.debug(LOG_TAG, "Core.registerExtension - Failed to register extension class %s (%s)", + extensionClass.getSimpleName(), e); + + if (errorCallback != null) { + errorCallback.error(ExtensionError.UNEXPECTED_ERROR); + } + } + } + + /** + * Clones the provided {@code event} and dispatches it through the {@code EventHub}. + * Passes an {@link ExtensionError} to {@code errorCallback} if the event is null. + * + * @param event provided {@link Event} from the public API + * @param errorCallback callback to return {@link ExtensionError} if an error occurs + * @return {@code boolean} indicating if the event was dispatched or not + */ + boolean dispatchEvent(final Event event, final ExtensionErrorCallback errorCallback) { + if (event == null) { + Log.debug(LOG_TAG, "%s (Core.dispatchEvent) - The event was not dispatched", Log.UNEXPECTED_NULL_VALUE); + + if (errorCallback != null) { + errorCallback.error(ExtensionError.EVENT_NULL); + } + + return false; + } + + eventHub.dispatch(event); + return true; + } + + /** + * This method will be used when the provided {@code Event} is used as a trigger and a response event is expected in return. + * The returned event needs to be sent using {@link #dispatchResponseEvent(Event, Event, ExtensionErrorCallback)} method. + *

+ * Clones the provided {@code event}, registers a {@link OneTimeListener} for the response and dispatches it through the + * {@code EventHub}. Passes an {@link ExtensionError} to {@code errorCallback} if the event is null. + * + * @param event provided {@link Event} from the public API + * @param responseCallback {@link AdobeCallback} to be called with the response event received in the {@link OneTimeListener} + * @param errorCallback callback to return {@link ExtensionError} if an error occurs + * @return {@code boolean} indicating if the paired event was dispatched or not + * @see #dispatchResponseEvent + */ + boolean dispatchEventWithResponseCallback(final Event event, + final AdobeCallback responseCallback, + final ExtensionErrorCallback errorCallback) { + if (responseCallback == null) { + Log.debug(LOG_TAG, + "%s (Core.dispatchEventWithResponseCallback) - The event was not dispatched", Log.UNEXPECTED_NULL_VALUE); + + if (errorCallback != null) { + errorCallback.error(ExtensionError.CALLBACK_NULL); + } + + return false; + } + + if (event == null) { + Log.debug(LOG_TAG, "%s (Core.dispatchEventWithResponseCallback) - The event was not dispatched", + Log.UNEXPECTED_NULL_VALUE); + + if (errorCallback != null) { + errorCallback.error(ExtensionError.EVENT_NULL); + } + + return false; + } + + eventHub.registerOneTimeListener(event.getResponsePairID(), new Module.OneTimeListenerBlock() { + @Override + public void call(final Event e) { + responseCallback.call(e); + } + }); + eventHub.dispatch(event); + return true; + } + + /** + * This method will be used when the provided {@code Event} is used as a trigger and a response event is expected in return. + * The returned event needs to be sent using {@link #dispatchResponseEvent(Event, Event, ExtensionErrorCallback)} method. + *

+ * Clones the provided {@code event}, registers a {@link OneTimeListener} for the response and dispatches it through the + * {@code EventHub} if the given {@link Event} and {@link AdobeCallbackWithError} are not null. + * The one-time listener will be unregistered by the event hub after the response event is received, or when event processing timeout (5000ms) occurs. + * + * @param event provided {@link Event} from the public API + * @param responseCallback {@link AdobeCallback} to be called with the response event received in the {@link OneTimeListener} + * @see #dispatchResponseEvent + */ + void dispatchEventWithResponseCallback(final Event event, + final AdobeCallbackWithError responseCallback) { + if (event == null || responseCallback == null) { + Log.debug(LOG_TAG, + "(Core.dispatchEventWithResponseCallback) - The event was not dispatched, the given Event or AdobeCallbackWithError is null"); + return; + } + + eventHub.registerOneTimeListener(event.getResponsePairID(), new Module.OneTimeListenerBlock() { + @Override + public void call(final Event e) { + responseCallback.call(e); + } + }, responseCallback); + eventHub.dispatch(event); + } + + /** + * This method will be used when a response event should be dispatched for a paired event that was previously sent + * using {@code dispatchEventWithResponseCallback} + *

+ * Clones the provided {@code event}, sets the pair id associated with the request event's pair id and dispatches + * it through the {@link EventHub}. Passes an {@link ExtensionError} to {@code errorCallback} if the event is null. + * + * @param responseEvent provided response {@link Event} from the public API + * @param requestEvent the trigger {@link Event} for the dispatched event + * @param errorCallback callback to return {@link ExtensionError} if an error occurs + * @return {@code boolean} indicating if the response event was dispatched or not + * @see #dispatchEventWithResponseCallback + */ + boolean dispatchResponseEvent(final Event responseEvent, final Event requestEvent, + final ExtensionErrorCallback errorCallback) { + if (requestEvent == null) { + Log.debug(LOG_TAG, + "%s (Core.dispatchResponseEvent) - The response event was not dispatched", Log.UNEXPECTED_NULL_VALUE); + + if (errorCallback != null) { + errorCallback.error(ExtensionError.EVENT_NULL); + } + + return false; + } + + if (responseEvent == null) { + Log.warning(LOG_TAG, "%s (Core.dispatchResponseEvent) - The response event was not dispatched", + Log.UNEXPECTED_NULL_VALUE); + + if (errorCallback != null) { + errorCallback.error(ExtensionError.EVENT_NULL); + } + + return false; + } + + responseEvent.setPairId(requestEvent.getResponsePairID()); + eventHub.dispatch(responseEvent); + return true; + } + + /** + * Registers an event listener for the provided event type and source. + * + * @param eventType required parameter, the event type as a valid string (not null or empty) + * @param eventSource required parameter, the event source as a valid string (not null or empty) + * @param callback required parameter, {@link AdobeCallbackWithError#call(Object)} will be called when the event is heard + */ + void registerEventListener(final String eventType, final String eventSource, + final AdobeCallbackWithError callback) { + eventHub.registerEventListener(EventType.get(eventType), EventSource.get(eventSource), callback); + } + + + // ------------------------------ Configuration methods ------------------------------ + + /** + * Load remote configuration specified by the given application ID. + *

+ * Configure the SDK by downloading the remote configuration file hosted on Adobe servers + * specified by the given application ID. The configuration file is cached once downloaded + * and used in subsequent calls to this API. If the remote file is updated after the first + * download, the updated file is downloaded and replaces the cached file. + *

+ * The {@code appId} is preserved, and on application restarts, the remote configuration file specified by {@code appId} + * is downloaded and applied to the SDK. + *

+ * On failure to download the remote configuration file, the SDK is configured using the cached + * file if it exists, or if no cache file exists then the existing configuration remains unchanged. + *

+ * Calls to this API will replace any existing SDK configuration except those set using + * {@link #updateConfiguration(Map)} or {@link #setPrivacyStatus(MobilePrivacyStatus)}. Configuration updates + * made using {@link #updateConfiguration(Map)} and {@link #setPrivacyStatus(MobilePrivacyStatus)} + * are always applied on top of configuration changes made using this API. + * + * @param appId a unique identifier assigned to the app instance by the Adobe Mobile Services. It is automatically + * added to the Mobile configuration JSON file when downloaded from the Adobe Mobile Services UI and can be + * found in Manage App Settings. A value of {@code null} or empty {@code String} will clear the preserved value. + */ + void configureWithAppID(final String appId) { + EventData eventData = new EventData(); + eventData.putString(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID, appId); + final Event event = new Event.Builder("Configure with AppID", EventType.CONFIGURATION, + EventSource.REQUEST_CONTENT).setData(eventData).build(); + eventHub.dispatch(event); + } + + /** + * Load configuration from local file. + *

+ * Configure the SDK by reading a local file containing the JSON configuration. On application relaunch, + * the configuration from the file at {@code filepath} is not preserved and this method must be called again if desired. + *

+ * On failure to read the file or parse the JSON contents, the existing configuration remains unchanged. + *

+ * Calls to this API will replace any existing SDK configuration except those set using + * {@link #updateConfiguration(Map)} or {@link #setPrivacyStatus(MobilePrivacyStatus)}. + * Configuration updates made using {@link #updateConfiguration(Map)} and {@link #setPrivacyStatus(MobilePrivacyStatus)} + * are always applied on top of configuration changes made using this API. + * + * @param filepath absolute path to a local configuration file. A value of {@code null} has no effect. + */ + void configureWithFileInPath(final String filepath) { + if (StringUtils.isNullOrEmpty(filepath)) { + Log.warning("Configuration", "Unable to configure with null or empty remoteURL"); + return; + } + + EventData eventData = new EventData(); + eventData.putString(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_FILE_PATH, filepath); + final Event event = new Event.Builder("Configure with FilePath", EventType.CONFIGURATION, + EventSource.REQUEST_CONTENT).setData(eventData).build(); + eventHub.dispatch(event); + } + + /** + * Load configuration from an asset file. + * + * @param fileName the name of the configure file in the assets folder. A value of {@code null} has no effect. + */ + void configureWithFileInAssets(final String fileName) { + if (StringUtils.isNullOrEmpty(fileName)) { + Log.warning("Configuration", "Unable to configure with null or empty file name"); + return; + } + + EventData eventData = new EventData(); + eventData.putString(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_ASSET_FILE, fileName); + final Event event = new Event.Builder("Configure with FilePath", EventType.CONFIGURATION, + EventSource.REQUEST_CONTENT).setData(eventData).build(); + eventHub.dispatch(event); + } + + /** + * Update specific configuration parameters. + *

+ * Update the current SDK configuration with specific key/value pairs. Keys not found in the current + * configuration are added. Configuration updates are preserved and applied over existing or new + * configurations set by calling {@link #configureWithAppID(String)} or {@link #configureWithFileInPath(String)}, + * even across application restarts. + *

+ * Using {@code null} values is allowed and effectively removes the configuration parameter from the current configuration. + * + * @param configMap configuration key/value pairs to be updated or added. A value of {@code null} has no effect. + */ + void updateConfiguration(final Map configMap) { + // Create a EventData Map + HashMap eventDataMap = new HashMap(); + Variant configMapVariant = Variant.fromTypedMap(configMap, PermissiveVariantSerializer.DEFAULT_INSTANCE); + eventDataMap.put(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_UPDATE_CONFIG, + configMapVariant); + EventData eventData = new EventData(eventDataMap); + final Event event = new Event.Builder("Configuration Update", EventType.CONFIGURATION, + EventSource.REQUEST_CONTENT).setData(eventData).build(); + eventHub.dispatch(event); + } + + /** + * Clear the changes made by {@link #updateConfiguration(Map)} to the initial configuration provided either by + * {@link #configureWithAppID(String)} or {@link #configureWithFileInPath(String)} or {@link #configureWithFileInAssets(String)} + */ + void clearUpdatedConfiguration() { + EventData eventData = new EventData(); + eventData.putBoolean(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_CLEAR_UPDATED_CONFIG, + true); + final Event event = new Event.Builder("Clear updated configuration", EventType.CONFIGURATION, + EventSource.REQUEST_CONTENT).setData(eventData).build(); + eventHub.dispatch(event); + } + + /** + * Gets the SDK's current version with wrapper type. + */ + String getSdkVersion() { + return eventHub.getSdkVersion(); + } + + /** + * Sets the SDK's current wrapper type. This API should only be used if + * being developed on platforms such as React Native. + * + * @param wrapperType the type of wrapper being used. + */ + void setWrapperType(final WrapperType wrapperType) { + eventHub.setWrapperType(wrapperType); + } + + /** + * Set the Adobe Mobile Privacy status. + *

+ * Sets the {@link MobilePrivacyStatus} for this SDK. The set privacy status is preserved and applied over any new + * configuration changes from calls to {@link #configureWithAppID(String)} or {@link #configureWithFileInPath(String)}, + * even across application restarts. + * + * @param privacyStatus {@link MobilePrivacyStatus} to be set to the SDK + * @see MobilePrivacyStatus + */ + void setPrivacyStatus(final MobilePrivacyStatus privacyStatus) { + final Map privacyStatusUpdateConfig = new HashMap(); + final String privacyStatusString = (privacyStatus == null ? null : privacyStatus.getValue()); + privacyStatusUpdateConfig.put(CoreConstants.EventDataKeys.Configuration.GLOBAL_CONFIG_PRIVACY, privacyStatusString); + updateConfiguration(privacyStatusUpdateConfig); + } + + /** + * Get the current Adobe Mobile Privacy Status. + *

+ * Gets the currently configured {@link MobilePrivacyStatus} and passes it as a parameter to the given + * {@link AdobeCallback#call(Object)} function. + * + * @param callback {@link AdobeCallback} instance which is invoked with the configured privacy status as a parameter + */ + void getPrivacyStatus(final AdobeCallback callback) { + if (callback == null) { + return; + } + + EventData eventData = new EventData(); + eventData.putBoolean(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_RETRIEVE_CONFIG, true); + Event event = new Event.Builder("PrivacyStatusRequest", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) + .setData(eventData).build(); + + + final AdobeCallbackWithError adobeCallbackWithError = callback instanceof AdobeCallbackWithError ? + (AdobeCallbackWithError) callback : null; + + eventHub.registerOneTimeListener(event.getResponsePairID(), new Module.OneTimeListenerBlock() { + @Override + public void call(final Event e) { + EventData eventData = e.getData(); + callback.call(MobilePrivacyStatus.fromString(eventData.getString(ConfigurationConstants.EventDataKeys + .Configuration.GLOBAL_CONFIG_PRIVACY))); + } + }, adobeCallbackWithError); + + eventHub.dispatch(event); + + } + + /** + * Retrieve all identities stored by/known to the SDK in a JSON {@code String} format. + *

+ * Dispatches an {@link EventType#CONFIGURATION} - {@link EventSource#REQUEST_IDENTITY} {@code Event}. + *

+ * Returns an empty string if the SDK is unable to retrieve any identifiers. + * + * @param callback {@link AdobeCallback} instance which is invoked with all the known identifier in JSON {@link String} format + * @see AdobeCallback + */ + void getSdkIdentities(final AdobeCallback callback) { + if (callback == null) { + return; + } + + final AdobeCallbackWithError adobeCallbackWithError = callback instanceof AdobeCallbackWithError ? + (AdobeCallbackWithError) callback : null; + + Event event = new Event.Builder("getSdkIdentities", EventType.CONFIGURATION, EventSource.REQUEST_IDENTITY).build(); + eventHub.registerOneTimeListener(event.getResponsePairID(), new Module.OneTimeListenerBlock() { + @Override + public void call(final Event e) { + EventData eventData = e.getData(); + callback.call(eventData.optString( + ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_RESPONSE_IDENTITY_ALL_IDENTIFIERS, "{}")); + } + }, adobeCallbackWithError); + + eventHub.dispatch(event); + } + + /** + * Dispatches a track action request event. + * + * @param action The action string + * @param contextData Any context data that needs to be associated with the {@code action} or {@code state} + */ + void trackAction(final String action, final Map contextData) { + EventData trackData = new EventData(); + trackData.putString(CoreConstants.EventDataKeys.Analytics.TRACK_ACTION, action); + trackData.putStringMap(CoreConstants.EventDataKeys.Analytics.CONTEXT_DATA, + contextData == null ? new HashMap() : contextData); + Event event = new Event.Builder("Analytics Track", EventType.GENERIC_TRACK, EventSource.REQUEST_CONTENT) + .setData(trackData).build(); + + eventHub.dispatch(event); + } + + /** + * Dispatches a track state request event. + * + * @param state The state string + * @param contextData Any context data that needs to be associated with the {@code action} or {@code state} + */ + void trackState(final String state, final Map contextData) { + EventData trackData = new EventData(); + trackData.putString(CoreConstants.EventDataKeys.Analytics.TRACK_STATE, state); + trackData.putStringMap(CoreConstants.EventDataKeys.Analytics.CONTEXT_DATA, + contextData == null ? new HashMap() : contextData); + Event event = new Event.Builder("Analytics Track", EventType.GENERIC_TRACK, EventSource.REQUEST_CONTENT) + .setData(trackData).build(); + + eventHub.dispatch(event); + } + + /** + * Dispatches an event with the Advertising Identifier + * + * @param adid the advertising idenifier string. + */ + void setAdvertisingIdentifier(final String adid) { + EventData eventData = new EventData(); + eventData.putString(CoreConstants.EventDataKeys.Identity.ADVERTISING_IDENTIFIER, adid); + + Event event = new Event.Builder("SetAdvertisingIdentifier", EventType.GENERIC_IDENTITY, EventSource.REQUEST_CONTENT) + .setData(eventData) + .build(); + + eventHub.dispatch(event); + + } + + /** + * Dispatches an event with the push token + * + * @param registrationID push token that needs to be set. + */ + void setPushIdentifier(final String registrationID) { + EventData eventData = new EventData(); + eventData.putString(CoreConstants.EventDataKeys.Identity.PUSH_IDENTIFIER, registrationID); + + Event event = new Event.Builder("SetPushIdentifier", EventType.GENERIC_IDENTITY, EventSource.REQUEST_CONTENT) + .setData(eventData) + .build(); + + eventHub.dispatch(event); + } + + /** + * Dispatches an event to resume/start a lifecycle session + * + * @param additionalContextData {@code Map} context data + */ + void lifecycleStart(final Map additionalContextData) { + EventData eventData = new EventData(); + eventData.putString(CoreConstants.EventDataKeys.Lifecycle.LIFECYCLE_ACTION_KEY, + CoreConstants.EventDataKeys.Lifecycle.LIFECYCLE_START); + + eventData.putStringMap(CoreConstants.EventDataKeys.Lifecycle.ADDITIONAL_CONTEXT_DATA, additionalContextData); + Event event = new Event.Builder("LifecycleResume", EventType.GENERIC_LIFECYLE, EventSource.REQUEST_CONTENT) + .setData(eventData) + .build(); + + eventHub.dispatch(event); + } + + /** + * Dispatches an event to pause/stop a lifecycle session + */ + void lifecyclePause() { + EventData eventData = new EventData(); + eventData.putString(CoreConstants.EventDataKeys.Lifecycle.LIFECYCLE_ACTION_KEY, + CoreConstants.EventDataKeys.Lifecycle.LIFECYCLE_PAUSE); + + Event event = new Event.Builder("LifecyclePause", EventType.GENERIC_LIFECYLE, EventSource.REQUEST_CONTENT) + .setData(eventData) + .build(); + + eventHub.dispatch(event); + } + + /** + * Create collect PII event, which is listened by Rules Engine module to determine if the data matches any PII request. + * + * @param data the PII data to be collected, which will be used in Rules Engine comparison and request token replacement. + */ + void collectPii(final Map data) { + if (data == null || data.isEmpty()) { + Log.debug(LOG_TAG, "Could not trigger PII, the data is null or empty."); + return; + } + + final EventData eventData = new EventData() + .putStringMap(CoreConstants.EventDataKeys.Signal.SIGNAL_CONTEXT_DATA, data); + eventHub.dispatch(new Event.Builder("CollectPII", EventType.GENERIC_PII, + EventSource.REQUEST_CONTENT).setData(eventData).build()); + Log.trace(LOG_TAG, "Collect PII event was sent"); + } + + /** + * Create collect data event, which may contain deep link information, messages info or referrer data. + * Dispatches an {@link EventType#GENERIC_DATA} {@link EventSource#OS} event to the {@link EventHub}. + * + * @param marshalledData OS launch data marshalled as {@code Map} + */ + void collectData(final Map marshalledData) { + if (marshalledData == null || marshalledData.isEmpty()) { + Log.debug(LOG_TAG, "collectData: Could not dispatch generic data event, data is null or empty."); + return; + } + + Event event = new Event.Builder("CollectData", EventType.GENERIC_DATA, EventSource.OS) + .setEventData(marshalledData) + .build(); + eventHub.dispatch(event); + Log.trace(LOG_TAG, "collectData: generic data OS event dispatched."); + } + + /** + * Dispatches a generic identity event to notify extensions to reset their stored identities + */ + void resetIdentities() { + Event event = new Event.Builder("Reset Identities Request", EventType.GENERIC_IDENTITY, EventSource.REQUEST_RESET) + .build(); + + eventHub.dispatch(event); + } + + /** + * Start the Core processing. This should be called after the initial set of extensions have been registered. + *

+ * This call will wait for any outstanding registrations to complete and then start event processing. + * You can use the callback to kickoff additional operations immediately after any operations kicked off during registration. + * + * @param completionCallback An optional {@link AdobeCallback} invoked after registrations are completed + */ + void start(final AdobeCallback completionCallback) { + if (startActionCalled) { + Log.debug(LOG_TAG, "Can't start Core more than once."); + return; + } + + startActionCalled = true; + eventHub.finishModulesRegistration(completionCallback); + } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java index b19b92ca7..8d28cb27b 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java @@ -11,6 +11,8 @@ package com.adobe.marketing.mobile; +import com.adobe.marketing.mobile.internal.utility.MapUtilsKt; + import java.util.*; import java.util.concurrent.TimeUnit; @@ -460,7 +462,7 @@ public String toString() { sb.append(" timestamp: ").append(timestamp).append(COMMA).append(NEWLINE); sb.append(" data: ").append(data.prettyString(2)).append(NEWLINE); sb.append(" mask: ").append(Arrays.toString(mask)).append(COMMA).append(NEWLINE); - sb.append(" fnv1aHash: ").append(data.toFnv1aHash(mask)).append(NEWLINE); + sb.append(" fnv1aHash: ").append(MapUtilsKt.convertMapToFnv1aHash(data.toObjectMap(),mask)).append(NEWLINE); sb.append("}"); return sb.toString(); diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventData.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventData.java index ced15164b..ab7cf2d48 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventData.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventData.java @@ -1063,51 +1063,6 @@ String prettyString(final int indentDepth) { return CollectionUtils.getPrettyString(internalMap, indentDepth); } - /** - * Converts the current {@link EventData} into a {@code long} decimal FNV1a 32-bit hash. - *

- * If a mask is provided, only use keys in the provided mask and alphabetize their order. - * - * @param mask {@code String[]} containing keys to be hashed - * @return {@code long} containing the decimal FNV1a 32-bit hash. - */ - long toFnv1aHash(final String[] mask) { - final StringBuilder kvpStringBuilder = new StringBuilder(); - final Map flattenedMap = EventDataFlattener.getFlattenedDataMap(this); - - // if a mask is provided, only use keys in the provided mask and alphabetize their order - try { - if (mask != null && mask.length > 0) { - final String[] maskCopy = mask.clone(); - Arrays.sort(maskCopy); - - for (final String key : maskCopy) { - // only retain keys which are present in the mask - if (!StringUtils.isNullOrEmpty(key) && flattenedMap.containsKey(key)) { - final Variant currentVariant = flattenedMap.get(key); - kvpStringBuilder.append(key).append(":").append(convertVariantToString(currentVariant)); - } - } - } else { - final SortedMap alphabeticalMap = new TreeMap(flattenedMap); - - for (final String key : alphabeticalMap.keySet()) { - // ignore MapVariants and only add flattened values - if (!(alphabeticalMap.get(key) instanceof MapVariant)) { - final Variant currentVariant = flattenedMap.get(key); - kvpStringBuilder.append(key).append(":").append(convertVariantToString(currentVariant)); - } - } - } - } catch (final VariantException variantException) { - Log.debug(LOG_TAG, "Unable to convert variant: %s.", variantException.getLocalizedMessage()); - return 0; - } - - // return hex string as decimal - return StringEncoder.convertStringToDecimalHash(kvpStringBuilder.toString()); - } - private String convertVariantToString(final Variant variant) throws VariantException { if (variant instanceof VectorVariant) { return variant.toString(); diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHistoryProvider.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHistoryProvider.java deleted file mode 100644 index 564defc9b..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHistoryProvider.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -/** - * Defines a class for setting or retrieving an {@link EventHistory} service. - */ -class EventHistoryProvider { - private static EventHistory eventHistory; - - /** - * Sets the {@link EventHistory} to use for historical event database operations. - * - * @param platformEventHistory {@code EventHistory} instance created on the platform side - */ - public static void setEventHistory(final EventHistory platformEventHistory) { - eventHistory = platformEventHistory; - } - - /** - * Retrieves the {@link EventHistory} to use for historical event database operations. - * - * @return {@code EventHistory} to use for historical event database operations - */ - public static EventHistory getEventHistory() { - return eventHistory; - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java index e102a75f0..bb682006d 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java @@ -10,6 +10,9 @@ */ package com.adobe.marketing.mobile; +import com.adobe.marketing.mobile.internal.eventhub.history.EventHistory; +import com.adobe.marketing.mobile.internal.eventhub.history.EventHistoryResultHandler; + import java.lang.reflect.Constructor; import java.util.*; import java.util.concurrent.*; @@ -180,7 +183,7 @@ void dispatch(final Event e) { this.eventHubThreadService.submit(new EventRunnable(e)); } - final EventHistory eventHistory = EventHistoryProvider.getEventHistory(); + final EventHistory eventHistory = MobileCore.getEventHistory(); // record the event in the event history database if the event has a mask if (eventHistory != null && e.getMask() != null) { diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleCondition.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleCondition.java index d5581a907..4bcfd81cf 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleCondition.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleCondition.java @@ -69,9 +69,6 @@ protected static RuleCondition ruleConditionFromJson(final JsonUtilityService.JS } else if (conditionJson.getString(RULE_CONDITION_TYPE_KEY_JSON).equals(RULE_CONDITION_TYPE_MATCHER_JSON)) { ruleCondition = RuleConditionMatcher.ruleConditionMatcherFromJson(conditionJson.getJSONObject( RULE_CONDITION_DEFINITION_KEY_JSON)); - } else if (conditionJson.getString(RULE_CONDITION_TYPE_KEY_JSON).equals(RULE_CONDITION_TYPE_HISTORICAL_JSON)) { - ruleCondition = RuleConditionHistorical.historicalConditionFromJsonObject(conditionJson.getJSONObject( - RULE_CONDITION_DEFINITION_KEY_JSON)); } if (ruleCondition == null) { diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConditionHistorical.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConditionHistorical.java deleted file mode 100644 index 8460e3f1f..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConditionHistorical.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.concurrent.*; - -/** - * A rule condition which represents historical events used to evaluate a rule. - */ -class RuleConditionHistorical extends RuleCondition { - private static final String LOG_TAG = "RuleConditionHistorical"; - private static final int TIMEOUT = 1000; - private static final int INVALID_VALUE = -1; - private Matcher matcher; - private EventHistoryRequest[] eventHistoryRequests; - private String searchType; - private int value; - private long from; - private long to; - - /** - * Evaluates if the stored {@link EventHistory} requests exist in the {@link EventHistoryDatabase}. - * - * @param ruleTokenParser {@link RuleTokenParser} instance. This is unused for {@link RuleConditionHistorical} objects. - * @param event triggering {@link Event} instance. This is unused for {@code RuleConditionHistorical} objects. - * @return true if the condition holds - */ - @Override - protected boolean evaluate(final RuleTokenParser ruleTokenParser, final Event event) { - if (eventHistoryRequests == null || eventHistoryRequests.length == 0) { - Log.trace(LOG_TAG, "No event history requests found in the RuleConditionHistorical object."); - return false; - } - - final boolean isOrdered = !this.searchType.equals(RulesEngineConstants.EventHistory.ANY); - final int[] eventHistoryResult = new int[1]; - final CountDownLatch latch = new CountDownLatch(1); - final EventHistoryResultHandler handler = new EventHistoryResultHandler() { - @Override - public void call(final Integer results) { - eventHistoryResult[0] = results; - latch.countDown(); - } - }; - final EventHistory eventHistory = EventHistoryProvider.getEventHistory(); - - if (eventHistory == null) { - Log.warning(LOG_TAG, "Unable to retrieve historical events, the event history is not available."); - return false; - } - - eventHistory.getEvents(eventHistoryRequests, isOrdered, handler); - - try { - latch.await(TIMEOUT, TimeUnit.MILLISECONDS); - } catch (final InterruptedException interruptedException) { - Log.warning(LOG_TAG, "Interrupted Exception occurred while waiting for the latch: %s.", - interruptedException.getMessage()); - return false; - } - - return matcher.matches(eventHistoryResult[0]); - } - - /** - * Creates a {@link RuleConditionHistorical} instance based on the given historical {@code JSONObject}. - * Searches the JSON object for a searchType, matcher type, value, "from" timestamp, and "to" timestamp and returns a {@code RuleConditionHistorical} instance - * populated with those values. - *

- * Returns null if an error occurs creating the {@code RuleConditionHistorical} instance. - * - * @param historicalConditionJson {@link JsonUtilityService.JSONObject} containing the definition for a {@code RuleConditionHistorical} instance - * @return the created {@code RuleConditionHistorical} object - */ - static RuleConditionHistorical historicalConditionFromJsonObject(final JsonUtilityService.JSONObject - historicalConditionJson) { - if (historicalConditionJson == null || historicalConditionJson.length() == 0) { - Log.trace(LOG_TAG, - "error creating historical rule condition from the Json object as the definition was empty."); - return null; - } - - RuleConditionHistorical ruleConditionHistorical = retrieveHistoryValuesFromJson(historicalConditionJson); - - if (ruleConditionHistorical == null) { - Log.trace(LOG_TAG, - "error creating historical rule condition from the Json object as a required value was missing."); - return ruleConditionHistorical; - } - - // set the matcher type and matcher value - ruleConditionHistorical.matcher = Matcher.matcherWithJsonObject(historicalConditionJson); - ruleConditionHistorical.matcher.values.add(ruleConditionHistorical.value); - - try { - // create EventHistoryRequest objects and add them to the EventHistoryRequest array - ruleConditionHistorical.eventHistoryRequests = parseEventHistoryRequestsFromJson(historicalConditionJson, - ruleConditionHistorical); - } catch (final JsonException exception) { - Log.trace(LOG_TAG, "error creating historical rule condition from the Json object: %s", exception.getMessage()); - return null; - } - - return ruleConditionHistorical; - } - - /** - * Searches the JSON object for {@link EventHistory} data and adds it to the created {@link RuleConditionHistorical} object. - * - * @param definition {@link JsonUtilityService.JSONObject} containing the search type - */ - private static RuleConditionHistorical retrieveHistoryValuesFromJson(final JsonUtilityService.JSONObject - definition) { - RuleConditionHistorical ruleConditionHistorical = new RuleConditionHistorical(); - final String searchType = definition.optString(RulesEngineConstants.EventHistory.RuleDefinition.SEARCH_TYPE, ""); - final String matcherType = definition.optString(RulesEngineConstants.EventHistory.RuleDefinition.MATCHER, ""); - final int value = definition.optInt(RulesEngineConstants.EventHistory.RuleDefinition.VALUE, INVALID_VALUE); - final long from = definition.optLong(RulesEngineConstants.EventHistory.RuleDefinition.FROM, 0l); - final long to = definition.optLong(RulesEngineConstants.EventHistory.RuleDefinition.TO, System.currentTimeMillis()); - - // historical search to be performed. values are "any" or "ordered" with "any" being the default value. - if (!StringUtils.isNullOrEmpty(searchType)) { - ruleConditionHistorical.searchType = searchType; - } else { - ruleConditionHistorical.searchType = "any"; - Log.trace(LOG_TAG, "%s (searchType), messages - setting searchType to any", Log.UNEXPECTED_EMPTY_VALUE); - } - - // matcher type for evaluating the historical search result. this is required and the historical condition creation will fail if this is not present. - if (StringUtils.isNullOrEmpty(matcherType)) { - Log.trace(LOG_TAG, "%s (matcherType), messages - error creating historical condition", Log.UNEXPECTED_EMPTY_VALUE); - return null; - } - - // the value to evaluate the search result against. this is required and the historical condition creation will fail if this is not present. - if (value > INVALID_VALUE) { - ruleConditionHistorical.value = value; - } else { - Log.trace(LOG_TAG, "%s (value), messages - error creating historical condition", Log.UNEXPECTED_EMPTY_VALUE); - return null; - } - - // the "from" value is the beginning timestamp to start the search from and the "to" value is the end timestamp. - // "0" is used if no "from" timestamp is provided and the current timestamp is used if no "to" value is provided. - ruleConditionHistorical.from = from; - ruleConditionHistorical.to = to; - - return ruleConditionHistorical; - } - - /** - * Searches the JSON object for events and converts them to an array of {@link EventHistoryRequest}. - * - * @param definition {@link JsonUtilityService.JSONObject} containing an array of {@link EventHistory} masks - * @param ruleConditionHistorical {@link RuleConditionHistorical} object to be populated with the created {@code EventHistoryRequest}s - * @return an array of {@code EventHistoryRequest}s created from events contained in the rule definition - * - * @throws JsonException if no {@code EventHistory} masks are found in the provided JSON object - */ - private static EventHistoryRequest[] parseEventHistoryRequestsFromJson(final JsonUtilityService.JSONObject definition, - final RuleConditionHistorical ruleConditionHistorical) throws JsonException { - EventHistoryRequest requests[] = null; - // loop through json array and populate the eventHistoryRequests list - JsonUtilityService.JSONArray jsonArray = definition.getJSONArray( - RulesEngineConstants.EventHistory.RuleDefinition.EVENTS); - - if (jsonArray == null || jsonArray.length() == 0) { - Log.debug(LOG_TAG, "%s - error creating historical rule condition as the rule definition did not contain any events.", - Log.UNEXPECTED_EMPTY_VALUE); - return null; - } - - final int arrayLength = jsonArray.length(); - requests = new EventHistoryRequest[arrayLength]; - - for (int i = 0; i < arrayLength; i++) { - final JsonUtilityService.JSONObject jsonObject = (JsonUtilityService.JSONObject) jsonArray.get(i); - final Iterator iterator = jsonObject.keys(); - final HashMap mask = new HashMap(); - - while (iterator.hasNext()) { - final String key = (String) iterator.next(); - mask.put(key, Variant.fromString(jsonObject.getString(key))); - } - - requests[i] = new EventHistoryRequest(mask, ruleConditionHistorical.from, - ruleConditionHistorical.to); - } - - return requests; - } - - @Override public String toString() { - final StringBuilder maskStringBuilder = new StringBuilder(); - maskStringBuilder.append("(HISTORICAL EVENTS FOUND: "); - - for (final EventHistoryRequest request : eventHistoryRequests) { - maskStringBuilder.append(request.mask); - maskStringBuilder.append(", "); - } - - maskStringBuilder.setLength(maskStringBuilder.length() - 2); - maskStringBuilder.append(")"); - return maskStringBuilder.toString(); - } - - // for unit tests - public EventHistoryRequest[] getEventHistoryRequests() { - return eventHistoryRequests; - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/StringEncoder.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/StringEncoder.java deleted file mode 100644 index 75c72accf..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/StringEncoder.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Map; - -/** - * Utility class for {@code String} related encoding methods - */ -final class StringEncoder { - - private static final String LOG_TAG = StringEncoder.class.getSimpleName(); - private static final char[] hexArray = "0123456789abcdef".toCharArray(); - private static final int ALL_BITS_ENABLED = 0xFF; - private static final int SHIFT_BY = 4; - private static final int HEX_CHAR_MULTIPLIER = 0x0F; - private static final int HEX_RADIX = 16; - private static final int PRIME = 0x1000193; // 16777619 as hex - private static final int OFFSET = 0x811c9dc5; // 2166136261 as hex - - private StringEncoder() {} - - static String getSha1HashedString(final String inputString) { - if (inputString == null || inputString.isEmpty()) { - return null; - } - - String hash = null; - - try { - final MessageDigest digest = MessageDigest.getInstance("SHA-1"); - byte[] bytes = inputString.getBytes("UTF-8"); - digest.update(bytes, 0, bytes.length); - bytes = digest.digest(); - - char[] hexChars = new char[bytes.length * 2]; - - for (int j = 0; j < bytes.length; j++) { - int v = bytes[j] & ALL_BITS_ENABLED; - hexChars[j * 2] = hexArray[v >>> SHIFT_BY]; - hexChars[j * 2 + 1] = hexArray[v & HEX_CHAR_MULTIPLIER]; - } - - hash = new String(hexChars); - } catch (final NoSuchAlgorithmException ex) { - Log.debug(LOG_TAG, "ADBMobile - error while attempting to encode a string (%s)", ex); - } catch (final UnsupportedEncodingException ex) { - Log.debug(LOG_TAG, "ADBMobile - error while attempting to encode a string (%s)", ex); - } - - return hash; - } - - /** - * Converts the given {@link Map} mask into a decimal representation of the signed 2's complement FNV1a 32-bit hash. - * - * @param mask {@link Map} to be hashed - * @return a {@code long} containing the decimal FNV1a 32-bit hash - */ - static long convertMapToDecimalHash(final Map mask) { - StringBuilder kvpStringBuilder = new StringBuilder(); - - if (mask != null && !mask.isEmpty()) { - for (String key : mask.keySet()) { - try { - kvpStringBuilder.append(key + ":" + mask.get(key).convertToString()); - } catch (final VariantException variantException) { - Log.trace(LOG_TAG, "Unable to convert variant %s to string.", mask.get(key)); - continue; - } - } - } - - return convertStringToDecimalHash(kvpStringBuilder.toString()); - } - - /** - * Converts the given {@code String} into a decimal representation of the signed 2's complement FNV1a 32-bit hash. - * - * @param inputString {@code String} containing to be hashed - * @return a {@link long} containing the decimal FNV1a 32-bit hash - */ - static long convertStringToDecimalHash(final String inputString) { - final int hash = getFnv1aHash(inputString); - // convert signed 2's complement hash to hex string - final String hexHash = Integer.toHexString(hash); - - return Long.parseLong(hexHash, HEX_RADIX); - } - - /** - * Converts the given {@code String} to a signed 2's complement FNV1a 32-bit hash. - *

- * https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - * Online validator - https://md5calc.com/hash/fnv1a32?str= - * - * @param input {@code String} to be hashed - * @return a {@code int} containing the signed 2's complement FNV1a 32-bit hash - */ - private static int getFnv1aHash(final String input) { - int hash = StringUtils.isNullOrEmpty(input) ? 0 : OFFSET; - final byte[] bytes = input.getBytes(); - - for (int i = 0; i < bytes.length; i++) { - hash ^= (bytes[i] & ALL_BITS_ENABLED); - hash *= PRIME; - } - - return hash; - } -} \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/AndroidEventHistory.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/AndroidEventHistory.java new file mode 100644 index 000000000..3962b1beb --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/AndroidEventHistory.java @@ -0,0 +1,155 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.internal.eventhub.history; + +import android.database.Cursor; +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; +import com.adobe.marketing.mobile.internal.utility.MapUtilsKt; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * The Android implementation of {@link EventHistory} which provides functionality for performing database + * operations on an {@link AndroidEventHistoryDatabase}. + */ +public class AndroidEventHistory implements EventHistory { + private static final String LOG_TAG = "AndroidEventHistory"; + private static final int THREAD_POOL_SIZE = 1; + private static final int COUNT_INDEX = 0; + private static final int OLDEST_INDEX = 1; + private final AndroidEventHistoryDatabase androidEventHistoryDatabase; + private final ExecutorService executorService; + + /** + * Constructor. + */ + public AndroidEventHistory() throws EventHistoryDatabaseCreationException { + androidEventHistoryDatabase = new AndroidEventHistoryDatabase(); + executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE); + } + + /** + * Record an event in the {@link AndroidEventHistoryDatabase}. + * + * @param event the {@link Event} to be recorded + * @param handler {@link EventHistoryResultHandler} a callback which will contain a {@code boolean} indicating if + * the database operation was successful + */ + public void recordEvent(final Event event, final EventHistoryResultHandler handler) { + final long fnv1aHash = MapUtilsKt.convertMapToFnv1aHash(event.getEventData(), event.getMask()); + + if (fnv1aHash == 0) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("The event with name \"%s\" has a fnv1a hash equal to 0. The event will not be recorded.", event.getName())); + return; + } + + executorService.submit(new Runnable() { + @Override + public void run() { + handler.call(androidEventHistoryDatabase.insert(fnv1aHash)); + } + }); + } + + /** + * Query the {@link AndroidEventHistoryDatabase} for {@link Event}s which match the contents of the + * {@link EventHistoryRequest} array. + * + * @param eventHistoryRequests an array of {@code EventHistoryRequest}s to be matched + * @param enforceOrder {@code boolean} if true, consecutive lookups will use the oldest timestamp from the previous event + * as their from date + * @param handler {@link EventHistoryResultHandler} containing the the total number of matching events in the {@code AndroidEventHistoryDatabase} + * if an "any" search was done. If an "ordered" search was done, the handler will contain a "1" + * if the event history requests were found in the order specified in the eventHistoryRequests array + * and a "0" if the events were not found in the order specified. + */ + @Override + public void getEvents(final EventHistoryRequest[] eventHistoryRequests, + final boolean enforceOrder, + final EventHistoryResultHandler handler) { + executorService.submit(new Runnable() { + @Override + public void run() { + long previousEventOldestOccurrence = 0L; + int foundEventCount = 0; + + for (final EventHistoryRequest request : eventHistoryRequests) { + final long from = (enforceOrder + && previousEventOldestOccurrence != 0) ? previousEventOldestOccurrence : request.getFromDate(); + final long to = request.getToDate() == 0 ? System.currentTimeMillis() : request.getToDate(); + final long eventHash = request.getMaskAsDecimalHash(); + final Cursor result = androidEventHistoryDatabase.select(eventHash, from, to); + + try { // columns are index 0: count, index 1: oldest, index 2: newest + result.moveToFirst(); + + if (result.getInt(COUNT_INDEX) != 0) { + previousEventOldestOccurrence = result.getLong(OLDEST_INDEX); + + if (enforceOrder) { + foundEventCount++; + } else { + foundEventCount += result.getInt(COUNT_INDEX); + } + } + } catch (final Exception exception) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Exception occurred when attempting to retrieve events with eventHash %s from the EventHistoryDatabase: %s", + eventHash, exception.getMessage())); + } + } + + // for ordered searches, if found event count matches the total number of requests, then all requests were found. return 1 / true. + if (enforceOrder) { + if (foundEventCount == eventHistoryRequests.length) { + handler.call(1); + } else { // otherwise return 0 / false + handler.call(0); + } + } else { // for "any" search, return total number of matching events + handler.call(foundEventCount); + } + } + }); + } + + /** + * Delete rows from the {@link AndroidEventHistoryDatabase} that contain {@link Event}s which match the contents + * of the {@link EventHistoryRequest} array. + * + * @param eventHistoryRequests an array of {@code EventHistoryRequest}s to be deleted + * @param handler a callback which will be called with a {@code int} containing the total number of rows deleted from the + * {@code AndroidEventHistoryDatabase} + */ + @Override + public void deleteEvents(final EventHistoryRequest[] eventHistoryRequests, + final EventHistoryResultHandler handler) { + executorService.submit(new Runnable() { + @Override + public void run() { + int deletedRows = 0; + + for (final EventHistoryRequest request : eventHistoryRequests) { + // if no "from" date is provided, delete from the beginning of the database + final long from = request.getFromDate() == 0 ? 0 : request.getFromDate(); + // if no "to" date is provided, delete until the end of the database + final long to = request.getToDate() == 0 ? System.currentTimeMillis() : request.getToDate(); + final long eventHash = request.getMaskAsDecimalHash(); + deletedRows += androidEventHistoryDatabase.delete(eventHash, from, to); + } + + handler.call(deletedRows); + } + }); + } +} \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/AndroidEventHistoryDatabase.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/AndroidEventHistoryDatabase.java new file mode 100644 index 000000000..4c2f84623 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/AndroidEventHistoryDatabase.java @@ -0,0 +1,194 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.internal.eventhub.history; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; + +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; +import com.adobe.marketing.mobile.internal.utility.SQLiteDatabaseHelper; +import com.adobe.marketing.mobile.services.ServiceProvider; + +import java.io.File; +import java.io.IOException; + +class AndroidEventHistoryDatabase implements EventHistoryDatabase { + private static final String LOG_TAG = "AndroidEventHistoryDatabase"; + private static final String DATABASE_NAME = "com.adobe.marketing.db.eventhistory"; + private static final String TABLE_NAME = "Events"; + private static final String COLUMN_HASH = "eventHash"; + private static final String COLUMN_TIMESTAMP = "timestamp"; + private static final String COUNT = "count"; + private static final String OLDEST = "oldest"; + private static final String NEWEST = "newest"; + + private final Object dbMutex = new Object(); + private File databaseFile = null; + private SQLiteDatabase database = null; + + /** + * Constructor. + * + * @throws {@link EventHistoryDatabaseCreationException} if any error occurred while creating the database + * or database table. + */ + AndroidEventHistoryDatabase() throws EventHistoryDatabaseCreationException { + try { + //TODO: we will create a utility method: Context.getDatabasePath(), we need to refactor the following code after that. + final File applicationCacheDir = ServiceProvider.getInstance().getDeviceInfoService().getApplicationCacheDir(); + if (applicationCacheDir != null) { + final String cacheDirCanonicalPath = applicationCacheDir.getCanonicalPath(); + databaseFile = new File(cacheDirCanonicalPath + "/" + DATABASE_NAME); + } + final String tableCreationQuery = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + + " (eventHash INTEGER, timestamp INTEGER);"; + + synchronized (dbMutex) { + if (SQLiteDatabaseHelper.createTableIfNotExist(databaseFile.getCanonicalPath(), tableCreationQuery)) { + MobileCore.log(LoggingMode.VERBOSE, LOG_TAG, + String.format("createTableIfNotExists - Successfully created/already existed table (%s) ", TABLE_NAME)); + } else { + throw new EventHistoryDatabaseCreationException("An error occurred while creating the \"Events\" table in the Android Event History database."); + } + } + } catch (final IOException e) { + throw new EventHistoryDatabaseCreationException(String.format("An error occurred while creating the \"Events\" table in the Android Event History database, error message: %s", + (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getMessage()))); + } + } + + /** + * Insert a row into the database. Each row will contain a hash and a timestamp. + * + * @param hash {@code long} containing the 32-bit FNV-1a hashed representation of an Event's data + * @return a {@code boolean} which will contain the status of the database insert operation + */ + @Override + public boolean insert(final long hash) { + boolean result; + synchronized (dbMutex) { + try { + openDatabase(); + final ContentValues contentValues = new ContentValues(); + contentValues.put(COLUMN_HASH, hash); + contentValues.put(COLUMN_TIMESTAMP, System.currentTimeMillis()); + result = database.insert(TABLE_NAME, null, contentValues) != -1; + } catch (final SQLException | IOException e) { + MobileCore.log(LoggingMode.WARNING, LOG_TAG, + String.format("Failed to insert rows into the table (%s)", + (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getMessage()))); + return false; + } finally { + closeDatabase(); + } + return result; + } + } + + private void openDatabase() throws IOException { + database = SQLiteDatabaseHelper.openDatabase(databaseFile.getCanonicalPath(), SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE); + } + + /** + * Queries the event history database to search for the existence of an event. + *

+ * This method will count all records in the event history database that match the provided hash and are within + * the bounds of the provided from and to timestamps. + * If the "from" date is equal to 0, the search will use the beginning of event history as the lower bounds of the date range. + * If the "to" date is equal to 0, the search will use the current system timestamp as the upper bounds of the date range. + * The {@link EventHistoryResultHandler} will be called with a {@link Cursor} which contains the number of matching records, + * the oldest timestamp, and the newest timestamp for a matching event. + * If no database connection is available, the handler will be called with a null {@code DatabaseService.QueryResult}. + * + * @param hash {@code long} containing the 32-bit FNV-1a hashed representation of an Event's data + * @param from {@code long} a timestamp representing the lower bounds of the date range to use when searching for the hash + * @param to {@code long} a timestamp representing the upper bounds of the date range to use when searching for the hash + * @return a {@code DatabaseService.QueryResult} which will contain the matching events + */ + @Override + public Cursor select(final long hash, final long from, final long to) { + // if the provided "to" date is equal to 0, use the current date + final long toValue = to == 0 ? System.currentTimeMillis() : to; + + synchronized (dbMutex) { + try { + openDatabase(); + final String[] whereArgs = new String[]{String.valueOf(hash), String.valueOf(from), String.valueOf(toValue)}; + final Cursor cursor = database.rawQuery( + "SELECT " + COUNT + "(*) as " + COUNT + ", " + + "min(" + COLUMN_TIMESTAMP + ") as " + OLDEST + ", " + + "max(" + COLUMN_TIMESTAMP + ") as " + NEWEST + + " FROM " + TABLE_NAME + " " + + " WHERE " + COLUMN_HASH + " = ?" + + " AND " + COLUMN_TIMESTAMP + " >= ?" + + " AND " + COLUMN_TIMESTAMP + " <= ?", + whereArgs); + cursor.moveToFirst(); + + return cursor; + } catch (final SQLException | IOException e) { + MobileCore.log(LoggingMode.WARNING, LOG_TAG, + String.format("Failed to execute query (%s)", + (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getMessage()))); + } finally { + closeDatabase(); + } + return null; + } + } + + /** + * Delete entries from the event history database. + * + * @param hash {@code long} containing the 32-bit FNV-1a hashed representation of an Event's data + * @param from {@code long} representing the lower bounds of the date range to use when searching for the hash + * @param to {@code long} representing the upper bounds of the date range to use when searching for the hash + * @return {@code int} containing the number of entries deleted for the given hash. + */ + @Override + public int delete(final long hash, final long from, final long to) { + + // if the provided "to" date is equal to 0, use the current date + final long toValue = to == 0 ? System.currentTimeMillis() : to; + + synchronized (dbMutex) { + try { + openDatabase(); + final String[] whereArgs = new String[]{String.valueOf(hash), String.valueOf(from), String.valueOf(toValue)}; + final int affectedRowsCount = database.delete(TABLE_NAME, + COLUMN_HASH + " = ?" + + " AND " + COLUMN_TIMESTAMP + " >= ?" + + " AND " + COLUMN_TIMESTAMP + " <= ?", + whereArgs); + MobileCore.log(LoggingMode.VERBOSE, LOG_TAG, + String.format("Count of rows deleted in table %s are %d", TABLE_NAME, affectedRowsCount)); + + return affectedRowsCount; + } catch (final SQLException | IOException e) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, + String.format("Failed to delete table rows (%s)", + (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getMessage()))); + } finally { + closeDatabase(); + } + return 0; + } + } + + @Override + public void closeDatabase() { + SQLiteDatabaseHelper.closeDatabase(database); + } +} \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHistory.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/EventHistory.java similarity index 95% rename from code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHistory.java rename to code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/EventHistory.java index 8a5d6c0f5..f90eeb505 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHistory.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/EventHistory.java @@ -9,12 +9,14 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile; +package com.adobe.marketing.mobile.internal.eventhub.history; + +import com.adobe.marketing.mobile.Event; /** * Defines an interface for performing database operations on an {@link EventHistoryDatabase}. */ -interface EventHistory { +public interface EventHistory { /** * Record an event in the {@link EventHistoryDatabase}. diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHistoryDatabase.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/EventHistoryDatabase.java similarity index 61% rename from code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHistoryDatabase.java rename to code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/EventHistoryDatabase.java index 0afbfc74f..cb6fdfd81 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHistoryDatabase.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/EventHistoryDatabase.java @@ -9,47 +9,15 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile; +package com.adobe.marketing.mobile.internal.eventhub.history; -import java.util.List; +import android.database.Cursor; -@SuppressWarnings("unused") /** * Interface defining a database to be used by the SDK for storing event history. */ interface EventHistoryDatabase { - /** - * Opens an {#EventHistoryDatabase} database file if it exists, otherwise creates a new one in the cache directory. - * - * @return {@link boolean} indicating whether the open database operation was successful - */ - boolean openDatabase(); - - /** - * Delete {@link EventHistoryDatabase} file created in the cache directory, if it exists. - * - * @return {@code boolean} indicating whether the {@code EventHistoryDatabase} file delete operation was successful - */ - boolean deleteDatabase(); - - /** - * Create a table if it doesn't exist. - * - * @param columnNames {@code String[]} array containing column names - * @param columnDataTypes {@code ColumnDataType[]} array containing data types for each column - * @param columnConstraints {@code List>} a list of lists containing column constraints - * for each table column - * - * @return {@code boolean} indicating whether the create table operation was successful - * - * @see {@link DatabaseService.Database.ColumnConstraint} - * @see {@link DatabaseService.Database.ColumnDataType} - */ - boolean createTable(final String[] columnNames, - final DatabaseService.Database.ColumnDataType[] columnDataTypes, - final List> columnConstraints); - /** * Insert a row into a table in the database. * @@ -65,18 +33,18 @@ boolean createTable(final String[] columnNames, * the bounds of the provided from and to timestamps. * If the "from" date is equal to 0, the search will use the beginning of event history as the lower bounds of the date range. * If the "to" date is equal to 0, the search will use the current system timestamp as the upper bounds of the date range. - * The {@link EventHistoryResultHandler} will be called with a {@link DatabaseService.QueryResult} which contains the number of matching records, + * The {@link EventHistoryResultHandler} will be called with a {@link Cursor} which contains the number of matching records, * the oldest timestamp, and the newest timestamp for a matching event. * If no database connection is available, the handler will be called with a null {@code DatabaseService.QueryResult}. * * @param hash {@code long} containing the 32-bit FNV-1a hashed representation of an Event's data * @param from {@code long} a timestamp representing the lower bounds of the date range to use when searching for the hash * @param to {@code long} a timestamp representing the upper bounds of the date range to use when searching for the hash - * @return a {@code DatabaseService.QueryResult} which will contain the matching events + * @return a {@code Cursor} which will contain the matching events */ - DatabaseService.QueryResult select(final long hash, - final long from, - final long to); + Cursor select(final long hash, + final long from, + final long to); /** * Delete entries from the event history database. @@ -89,9 +57,8 @@ DatabaseService.QueryResult select(final long hash, int delete (final long hash, final long from, final long to); - /** * Close this database. */ - void close(); + void closeDatabase(); } \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHistoryDatabaseCreationException.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/EventHistoryDatabaseCreationException.java similarity index 86% rename from code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHistoryDatabaseCreationException.java rename to code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/EventHistoryDatabaseCreationException.java index db73ad97c..b6b78b66d 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHistoryDatabaseCreationException.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/EventHistoryDatabaseCreationException.java @@ -9,9 +9,9 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile; +package com.adobe.marketing.mobile.internal.eventhub.history; -class EventHistoryDatabaseCreationException extends Exception { +public class EventHistoryDatabaseCreationException extends Exception { /** * Constructor. diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/EventHistoryRequest.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/EventHistoryRequest.kt new file mode 100644 index 000000000..87317c333 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/EventHistoryRequest.kt @@ -0,0 +1,38 @@ +package com.adobe.marketing.mobile.internal.eventhub.history + +import com.adobe.marketing.mobile.internal.utility.StringEncoder + +/** + * Used for selecting or deleting Events from Event History. + * + * @property mask Key-value pairs that will be used to generate the hash when looking up an Event. + * @property fromDate Date that represents the lower bounds of the date range used when looking up an Event. If not provided, the lookup will use the beginning of Event History as the lower bounds. + * @property toDate Date that represents the upper bounds of the date range used when looking up an Event. If not provided, there will be no upper bound on the date range. + */ +internal data class EventHistoryRequest( + val mask: Map, + val fromDate: Long, + val toDate: Long +) { + @JvmName("getMaskAsDecimalHash") + internal fun getMaskAsDecimalHash(): Long { + val kvpStringBuilder = StringBuilder() + mask.forEach { entry -> + kvpStringBuilder.append(entry.key + ":" + convertToString(entry.value)) + } + return StringEncoder.convertStringToDecimalHash(kvpStringBuilder.toString()) + } +} + +private fun convertToString(value: Any?): String { + if (value == null) return "" + return when (value) { + is String -> value + is Int -> value.toString() + is Char -> value.toString() + is Float -> value.toString() + is Double -> value.toString() + is Boolean -> value.toString() + else -> value.toString() + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHistoryResultHandler.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/EventHistoryResultHandler.java similarity index 87% rename from code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHistoryResultHandler.java rename to code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/EventHistoryResultHandler.java index da1402c22..e265d9b14 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHistoryResultHandler.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/history/EventHistoryResultHandler.java @@ -9,11 +9,12 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile; +package com.adobe.marketing.mobile.internal.eventhub.history; + /** * Interface defining a callback which contains the result of {@link EventHistoryDatabase} operations. */ -interface EventHistoryResultHandler { +public interface EventHistoryResultHandler { void call(final T value); } \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/JSONUtils.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/JSONExtensions.kt similarity index 100% rename from code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/JSONUtils.kt rename to code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/JSONExtensions.kt diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt new file mode 100644 index 000000000..ef7f3f0ad --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt @@ -0,0 +1,73 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.internal.utility + +/** + * Convert map to a decimal FNV1a 32-bit hash. If a mask is provided, only use keys in the provided mask and alphabetize their order. + * + * @param masks contain keys to be hashed. + * @return the decimal FNV1a 32-bit hash. + */ +@JvmSynthetic +internal fun Map.fnv1a32(masks: Array? = null): Long { + val flattenedMap = this.flattening() + val kvPairs = StringBuilder() + var innerMasks = masks + if (innerMasks?.isEmpty() == true) innerMasks = null + innerMasks?.let { + it.sortedArray().forEach { mask -> + if (mask.isNotEmpty() && flattenedMap.containsKey(mask)) { + kvPairs.append(mask).append(":").append(flattenedMap[mask].toString()) + } + } + } ?: run { + flattenedMap.toSortedMap().forEach { entry -> + kvPairs.append(entry.key).append(":").append(entry.value.toString()) + } + } + return StringEncoder.convertStringToDecimalHash(kvPairs.toString()) +} + +/** + * Flatten nested [Map]s and concatenate [String] keys + * For example, an input [Map] of: + * `[rootKey: [key1: value1, key2: value2]]` + * will return a [Map] represented as: + * `[rootKey.key1: value1, rootKey.key2: value2]` + * + * + * @param prefix a prefix to append to the front of the key + * @return flattened [Map] + */ +@JvmSynthetic +internal fun Map.flattening(prefix: String = ""): Map { + val keyPrefix = if (prefix.isNotEmpty()) "$prefix." else prefix + val flattenedMap = mutableMapOf() + this.forEach { entry -> + val expandedKey = keyPrefix + entry.key + val value = entry.value + if (value is Map<*, *> && value.keys.isAllString()) { + @Suppress("UNCHECKED_CAST") + flattenedMap.putAll((value as Map).flattening(expandedKey)) + } else { + flattenedMap[expandedKey] = value + } + } + return flattenedMap +} + +private fun Set<*>.isAllString(): Boolean { + this.forEach { + if (it !is String) return false + } + return true +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHistoryRequest.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapUtils.kt similarity index 57% rename from code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHistoryRequest.java rename to code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapUtils.kt index 0be232359..956616b26 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHistoryRequest.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapUtils.kt @@ -8,22 +8,15 @@ OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - -package com.adobe.marketing.mobile; - -import java.util.Map; +package com.adobe.marketing.mobile.internal.utility /** - * This object is used to make select or delete queries with the {@link EventHistoryDatabase}. + * Convert map to a decimal FNV1a 32-bit hash. If a mask is provided, only use keys in the provided mask and alphabetize their order. + * + * @param map the [Map] to be converted to FNV1a 32-bit hash + * @param masks contain keys to be hashed. + * @return the decimal FNV1a 32-bit hash. */ -final class EventHistoryRequest { - Map mask; - long fromDate; - long toDate; - - EventHistoryRequest(final Map mask, final long fromDate, final long toDate) { - this.mask = mask; - this.fromDate = fromDate; - this.toDate = toDate; - } +internal fun convertMapToFnv1aHash(map: Map?, masks: Array?): Long { + return map?.fnv1a32(masks) ?: -1 } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/SQLiteDatabaseHelper.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/SQLiteDatabaseHelper.java new file mode 100644 index 000000000..fd17bdf4f --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/SQLiteDatabaseHelper.java @@ -0,0 +1,315 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.internal.utility; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteStatement; +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Helper class for performing atomic operation on SQLite Database. + */ +public class SQLiteDatabaseHelper { + + private static final String LOG_PREFIX = "SQLiteDatabaseHelper"; + + private SQLiteDatabaseHelper() { + } + + /** + * Creates the Table if not already exists in database. + * + * @param dbPath the path to Database. + * @param query the query for creating table. + * @return true if successfully created table else false. + */ + public static boolean createTableIfNotExist(final String dbPath, final String query) { + SQLiteDatabase database = null; + + try { + database = openDatabase(dbPath, DatabaseOpenMode.READ_WRITE); + database.execSQL(query); + return true; + } catch (final SQLiteException e) { + MobileCore.log(LoggingMode.WARNING, LOG_PREFIX, + String.format("createTableIfNotExists - Error in creating/accessing table. Error: (%s)", e.getMessage())); + return false; + } finally { + closeDatabase(database); + } + } + + /** + * Inserts a new entity in database. + * + * @param dbPath path to database. + * @param tableName table name in which new row has to be inserted. + * @param data a {@link Map} contains mapping of column and values for new row. + * @return true if row is successfully inserted else false. + */ + public static boolean insertRow(final String dbPath, final String tableName, final Map data) { + SQLiteDatabase database = null; + + try { + database = openDatabase(dbPath, DatabaseOpenMode.READ_WRITE); + long rowId = database.insert(tableName, null, getContentValueFromMap(data)); + return rowId != -1; + } catch (final SQLiteException e) { + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, + String.format("insertRow - Error in inserting row into table (%s). Error: (%s)", tableName, e.getMessage())); + return false; + } finally { + closeDatabase(database); + } + } + + /** + * Returns a read only instance of {@link SQLiteDatabase} for reading data from database at path @dbPath. + * + * @param dbPath path to database from where data has to be read. + * @param columns the names of columns to read. + * @param count the number of rows to be read + * @return {@link List} of {@link ContentValues} where each ContentValue represents a row read from database. + */ + public static List query(final String dbPath, final String tableName, final String[] columns, final int count) { + SQLiteDatabase database = null; + Cursor cursor = null; + + try { + database = openDatabase(dbPath, DatabaseOpenMode.READ_ONLY); + cursor = database.query(tableName, columns, + null, null, null, null, "id ASC", String.valueOf(count)); + List rows = new ArrayList<>(cursor.getCount()); + + if (cursor.moveToFirst()) { + do { + ContentValues contentValues = new ContentValues(); + DatabaseUtils.cursorRowToContentValues(cursor, contentValues); + rows.add(contentValues); + } while (cursor.moveToNext()); + } + + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, String.format("query - Successfully read %d rows from table(%s)", + rows.size(), tableName)); + return Collections.unmodifiableList(rows); + } catch (final SQLiteException e) { + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, + String.format("query - Error in querying database table (%s). Error: (%s)", tableName, e.getMessage())); + return Collections.EMPTY_LIST; + } finally { + if (cursor != null) { + cursor.close(); + } + + closeDatabase(database); + } + } + + /** + * Returns the count of rows in table @tableName + * + * @param dbPath path to database + * @param tableName name of table to calculate size of. + * @return number of rows in Table @tableName. + */ + public static int getTableSize(final String dbPath, final String tableName) { + final String tableSizeQuery = "Select Count (*) from " + tableName; + SQLiteDatabase database = null; + Cursor cursor = null; + + try { + database = openDatabase(dbPath, DatabaseOpenMode.READ_ONLY); + cursor = database.rawQuery(tableSizeQuery, null); + + if (cursor.getCount() > 0 && cursor.moveToFirst()) { + return cursor.getInt(0); + } else { + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, + String.format("getTableSize - Error in querying table(%s) size. Returning 0.", tableName)); + return 0; + } + } catch (final SQLiteException e) { + MobileCore.log(LoggingMode.WARNING, LOG_PREFIX, + String.format("getTableSize - Error in querying table(%s) size. Returning 0. Error: (%s)", tableName, + e.getMessage())); + return 0; + } finally { + if (cursor != null) { + cursor.close(); + } + + closeDatabase(database); + } + } + + /** + * Deletes the @count rows from Database. + * + * @param dbPath path to database. + * @param tableName name of table + * @param orderBy the order in which rows need to be read. It should be in the format "{columnname} asc/des" + * @param count the number of rows need to be deleted. + * @return number of affected rows. + */ + public static int removeRows(final String dbPath, final String tableName, final String orderBy, final int count) { + SQLiteDatabase database = null; + SQLiteStatement statement = null; + + try { + database = openDatabase(dbPath, DatabaseOpenMode.READ_WRITE); + StringBuilder builder = new StringBuilder("DELETE FROM ").append( + tableName).append(" WHERE id in (").append("SELECT id from ").append(tableName).append(" order by ").append( + orderBy).append(" limit ").append(count).append(')'); + statement = database.compileStatement(builder.toString()); + int deletedRowsCount = statement.executeUpdateDelete(); + return deletedRowsCount; + } catch (final SQLiteException e) { + MobileCore.log(LoggingMode.WARNING, LOG_PREFIX, + String.format("removeRows - Error in deleting rows from table(%s). Returning 0. Error: (%s)", tableName, + e.getMessage())); + return -1; //-1 indicates error in deleting rows. + } finally { + if (statement != null) { + statement.close(); + } + + closeDatabase(database); + } + } + + /** + * Deletes all the rows in table. + * + * @param dbPath path to database. + * @param tableName name of table to empty. + * @return true if successfully clears the table else returns false. + */ + public static boolean clearTable(final String dbPath, final String tableName) { + SQLiteDatabase database = null; + + try { + database = openDatabase(dbPath, DatabaseOpenMode.READ_WRITE); + database.delete(tableName, "1", null); + return true; + } catch (final SQLiteException e) { + MobileCore.log(LoggingMode.WARNING, LOG_PREFIX, + String.format("clearTable - Error in clearing table(%s). Returning false. Error: (%s)", tableName, + e.getMessage())); + return false; + } finally { + closeDatabase(database); + } + } + + /** + * Opens the database exists at path @filePath. If database doesn't exist than creates the new one. + * + * @param filePath the absolute path to database. + * @param dbOpenMode an instance of {@link DatabaseOpenMode} + * @return an instance of {@link SQLiteDatabase} to interact with database. + * @throws SQLiteException if there is an error in opening database. + */ + public static SQLiteDatabase openDatabase(final String filePath, final DatabaseOpenMode dbOpenMode) throws SQLiteException { + if (filePath == null || filePath.isEmpty()) { + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "openDatabase - Failed to open database - filepath is null or empty"); + throw new SQLiteException("Invalid database path. Database path is null or empty."); + } + + SQLiteDatabase database = SQLiteDatabase.openDatabase(filePath, + null, + SQLiteDatabase.NO_LOCALIZED_COLLATORS | SQLiteDatabase.CREATE_IF_NECESSARY | dbOpenMode.mode); + MobileCore.log(LoggingMode.VERBOSE, LOG_PREFIX, + String.format("openDatabase - Successfully opened the database at path (%s)", filePath)); + return database; + } + + /** + * Closes the database. + * + * @param database, an instance of {@link SQLiteDatabase}, pointing to database to close. + */ + public static void closeDatabase(final SQLiteDatabase database) { + if (database == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "closeDatabase - Unable to close database, database passed is null."); + return; + } + + database.close(); + MobileCore.log(LoggingMode.VERBOSE, LOG_PREFIX, "closeDatabase - Successfully closed the database."); + } + + /** + * Convert values in {@link Map} to @{@link ContentValues} + * + * @param values and instance of {@link Map} + * @return instance of {@link ContentValues} + */ + private static ContentValues getContentValueFromMap(final Map values) { + ContentValues contentValues = new ContentValues(); + + for (Map.Entry value : values.entrySet()) { + String columnName = value.getKey(); + Object columnValue = value.getValue(); + + if (columnValue == null) { + contentValues.putNull(columnName); + } else if (columnValue instanceof String) { + contentValues.put(columnName, (String) columnValue); + } else if (columnValue instanceof Long) { + contentValues.put(columnName, (Long) columnValue); + } else if (columnValue instanceof Integer) { + contentValues.put(columnName, (Integer) columnValue); + } else if (columnValue instanceof Short) { + contentValues.put(columnName, (Short) columnValue); + } else if (columnValue instanceof Byte) { + contentValues.put(columnName, (Byte) columnValue); + } else if (columnValue instanceof Double) { + contentValues.put(columnName, (Double) columnValue); + } else if (columnValue instanceof Float) { + contentValues.put(columnName, (Float) columnValue); + } else if (columnValue instanceof Boolean) { + contentValues.put(columnName, (Boolean) columnValue); + } else if (columnValue instanceof byte[]) { + contentValues.put(columnName, (byte[]) columnValue); + } else { + MobileCore.log(LoggingMode.WARNING, LOG_PREFIX, + String.format("Unsupported data type received for database insertion: columnName (%s) value (%s)", columnName, + columnValue)); + } + } + + return contentValues; + } + + /** + * Enum type to pass to function open database. It determined whether to open Database connection in READ only mode or READ WRITE mode. + */ + public enum DatabaseOpenMode { + READ_ONLY(SQLiteDatabase.OPEN_READONLY), + READ_WRITE(SQLiteDatabase.OPEN_READWRITE); + + final int mode; + + DatabaseOpenMode(int mode) { + this.mode = mode; + } + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/StringEncoder.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/StringEncoder.java new file mode 100644 index 000000000..60ccf6735 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/StringEncoder.java @@ -0,0 +1,99 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.internal.utility; + +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +/** + * Utility class for {@code String} related encoding methods + */ +final public class StringEncoder { + + private static final String LOG_TAG = "StringEncoder"; + private static final char[] hexArray = "0123456789abcdef".toCharArray(); + private static final int ALL_BITS_ENABLED = 0xFF; + private static final int SHIFT_BY = 4; + private static final int HEX_CHAR_MULTIPLIER = 0x0F; + private static final int HEX_RADIX = 16; + private static final int PRIME = 0x1000193; // 16777619 as hex + private static final int OFFSET = 0x811c9dc5; // 2166136261 as hex + + private StringEncoder() { + } + + public static String getSha1HashedString(final String inputString) { + if (inputString == null || inputString.isEmpty()) { + return null; + } + + String hash = null; + + try { + final MessageDigest digest = MessageDigest.getInstance("SHA-1"); + byte[] bytes = inputString.getBytes(StandardCharsets.UTF_8); + digest.update(bytes, 0, bytes.length); + bytes = digest.digest(); + + char[] hexChars = new char[bytes.length * 2]; + + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & ALL_BITS_ENABLED; + hexChars[j * 2] = hexArray[v >>> SHIFT_BY]; + hexChars[j * 2 + 1] = hexArray[v & HEX_CHAR_MULTIPLIER]; + } + + hash = new String(hexChars); + } catch (final NoSuchAlgorithmException ex) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "ADBMobile - error while attempting to encode a string (%s)" + ex); + } + + return hash; + } + + /** + * Converts the given {@code String} into a decimal representation of the signed 2's complement FNV1a 32-bit hash. + * + * @param inputString {@code String} containing to be hashed + * @return a {@link long} containing the decimal FNV1a 32-bit hash + */ + public static long convertStringToDecimalHash(final String inputString) { + final int hash = getFnv1aHash(inputString); + // convert signed 2's complement hash to hex string + final String hexHash = Integer.toHexString(hash); + + return Long.parseLong(hexHash, HEX_RADIX); + } + + /** + * Converts the given {@code String} to a signed 2's complement FNV1a 32-bit hash. + *

+ * https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + * Online validator - https://md5calc.com/hash/fnv1a32?str= + * + * @param input {@code String} to be hashed + * @return a {@code int} containing the signed 2's complement FNV1a 32-bit hash + */ + private static int getFnv1aHash(final String input) { + int hash = (input == null || input.trim().isEmpty()) ? 0 : OFFSET; + final byte[] bytes = input.getBytes(); + + for (byte aByte : bytes) { + hash ^= (aByte & ALL_BITS_ENABLED); + hash *= PRIME; + } + + return hash; + } +} \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/HistoricalEventsQuerying.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/HistoricalEventsQuerying.kt new file mode 100644 index 000000000..33620d4ef --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/HistoricalEventsQuerying.kt @@ -0,0 +1,54 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.launch.rulesengine + +import com.adobe.marketing.mobile.LoggingMode +import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.internal.eventhub.history.EventHistoryRequest +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +private const val LOG_TAG = "historicalEventsQuerying" +private const val SEARCH_TYPE_ANY = "any" +private const val ASYNC_TIMEOUT = 1000L + +@JvmSynthetic +internal fun historicalEventsQuerying( + requests: List, + searchType: String +): Int { + val eventHistory = MobileCore.getEventHistory() + if (eventHistory == null) { + MobileCore.log( + LoggingMode.ERROR, + LOG_TAG, + "Unable to retrieve historical events, the event history is not available." + ) + return 0 + } + return try { + val latch = CountDownLatch(1) + var eventCounts = 0 + eventHistory.getEvents(requests.toTypedArray(), searchType == SEARCH_TYPE_ANY) { + latch.countDown() + eventCounts = it + } + latch.await(ASYNC_TIMEOUT, TimeUnit.MILLISECONDS) + eventCounts + } catch (e: Exception) { + MobileCore.log( + LoggingMode.WARNING, + LOG_TAG, + "Unable to retrieve historical events, caused by the exception: ${e.localizedMessage}" + ) + 0 + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt index 43b09cc1b..cd89a8fb3 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt @@ -10,11 +10,53 @@ */ package com.adobe.marketing.mobile.launch.rulesengine.json +import com.adobe.marketing.mobile.LoggingMode +import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.internal.eventhub.history.EventHistoryRequest +import com.adobe.marketing.mobile.launch.rulesengine.historicalEventsQuerying +import com.adobe.marketing.mobile.rulesengine.ComparisonExpression import com.adobe.marketing.mobile.rulesengine.Evaluable +import com.adobe.marketing.mobile.rulesengine.OperandFunction +import com.adobe.marketing.mobile.rulesengine.OperandLiteral internal class HistoricalCondition(val definition: JSONDefinition) : JSONCondition() { + companion object { + private const val LOG_TAG = "HistoricalCondition" + } + override fun toEvaluable(): Evaluable? { - TODO("Not yet implemented") + val matcher = definition.matcher + val valueAsInt = definition.value + if (definition.events !is List<*> || + matcher !is String || + matcher !in MatcherCondition.MATCHER_MAPPING || + valueAsInt !is Int + ) { + MobileCore.log( + LoggingMode.ERROR, + LOG_TAG, + "Failed to build Evaluable from definition JSON: \n $definition" + ) + return null + } + val fromDate = definition.from ?: 0 + val toDate = definition.to ?: 0 + val searchType = definition.searchType ?: "any" + val requestEvents = definition.events.map { + EventHistoryRequest(it, fromDate, toDate) + } + return ComparisonExpression( + OperandFunction({ + try { + @Suppress("UNCHECKED_CAST") + historicalEventsQuerying(it[0] as List, it[1] as String) + } catch (e: Exception) { + 0 + } + }, requestEvents, searchType), + matcher, + OperandLiteral(valueAsInt) + ) } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONDefinition.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONDefinition.kt index 7a32bffd2..531037a3c 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONDefinition.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONDefinition.kt @@ -39,8 +39,8 @@ internal data class JSONDefinition( val values: List?, val events: List>?, val value: Any?, - val from: Int?, - val to: Int?, + val from: Long?, + val to: Long?, val searchType: String? ) { companion object { @@ -73,8 +73,8 @@ internal data class JSONDefinition( val events = buildValueMapList(jsonObject.optJSONArray(DEFINITION_KEY_EVENTS)) val value = jsonObject.opt(DEFINITION_KEY_VALUE) - val from = jsonObject.opt(DEFINITION_KEY_FROM) as? Int - val to = jsonObject.opt(DEFINITION_KEY_TO) as? Int + val from = jsonObject.opt(DEFINITION_KEY_FROM) as? Long + val to = jsonObject.opt(DEFINITION_KEY_TO) as? Long val searchType = jsonObject.opt(DEFINITION_KEY_SEARCH_TYPE) as? String return JSONDefinition( logic, diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/MatcherCondition.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/MatcherCondition.kt index 4361d566e..4ca251c01 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/MatcherCondition.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/MatcherCondition.kt @@ -27,7 +27,7 @@ internal class MatcherCondition(val definition: JSONDefinition) : JSONCondition( companion object { private const val LOG_TAG = "MatcherCondition" private const val OPERATION_NAME_OR = "or" - private val MATCHER_MAPPING = mapOf( + internal val MATCHER_MAPPING = mapOf( "eq" to "equals", "ne" to "notEquals", "gt" to "greaterThan", diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java index 7f2fbe0fe..13a52464b 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java @@ -15,805 +15,802 @@ import android.app.Application; import android.content.Context; +import com.adobe.marketing.mobile.internal.eventhub.history.EventHistory; + import java.util.Date; import java.util.Map; public class MobileCore { - private final static String VERSION = "1.10.0"; - private final static String TAG = MobileCore.class.getSimpleName(); - private static final String NULL_CONTEXT_MESSAGE = "Context must be set before calling SDK methods"; - - private static Core core; - private static PlatformServices platformServices; - private static final Object mutex = new Object(); - - private MobileCore() { - } - - // For test - static void setCore(final Core core) { - synchronized (mutex) { - MobileCore.core = core; - } - } - - // For test - static void setPlatformServices(final PlatformServices platformServices) { - synchronized (mutex) { - MobileCore.platformServices = platformServices; - } - } - - static Core getCore() { - synchronized (mutex) { - return core; - } - } - - /** - * Returns the version for the {@code MobileCore} extension - * @return The version string - */ - public static String extensionVersion() { - synchronized (mutex) { - if (core == null) { - Log.warning(TAG, "Returning version without wrapper type info. Make sure setApplication API is called."); - return VERSION; - } - - return core.getSdkVersion(); - } - } - - /** - * Set the current {@link Application}, which enables the SDK get the app {@code Context}, - * register a {@link Application.ActivityLifecycleCallbacks} - * to monitor the lifecycle of the app and get the {@link android.app.Activity} on top of the screen. - *

- * NOTE: This method should be called right after the app starts, so it gives the SDK all the - * contexts it needed. - * - * @param app the current {@code Application} - */ - public static void setApplication(final Application app) { - // AMSDK-8502 - // workaround to prevent a crash happening on Android 8.0/8.1 related to TimeZoneNamesImpl - // https://issuetracker.google.com/issues/110848122 - try { - new Date().toString(); - } catch (AssertionError e) { - // Workaround for a bug in Android that can cause crashes on Android 8.0 and 8.1 - } catch (Exception e) { - // Workaround for a bug in Android that can cause crashes on Android 8.0 and 8.1 - } - - App.setApplication(app); - V4ToV5Migration migrationTool = new V4ToV5Migration(); - migrationTool.migrate(); - - if (core == null) { - synchronized (mutex) { - if (platformServices == null) { - platformServices = new AndroidPlatformServices(); - } - - core = new Core(platformServices, VERSION); - } - } - - com.adobe.marketing.mobile.internal.context.App.getInstance().initializeApp(new - com.adobe.marketing.mobile.internal.context.App.AppContextProvider() { - @Override - public Context getAppContext() { - return App.getAppContext(); - } - - @Override - public Activity getCurrentActivity() { - return App.getCurrentActivity(); - } - }); - - } - - /** - * Get the global {@link Application} object of the current process. - *

- * NOTE: {@link #setApplication(Application)} must be called before calling this method. - * - * @return the current {@code Application}, or null if no {@code Application} was set or - * the {@code Application} process was destroyed. - */ - public static Application getApplication() { - return App.getApplication(); - } - - /** - * Set the {@link LoggingMode} level for the Mobile SDK. - * - * @param mode the logging mode - */ - public static void setLogLevel(LoggingMode mode) { - Log.setLogLevel(mode); - } - - /** - * Get the {@link LoggingMode} level for the Mobile SDK - * @return the set {@code LoggingMode} - */ - public static LoggingMode getLogLevel() { - return Log.getLogLevel(); - } - - /** - * Sends a log message of the given {@code LoggingMode}. If the specified {@code mode} is - * more verbose than the current {@link LoggingMode} set from {@link #setLogLevel(LoggingMode)} - * then the message is not printed. - * - * @param mode the {@link LoggingMode} used to print the message - * @param tag used to identify the source of the log message - * @param message the message to log - */ - public static void log(final LoggingMode mode, final String tag, final String message) { - if (mode == null) { - return; - } - - switch (mode) { - case ERROR: - Log.error(tag, message); - break; - - case WARNING: - Log.warning(tag, message); - break; - - case DEBUG: - Log.debug(tag, message); - break; - - case VERBOSE: - Log.trace(tag, message); - break; - } - } - - /** - * Start the Core processing. This should be called after the initial set of extensions have been registered. - *

- * This call will wait for any outstanding registrations to complete and then start event processing. - * You can use the callback to kickoff additional operations immediately after any operations kicked off during registration. - * You shouldn't call this method more than once in your app, if so, sdk will ignore it and print error log. - * - * @param completionCallback An optional {@link AdobeCallback} invoked after registrations are completed - */ - public static void start(final AdobeCallback completionCallback) { - synchronized (mutex) { - if (core == null) { - Log.debug(TAG, "Failed to start SDK (%s)", NULL_CONTEXT_MESSAGE); - - if (completionCallback != null & completionCallback instanceof AdobeCallbackWithError) { - ((AdobeCallbackWithError) completionCallback).fail(AdobeError.EXTENSION_NOT_INITIALIZED); - } - - return; - } - - // initialize the AndroidEventHistory service and set it within the EventHistoryProvider instance - try { - if (EventHistoryProvider.getEventHistory() == null) { - EventHistoryProvider.setEventHistory(new AndroidEventHistory()); - Log.trace(TAG, "Android EventHistory created and set in the EventHistoryProvider"); - } - } catch (final EventHistoryDatabaseCreationException exception) { - Log.warning(TAG, "Failed to create the android event history service: %s", - exception.getMessage()); - } - - core.start(completionCallback); - } - } - - // ======================================================== - // Configuration methods - // ======================================================== - - - public static void configureWithAppID(final String appId) { - if (core == null) { - Log.debug(TAG, "Failed to set Adobe App ID (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.configureWithAppID(appId); - } - - - /** - * Registers an extension class which has {@code Extension} as parent. - *

- * In order to ensure that your extension receives all the internal events, this method needs - * to be called after {@link MobileCore#setApplication(Application)} is called, but before - * any other method in this class. - * - * @param extensionClass a class whose parent is {@link Extension} - * @param errorCallback an optional {@link ExtensionErrorCallback} for the eventuality of an error, - * called when this method returns false - * @return {@code boolean} indicating if the provided parameters are valid and no error occurs - */ - public static boolean registerExtension(final Class extensionClass, - final ExtensionErrorCallback errorCallback) { - if (core == null) { - Log.debug(TAG, "Failed to register the extension. (%s)", NULL_CONTEXT_MESSAGE); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.UNEXPECTED_ERROR); - } - - return false; - } - - if (extensionClass == null) { - if (errorCallback != null) { - errorCallback.error(ExtensionError.UNEXPECTED_ERROR); - } - - return false; - } - - core.registerExtension(extensionClass, errorCallback); - return true; - } - - /** - * Called by the extension public API to dispatch an event for other extensions or the internal SDK to consume. - * - * @param event required parameter, {@link Event} instance to be dispatched, should not be null - * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred during dispatching - * @return {@code boolean} indicating if the the event dispatching operation succeeded - */ - public static boolean dispatchEvent(final Event event, final ExtensionErrorCallback errorCallback) { - if (core == null) { - Log.debug(TAG, "Failed to dispatch event. (%s)", NULL_CONTEXT_MESSAGE); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.UNEXPECTED_ERROR); - } - - return false; - } - - return core.dispatchEvent(event, errorCallback); - } - - /** - * This method will be used when the provided {@code Event} is used as a trigger and a response event - * is expected in return. The returned event needs to be sent using - * {@link #dispatchResponseEvent(Event, Event, ExtensionErrorCallback)}. - *

- * Passes an {@link ExtensionError} to {@code errorCallback} if {@code event} or - * {@code responseCallback} are null. - * - * @param event required parameter, {@link Event} instance to be dispatched, used as a trigger - * @param responseCallback required parameters, {@link AdobeCallback} to be called with the response event received - * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred during dispatching - * @return {@code boolean} indicating if the the event dispatching operation succeeded - * - * @see MobileCore#dispatchResponseEvent(Event, Event, ExtensionErrorCallback) - */ - public static boolean dispatchEventWithResponseCallback(final Event event, - final AdobeCallback responseCallback, - final ExtensionErrorCallback errorCallback) { - if (core == null) { - Log.debug(TAG, "Failed to dispatch event with a response callback. (%s)", NULL_CONTEXT_MESSAGE); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.UNEXPECTED_ERROR); - } - - return false; - } - - // the core will validate and copy this event - return core.dispatchEventWithResponseCallback(event, responseCallback, errorCallback); - } - - /** - * This method will be used when the provided {@code Event} is used as a trigger and a response event - * is expected in return. The returned event needs to be sent using - * {@link #dispatchResponseEvent(Event, Event, ExtensionErrorCallback)}. - *

- * Passes an {@link AdobeError} to {@link AdobeCallbackWithError#fail(AdobeError)} if {@code event} is null. - * Passes an {@link AdobeError} to {@link AdobeCallbackWithError#fail(AdobeError)} if {@code event} processing timeout occurs. - * - * @param event required parameter, {@link Event} instance to be dispatched, used as a trigger - * @param responseCallback required parameters, {@link AdobeCallback} to be called with the response event received - * - * @see MobileCore#dispatchResponseEvent(Event, Event, ExtensionErrorCallback) - */ - public static void dispatchEventWithResponseCallback(final Event event, - final AdobeCallbackWithError responseCallback) { - if (core == null) { - Log.debug(TAG, "Failed to dispatch event with a response callback. (%s)", NULL_CONTEXT_MESSAGE); - - if (responseCallback != null) { - responseCallback.fail(AdobeError.UNEXPECTED_ERROR); - } - - return; - } - - if (event == null) { - Log.debug(TAG, "Failed to dispatch event with a response callback: the given event is null "); - - if (responseCallback != null) { - responseCallback.fail(AdobeError.UNEXPECTED_ERROR); - } - - return; - } - - if (responseCallback == null) { - Log.warning(TAG, - "Failed to dispatch event with a response callback: the given callback (AdobeCallbackWithError) object is null "); - return; - } - - // the core will validate and copy this event - core.dispatchEventWithResponseCallback(event, responseCallback); - } - - /** - * Dispatches a response event for a paired event that was sent to {@code dispatchEventWithResponseCallback} - * and received by an extension listener {@code hear} method. - *

- * Passes an {@link ExtensionError} to {@code errorCallback} if {@code responseEvent} or {@code requestEvent} are null. - *

- * Note: The {@code responseEvent} will not be sent to any listeners, it is sent only to the response callback registered - * using {@link MobileCore#dispatchEventWithResponseCallback(Event, AdobeCallback, ExtensionErrorCallback)}. - * - * @param responseEvent required parameter, {@link Event} instance to be dispatched as a response for the - * event sent using {@link MobileCore#dispatchEventWithResponseCallback(Event, AdobeCallback, ExtensionErrorCallback)} - * @param requestEvent required parameter, the event sent using - * {@link MobileCore#dispatchEventWithResponseCallback(Event, AdobeCallback, ExtensionErrorCallback)} - * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred during dispatching - * @return {@code boolean} indicating if the the event dispatching operation succeeded - * - * @see MobileCore#dispatchEventWithResponseCallback(Event, AdobeCallback, ExtensionErrorCallback) - */ - public static boolean dispatchResponseEvent(final Event responseEvent, final Event requestEvent, - final ExtensionErrorCallback errorCallback) { - - if (core == null) { - Log.debug(TAG, "Failed to dispatch the response event. (%s)", NULL_CONTEXT_MESSAGE); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.UNEXPECTED_ERROR); - } - - return false; - } - - // the core will validate and copy this events - return core.dispatchResponseEvent(responseEvent, requestEvent, errorCallback); - } - - /** - * Load configuration from the file in the assets folder. SDK automatically reads config from `ADBMobileConfig.json` file if - * it exists in the assets folder. Use this API only if the config needs to be read from a different file. - *

- * On application relaunch, the configuration from the file at {@code filepath} is not preserved and this method must be called - * again if desired. - *

- * On failure to read the file or parse the JSON contents, the existing configuration remains unchanged. - *

- * Calls to this API will replace any existing SDK configuration except those set using - * {@link #updateConfiguration(Map)} or {@link #setPrivacyStatus(MobilePrivacyStatus)}. - * Configuration updates made using {@link #updateConfiguration(Map)} and {@link #setPrivacyStatus(MobilePrivacyStatus)} - * are always applied on top of configuration changes made using this API. - *absolute - * @param fileName the name of the configure file in the assets folder. A value of {@code null} has no effect. - */ - public static void configureWithFileInAssets(final String fileName) { - if (core == null) { - Log.debug(TAG, "Failed to load configuration with asset file (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.configureWithFileInAssets(fileName); - } - - /** - * Load configuration from local file. - *

- * Configure the SDK by reading a local file containing the JSON configuration. On application relaunch, - * the configuration from the file at {@code filepath} is not preserved and this method must be called again if desired. - *

- * On failure to read the file or parse the JSON contents, the existing configuration remains unchanged. - *

- * Calls to this API will replace any existing SDK configuration except those set using - * {@link #updateConfiguration(Map)} or {@link #setPrivacyStatus(MobilePrivacyStatus)}. - * Configuration updates made using {@link #updateConfiguration(Map)} and {@link #setPrivacyStatus(MobilePrivacyStatus)} - * are always applied on top of configuration changes made using this API. - * - * @param filepath absolute path to a local configuration file. A value of {@code null} has no effect. - */ - public static void configureWithFileInPath(final String filepath) { - if (core == null) { - Log.debug(TAG, "Failed to load configuration with file path (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.configureWithFileInPath(filepath); - } - - /** - * Update specific configuration parameters. - *

- * Update the current SDK configuration with specific key/value pairs. Keys not found in the current - * configuration are added. Configuration updates are preserved and applied over existing or new - * configurations set by calling {@link #configureWithAppID(String)} or {@link #configureWithFileInPath(String)}, - * even across application restarts. - *

- * Using {@code null} values is allowed and effectively removes the configuration parameter from the current configuration. - * - * @param configMap configuration key/value pairs to be updated or added. A value of {@code null} has no effect. - */ - public static void updateConfiguration(final Map configMap) { - if (core == null) { - Log.debug(TAG, "Failed to update configuration (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.updateConfiguration(configMap); - } - - /** - * Clear the changes made by {@link #updateConfiguration(Map)} and {@link #setPrivacyStatus(MobilePrivacyStatus)} - * to the initial configuration provided either by {@link #configureWithAppID(String)} - * or {@link #configureWithFileInPath(String)} or {@link #configureWithFileInAssets(String)} - */ - public static void clearUpdatedConfiguration() { - if (core == null) { - Log.debug(TAG, "Failed to clear updated configuration (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.clearUpdatedConfiguration(); - } - - /** - * Set the Adobe Mobile Privacy status. - *

- * Sets the {@link MobilePrivacyStatus} for this SDK. The set privacy status is preserved and applied over any new - * configuration changes from calls to {@link #configureWithAppID(String)} or {@link #configureWithFileInPath(String)}, - * even across application restarts. - * - * @param privacyStatus {@link MobilePrivacyStatus} to be set to the SDK - * @see MobilePrivacyStatus - */ - public static void setPrivacyStatus(final MobilePrivacyStatus privacyStatus) { - if (core == null) { - Log.debug(TAG, "Failed to set privacy status (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.setPrivacyStatus(privacyStatus); - } - - /** - * Get the current Adobe Mobile Privacy Status. - *

- * Gets the currently configured {@link MobilePrivacyStatus} and passes it as a parameter to the given - * {@link AdobeCallback#call(Object)} function. - * - * @param callback {@link AdobeCallback} instance which is invoked with the configured privacy status as a parameter - * @see AdobeCallback - * @see MobilePrivacyStatus - */ - public static void getPrivacyStatus(final AdobeCallback callback) { - if (core == null) { - Log.debug(TAG, "Failed to retrieve the privacy status (%s)", NULL_CONTEXT_MESSAGE); - - if (callback != null & callback instanceof AdobeCallbackWithError) { - ((AdobeCallbackWithError) callback).fail(AdobeError.EXTENSION_NOT_INITIALIZED); - } - - return; - } - - core.getPrivacyStatus(callback); - } - - /** - * Retrieve all identities stored by/known to the SDK in a JSON {@code String} format. - * - * @param callback {@link AdobeCallback} instance which is invoked with all the known identifier in JSON {@link String} format - * @see AdobeCallback - */ - public static void getSdkIdentities(final AdobeCallback callback) { - if (callback == null) { - Log.debug(TAG, "%s (Callback), provide a callback to retrieve the all SDK identities", Log.UNEXPECTED_NULL_VALUE); - return; - } - - if (core == null) { - Log.debug(TAG, "Failed to retrieve the all SDK identities (%s)", NULL_CONTEXT_MESSAGE); - - if (callback != null & callback instanceof AdobeCallbackWithError) { - ((AdobeCallbackWithError) callback).fail(AdobeError.EXTENSION_NOT_INITIALIZED); - } - - return; - } - - core.getSdkIdentities(callback); - } - - // ======================================================== - // Generic methods - // ======================================================== - - - /** - * This method dispatches an Analytics track {@code action} event - *

- * Actions represent events that occur in your application that you want to measure; the corresponding metrics will - * be incremented each time the event occurs. For example, you may want to track when an user click on the login - * button or a certain article was viewed. - *

- * - * @param action {@code String} containing the name of the action to track - * @param contextData {@code Map} containing context data to attach on this hit - */ - public static void trackAction(final String action, final Map contextData) { - if (core == null) { - Log.debug(TAG, "Failed to track action %s (%s)", action, NULL_CONTEXT_MESSAGE); - return; - } - - core.trackAction(action, contextData); - } - - /** - * This method dispatches an Analytics track {@code state} event - *

- * States represent different screens or views of your application. When the user navigates between application pages, - * a new track call should be sent with current state name. Tracking state name is typically called from an - * Activity in the onResume method. - *

- * - * @param state {@code String} containing the name of the state to track - * @param contextData contextData {@code Map} containing context data to attach on this hit - */ - public static void trackState(final String state, final Map contextData) { - if (core == null) { - Log.debug(TAG, "Failed to track state %s (%s)", state, NULL_CONTEXT_MESSAGE); - return; - } - - core.trackState(state, contextData); - } - - /** - * This method dispatches an event to notify the SDK of a new {@code advertisingIdentifier} - * - * @param advertisingIdentifier {@code String} representing Android advertising identifier - */ - public static void setAdvertisingIdentifier(final String advertisingIdentifier) { - if (core == null) { - Log.debug(TAG, "Failed to set advertising identifier (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.setAdvertisingIdentifier(advertisingIdentifier); - } - - /** - * This method dispatches an event to notify the SDK of a new {@code pushIdentifier} - * - * @param pushIdentifier {@code String} representing the new push identifier - */ - public static void setPushIdentifier(final String pushIdentifier) { - if (core == null) { - Log.debug(TAG, "Failed to set push identifier (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.setPushIdentifier(pushIdentifier); - } - - - /** - * Start/resume lifecycle session. - *

- * Start a new lifecycle session or resume a previously paused lifecycle session. If a previously paused session - * timed out, then a new session is created. If a current session is running, then calling this method does nothing. - *

- * Additional context data may be passed when calling this method. Lifecycle data and any additional data are - * sent as context data parameters to Analytics, to Target as mbox parameters, and for Audience Manager they are - * sent as customer variables. Any additional data is also used by the Rules Engine when processing rules. - *

- * This method should be called from the Activity onResume method. - * - * @param additionalContextData optional additional context for this session. - */ - public static void lifecycleStart(final Map additionalContextData) { - if (core == null) { - Log.debug(TAG, "Failed to start lifecycle session (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.lifecycleStart(additionalContextData); - } - - /** - * Pause/stop lifecycle session. - *

- * Pauses the current lifecycle session. Calling pause on an already paused session updates the paused timestamp, - * having the effect of resetting the session timeout timer. If no lifecycle session is running, then calling - * this method does nothing. - *

- * A paused session is resumed if {@link #lifecycleStart(Map)} is called before the session timeout. After - * the session timeout, a paused session is closed and calling {@link #lifecycleStart(Map)} will create - * a new session. The session timeout is defined by the {@code lifecycle.sessionTimeout} configuration parameter. - * If not defined, the default session timeout is five minutes. - *

- * This method should be called from the Activity onPause method. - */ - public static void lifecyclePause() { - if (core == null) { - Log.debug(TAG, "Failed to pause lifecycle session (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.lifecyclePause(); - } - - /** - * Collect PII data. Although using this call enables collection of PII data, the SDK does not - * automatically send the data to any Adobe endpoint. - * - * @param data the map containing the PII data to be collected - */ - public static void collectPii(final Map data) { - if (core == null) { - Log.debug(TAG, "Failed to collect PII (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.collectPii(data); - } - - /** - * Sets the resource Id for small icon. - * - * @param resourceID the resource Id of the icon - */ - public static void setSmallIconResourceID(final int resourceID) { - App.setSmallIconResourceID(resourceID); - } - - /** - * Sets the resource Id for small icon. - * - * @param resourceID the resource Id of the icon - */ - public static void setLargeIconResourceID(final int resourceID) { - App.setLargeIconResourceID(resourceID); - } - - /** - * Collects message data from various points in the application. - * - * This method can be invoked to support the following use cases: - *

    - *
  1. Tracking Push Message receive and click.
  2. - *
  3. Tracking Local Notification receive and click.
  4. - *
- *

- * The message tracking information can be supplied in the {@code messageInfo} Map. For scenarios where the application - * is launched as a result of notification click, {@link #collectLaunchInfo(Activity)} will be invoked with the target - * Activity and message data will be extracted from the Intent extras. - * - * @param messageInfo {@code Map} containing message tracking information - */ - public static void collectMessageInfo(final Map messageInfo) { - if (core == null) { - Log.debug(TAG, "Failed to collect Message Info (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.collectData(messageInfo); - } - - /** - * Collects data from the Activity / context to be used later by the SDK. - * - * This method marshals the {@code activity} instance and extracts the intent data / extras. It should be called to support - * the following use cases: - *

    - *
  1. Tracking Deep Link click-through - *
      - *
    • Update AndroidManifest.xml to support intent-filter in the activity with the intended action and type of data.
    • - *
    • Handle the intent in the activity.
    • - *
    • Pass activity with deepLink intent to SDK in {@code collectLaunchInfo}.
    • - *
    - *
  2. - *
  3. Tracking Push Message click-through - *
      - *
    • Push message data must be added to the Intent used to open target activity on click-through.
    • - *
    • The data can be added in intent extras which is then collected by SDK when target activity is passed in {@code collectedLaunchInfo}.
    • - *
    - *
  4. - *
  5. Tracking Local Notification click-through - *
      - *
    • Add manifest-declared broadcast receiver {@code } in your app.
    • - *
    • Pass notifications activity reference in {@code collectLaunchInfo}.
    • - *
    - *
  6. - *
- *

- * Invoke this method from {@link Activity#onResume} callback in your activity. - * - * @param activity current {@link Activity} reference. - */ - static void collectLaunchInfo(Activity activity) { - if (core == null) { - Log.debug(TAG, "Failed to collect Activity data (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - DataMarshaller marshaller = new DataMarshaller(); - marshaller.marshal(activity); - core.collectData(marshaller.getData()); - } - - /** - * Sets the SDK's current wrapper type. This API should only be used if - * being developed on platforms such as React Native. - *

- * NOTE: {@link #setApplication(Application)} must be called before calling this method. - * - * @param wrapperType the type of wrapper being used. - */ - public static void setWrapperType(WrapperType wrapperType) { - if (core == null) { - Log.warning(TAG, "Cannot set wrapper type, core is null. Make sure setApplication API is called."); - return; - } - - core.setWrapperType(wrapperType); - } - - /** - * Registers an event listener for the provided event type and source. - * - * @param eventType required parameter, the event type as a valid string (not null or empty) - * @param eventSource required parameter, the event source as a valid string (not null or empty) - * @param callback required parameter, {@link AdobeCallbackWithError#call(Object)} will be called when the event is heard - */ - public static void registerEventListener(final String eventType, final String eventSource, - final AdobeCallbackWithError callback) { - if (core == null) { - Log.debug(TAG, "Failed to register the event listener (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.registerEventListener(eventType, eventSource, callback); - } - - /** - * Clears all identifiers from Edge extensions and generates a new Experience Cloud ID (ECID). - */ - public static void resetIdentities() { - if (core == null) { - Log.debug(TAG, "Failed to reset identities (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.resetIdentities(); - } + private final static String VERSION = "1.10.0"; + private final static String TAG = MobileCore.class.getSimpleName(); + private static final String NULL_CONTEXT_MESSAGE = "Context must be set before calling SDK methods"; + + private static Core core; + private static PlatformServices platformServices; + private static final Object mutex = new Object(); + + private MobileCore() { + } + + // For test + static void setCore(final Core core) { + synchronized (mutex) { + MobileCore.core = core; + } + } + + // For test + static void setPlatformServices(final PlatformServices platformServices) { + synchronized (mutex) { + MobileCore.platformServices = platformServices; + } + } + + static Core getCore() { + synchronized (mutex) { + return core; + } + } + + public static EventHistory getEventHistory() { + if (core == null) return null; + return core.getEventHistory(); + } + + + /** + * Returns the version for the {@code MobileCore} extension + * + * @return The version string + */ + public static String extensionVersion() { + synchronized (mutex) { + if (core == null) { + Log.warning(TAG, "Returning version without wrapper type info. Make sure setApplication API is called."); + return VERSION; + } + + return core.getSdkVersion(); + } + } + + /** + * Set the current {@link Application}, which enables the SDK get the app {@code Context}, + * register a {@link Application.ActivityLifecycleCallbacks} + * to monitor the lifecycle of the app and get the {@link android.app.Activity} on top of the screen. + *

+ * NOTE: This method should be called right after the app starts, so it gives the SDK all the + * contexts it needed. + * + * @param app the current {@code Application} + */ + public static void setApplication(final Application app) { + // AMSDK-8502 + // workaround to prevent a crash happening on Android 8.0/8.1 related to TimeZoneNamesImpl + // https://issuetracker.google.com/issues/110848122 + try { + new Date().toString(); + } catch (AssertionError e) { + // Workaround for a bug in Android that can cause crashes on Android 8.0 and 8.1 + } catch (Exception e) { + // Workaround for a bug in Android that can cause crashes on Android 8.0 and 8.1 + } + + App.setApplication(app); + V4ToV5Migration migrationTool = new V4ToV5Migration(); + migrationTool.migrate(); + + if (core == null) { + synchronized (mutex) { + if (platformServices == null) { + platformServices = new AndroidPlatformServices(); + } + + core = new Core(platformServices, VERSION); + } + } + + com.adobe.marketing.mobile.internal.context.App.getInstance().initializeApp(new + com.adobe.marketing.mobile.internal.context.App.AppContextProvider() { + @Override + public Context getAppContext() { + return App.getAppContext(); + } + + @Override + public Activity getCurrentActivity() { + return App.getCurrentActivity(); + } + }); + + } + + /** + * Get the global {@link Application} object of the current process. + *

+ * NOTE: {@link #setApplication(Application)} must be called before calling this method. + * + * @return the current {@code Application}, or null if no {@code Application} was set or + * the {@code Application} process was destroyed. + */ + public static Application getApplication() { + return App.getApplication(); + } + + /** + * Set the {@link LoggingMode} level for the Mobile SDK. + * + * @param mode the logging mode + */ + public static void setLogLevel(LoggingMode mode) { + Log.setLogLevel(mode); + } + + /** + * Get the {@link LoggingMode} level for the Mobile SDK + * + * @return the set {@code LoggingMode} + */ + public static LoggingMode getLogLevel() { + return Log.getLogLevel(); + } + + /** + * Sends a log message of the given {@code LoggingMode}. If the specified {@code mode} is + * more verbose than the current {@link LoggingMode} set from {@link #setLogLevel(LoggingMode)} + * then the message is not printed. + * + * @param mode the {@link LoggingMode} used to print the message + * @param tag used to identify the source of the log message + * @param message the message to log + */ + public static void log(final LoggingMode mode, final String tag, final String message) { + if (mode == null) { + return; + } + + switch (mode) { + case ERROR: + Log.error(tag, message); + break; + + case WARNING: + Log.warning(tag, message); + break; + + case DEBUG: + Log.debug(tag, message); + break; + + case VERBOSE: + Log.trace(tag, message); + break; + } + } + + /** + * Start the Core processing. This should be called after the initial set of extensions have been registered. + *

+ * This call will wait for any outstanding registrations to complete and then start event processing. + * You can use the callback to kickoff additional operations immediately after any operations kicked off during registration. + * You shouldn't call this method more than once in your app, if so, sdk will ignore it and print error log. + * + * @param completionCallback An optional {@link AdobeCallback} invoked after registrations are completed + */ + public static void start(final AdobeCallback completionCallback) { + synchronized (mutex) { + if (core == null) { + Log.debug(TAG, "Failed to start SDK (%s)", NULL_CONTEXT_MESSAGE); + + if (completionCallback != null & completionCallback instanceof AdobeCallbackWithError) { + ((AdobeCallbackWithError) completionCallback).fail(AdobeError.EXTENSION_NOT_INITIALIZED); + } + + return; + } + + core.start(completionCallback); + } + } + + // ======================================================== + // Configuration methods + // ======================================================== + + + public static void configureWithAppID(final String appId) { + if (core == null) { + Log.debug(TAG, "Failed to set Adobe App ID (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + core.configureWithAppID(appId); + } + + + /** + * Registers an extension class which has {@code Extension} as parent. + *

+ * In order to ensure that your extension receives all the internal events, this method needs + * to be called after {@link MobileCore#setApplication(Application)} is called, but before + * any other method in this class. + * + * @param extensionClass a class whose parent is {@link Extension} + * @param errorCallback an optional {@link ExtensionErrorCallback} for the eventuality of an error, + * called when this method returns false + * @return {@code boolean} indicating if the provided parameters are valid and no error occurs + */ + public static boolean registerExtension(final Class extensionClass, + final ExtensionErrorCallback errorCallback) { + if (core == null) { + Log.debug(TAG, "Failed to register the extension. (%s)", NULL_CONTEXT_MESSAGE); + + if (errorCallback != null) { + errorCallback.error(ExtensionError.UNEXPECTED_ERROR); + } + + return false; + } + + if (extensionClass == null) { + if (errorCallback != null) { + errorCallback.error(ExtensionError.UNEXPECTED_ERROR); + } + + return false; + } + + core.registerExtension(extensionClass, errorCallback); + return true; + } + + /** + * Called by the extension public API to dispatch an event for other extensions or the internal SDK to consume. + * + * @param event required parameter, {@link Event} instance to be dispatched, should not be null + * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred during dispatching + * @return {@code boolean} indicating if the the event dispatching operation succeeded + */ + public static boolean dispatchEvent(final Event event, final ExtensionErrorCallback errorCallback) { + if (core == null) { + Log.debug(TAG, "Failed to dispatch event. (%s)", NULL_CONTEXT_MESSAGE); + + if (errorCallback != null) { + errorCallback.error(ExtensionError.UNEXPECTED_ERROR); + } + + return false; + } + + return core.dispatchEvent(event, errorCallback); + } + + /** + * This method will be used when the provided {@code Event} is used as a trigger and a response event + * is expected in return. The returned event needs to be sent using + * {@link #dispatchResponseEvent(Event, Event, ExtensionErrorCallback)}. + *

+ * Passes an {@link ExtensionError} to {@code errorCallback} if {@code event} or + * {@code responseCallback} are null. + * + * @param event required parameter, {@link Event} instance to be dispatched, used as a trigger + * @param responseCallback required parameters, {@link AdobeCallback} to be called with the response event received + * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred during dispatching + * @return {@code boolean} indicating if the the event dispatching operation succeeded + * @see MobileCore#dispatchResponseEvent(Event, Event, ExtensionErrorCallback) + */ + public static boolean dispatchEventWithResponseCallback(final Event event, + final AdobeCallback responseCallback, + final ExtensionErrorCallback errorCallback) { + if (core == null) { + Log.debug(TAG, "Failed to dispatch event with a response callback. (%s)", NULL_CONTEXT_MESSAGE); + + if (errorCallback != null) { + errorCallback.error(ExtensionError.UNEXPECTED_ERROR); + } + + return false; + } + + // the core will validate and copy this event + return core.dispatchEventWithResponseCallback(event, responseCallback, errorCallback); + } + + /** + * This method will be used when the provided {@code Event} is used as a trigger and a response event + * is expected in return. The returned event needs to be sent using + * {@link #dispatchResponseEvent(Event, Event, ExtensionErrorCallback)}. + *

+ * Passes an {@link AdobeError} to {@link AdobeCallbackWithError#fail(AdobeError)} if {@code event} is null. + * Passes an {@link AdobeError} to {@link AdobeCallbackWithError#fail(AdobeError)} if {@code event} processing timeout occurs. + * + * @param event required parameter, {@link Event} instance to be dispatched, used as a trigger + * @param responseCallback required parameters, {@link AdobeCallback} to be called with the response event received + * @see MobileCore#dispatchResponseEvent(Event, Event, ExtensionErrorCallback) + */ + public static void dispatchEventWithResponseCallback(final Event event, + final AdobeCallbackWithError responseCallback) { + if (core == null) { + Log.debug(TAG, "Failed to dispatch event with a response callback. (%s)", NULL_CONTEXT_MESSAGE); + + if (responseCallback != null) { + responseCallback.fail(AdobeError.UNEXPECTED_ERROR); + } + + return; + } + + if (event == null) { + Log.debug(TAG, "Failed to dispatch event with a response callback: the given event is null "); + + if (responseCallback != null) { + responseCallback.fail(AdobeError.UNEXPECTED_ERROR); + } + + return; + } + + if (responseCallback == null) { + Log.warning(TAG, + "Failed to dispatch event with a response callback: the given callback (AdobeCallbackWithError) object is null "); + return; + } + + // the core will validate and copy this event + core.dispatchEventWithResponseCallback(event, responseCallback); + } + + /** + * Dispatches a response event for a paired event that was sent to {@code dispatchEventWithResponseCallback} + * and received by an extension listener {@code hear} method. + *

+ * Passes an {@link ExtensionError} to {@code errorCallback} if {@code responseEvent} or {@code requestEvent} are null. + *

+ * Note: The {@code responseEvent} will not be sent to any listeners, it is sent only to the response callback registered + * using {@link MobileCore#dispatchEventWithResponseCallback(Event, AdobeCallback, ExtensionErrorCallback)}. + * + * @param responseEvent required parameter, {@link Event} instance to be dispatched as a response for the + * event sent using {@link MobileCore#dispatchEventWithResponseCallback(Event, AdobeCallback, ExtensionErrorCallback)} + * @param requestEvent required parameter, the event sent using + * {@link MobileCore#dispatchEventWithResponseCallback(Event, AdobeCallback, ExtensionErrorCallback)} + * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred during dispatching + * @return {@code boolean} indicating if the the event dispatching operation succeeded + * @see MobileCore#dispatchEventWithResponseCallback(Event, AdobeCallback, ExtensionErrorCallback) + */ + public static boolean dispatchResponseEvent(final Event responseEvent, final Event requestEvent, + final ExtensionErrorCallback errorCallback) { + + if (core == null) { + Log.debug(TAG, "Failed to dispatch the response event. (%s)", NULL_CONTEXT_MESSAGE); + + if (errorCallback != null) { + errorCallback.error(ExtensionError.UNEXPECTED_ERROR); + } + + return false; + } + + // the core will validate and copy this events + return core.dispatchResponseEvent(responseEvent, requestEvent, errorCallback); + } + + /** + * Load configuration from the file in the assets folder. SDK automatically reads config from `ADBMobileConfig.json` file if + * it exists in the assets folder. Use this API only if the config needs to be read from a different file. + *

+ * On application relaunch, the configuration from the file at {@code filepath} is not preserved and this method must be called + * again if desired. + *

+ * On failure to read the file or parse the JSON contents, the existing configuration remains unchanged. + *

+ * Calls to this API will replace any existing SDK configuration except those set using + * {@link #updateConfiguration(Map)} or {@link #setPrivacyStatus(MobilePrivacyStatus)}. + * Configuration updates made using {@link #updateConfiguration(Map)} and {@link #setPrivacyStatus(MobilePrivacyStatus)} + * are always applied on top of configuration changes made using this API. + * absolute + * + * @param fileName the name of the configure file in the assets folder. A value of {@code null} has no effect. + */ + public static void configureWithFileInAssets(final String fileName) { + if (core == null) { + Log.debug(TAG, "Failed to load configuration with asset file (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + core.configureWithFileInAssets(fileName); + } + + /** + * Load configuration from local file. + *

+ * Configure the SDK by reading a local file containing the JSON configuration. On application relaunch, + * the configuration from the file at {@code filepath} is not preserved and this method must be called again if desired. + *

+ * On failure to read the file or parse the JSON contents, the existing configuration remains unchanged. + *

+ * Calls to this API will replace any existing SDK configuration except those set using + * {@link #updateConfiguration(Map)} or {@link #setPrivacyStatus(MobilePrivacyStatus)}. + * Configuration updates made using {@link #updateConfiguration(Map)} and {@link #setPrivacyStatus(MobilePrivacyStatus)} + * are always applied on top of configuration changes made using this API. + * + * @param filepath absolute path to a local configuration file. A value of {@code null} has no effect. + */ + public static void configureWithFileInPath(final String filepath) { + if (core == null) { + Log.debug(TAG, "Failed to load configuration with file path (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + core.configureWithFileInPath(filepath); + } + + /** + * Update specific configuration parameters. + *

+ * Update the current SDK configuration with specific key/value pairs. Keys not found in the current + * configuration are added. Configuration updates are preserved and applied over existing or new + * configurations set by calling {@link #configureWithAppID(String)} or {@link #configureWithFileInPath(String)}, + * even across application restarts. + *

+ * Using {@code null} values is allowed and effectively removes the configuration parameter from the current configuration. + * + * @param configMap configuration key/value pairs to be updated or added. A value of {@code null} has no effect. + */ + public static void updateConfiguration(final Map configMap) { + if (core == null) { + Log.debug(TAG, "Failed to update configuration (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + core.updateConfiguration(configMap); + } + + /** + * Clear the changes made by {@link #updateConfiguration(Map)} and {@link #setPrivacyStatus(MobilePrivacyStatus)} + * to the initial configuration provided either by {@link #configureWithAppID(String)} + * or {@link #configureWithFileInPath(String)} or {@link #configureWithFileInAssets(String)} + */ + public static void clearUpdatedConfiguration() { + if (core == null) { + Log.debug(TAG, "Failed to clear updated configuration (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + core.clearUpdatedConfiguration(); + } + + /** + * Set the Adobe Mobile Privacy status. + *

+ * Sets the {@link MobilePrivacyStatus} for this SDK. The set privacy status is preserved and applied over any new + * configuration changes from calls to {@link #configureWithAppID(String)} or {@link #configureWithFileInPath(String)}, + * even across application restarts. + * + * @param privacyStatus {@link MobilePrivacyStatus} to be set to the SDK + * @see MobilePrivacyStatus + */ + public static void setPrivacyStatus(final MobilePrivacyStatus privacyStatus) { + if (core == null) { + Log.debug(TAG, "Failed to set privacy status (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + core.setPrivacyStatus(privacyStatus); + } + + /** + * Get the current Adobe Mobile Privacy Status. + *

+ * Gets the currently configured {@link MobilePrivacyStatus} and passes it as a parameter to the given + * {@link AdobeCallback#call(Object)} function. + * + * @param callback {@link AdobeCallback} instance which is invoked with the configured privacy status as a parameter + * @see AdobeCallback + * @see MobilePrivacyStatus + */ + public static void getPrivacyStatus(final AdobeCallback callback) { + if (core == null) { + Log.debug(TAG, "Failed to retrieve the privacy status (%s)", NULL_CONTEXT_MESSAGE); + + if (callback != null & callback instanceof AdobeCallbackWithError) { + ((AdobeCallbackWithError) callback).fail(AdobeError.EXTENSION_NOT_INITIALIZED); + } + + return; + } + + core.getPrivacyStatus(callback); + } + + /** + * Retrieve all identities stored by/known to the SDK in a JSON {@code String} format. + * + * @param callback {@link AdobeCallback} instance which is invoked with all the known identifier in JSON {@link String} format + * @see AdobeCallback + */ + public static void getSdkIdentities(final AdobeCallback callback) { + if (callback == null) { + Log.debug(TAG, "%s (Callback), provide a callback to retrieve the all SDK identities", Log.UNEXPECTED_NULL_VALUE); + return; + } + + if (core == null) { + Log.debug(TAG, "Failed to retrieve the all SDK identities (%s)", NULL_CONTEXT_MESSAGE); + + if (callback != null & callback instanceof AdobeCallbackWithError) { + ((AdobeCallbackWithError) callback).fail(AdobeError.EXTENSION_NOT_INITIALIZED); + } + + return; + } + + core.getSdkIdentities(callback); + } + + // ======================================================== + // Generic methods + // ======================================================== + + + /** + * This method dispatches an Analytics track {@code action} event + *

+ * Actions represent events that occur in your application that you want to measure; the corresponding metrics will + * be incremented each time the event occurs. For example, you may want to track when an user click on the login + * button or a certain article was viewed. + *

+ * + * @param action {@code String} containing the name of the action to track + * @param contextData {@code Map} containing context data to attach on this hit + */ + public static void trackAction(final String action, final Map contextData) { + if (core == null) { + Log.debug(TAG, "Failed to track action %s (%s)", action, NULL_CONTEXT_MESSAGE); + return; + } + + core.trackAction(action, contextData); + } + + /** + * This method dispatches an Analytics track {@code state} event + *

+ * States represent different screens or views of your application. When the user navigates between application pages, + * a new track call should be sent with current state name. Tracking state name is typically called from an + * Activity in the onResume method. + *

+ * + * @param state {@code String} containing the name of the state to track + * @param contextData contextData {@code Map} containing context data to attach on this hit + */ + public static void trackState(final String state, final Map contextData) { + if (core == null) { + Log.debug(TAG, "Failed to track state %s (%s)", state, NULL_CONTEXT_MESSAGE); + return; + } + + core.trackState(state, contextData); + } + + /** + * This method dispatches an event to notify the SDK of a new {@code advertisingIdentifier} + * + * @param advertisingIdentifier {@code String} representing Android advertising identifier + */ + public static void setAdvertisingIdentifier(final String advertisingIdentifier) { + if (core == null) { + Log.debug(TAG, "Failed to set advertising identifier (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + core.setAdvertisingIdentifier(advertisingIdentifier); + } + + /** + * This method dispatches an event to notify the SDK of a new {@code pushIdentifier} + * + * @param pushIdentifier {@code String} representing the new push identifier + */ + public static void setPushIdentifier(final String pushIdentifier) { + if (core == null) { + Log.debug(TAG, "Failed to set push identifier (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + core.setPushIdentifier(pushIdentifier); + } + + + /** + * Start/resume lifecycle session. + *

+ * Start a new lifecycle session or resume a previously paused lifecycle session. If a previously paused session + * timed out, then a new session is created. If a current session is running, then calling this method does nothing. + *

+ * Additional context data may be passed when calling this method. Lifecycle data and any additional data are + * sent as context data parameters to Analytics, to Target as mbox parameters, and for Audience Manager they are + * sent as customer variables. Any additional data is also used by the Rules Engine when processing rules. + *

+ * This method should be called from the Activity onResume method. + * + * @param additionalContextData optional additional context for this session. + */ + public static void lifecycleStart(final Map additionalContextData) { + if (core == null) { + Log.debug(TAG, "Failed to start lifecycle session (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + core.lifecycleStart(additionalContextData); + } + + /** + * Pause/stop lifecycle session. + *

+ * Pauses the current lifecycle session. Calling pause on an already paused session updates the paused timestamp, + * having the effect of resetting the session timeout timer. If no lifecycle session is running, then calling + * this method does nothing. + *

+ * A paused session is resumed if {@link #lifecycleStart(Map)} is called before the session timeout. After + * the session timeout, a paused session is closed and calling {@link #lifecycleStart(Map)} will create + * a new session. The session timeout is defined by the {@code lifecycle.sessionTimeout} configuration parameter. + * If not defined, the default session timeout is five minutes. + *

+ * This method should be called from the Activity onPause method. + */ + public static void lifecyclePause() { + if (core == null) { + Log.debug(TAG, "Failed to pause lifecycle session (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + core.lifecyclePause(); + } + + /** + * Collect PII data. Although using this call enables collection of PII data, the SDK does not + * automatically send the data to any Adobe endpoint. + * + * @param data the map containing the PII data to be collected + */ + public static void collectPii(final Map data) { + if (core == null) { + Log.debug(TAG, "Failed to collect PII (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + core.collectPii(data); + } + + /** + * Sets the resource Id for small icon. + * + * @param resourceID the resource Id of the icon + */ + public static void setSmallIconResourceID(final int resourceID) { + App.setSmallIconResourceID(resourceID); + } + + /** + * Sets the resource Id for small icon. + * + * @param resourceID the resource Id of the icon + */ + public static void setLargeIconResourceID(final int resourceID) { + App.setLargeIconResourceID(resourceID); + } + + /** + * Collects message data from various points in the application. + *

+ * This method can be invoked to support the following use cases: + *

    + *
  1. Tracking Push Message receive and click.
  2. + *
  3. Tracking Local Notification receive and click.
  4. + *
+ *

+ * The message tracking information can be supplied in the {@code messageInfo} Map. For scenarios where the application + * is launched as a result of notification click, {@link #collectLaunchInfo(Activity)} will be invoked with the target + * Activity and message data will be extracted from the Intent extras. + * + * @param messageInfo {@code Map} containing message tracking information + */ + public static void collectMessageInfo(final Map messageInfo) { + if (core == null) { + Log.debug(TAG, "Failed to collect Message Info (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + core.collectData(messageInfo); + } + + /** + * Collects data from the Activity / context to be used later by the SDK. + *

+ * This method marshals the {@code activity} instance and extracts the intent data / extras. It should be called to support + * the following use cases: + *

    + *
  1. Tracking Deep Link click-through + *
      + *
    • Update AndroidManifest.xml to support intent-filter in the activity with the intended action and type of data.
    • + *
    • Handle the intent in the activity.
    • + *
    • Pass activity with deepLink intent to SDK in {@code collectLaunchInfo}.
    • + *
    + *
  2. + *
  3. Tracking Push Message click-through + *
      + *
    • Push message data must be added to the Intent used to open target activity on click-through.
    • + *
    • The data can be added in intent extras which is then collected by SDK when target activity is passed in {@code collectedLaunchInfo}.
    • + *
    + *
  4. + *
  5. Tracking Local Notification click-through + *
      + *
    • Add manifest-declared broadcast receiver {@code } in your app.
    • + *
    • Pass notifications activity reference in {@code collectLaunchInfo}.
    • + *
    + *
  6. + *
+ *

+ * Invoke this method from {@link Activity#onResume} callback in your activity. + * + * @param activity current {@link Activity} reference. + */ + static void collectLaunchInfo(Activity activity) { + if (core == null) { + Log.debug(TAG, "Failed to collect Activity data (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + DataMarshaller marshaller = new DataMarshaller(); + marshaller.marshal(activity); + core.collectData(marshaller.getData()); + } + + /** + * Sets the SDK's current wrapper type. This API should only be used if + * being developed on platforms such as React Native. + *

+ * NOTE: {@link #setApplication(Application)} must be called before calling this method. + * + * @param wrapperType the type of wrapper being used. + */ + public static void setWrapperType(WrapperType wrapperType) { + if (core == null) { + Log.warning(TAG, "Cannot set wrapper type, core is null. Make sure setApplication API is called."); + return; + } + + core.setWrapperType(wrapperType); + } + + /** + * Registers an event listener for the provided event type and source. + * + * @param eventType required parameter, the event type as a valid string (not null or empty) + * @param eventSource required parameter, the event source as a valid string (not null or empty) + * @param callback required parameter, {@link AdobeCallbackWithError#call(Object)} will be called when the event is heard + */ + public static void registerEventListener(final String eventType, final String eventSource, + final AdobeCallbackWithError callback) { + if (core == null) { + Log.debug(TAG, "Failed to register the event listener (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + core.registerEventListener(eventType, eventSource, callback); + } + + /** + * Clears all identifiers from Edge extensions and generates a new Experience Cloud ID (ECID). + */ + public static void resetIdentities() { + if (core == null) { + Log.debug(TAG, "Failed to reset identities (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + core.resetIdentities(); + } } diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java index 03c626a71..a8239e836 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java @@ -13,6 +13,7 @@ import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; +import com.adobe.marketing.mobile.internal.utility.SQLiteDatabaseHelper; import java.io.File; import java.util.HashMap; @@ -27,11 +28,9 @@ class DataQueueService implements DataQueuing { private static final String LOG_TAG = "DataQueueService"; private Map dataQueueCache; - private final SQLiteDatabaseHelper databaseHelper; DataQueueService() { dataQueueCache = new HashMap<>(); - databaseHelper = new SQLiteDatabaseHelper(); } @Override @@ -51,7 +50,7 @@ public DataQueue getDataQueue(final String databaseName) { return null; } - dataQueue = new SQLiteDataQueue(cacheDir, databaseName, databaseHelper); + dataQueue = new SQLiteDataQueue(cacheDir, databaseName); dataQueueCache.put(databaseName, dataQueue); } } diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java index 7addb1d4d..e12b4bd1c 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java @@ -15,6 +15,7 @@ import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; +import com.adobe.marketing.mobile.internal.utility.SQLiteDatabaseHelper; import java.io.File; import java.util.ArrayList; @@ -35,12 +36,10 @@ final class SQLiteDataQueue implements DataQueue { private static final String LOG_PREFIX = "SQLiteDataQueue"; private final String databasePath; - private final SQLiteDatabaseHelper databaseHelper; private boolean isClose = false; private final Object dbMutex = new Object(); - SQLiteDataQueue(final File cacheDir, final String databaseName, final SQLiteDatabaseHelper databaseHelper) { - this.databaseHelper = databaseHelper; + SQLiteDataQueue(final File cacheDir, final String databaseName) { this.databasePath = new File(cacheDir, removeRelativePath(databaseName)).getPath(); createTableIfNotExists(); } @@ -63,7 +62,7 @@ public boolean add(final DataEntity dataEntity) { dataToInsert.put(TB_KEY_DATA, dataEntity.getData() != null ? dataEntity.getData() : ""); synchronized (dbMutex) { - boolean result = databaseHelper.insertRow(databasePath, TABLE_NAME, dataToInsert); + boolean result = SQLiteDatabaseHelper.insertRow(databasePath, TABLE_NAME, dataToInsert); MobileCore.log(LoggingMode.VERBOSE, LOG_PREFIX, String.format("add - Successfully added DataEntity (%s) to DataQueue", dataEntity.toString())); return result; @@ -85,7 +84,7 @@ public List peek(final int n) { List rows; synchronized (dbMutex) { - rows = databaseHelper.query(databasePath, TABLE_NAME, new String[] {TB_KEY_TIMESTAMP, TB_KEY_UNIQUE_IDENTIFIER, TB_KEY_DATA}, + rows = SQLiteDatabaseHelper.query(databasePath, TABLE_NAME, new String[] {TB_KEY_TIMESTAMP, TB_KEY_UNIQUE_IDENTIFIER, TB_KEY_DATA}, n); } @@ -145,7 +144,7 @@ public boolean remove(final int n) { } synchronized (dbMutex) { - int count = databaseHelper.removeRows(databasePath, TABLE_NAME, "id ASC", n); + int count = SQLiteDatabaseHelper.removeRows(databasePath, TABLE_NAME, "id ASC", n); MobileCore.log(LoggingMode.VERBOSE, LOG_PREFIX, String.format("remove n - Successfully removed %d DataEntities", count)); return count != -1; @@ -170,7 +169,7 @@ public boolean clear() { } synchronized (dbMutex) { - boolean result = databaseHelper.clearTable(databasePath, TABLE_NAME); + boolean result = SQLiteDatabaseHelper.clearTable(databasePath, TABLE_NAME); MobileCore.log(LoggingMode.VERBOSE, LOG_PREFIX, String.format("clear - %s in clearing Table %s", (result ? "Successful" : "Failed"), TABLE_NAME)); return result; @@ -185,7 +184,7 @@ public int count() { } synchronized (dbMutex) { - return databaseHelper.getTableSize(databasePath, TABLE_NAME); + return SQLiteDatabaseHelper.getTableSize(databasePath, TABLE_NAME); } } @@ -198,12 +197,6 @@ public void close() { * Creates a Table with name {@link #TABLE_NAME}, if not already exists in database at path {@link #databasePath}. */ private void createTableIfNotExists() { - if (databaseHelper == null) { - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, String.format("Unable to create table (%s), database helper is null", - TABLE_NAME)); - return; - } - final String tableCreationQuery = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, " + "uniqueIdentifier TEXT NOT NULL UNIQUE, " + @@ -211,7 +204,7 @@ private void createTableIfNotExists() { "data TEXT);"; synchronized (dbMutex) { - if (databaseHelper.createTableIfNotExist(databasePath, tableCreationQuery)) { + if (SQLiteDatabaseHelper.createTableIfNotExist(databasePath, tableCreationQuery)) { MobileCore.log(LoggingMode.VERBOSE, LOG_PREFIX, String.format("createTableIfNotExists - Successfully created/already existed table (%s) ", TABLE_NAME)); return; diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDatabaseHelper.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDatabaseHelper.java deleted file mode 100644 index 25888a4e0..000000000 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDatabaseHelper.java +++ /dev/null @@ -1,317 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile.services; - -import android.content.ContentValues; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.database.sqlite.SQLiteStatement; - -import com.adobe.marketing.mobile.LoggingMode; -import com.adobe.marketing.mobile.MobileCore; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -/** - * Helper class for performing atomic operation on SQLite Database. - */ -class SQLiteDatabaseHelper { - - private static final String LOG_PREFIX = "ADBDatabase"; - - SQLiteDatabaseHelper() { - } - - /** - * Creates the Table if not already exists in database. - * - * @param dbPath the path to Database. - * @param query the query for creating table. - * @return true if successfully created table else false. - */ - boolean createTableIfNotExist(final String dbPath, final String query) { - SQLiteDatabase database = null; - - try { - database = openDatabase(dbPath, DatabaseOpenMode.READ_WRITE); - database.execSQL(query); - return true; - } catch (final SQLiteException e) { - MobileCore.log(LoggingMode.WARNING, LOG_PREFIX, - String.format("createTableIfNotExists - Error in creating/accessing table. Error: (%s)", e.getMessage())); - return false; - } finally { - closeDatabase(database); - } - } - - /** - * Inserts a new entity in database. - * - * @param dbPath path to database. - * @param tableName table name in which new row has to be inserted. - * @param data a {@link Map} contains mapping of column and values for new row. - * @return true if row is successfully inserted else false. - */ - boolean insertRow(final String dbPath, final String tableName, final Map data) { - SQLiteDatabase database = null; - - try { - database = openDatabase(dbPath, DatabaseOpenMode.READ_WRITE); - long rowId = database.insert(tableName, null, getContentValueFromMap(data)); - return rowId != -1; - } catch (final SQLiteException e) { - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, - String.format("insertRow - Error in inserting row into table (%s). Error: (%s)", tableName, e.getMessage())); - return false; - } finally { - closeDatabase(database); - } - } - - /** - * Returns a read only instance of {@link SQLiteDatabase} for reading data from database at path @dbPath. - * - * @param dbPath path to database from where data has to be read. - * @param columns the names of columns to read. - * @param count the number of rows to be read - * @return {@link List} of {@link ContentValues} where each ContentValue represents a row read from database. - */ - List query(final String dbPath, final String tableName, final String[] columns, final int count) { - SQLiteDatabase database = null; - Cursor cursor = null; - - try { - database = openDatabase(dbPath, DatabaseOpenMode.READ_ONLY); - cursor = database.query(tableName, columns, - null, null, null, null, "id ASC", String.valueOf(count)); - List rows = new ArrayList<>(cursor.getCount()); - - if (cursor.moveToFirst()) { - do { - ContentValues contentValues = new ContentValues(); - DatabaseUtils.cursorRowToContentValues(cursor, contentValues); - rows.add(contentValues); - } while (cursor.moveToNext()); - } - - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, String.format("query - Successfully read %d rows from table(%s)", - rows.size(), tableName)); - return Collections.unmodifiableList(rows); - } catch (final SQLiteException e) { - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, - String.format("query - Error in querying database table (%s). Error: (%s)", tableName, e.getMessage())); - return Collections.EMPTY_LIST; - } finally { - if (cursor != null) { - cursor.close(); - } - - closeDatabase(database); - } - } - - /** - * Returns the count of rows in table @tableName - * - * @param dbPath path to database - * @param tableName name of table to calculate size of. - * @return number of rows in Table @tableName. - */ - int getTableSize(final String dbPath, final String tableName) { - final String tableSizeQuery = "Select Count (*) from " + tableName; - SQLiteDatabase database = null; - Cursor cursor = null; - - try { - database = openDatabase(dbPath, DatabaseOpenMode.READ_ONLY); - cursor = database.rawQuery(tableSizeQuery, null); - - if (cursor.getCount() > 0 && cursor.moveToFirst()) { - return cursor.getInt(0); - } else { - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, - String.format("getTableSize - Error in querying table(%s) size. Returning 0.", tableName)); - return 0; - } - } catch (final SQLiteException e) { - MobileCore.log(LoggingMode.WARNING, LOG_PREFIX, - String.format("getTableSize - Error in querying table(%s) size. Returning 0. Error: (%s)", tableName, - e.getMessage())); - return 0; - } finally { - if (cursor != null) { - cursor.close(); - } - - closeDatabase(database); - } - } - - /** - * Deletes the @count rows from Database. - * - * @param dbPath path to database. - * @param tableName name of table - * @param orderBy the order in which rows need to be read. It should be in the format "{columnname} asc/des" - * @param count the number of rows need to be deleted. - * @return number of affected rows. - */ - int removeRows(final String dbPath, final String tableName, final String orderBy, final int count) { - SQLiteDatabase database = null; - SQLiteStatement statement = null; - - try { - database = openDatabase(dbPath, DatabaseOpenMode.READ_WRITE); - StringBuilder builder = new StringBuilder("DELETE FROM ").append( - tableName).append(" WHERE id in (").append("SELECT id from ").append(tableName).append(" order by ").append( - orderBy).append(" limit ").append(count).append(')'); - statement = database.compileStatement(builder.toString()); - int deletedRowsCount = statement.executeUpdateDelete(); - return deletedRowsCount; - } catch (final SQLiteException e) { - MobileCore.log(LoggingMode.WARNING, LOG_PREFIX, - String.format("removeRows - Error in deleting rows from table(%s). Returning 0. Error: (%s)", tableName, - e.getMessage())); - return -1; //-1 indicates error in deleting rows. - } finally { - if (statement != null) { - statement.close(); - } - - closeDatabase(database); - } - } - - /** - * Deletes all the rows in table. - * - * @param dbPath path to database. - * @param tableName name of table to empty. - * @return true if successfully clears the table else returns false. - */ - boolean clearTable(final String dbPath, final String tableName) { - SQLiteDatabase database = null; - - try { - database = openDatabase(dbPath, DatabaseOpenMode.READ_WRITE); - database.delete(tableName, "1", null); - return true; - } catch (final SQLiteException e) { - MobileCore.log(LoggingMode.WARNING, LOG_PREFIX, - String.format("clearTable - Error in clearing table(%s). Returning false. Error: (%s)", tableName, - e.getMessage())); - return false; - } finally { - closeDatabase(database); - } - } - - /** - * Opens the database exists at path @filePath. If database doesn't exist than creates the new one. - * - * @param filePath the absolute path to database. - * @param dbOpenMode an instance of {@link DatabaseOpenMode} - * @return an instance of {@link SQLiteDatabase} to interact with database. - * @throws SQLiteException if there is an error in opening database. - */ - SQLiteDatabase openDatabase(final String filePath, final DatabaseOpenMode dbOpenMode) throws SQLiteException { - if (filePath == null || filePath.isEmpty()) { - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "openDatabase - Failed to open database - filepath is null or empty"); - throw new SQLiteException("Invalid database path. Database path is null or empty."); - } - - SQLiteDatabase database = SQLiteDatabase.openDatabase(filePath, - null, - SQLiteDatabase.NO_LOCALIZED_COLLATORS | SQLiteDatabase.CREATE_IF_NECESSARY | dbOpenMode.mode); - MobileCore.log(LoggingMode.VERBOSE, LOG_PREFIX, - String.format("openDatabase - Successfully opened the database at path (%s)", filePath)); - return database; - } - - /** - * Closes the database. - * - * @param database, an instance of {@link SQLiteDatabase}, pointing to database to close. - */ - private void closeDatabase(final SQLiteDatabase database) { - if (database == null) { - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "closeDatabase - Unable to close database, database passed is null."); - return; - } - - database.close(); - MobileCore.log(LoggingMode.VERBOSE, LOG_PREFIX, "closeDatabase - Successfully closed the database."); - } - - /** - * Convert values in {@link Map} to @{@link ContentValues} - * - * @param values and instance of {@link Map} - * @return instance of {@link ContentValues} - */ - private static ContentValues getContentValueFromMap(final Map values) { - ContentValues contentValues = new ContentValues(); - - for (Map.Entry value : values.entrySet()) { - String columnName = value.getKey(); - Object columnValue = value.getValue(); - - if (columnValue == null) { - contentValues.putNull(columnName); - } else if (columnValue instanceof String) { - contentValues.put(columnName, (String) columnValue); - } else if (columnValue instanceof Long) { - contentValues.put(columnName, (Long) columnValue); - } else if (columnValue instanceof Integer) { - contentValues.put(columnName, (Integer) columnValue); - } else if (columnValue instanceof Short) { - contentValues.put(columnName, (Short) columnValue); - } else if (columnValue instanceof Byte) { - contentValues.put(columnName, (Byte) columnValue); - } else if (columnValue instanceof Double) { - contentValues.put(columnName, (Double) columnValue); - } else if (columnValue instanceof Float) { - contentValues.put(columnName, (Float) columnValue); - } else if (columnValue instanceof Boolean) { - contentValues.put(columnName, (Boolean) columnValue); - } else if (columnValue instanceof byte[]) { - contentValues.put(columnName, (byte[]) columnValue); - } else { - MobileCore.log(LoggingMode.WARNING, LOG_PREFIX, - String.format("Unsupported data type received for database insertion: columnName (%s) value (%s)", columnName, - columnValue)); - } - } - - return contentValues; - } - - /** - * Enum type to pass to function open database. It determined whether to open Database connection in READ only mode or READ WRITE mode. - */ - enum DatabaseOpenMode { - READ_ONLY(SQLiteDatabase.OPEN_READONLY), - READ_WRITE(SQLiteDatabase.OPEN_READWRITE); - - final int mode; - - DatabaseOpenMode(int mode) { - this.mode = mode; - } - } -} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/EventDataTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/EventDataTest.java index 6eceb3835..4d10ec5af 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/EventDataTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/EventDataTest.java @@ -1573,309 +1573,4 @@ public void testOptTypedMapWithNullDefaultValue() { assertNull(new EventData().optTypedMap("k", null, new CircleSerializer())); } - // FNV1a 32-bit hash tests - // basic smoke tests for comparison with iOS - @Test - public void testGetFnv1aHash_String_Smoke() { - // setup - final Map map = new HashMap() { - { - put("key", Variant.fromString("value")); - } - }; - final EventData eventData = new EventData(map); - // test - final long hash = eventData.toFnv1aHash(null); - // verify flattened map string "key:value" - final long expectedHash = 4007910315l; - assertEquals(expectedHash, hash); - } - - @Test - public void testGetFnv1aHash_Integer_Smoke() { - // setup - final Map map = new HashMap() { - { - put("key", Variant.fromInteger(552)); - } - }; - final EventData eventData = new EventData(map); - // test - final long hash = eventData.toFnv1aHash(null); - // verify flattened map string "key:552" - final long expectedHash = 874166902; - assertEquals(expectedHash, hash); - } - - @Test - public void testGetFnv1aHash_Boolean_Smoke() { - // setup - final Map map = new HashMap() { - { - put("key", Variant.fromBoolean(false)); - } - }; - final EventData eventData = new EventData(map); - // test - final long hash = eventData.toFnv1aHash(null); - // verify flattened map string "key:false" - final long expectedHash = 138493769; - assertEquals(expectedHash, hash); - } - - @Test - public void testGetFnv1aHash_AsciiSorted_Smoke() { - // setup - final Map map = new HashMap() { - { - put("key", Variant.fromString("value")); - put("number", Variant.fromInteger(1234)); - put("UpperCase", Variant.fromString("abc")); - put("_underscore", Variant.fromString("score")); - } - }; - final EventData eventData = new EventData(map); - // test - final long hash = eventData.toFnv1aHash(null); - // verify flattened map string "UpperCase:abc_underscore:scorekey:valuenumber:1234" - final long expectedHash = 960895195; - assertEquals(expectedHash, hash); - } - - @Test - public void testGetFnv1aHash_NoMask_Happy() { - // setup - final Map map = new HashMap() { - { - put("aaa", Variant.fromString("1")); - put("zzz", Variant.fromBoolean(true)); - } - }; - final EventData eventData = new EventData(map); - // test - final long hash = eventData.toFnv1aHash(null); - // verify flattened map string "aaa:1zzz:true" - final long expectedHash = 3251025831l; - assertEquals(expectedHash, hash); - } - - @Test - public void testGetFnv1aHash_WithMask_Happy() { - // setup - final Map map = new HashMap() { - { - put("aaa", Variant.fromString("1")); - put("c", Variant.fromInteger(2)); - put("m", Variant.fromDouble(1.11)); - put("zzz", Variant.fromBoolean(true)); - } - }; - final EventData eventData = new EventData(map); - final String[] mask = new String[] {"c", "m"}; - // test - final long hash = eventData.toFnv1aHash(mask); - // verify flattened map string "c:2m:1.11" - final long expectedHash = 2718815288l; - assertEquals(expectedHash, hash); - } - - @Test - public void testGetFnv1aHash_ArrayOfMaps() { - // setup - final Map map = new HashMap() { - { - put("aaa", Variant.fromString("1")); - put("zzz", Variant.fromBoolean(true)); - } - }; - final Map map2 = new HashMap() { - { - put("number", Variant.fromInteger(123)); - put("double", Variant.fromDouble(1.5)); - } - }; - final List list = new ArrayList<>(); - list.add(Variant.fromVariantMap(map)); - list.add(Variant.fromVariantMap(map2)); - final EventData eventData = new EventData(); - eventData.putVariantList("key", list); - // test - final long hash = eventData.toFnv1aHash(null); - // verify flattened map string "key:[{"aaa":"1","zzz":true},{"number":123,"double":1.5}]" - final long expectedHash = 3841285024l; - assertEquals(expectedHash, hash); - } - - @Test - public void testGetFnv1aHash_ArrayOfLists() { - // setup - final ArrayList innerList = new ArrayList() { - { - add(Variant.fromString("aaa")); - add(Variant.fromString("zzz")); - add(Variant.fromInteger(111)); - } - }; - final ArrayList innerList2 = new ArrayList() { - { - add(Variant.fromString("2")); - } - }; - final List list = new ArrayList<>(); - list.add(Variant.fromVariantList(innerList)); - list.add(Variant.fromVariantList(innerList2)); - final EventData eventData = new EventData(); - eventData.putVariantList("key", list); - // test - final long hash = eventData.toFnv1aHash(null); - // verify flattened map string "key:[["aaa","zzz",111],["2"]]" - final long expectedHash = 1785496830l; - assertEquals(expectedHash, hash); - } - - @Test - public void testGetFnv1aHash_WithNestedMap() { - // setup - final Map innerMap = new HashMap() { - { - put("bbb", "5"); - put("hhh", "false"); - } - }; - final Map map = new HashMap() { - { - put("aaa", Variant.fromString("1")); - put("zzz", Variant.fromBoolean(true)); - put("inner", Variant.fromStringMap(innerMap)); - } - }; - final EventData eventData = new EventData(map); - // test - final long hash = eventData.toFnv1aHash(null); - // verify flattened map string "aaa:1inner.bbb:5inner.hhh:falsezzz:true" - final long expectedHash = 4230384023l; - assertEquals(expectedHash, hash); - } - - @Test - public void testGetFnv1aHash_WithNestedMapContainingNestedMap() { - // setup - final Map secondInnerMap = new HashMap() { - { - put("ccc", "10"); - put("iii", "1.1"); - } - }; - final Map innerMap = new HashMap() { - { - put("bbb", Variant.fromInteger(5)); - put("hhh", Variant.fromBoolean(false)); - put("secondInner", Variant.fromStringMap(secondInnerMap)); - } - }; - final Map map = new HashMap() { - { - put("aaa", Variant.fromString("1")); - put("zzz", Variant.fromBoolean(true)); - put("inner", Variant.fromVariantMap(innerMap)); - } - }; - final EventData eventData = new EventData(map); - // test - final long hash = eventData.toFnv1aHash(null); - // verify flattened map string "aaa:1inner.bbb:5inner.hhh:falseinner.secondInner.ccc:10inner.secondInner.iii:1.1zzz:true" - final long expectedHash = 1786696518; - assertEquals(expectedHash, hash); - } - - @Test - public void testGetFnv1aHash_WithEmptyMask() { - // setup - final Map map = new HashMap() { - { - put("a", Variant.fromString("1")); - put("b", Variant.fromString("2")); - } - }; - final EventData eventData = new EventData(map); - final String[] mask = new String[] {}; - // test - final long hash = eventData.toFnv1aHash(mask); - // verify flattened map string "a:1b:2" - final long expectedHash = 3371500665l; - assertEquals(expectedHash, hash); - } - - @Test - public void testGetFnv1aHash_WithMaskMatchingNoKeys() { - // setup - final Map map = new HashMap() { - { - put("a", Variant.fromString("1")); - put("b", Variant.fromString("2")); - } - }; - final EventData eventData = new EventData(map); - final String[] mask = new String[] {"c", "d"}; - // test - final long hash = eventData.toFnv1aHash(mask); - // verify 0 / no hash generated due to mask keys not being present in the map - final long expectedHash = 0; - assertEquals(expectedHash, hash); - } - - @Test - public void testGetFnv1aHash_NoMask_VerifyEventDataMapSortedWithCaseSensitivity() { - // setup - final Map map = new HashMap() { - { - put("a", Variant.fromString("1")); - put("A", Variant.fromString("2")); - put("ba", Variant.fromString("3")); - put("Ba", Variant.fromString("4")); - put("Z", Variant.fromString("5")); - put("z", Variant.fromString("6")); - put("r", Variant.fromString("7")); - put("R", Variant.fromString("8")); - put("bc", Variant.fromString("9")); - put("Bc", Variant.fromString("10")); - put("1", Variant.fromInteger(1)); - put("222", Variant.fromInteger(222)); - } - }; - final EventData eventData = new EventData(map); - // test - final long hash = eventData.toFnv1aHash(null); - // verify flattened map string "1:1222:222A:2Ba:4Bc:10R:8Z:5a:1ba:3bc:9r:7z:6" - final long expectedHash = 2933724447l; - assertEquals(expectedHash, hash); - } - - @Test - public void testGetFnv1aHash_WithMask_VerifyEventDataMapSortedWithCaseSensitivity() { - // setup - final Map map = new HashMap() { - { - put("a", Variant.fromString("1")); - put("A", Variant.fromString("2")); - put("ba", Variant.fromString("3")); - put("Ba", Variant.fromString("4")); - put("Z", Variant.fromString("5")); - put("z", Variant.fromString("6")); - put("r", Variant.fromString("7")); - put("R", Variant.fromString("8")); - put("bc", Variant.fromString("9")); - put("Bc", Variant.fromString("10")); - put("1", Variant.fromInteger(1)); - put("222", Variant.fromInteger(222)); - } - }; - final EventData eventData = new EventData(map); - final String[] mask = new String[] {"A", "a", "ba", "Ba", "bc", "Bc", "1"}; - // test - final long hash = eventData.toFnv1aHash(mask); - // verify flattened map string "1:1A:2Ba:4Bc:10a:1ba:3bc:9" - final long expectedHash = 3344627991l; - assertEquals(expectedHash, hash); - } } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/StringUtilsTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/StringUtilsTest.java index 4f8088a25..a698f6cbe 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/StringUtilsTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/StringUtilsTest.java @@ -14,12 +14,11 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import static org.junit.Assert.*; +import com.adobe.marketing.mobile.internal.utility.StringEncoder; + public class StringUtilsTest { diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/TestHelper.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/TestHelper.java index 847632930..9e6edf064 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/TestHelper.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/TestHelper.java @@ -18,14 +18,14 @@ import static org.junit.Assert.*; -class TestHelper { +public class TestHelper { /** * Verifies that an utility class is well defined. * * @param clazz utility class to verify. */ - static void assertUtilityClassWellDefined(final Class clazz) throws NoSuchMethodException, InvocationTargetException, + public static void assertUtilityClassWellDefined(final Class clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { assertTrue("Class must be final", Modifier.isFinal(clazz.getModifiers())); assertEquals("There must be only one constructor", 1, clazz.getDeclaredConstructors().length); diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/JSONExtensionsTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/JSONExtensionsTests.kt new file mode 100644 index 000000000..2bfcfb110 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/JSONExtensionsTests.kt @@ -0,0 +1,179 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.internal.utility + +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import org.json.JSONArray +import org.json.JSONObject +import org.json.JSONTokener +import org.junit.Test + +class JSONExtensionsTests { + + @Test + fun testJSONObjectToMapBasic() { + val jsonString = """ + { + "IntKey": 123, + "StringKey": "StringValue", + "DoubleKey": 1.23, + "NullKey": null, + "ObjectKey": { + "innerKey": "innerValue" + }, + "ArrayKey": [ + "StringValue", + 123, + 1.23, + null, + { + "objKey": "objValue" + } + ] + } + """.trimIndent() + val jsonObject = JSONTokener(jsonString).nextValue() + assertTrue(jsonObject is JSONObject) + val expectedMap = mapOf( + "IntKey" to 123, + "StringKey" to "StringValue", + "DoubleKey" to 1.23, + "NullKey" to null, + "ObjectKey" to mapOf( + "innerKey" to "innerValue" + ), + "ArrayKey" to listOf( + "StringValue", + 123, + 1.23, + null, + mapOf( + "objKey" to "objValue" + ) + ) + ) + assertEquals(expectedMap, jsonObject.toMap()) + } + + @Test + fun testJSONObjectToNestedMap() { + val jsonString = """ + { + "rootKey": "rootValue", + "nestedMap1": { + "key1": "value1", + "nestedMap2": { + "key2": "value2", + "nestedMap3": { + "key3": "value3" + } + } + } + } + """.trimIndent() + val jsonObject = JSONTokener(jsonString).nextValue() + assertTrue(jsonObject is JSONObject) + val expectedMap = mapOf( + "rootKey" to "rootValue", + "nestedMap1" to mapOf( + "key1" to "value1", + "nestedMap2" to mapOf( + "key2" to "value2", + "nestedMap3" to mapOf( + "key3" to "value3" + ) + ) + ) + ) + assertEquals(expectedMap, jsonObject.toMap()) + } + + @Test + fun testJSONArrayMappingBasic() { + val jsonString = """ + [ + "a", + "b", + "c" + ] + """.trimIndent() + val jsonArray = JSONTokener(jsonString).nextValue() + assertTrue(jsonArray is JSONArray) + val expectedList = listOf( + "a", + "b", + "c" + ) + assertEquals(expectedList, jsonArray.map { + if (it is String) it else "" + }) + } + + @Test + fun testJSONArrayMappingToMapList() { + val jsonString = """ + [ + { + "name": "obj1", + "key": "value" + }, + { + "name": "obj2", + "key": "value" + } + ] + """.trimIndent() + val jsonArray = JSONTokener(jsonString).nextValue() + assertTrue(jsonArray is JSONArray) + val expectedList = listOf( + mapOf( + "name" to "obj1", + "key" to "value" + ), + mapOf( + "name" to "obj2", + "key" to "value" + ) + ) + assertEquals(expectedList, jsonArray.map { + if (it is JSONObject) it.toMap() else null + }) + } + @Test + fun testJSONArrayToAnyList() { + val jsonString = """ + [ + { + "name": "obj1", + "key": "value" + }, + { + "name": "obj2", + "key": "value" + } + ] + """.trimIndent() + val jsonArray = JSONTokener(jsonString).nextValue() + assertTrue(jsonArray is JSONArray) + val expectedList = listOf( + mapOf( + "name" to "obj1", + "key" to "value" + ), + mapOf( + "name" to "obj2", + "key" to "value" + ) + ) + assertEquals(expectedList, jsonArray.toList()) + } +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt new file mode 100644 index 000000000..a13d5f276 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt @@ -0,0 +1,123 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.internal.utility + +import kotlin.test.assertEquals +import org.junit.Test + +class MapExtensionsTests { + + @Test + fun testSimpleMapFlattening() { + val map = mapOf( + "a" to mapOf( + "b" to mapOf( + "c" to "a_b_c_value" + ) + ), + "d" to "d_value" + ) + val flattenedMap = map.flattening() + val expectedMap = mapOf( + "a.b.c" to "a_b_c_value", + "d" to "d_value" + ) + assertEquals(2, flattenedMap.size) + assertEquals(expectedMap, flattenedMap) + } + + @Test + fun testNullValueMapFlattening() { + val map = mapOf( + "a" to mapOf( + "b1" to mapOf( + "c" to "a_b1_c_value" + ), + "b2" to null + ), + "d" to "d_value" + ) + val flattenedMap = map.flattening() + val expectedMap = mapOf( + "a.b1.c" to "a_b1_c_value", + "a.b2" to null, + "d" to "d_value" + ) + assertEquals(3, flattenedMap.size) + assertEquals(expectedMap, flattenedMap) + } + + @Test + fun testMultipleNestedKeysMapFlattening() { + val map = mapOf( + "a" to mapOf( + "b1" to mapOf( + "c1" to "a_b1_c1_value" + ), + "b2" to mapOf( + "c2" to "a_b2_c2_value" + ), + "b3" to mapOf( + "c3" to "a_b3_c3_value", + "c4" to "a_b3_c4_value" + ), + "b4" to "a_b4_value" + ), + "d" to "d_value" + ) + val flattenedMap = map.flattening() + assertEquals(6, flattenedMap.size) + val expectedMap = mapOf( + "a.b1.c1" to "a_b1_c1_value", + "a.b2.c2" to "a_b2_c2_value", + "a.b3.c3" to "a_b3_c3_value", + "a.b3.c4" to "a_b3_c4_value", + "a.b4" to "a_b4_value", + "d" to "d_value" + ) + assertEquals(expectedMap, flattenedMap) + } + + @Test + fun testContainsNonStringKeysMapFlattening() { + val map = mapOf( + "a" to mapOf( + "b1" to mapOf( + "c1" to "a_b1_c1_value" + ), + "b2" to mapOf( + 1 to "a_b2_value", + 2 to "a_b2_value" + ), + "b3" to mapOf( + 1 to "a_b3_value", + "2" to "a_b3_value" + ) + ), + "d" to "d_value" + ) + val flattenedMap = map.flattening() + assertEquals(4, flattenedMap.size) + val expectedMap = mapOf( + "a.b1.c1" to "a_b1_c1_value", + "a.b2" to mapOf( + 1 to "a_b2_value", + 2 to "a_b2_value" + ), + "a.b3" to mapOf( + 1 to "a_b3_value", + "2" to "a_b3_value" + ), + "d" to "d_value" + ) + assertEquals(expectedMap, flattenedMap) + } +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapUtilsTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapUtilsTests.java new file mode 100644 index 000000000..548b70cd3 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapUtilsTests.java @@ -0,0 +1,320 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.internal.utility; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class MapUtilsTests { + // FNV1a 32-bit hash tests + // basic smoke tests for comparison with iOS + @Test + public void testGetFnv1aHash_String_Smoke() { + // setup + final Map map = new HashMap() { + { + put("key", "value"); + } + }; + + // test + final long hash = MapUtilsKt.convertMapToFnv1aHash(map, null); + // verify flattened map string "key:value" + final long expectedHash = 4007910315L; + assertEquals(expectedHash, hash); + } + + @Test + public void testGetFnv1aHash_Integer_Smoke() { + // setup + final Map map = new HashMap() { + { + put("key", 552); + } + }; + // test + final long hash = MapUtilsKt.convertMapToFnv1aHash(map, null); + // verify flattened map string "key:552" + final long expectedHash = 874166902L; + assertEquals(expectedHash, hash); + } + + @Test + public void testGetFnv1aHash_Boolean_Smoke() { + // setup + final Map map = new HashMap() { + { + put("key", false); + } + }; + // test + final long hash = MapUtilsKt.convertMapToFnv1aHash(map, null); + // verify flattened map string "key:false" + final long expectedHash = 138493769L; + assertEquals(expectedHash, hash); + } + + @Test + public void testGetFnv1aHash_AsciiSorted_Smoke() { + // setup + final Map map = new HashMap() { + { + put("key", "value"); + put("number", 1234); + put("UpperCase", "abc"); + put("_underscore", "score"); + } + }; + // test + final long hash = MapUtilsKt.convertMapToFnv1aHash(map, null); + // verify flattened map string "UpperCase:abc_underscore:scorekey:valuenumber:1234" + final long expectedHash = 960895195L; + assertEquals(expectedHash, hash); + } + + @Test + public void testGetFnv1aHash_NoMask_Happy() { + // setup + final Map map = new HashMap() { + { + put("aaa", "1"); + put("zzz", true); + } + }; + // test + final long hash = MapUtilsKt.convertMapToFnv1aHash(map, null); + // verify flattened map string "aaa:1zzz:true" + final long expectedHash = 3251025831L; + assertEquals(expectedHash, hash); + } + + @Test + public void testGetFnv1aHash_WithMask_Happy() { + // setup + final Map map = new HashMap() { + { + put("aaa", "1"); + put("c", 2); + put("m", 1.11); + put("zzz", true); + } + }; + final String[] mask = new String[]{"c", "m"}; + // test + final long hash = MapUtilsKt.convertMapToFnv1aHash(map, mask); + // verify flattened map string "c:2m:1.11" + final long expectedHash = 2718815288L; + assertEquals(expectedHash, hash); + } + + @Test + public void testGetFnv1aHash_ArrayOfMaps() { + // setup + final Map map1 = new HashMap() { + { + put("aaa", "1"); + put("zzz", true); + } + }; + final Map map2 = new HashMap() { + { + put("number", 123); + put("double", 1.5); + } + }; + final List> list = new ArrayList<>(); + list.add(map1); + list.add(map2); + final Map map = new HashMap() { + { + put("key", list); + } + }; + // test + final long hash = MapUtilsKt.convertMapToFnv1aHash(map, null); + // verify flattened map string "key:[{aaa=1, zzz=true}, {number=123, double=1.5}]" + final long expectedHash = 2052811266L; + assertEquals(expectedHash, hash); + } + + @Test + public void testGetFnv1aHash_ArrayOfLists() { + // setup + final List innerList = new ArrayList() { + { + add("aaa"); + add("zzz"); + add(111); + } + }; + final List innerList2 = new ArrayList() { + { + add("2"); + } + }; + final List list = new ArrayList<>(); + list.add(innerList); + list.add(innerList2); + final Map map = new HashMap() { + { + put("key", list); + } + }; + // test + final long hash = MapUtilsKt.convertMapToFnv1aHash(map, null); + // verify flattened map string "key:[[aaa, zzz, 111], [2]]" + final long expectedHash = 390515610L; + assertEquals(expectedHash, hash); + } + + @Test + public void testGetFnv1aHash_WithNestedMap() { + // setup + final Map innerMap = new HashMap() { + { + put("bbb", "5"); + put("hhh", "false"); + } + }; + final Map map = new HashMap() { + { + put("aaa", "1"); + put("zzz", true); + put("inner", innerMap); + } + }; + // test + final long hash = MapUtilsKt.convertMapToFnv1aHash(map, null); + // verify flattened map string "aaa:1inner.bbb:5inner.hhh:falsezzz:true" + final long expectedHash = 4230384023L; + assertEquals(expectedHash, hash); + } + + @Test + public void testGetFnv1aHash_WithNestedMapContainingNestedMap() { + // setup + final Map secondInnerMap = new HashMap() { + { + put("ccc", "10"); + put("iii", "1.1"); + } + }; + final Map innerMap = new HashMap() { + { + put("bbb", 5); + put("hhh", false); + put("secondInner", secondInnerMap); + } + }; + final Map map = new HashMap() { + { + put("aaa", "1"); + put("zzz", true); + put("inner", innerMap); + } + }; + // test + final long hash = MapUtilsKt.convertMapToFnv1aHash(map, null); + // verify flattened map string "aaa:1inner.bbb:5inner.hhh:falseinner.secondInner.ccc:10inner.secondInner.iii:1.1zzz:true" + final long expectedHash = 1786696518L; + assertEquals(expectedHash, hash); + } + + @Test + public void testGetFnv1aHash_WithEmptyMask() { + // setup + final Map map = new HashMap() { + { + put("a", "1"); + put("b", "2"); + } + }; + // test + final long hash = MapUtilsKt.convertMapToFnv1aHash(map, new String[]{}); + // verify flattened map string "a:1b:2" + final long expectedHash = 3371500665L; + assertEquals(expectedHash, hash); + } + + @Test + public void testGetFnv1aHash_WithMaskMatchingNoKeys() { + // setup + final Map map = new HashMap() { + { + put("a", "1"); + put("b", "2"); + } + }; + // test + final long hash = MapUtilsKt.convertMapToFnv1aHash(map, new String[]{"c", "d"}); + // verify 0 / no hash generated due to mask keys not being present in the map + final long expectedHash = 0; + assertEquals(expectedHash, hash); + } + + @Test + public void testGetFnv1aHash_NoMask_VerifyEventDataMapSortedWithCaseSensitivity() { + // setup + final Map map = new HashMap() { + { + put("a", "1"); + put("A", "2"); + put("ba", "3"); + put("Ba", "4"); + put("Z", "5"); + put("z", "6"); + put("r", "7"); + put("R", "8"); + put("bc", "9"); + put("Bc", "10"); + put("1", 1); + put("222", 222); + } + }; + // test + final long hash = MapUtilsKt.convertMapToFnv1aHash(map, null); + // verify flattened map string "1:1222:222A:2Ba:4Bc:10R:8Z:5a:1ba:3bc:9r:7z:6" + final long expectedHash = 2933724447L; + assertEquals(expectedHash, hash); + } + + @Test + public void testGetFnv1aHash_WithMask_VerifyEventDataMapSortedWithCaseSensitivity() { + // setup + final Map map = new HashMap() { + { + put("a", "1"); + put("A", "2"); + put("ba", "3"); + put("Ba", "4"); + put("Z", "5"); + put("z", "6"); + put("r", "7"); + put("R", "8"); + put("bc", "9"); + put("Bc", "10"); + put("1", 1); + put("222", 222); + } + }; + // test + final long hash = MapUtilsKt.convertMapToFnv1aHash(map, new String[]{"A", "a", "ba", "Ba", "bc", "Bc", "1"}); + // verify flattened map string "1:1A:2Ba:4Bc:10a:1ba:3bc:9" + final long expectedHash = 3344627991L; + assertEquals(expectedHash, hash); + } +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/StringEncoderTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/StringEncoderTest.java similarity index 93% rename from code/android-core-library/src/test/java/com/adobe/marketing/mobile/StringEncoderTest.java rename to code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/StringEncoderTest.java index 16c85bf1d..3c1ffe5f4 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/StringEncoderTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/StringEncoderTest.java @@ -8,12 +8,15 @@ OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile; +package com.adobe.marketing.mobile.internal.utility; import org.junit.Test; import static org.junit.Assert.*; +import com.adobe.marketing.mobile.TestHelper; +import com.adobe.marketing.mobile.internal.utility.StringEncoder; + public class StringEncoderTest { @Test diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java index df109a05b..7124ce864 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java @@ -11,9 +11,18 @@ package com.adobe.marketing.mobile.services; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyString; + import android.content.ContentValues; import android.database.sqlite.SQLiteException; +import com.adobe.marketing.mobile.internal.utility.SQLiteDatabaseHelper; + import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -21,227 +30,223 @@ import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; import java.util.Arrays; import java.util.List; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.anyString; - -@RunWith(MockitoJUnitRunner.class) +@RunWith(PowerMockRunner.class) +@PrepareForTest({SQLiteDatabaseHelper.class}) public class SqliteDataQueueTests { - private DataQueue dataQueue; + private DataQueue dataQueue; - @Mock - private SQLiteDatabaseHelper database; + @Mock + ContentValues contentValues; - @Mock - ContentValues contentValues; + private static final String DATABASE_NAME = "test.sqlite"; + private static final String TABLE_NAME = "TB_AEP_DATA_ENTITY"; + private static final String EMPTY_JSON_STRING = "{}"; + private static final String TB_KEY_UNIQUE_IDENTIFIER = "uniqueIdentifier"; + private static final String TB_KEY_TIMESTAMP = "timestamp"; + private static final String TB_KEY_DATA = "data"; - private static final String DATABASE_NAME = "test.sqlite"; - private static final String TABLE_NAME = "TB_AEP_DATA_ENTITY"; - private static final String EMPTY_JSON_STRING = "{}"; - private static final String TB_KEY_UNIQUE_IDENTIFIER = "uniqueIdentifier"; - private static final String TB_KEY_TIMESTAMP = "timestamp"; - private static final String TB_KEY_DATA = "data"; + public SqliteDataQueueTests() { + } - public SqliteDataQueueTests() { - } + @Before + public void setUp() { + PowerMockito.mockStatic(SQLiteDatabaseHelper.class); + PowerMockito.when(SQLiteDatabaseHelper.createTableIfNotExist(Mockito.anyString(), Mockito.anyString())).thenReturn(true); + dataQueue = new SQLiteDataQueue(null, DATABASE_NAME); - @Before - public void setUp() { - dataQueue = new SQLiteDataQueue(null, DATABASE_NAME, database); - } + } + @Test + public void addDataEntitySuccess() { + //Setup + DataEntity dataEntity = new DataEntity(EMPTY_JSON_STRING); - @Test - public void addDataEntitySuccess() { - //Setup - DataEntity dataEntity = new DataEntity(EMPTY_JSON_STRING); - Mockito.when(database.insertRow(Mockito.anyString(), Mockito.anyString(), - Mockito.anyMap())).thenReturn(true); + PowerMockito.when(SQLiteDatabaseHelper.insertRow(Mockito.anyString(), Mockito.anyString(), + Mockito.anyMap())).thenReturn(true); - //Actions - boolean result = dataQueue.add(dataEntity); + //Actions + boolean result = dataQueue.add(dataEntity); - //Assertions - assertTrue(result); + //Assertions + assertTrue(result); - } + } - @Test - public void addDataEntityFailure() { - //Setup + @Test + public void addDataEntityFailure() { + //Setup - DataEntity dataEntity = new DataEntity(EMPTY_JSON_STRING); - Mockito.when(database.insertRow(Mockito.anyString(), Mockito.anyString(), - Mockito.anyMap())).thenReturn(false); + DataEntity dataEntity = new DataEntity(EMPTY_JSON_STRING); + Mockito.when(SQLiteDatabaseHelper.insertRow(Mockito.anyString(), Mockito.anyString(), + Mockito.anyMap())).thenReturn(false); - //Action - boolean result = dataQueue.add(dataEntity); + //Action + boolean result = dataQueue.add(dataEntity); - //Assertions - assertFalse(result); - } + //Assertions + assertFalse(result); + } - @Test - public void testPeekSuccess() { - //Setup + @Test + public void testPeekSuccess() { + //Setup - Mockito.when(contentValues.getAsString(TB_KEY_UNIQUE_IDENTIFIER)).thenReturn(TB_KEY_UNIQUE_IDENTIFIER); - Mockito.when(contentValues.getAsLong(TB_KEY_TIMESTAMP)).thenReturn(System.currentTimeMillis()); - Mockito.when(contentValues.getAsString(TB_KEY_DATA)).thenReturn(EMPTY_JSON_STRING); - Mockito.when(database.query(anyString(), anyString(), (String[]) Mockito.any(), - anyInt())).thenReturn(Arrays.asList(contentValues)); + Mockito.when(contentValues.getAsString(TB_KEY_UNIQUE_IDENTIFIER)).thenReturn(TB_KEY_UNIQUE_IDENTIFIER); + Mockito.when(contentValues.getAsLong(TB_KEY_TIMESTAMP)).thenReturn(System.currentTimeMillis()); + Mockito.when(contentValues.getAsString(TB_KEY_DATA)).thenReturn(EMPTY_JSON_STRING); + Mockito.when(SQLiteDatabaseHelper.query(anyString(), anyString(), (String[]) Mockito.any(), + anyInt())).thenReturn(Arrays.asList(contentValues)); - //Actions - DataEntity dataEntity = dataQueue.peek(); + //Actions + DataEntity dataEntity = dataQueue.peek(); - //Assertions - assertTrue(dataEntity != null); + //Assertions + assertTrue(dataEntity != null); - } + } - @Test - public void testPeekNSuccess() { - //Setup - Mockito.when(contentValues.getAsString(TB_KEY_UNIQUE_IDENTIFIER)).thenReturn(TB_KEY_UNIQUE_IDENTIFIER); - Mockito.when(contentValues.getAsLong(TB_KEY_TIMESTAMP)).thenReturn(System.currentTimeMillis()); - Mockito.when(contentValues.getAsString(TB_KEY_DATA)).thenReturn(EMPTY_JSON_STRING); - Mockito.when(database.query(anyString(), anyString(), (String[]) Mockito.any(), - anyInt())).thenReturn(Arrays.asList(contentValues, contentValues)); + @Test + public void testPeekNSuccess() { + //Setup + Mockito.when(contentValues.getAsString(TB_KEY_UNIQUE_IDENTIFIER)).thenReturn(TB_KEY_UNIQUE_IDENTIFIER); + Mockito.when(contentValues.getAsLong(TB_KEY_TIMESTAMP)).thenReturn(System.currentTimeMillis()); + Mockito.when(contentValues.getAsString(TB_KEY_DATA)).thenReturn(EMPTY_JSON_STRING); + Mockito.when(SQLiteDatabaseHelper.query(anyString(), anyString(), (String[]) Mockito.any(), + anyInt())).thenReturn(Arrays.asList(contentValues, contentValues)); - //Actions - List dataEntityList = dataQueue.peek(2); + //Actions + List dataEntityList = dataQueue.peek(2); - //Assertions - assertEquals(2, dataEntityList.size()); + //Assertions + assertEquals(2, dataEntityList.size()); - } + } - @Test - public void testRemoveRows() { - //setup - Mockito.when(database.removeRows(anyString(), anyString(), anyString(), anyInt())).thenReturn(1); + @Test + public void testRemoveRows() { + //setup + Mockito.when(SQLiteDatabaseHelper.removeRows(anyString(), anyString(), anyString(), anyInt())).thenReturn(1); - //Action - boolean result = dataQueue.remove(); + //Action + boolean result = dataQueue.remove(); - //Assertions - assertTrue(result); - } + //Assertions + assertTrue(result); + } - @Test - public void testRemoveNRows() { - //Setup - Mockito.when(database.removeRows(anyString(), anyString(), anyString(), anyInt())).thenReturn(1); + @Test + public void testRemoveNRows() { + //Setup + Mockito.when(SQLiteDatabaseHelper.removeRows(anyString(), anyString(), anyString(), anyInt())).thenReturn(1); - //Actions - boolean result = dataQueue.remove(1); + //Actions + boolean result = dataQueue.remove(1); - //Assertions - assertTrue(result); - } + //Assertions + assertTrue(result); + } - @Test - public void testClearTable() { - //etup - Mockito.when(database.clearTable(anyString(), anyString())).thenReturn(true); + @Test + public void testClearTable() { + //etup + Mockito.when(SQLiteDatabaseHelper.clearTable(anyString(), anyString())).thenReturn(true); - //Actions - boolean result = database.clearTable(DATABASE_NAME, TABLE_NAME); + //Actions + boolean result = SQLiteDatabaseHelper.clearTable(DATABASE_NAME, TABLE_NAME); - //Assertions - assertTrue(result); - } + //Assertions + assertTrue(result); + } - @Test - public void testTableCount() { - //Setup - final int mockedTableSize = 10; - Mockito.when(database.getTableSize(anyString(), anyString())).thenReturn(mockedTableSize); + @Test + public void testTableCount() { + //Setup + final int mockedTableSize = 10; + Mockito.when(SQLiteDatabaseHelper.getTableSize(anyString(), anyString())).thenReturn(mockedTableSize); - //Actions - int tableSize = database.getTableSize(DATABASE_NAME, TABLE_NAME); + //Actions + int tableSize = SQLiteDatabaseHelper.getTableSize(DATABASE_NAME, TABLE_NAME); - //Assertions - assertEquals(tableSize, mockedTableSize); - } + //Assertions + assertEquals(tableSize, mockedTableSize); + } - @Test - public void testClose() { - //Actions - dataQueue.close(); + @Test + public void testClose() { + //Actions + dataQueue.close(); - //Assertions - assertFalse(dataQueue.add(new DataEntity(EMPTY_JSON_STRING))); - assertNull(dataQueue.peek()); - assertNull(dataQueue.peek(10)); - assertFalse(dataQueue.remove()); - assertFalse(dataQueue.remove(10)); - assertFalse(dataQueue.clear()); - assertEquals(dataQueue.count(), 0); - } + //Assertions + assertFalse(dataQueue.add(new DataEntity(EMPTY_JSON_STRING))); + assertNull(dataQueue.peek()); + assertNull(dataQueue.peek(10)); + assertFalse(dataQueue.remove()); + assertFalse(dataQueue.remove(10)); + assertFalse(dataQueue.clear()); + assertEquals(dataQueue.count(), 0); + } - //Unit test failure in opening database in different scenarios. + //Unit test failure in opening database in different scenarios. - @Test - public void addDataEntityWithDatabaseOpenError() { + @Test + public void addDataEntityWithDatabaseOpenError() { - Mockito.when(database.insertRow(anyString(), anyString(), - ArgumentMatchers.anyMap())).thenCallRealMethod(); - Mockito.when(database.openDatabase(DATABASE_NAME, - SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE)).thenThrow(SQLiteException.class); + Mockito.when(SQLiteDatabaseHelper.insertRow(anyString(), anyString(), + ArgumentMatchers.anyMap())).thenCallRealMethod(); + Mockito.when(SQLiteDatabaseHelper.openDatabase(DATABASE_NAME, + SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE)).thenThrow(SQLiteException.class); - boolean result = dataQueue.add(new DataEntity(EMPTY_JSON_STRING)); + boolean result = dataQueue.add(new DataEntity(EMPTY_JSON_STRING)); - //Assertions - Assert.assertFalse(result); - } + //Assertions + Assert.assertFalse(result); + } - @Test - public void peekNWithDatabaseOpenError() { + @Test + public void peekNWithDatabaseOpenError() { - Mockito.when(database.removeRows(anyString(), anyString(), anyString(), anyInt())).thenCallRealMethod(); - Mockito.when(database.openDatabase(DATABASE_NAME, - SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE)).thenThrow(SQLiteException.class); + Mockito.when(SQLiteDatabaseHelper.removeRows(anyString(), anyString(), anyString(), anyInt())).thenCallRealMethod(); + Mockito.when(SQLiteDatabaseHelper.openDatabase(DATABASE_NAME, + SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE)).thenThrow(SQLiteException.class); - boolean result = dataQueue.remove(2); + boolean result = dataQueue.remove(2); - //Assertions - Assert.assertFalse(result); - } + //Assertions + Assert.assertFalse(result); + } - @Test - public void clearTableWithDatabaseOpenError() { + @Test + public void clearTableWithDatabaseOpenError() { - Mockito.when(database.clearTable(anyString(), anyString())).thenCallRealMethod(); - Mockito.when(database.openDatabase(DATABASE_NAME, - SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE)).thenThrow(SQLiteException.class); + Mockito.when(SQLiteDatabaseHelper.clearTable(anyString(), anyString())).thenCallRealMethod(); + Mockito.when(SQLiteDatabaseHelper.openDatabase(DATABASE_NAME, + SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE)).thenThrow(SQLiteException.class); - boolean result = dataQueue.clear(); + boolean result = dataQueue.clear(); - //Assertions - Assert.assertFalse(result); - } + //Assertions + Assert.assertFalse(result); + } - @Test - public void getTableSizeWithDatabaseOpenError() { + @Test + public void getTableSizeWithDatabaseOpenError() { - Mockito.when(database.getTableSize(anyString(), anyString())).thenCallRealMethod(); - Mockito.when(database.openDatabase(DATABASE_NAME, - SQLiteDatabaseHelper.DatabaseOpenMode.READ_ONLY)).thenThrow(SQLiteException.class); + Mockito.when(SQLiteDatabaseHelper.getTableSize(anyString(), anyString())).thenCallRealMethod(); + Mockito.when(SQLiteDatabaseHelper.openDatabase(DATABASE_NAME, + SQLiteDatabaseHelper.DatabaseOpenMode.READ_ONLY)).thenThrow(SQLiteException.class); - int result = dataQueue.count(); + int result = dataQueue.count(); - //Assertions - Assert.assertEquals(result, 0); - } + //Assertions + Assert.assertEquals(result, 0); + } } From 422bfd405341ad9b7b26e9108999d993a7ff212c Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Tue, 3 May 2022 10:56:52 -0700 Subject: [PATCH 041/476] adding license headers --- .../mobile/services/DataQueueServiceTests.java | 11 +++++++++++ .../adobe/marketing/mobile/utility/FileUtilTests.kt | 10 ++++++++++ .../marketing/mobile/services/utility/FileUtil.kt | 10 ++++++++++ 3 files changed, 31 insertions(+) diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/DataQueueServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/DataQueueServiceTests.java index be6221834..7978de120 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/DataQueueServiceTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/DataQueueServiceTests.java @@ -1,3 +1,14 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + package com.adobe.marketing.mobile.services; import static junit.framework.Assert.assertEquals; diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/utility/FileUtilTests.kt b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/utility/FileUtilTests.kt index 87524081c..e29616544 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/utility/FileUtilTests.kt +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/utility/FileUtilTests.kt @@ -1,3 +1,13 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ package com.adobe.marketing.mobile.utility import com.adobe.marketing.mobile.services.utility.FileUtil diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/utility/FileUtil.kt b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/utility/FileUtil.kt index bf56e6739..90585d4c1 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/utility/FileUtil.kt +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/utility/FileUtil.kt @@ -1,3 +1,13 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ package com.adobe.marketing.mobile.services.utility import java.io.File From 5117bdd740ad171df06bfee630d346c75cb2db20 Mon Sep 17 00:00:00 2001 From: Yansong Yang Date: Tue, 3 May 2022 15:23:58 -0600 Subject: [PATCH 042/476] Move deprecated non public classes into legacy folder (#65) * Update Gradle to let deprecated package-private classes move to the legacy folder * format --- code/android-core-library/build.gradle | 5 +++++ .../main/java/com/adobe/marketing/mobile/App.java | 0 .../adobe/marketing/mobile/internal/eventhub/EventHub.kt | 2 +- code/android-core-library/src/test/kotlin/EventHubTests.kt | 6 +++--- 4 files changed, 9 insertions(+), 4 deletions(-) rename code/android-core-library/src/{ => legacy}/main/java/com/adobe/marketing/mobile/App.java (100%) diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index 5c7216e2b..be7ea15f7 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -26,6 +26,11 @@ android { srcDirs += "src/legacy/test-module/resources" } } + main{ + java{ + srcDirs += "src/legacy/main/java" + } + } androidTest{ java.srcDirs += "src/legacy/androidTest-common/java" } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/App.java b/code/android-core-library/src/legacy/main/java/com/adobe/marketing/mobile/App.java similarity index 100% rename from code/android-core-library/src/main/java/com/adobe/marketing/mobile/App.java rename to code/android-core-library/src/legacy/main/java/com/adobe/marketing/mobile/App.java diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index dfb4f6db4..6b8c2d6f0 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -4,4 +4,4 @@ class EventHub { companion object { val version = "2.0.0" } -} \ No newline at end of file +} diff --git a/code/android-core-library/src/test/kotlin/EventHubTests.kt b/code/android-core-library/src/test/kotlin/EventHubTests.kt index 303970ecb..897d50a94 100644 --- a/code/android-core-library/src/test/kotlin/EventHubTests.kt +++ b/code/android-core-library/src/test/kotlin/EventHubTests.kt @@ -1,6 +1,6 @@ -import org.junit.Test -import kotlin.test.assertEquals import com.adobe.marketing.mobile.internal.eventhub.EventHub +import kotlin.test.assertEquals +import org.junit.Test internal class EventHubTests { @@ -8,4 +8,4 @@ internal class EventHubTests { fun testVersion() { assertEquals(EventHub.version, "2.0.0") } -} \ No newline at end of file +} From 01f5de0e86b76581f4edd8301b58e5566f0c664a Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Tue, 10 May 2022 10:18:28 -0700 Subject: [PATCH 043/476] [#29] Propagate SharedState Satus to EventHub - Change API of SharedStateManager and EventContainer to propagate SharedState.Status and SharedState to EventHub. This will allow any future APIs that need returning the state or status to be simple changes. - Let EventHub translate the result of the shared state operation to return it appropriately to the ExtensionApi. This allows keeping the existing API contracts inline with current production usage. --- .../mobile/internal/eventhub/EventHub.kt | 23 +- .../internal/eventhub/ExtensionContainer.kt | 71 +++--- .../internal/eventhub/SharedStateManager.kt | 78 ++++--- .../eventhub/ExtensionContainerTest.kt | 220 ++++++++++++++++++ .../eventhub/SharedStateManagerTest.kt | 74 +++--- 5 files changed, 355 insertions(+), 111 deletions(-) create mode 100644 code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainerTest.kt diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index 4d181eaa5..b2cac87c6 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -134,8 +134,7 @@ internal class EventHub { val setSharedStateCallable: Callable = Callable { if (extensionName.isNullOrEmpty() || extensionName.isBlank()) { - MobileCore.log(LoggingMode.ERROR, LOG_TAG, - String.format("Unable to set SharedState for extension: [%s]. ExtensionName is invalid.", extensionName)) + MobileCore.log(LoggingMode.ERROR, LOG_TAG, "Unable to set SharedState for extension: [$extensionName]. ExtensionName is invalid.") errorCallback?.error(ExtensionError.BAD_NAME) return@Callable false @@ -144,8 +143,7 @@ internal class EventHub { val extensionContainer: ExtensionContainer? = registeredExtensions[extensionName] if (extensionContainer == null) { - MobileCore.log(LoggingMode.ERROR, LOG_TAG, - String.format("Error seting SharedState for extension: [%s]. Extension may not have been registered.", extensionName)) + MobileCore.log(LoggingMode.ERROR, LOG_TAG, "Error setting SharedState for extension: [$extensionName]. Extension may not have been registered.") errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) return@Callable false @@ -161,7 +159,7 @@ internal class EventHub { getEventNumber(event) ?: lastEventNumber.incrementAndGet() } - val wasSet: Boolean = extensionContainer.setSharedState(sharedStateType, data, version) + val wasSet: Boolean = extensionContainer.setSharedState(sharedStateType, data, version) == SharedState.Status.SET // Check if the new state can be dispatched as a state change event(currently implies a // non null/non pending state according to the ExtensionAPI) @@ -199,7 +197,7 @@ internal class EventHub { val getSharedStateCallable: Callable?> = Callable { if (extensionName.isNullOrEmpty() || extensionName.isBlank()) { - MobileCore.log(LoggingMode.ERROR, LOG_TAG, String.format("Unable to get SharedState. State name [%s] is invalid.", extensionName)) + MobileCore.log(LoggingMode.ERROR, LOG_TAG, "Unable to get SharedState. State name [$extensionName] is invalid.") errorCallback?.error(ExtensionError.BAD_NAME) return@Callable null @@ -208,9 +206,8 @@ internal class EventHub { val extensionContainer: ExtensionContainer? = registeredExtensions[extensionName] if (extensionContainer == null) { - MobileCore.log(LoggingMode.ERROR, LOG_TAG, - String.format("Error retrieving SharedState for extension: [%s]." + - "Extension may not have been registered.", extensionName)) + MobileCore.log(LoggingMode.ERROR, LOG_TAG, "Error retrieving SharedState for extension: [$extensionName]." + + "Extension may not have been registered.") errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) return@Callable null } @@ -224,7 +221,7 @@ internal class EventHub { getEventNumber(event) ?: SharedStateManager.VERSION_LATEST } - return@Callable extensionContainer.getSharedState(sharedStateType, version) + return@Callable extensionContainer.getSharedState(sharedStateType, version)?.data } return eventHubExecutor.submit(getSharedStateCallable).get() @@ -236,6 +233,7 @@ internal class EventHub { * @param sharedStateType the type of shared state that needs to be cleared. * @param extensionName the name of the extension for which the state is being cleared * @param errorCallback the callback which will be notified in the event of an error + * @return true - if the shared state has been cleared, false otherwise */ fun clearSharedState( sharedStateType: SharedStateType, @@ -244,7 +242,7 @@ internal class EventHub { ): Boolean { val clearSharedStateCallable: Callable = Callable { if (extensionName.isNullOrEmpty() || extensionName.isBlank()) { - MobileCore.log(LoggingMode.ERROR, LOG_TAG, String.format("Unable to clear SharedState. State name [%s] is invalid.", extensionName)) + MobileCore.log(LoggingMode.ERROR, LOG_TAG, "Unable to clear SharedState. State name [$extensionName] is invalid.") errorCallback?.error(ExtensionError.BAD_NAME) return@Callable null @@ -254,8 +252,7 @@ internal class EventHub { if (extensionContainer == null) { MobileCore.log(LoggingMode.ERROR, - LOG_TAG, - String.format("Error clearing SharedState for extension: [%s]. Extension may not have been registered.")) + LOG_TAG, "Error clearing SharedState for extension: [$extensionName]. Extension may not have been registered.") errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) return@Callable false } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt index 888c1cf11..4f9e26e66 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt @@ -68,9 +68,9 @@ internal class ExtensionRuntime() : ExtensionApi() { ): Boolean { try { return EventHub.shared.setSharedState(SharedStateType.XDM, extensionName, state, event, errorCallback) - } catch (ex: Exception) { + } catch (exception: Exception) { MobileCore.log(LoggingMode.ERROR, getTag(), - String.format("Failed to set shared state at EventID: %s. %s", event?.uniqueIdentifier, ex)) + "Failed to set shared state at EventID: ${event?.uniqueIdentifier}. $exception") errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) } @@ -84,9 +84,9 @@ internal class ExtensionRuntime() : ExtensionApi() { ): Boolean { try { return EventHub.shared.setSharedState(SharedStateType.XDM, extensionName, state, event, errorCallback) - } catch (ex: Exception) { + } catch (exception: Exception) { MobileCore.log(LoggingMode.ERROR, getTag(), - String.format("Failed to set XDM shared state at EventID: %s. %s", event?.uniqueIdentifier, ex)) + "Failed to set XDM shared state at EventID: ${event?.uniqueIdentifier}. $exception") errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) } @@ -96,8 +96,8 @@ internal class ExtensionRuntime() : ExtensionApi() { override fun clearSharedEventStates(errorCallback: ExtensionErrorCallback?): Boolean { try { EventHub.shared.clearSharedState(SharedStateType.STANDARD, extensionName, errorCallback) - } catch (ex: Exception) { - MobileCore.log(LoggingMode.ERROR, getTag(), String.format("Failed to clear shared state. %s", ex)) + } catch (exception: Exception) { + MobileCore.log(LoggingMode.ERROR, getTag(), "Failed to clear shared state. $exception") errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) } @@ -107,8 +107,8 @@ internal class ExtensionRuntime() : ExtensionApi() { override fun clearXDMSharedEventStates(errorCallback: ExtensionErrorCallback?): Boolean { try { EventHub.shared.clearSharedState(SharedStateType.XDM, extensionName, errorCallback) - } catch (ex: Exception) { - MobileCore.log(LoggingMode.ERROR, getTag(), String.format("Failed to clear XDM shared state. %s", ex)) + } catch (exception: Exception) { + MobileCore.log(LoggingMode.ERROR, getTag(), "Failed to clear XDM shared state. $exception") errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) } @@ -122,9 +122,9 @@ internal class ExtensionRuntime() : ExtensionApi() { ): Map? { try { return EventHub.shared.getSharedState(SharedStateType.STANDARD, stateName, event, errorCallback) - } catch (ex: Exception) { + } catch (exception: Exception) { MobileCore.log(LoggingMode.ERROR, getTag(), - String.format("Failed to get shared state at EventID: %s. %s", event?.uniqueIdentifier, ex)) + "Failed to get shared state at EventID: ${event?.uniqueIdentifier}. $exception") errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) } return null @@ -137,9 +137,9 @@ internal class ExtensionRuntime() : ExtensionApi() { ): Map? { try { return EventHub.shared.getSharedState(SharedStateType.STANDARD, stateName, event, errorCallback) - } catch (ex: Exception) { + } catch (exception: Exception) { MobileCore.log(LoggingMode.ERROR, getTag(), - String.format("Failed to get XDM shared state at EventID: %s. %s", event?.uniqueIdentifier, ex)) + "Failed to get XDM shared state at EventID: ${event?.uniqueIdentifier}. $exception") errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) } @@ -157,7 +157,7 @@ internal class ExtensionRuntime() : ExtensionApi() { if (extension == null) { return LOG_TAG } - return String.format("%s-%s", extensionName, extensionVersion) + return "$extensionName-$extensionVersion" } } @@ -212,29 +212,43 @@ internal class ExtensionContainer constructor( * @param sharedStateType the type of the shared state that need to be set * @param data the content that the shared state needs to be populated with * @param version the version of the shared state to be set - * @return true - if a new shared state has been created or updated at [version], false otherwise + * @return [SharedState.Status.SET] if a new shared state has been created or updated at [version], + * [SharedState.Status.PENDING] if the shared state is set to pending, + * [SharedState.Status.NOT_SET] if the shared state was not set. */ fun setSharedState( sharedStateType: SharedStateType, data: MutableMap?, version: Int - ): Boolean { - if (taskExecutor.isShutdown) return false - - return taskExecutor.submit(Callable { - val stateManager: SharedStateManager = sharedStateManagers[sharedStateType] ?: return@Callable false - val isPending = (data == null) - - // Attempt to create the state first and then attempt to update it if creation fails. - return@Callable stateManager.createSharedState(data, version, isPending) || - stateManager.updateSharedState(data, version, isPending) + ): SharedState.Status { + if (taskExecutor.isShutdown) return SharedState.Status.NOT_SET + + return taskExecutor.submit(Callable { + val stateManager: SharedStateManager = sharedStateManagers[sharedStateType] + ?: return@Callable SharedState.Status.NOT_SET + + // Existing public API infers a pending state as one with no data + val isPending: Boolean = (data == null) + + // Attempt to create the state first + val createResult: SharedState.Status = stateManager.createSharedState(data, version, isPending) + + if (createResult != SharedState.Status.NOT_SET) { + // If the creation was successful i.e the result of the operation was either SET or + // PENDING, return the result. + return@Callable createResult + } else { + // else, attempt to update it and return the update result. + return@Callable stateManager.updateSharedState(data, version, isPending) + } }).get() } /** * Clears the shares states of type [sharedStateType] for this extension. * - * @return true unless an exception occurs clearing the state or if the extension is unregistered + * @return false if an exception occurs clearing the state or if the extension is unregistered, + * true otherwise. */ fun clearSharedState(sharedStateType: SharedStateType): Boolean { if (taskExecutor.isShutdown) return false @@ -251,14 +265,13 @@ internal class ExtensionContainer constructor( * * @param sharedStateType the type of the shared state that need to be retrieved * @param version the version of the pending shared state to be retrieved - * @return shared state at [version] if it exists or the most recent shared state before [version]. - * null If no state at or before [version] is found, or if the state fetched above is pending, - * or if the extension is unregistered + * @return [SharedState] at [version] or the most recent shared state before [version] if state at [version] does not exist. + * null - if no state at or before [version] is found or, if the extension is unregistered */ fun getSharedState( sharedStateType: SharedStateType, version: Int - ): Map? { + ): SharedState? { if (taskExecutor.isShutdown) return null return taskExecutor.submit(Callable { diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedStateManager.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedStateManager.kt index 4b80dbf9f..5a3a31725 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedStateManager.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedStateManager.kt @@ -40,33 +40,36 @@ internal class SharedStateManager { * @param data the content that the shared state needs to be populated with * @param version the version of the shared state to be created * @param isPending a boolean to indicate if the state content is not final (i.e will be updated later) - * @return true - if a new shared state has been created at [version], false otherwise + * @return [SharedState.Status.SET] - if a new shared state has been created at [version], + * [SharedState.Status.PENDING] - if a pending shared state has been created at version [version], + * [SharedState.Status.NOT_SET] otherwise */ @Synchronized fun createSharedState( data: Map?, version: Int, isPending: Boolean - ): Boolean { + ): SharedState.Status { // Check if there exists a state at a version equal to, or higher than the one provided. if (states.ceilingEntry(version) != null) { - MobileCore.log(LoggingMode.VERBOSE, LOG_TAG, String.format("Cannot create state st version %d. More recent state exists.", version)) + MobileCore.log(LoggingMode.VERBOSE, LOG_TAG, "Cannot create state st version $version. More recent state exists.") // If such a state exists a new state cannot be created, it can be updated, if pending, // via SharedStateManager#updateSharedState(..) - return false + return SharedState.Status.NOT_SET } // At this point, there does not exist a state at the provided version. Create one and add it to cache // TODO: USE EventDataUtils.cloneMap to do an immutable clone when available - val sharedState = SharedState(data?.toMap(), version, isPending) + val status: SharedState.Status = if (isPending) SharedState.Status.PENDING else SharedState.Status.SET + val sharedState = SharedState(data?.toMap(), status) states[version] = sharedState cache.put(version, sharedState) // Only update the VERSION_LATEST to the cache. Not to the state store! cache.put(VERSION_LATEST, sharedState) - return true + return status } /** @@ -76,36 +79,35 @@ internal class SharedStateManager { * @param data the content that the shared state needs to be updated with * @param version the version of the pending shared state to be updated * @param isPending a boolean to indicate if the new state content is not final - * @return true - if a new shared state has been created at [version], false otherwise + * @return [SharedState.Status.SET] - if shared state has been updated at [version], + * [SharedState.Status.NOT_SET] otherwise */ @Synchronized fun updateSharedState( data: Map?, version: Int, isPending: Boolean - ): Boolean { + ): SharedState.Status { - // Check if new state is pending. A state cannot be overwritten by another pending state + // Check if new state is pending. A pending state cannot be overwritten by another pending state if (isPending) { - MobileCore.log(LoggingMode.VERBOSE, LOG_TAG, - String.format("Cannot update pending state at version %d. With a pending state.", version)) - return false + MobileCore.log(LoggingMode.VERBOSE, LOG_TAG, "Cannot update pending state at version $version with a pending state.") + return SharedState.Status.NOT_SET } // Check if state exists at the exact version provided for updating. - val stateAtVersion = states[version] ?: return false + val stateAtVersion = states[version] ?: return SharedState.Status.NOT_SET // Check there is a valid pending state for updating. - if (!stateAtVersion.isPending) { - MobileCore.log(LoggingMode.WARNING, LOG_TAG, - String.format("Cannot update a non pending state state version %d.", version)) - return false + if (stateAtVersion.status != SharedState.Status.PENDING) { + MobileCore.log(LoggingMode.WARNING, LOG_TAG, "Cannot update a non pending state state version $version.") + return SharedState.Status.NOT_SET } // At this point, there exists a previously recorded state at the version provided. // Overwrite its value with a confirmed state. // TODO: USE EventDataUtils.cloneMap to do an immutable clone when available - val sharedState = SharedState(data?.toMap(), version, false) + val sharedState = SharedState(data?.toMap(), SharedState.Status.SET) states[version] = sharedState cache.put(version, sharedState) @@ -113,7 +115,7 @@ internal class SharedStateManager { // should only happen on a state that exists in the tree. If we reach a point where // VERSION_LATEST should be updated, it means it is already in the cache - return true + return SharedState.Status.SET } /** @@ -121,11 +123,12 @@ internal class SharedStateManager { * retrieves the most recent version of the shared state available. * * @param version the version of the shared state to be retrieved - * @return shared state at [version] if it exists, or the most recent shared state before [version]. - * null - If no state at or before [version] is found, or if the state fetched above is pending. + * @return shared state at [version] if it exists, or the most recent shared state before [version] if + * shared state at [version] does not exist, + * null - If no state at or before [version] is found */ @Synchronized - fun getSharedState(version: Int): Map? { + fun getSharedState(version: Int): SharedState? { if (states.isEmpty()) { // No states have been added to the state store yet. @@ -137,25 +140,18 @@ internal class SharedStateManager { val stateAtVersion = cache.get(version) ?: states[version] if (stateAtVersion != null) { - return if (stateAtVersion.isPending) { - // State is yet to be set (is pending) - null - } else { - // If shared state at exact version exists and it is not pending, use it - stateAtVersion.data - } + return stateAtVersion } // Otherwise, find state at the highest version less than the version being queried for val resolvedSharedState: SharedState? = states.floorEntry(version)?.value - // If the resolved state is not set or is pending, return null. Otherwise return the state - return if (resolvedSharedState == null || resolvedSharedState.isPending) { - null - } else { - cache.put(resolvedSharedState.version, resolvedSharedState) - resolvedSharedState.data + if (resolvedSharedState != null) { + cache.put(version, resolvedSharedState) } + + // If the resolved state is not set, return null. Otherwise return the state + return resolvedSharedState } /** @@ -172,7 +168,17 @@ internal class SharedStateManager { * Internal representation of a shared event state. * Allows associating version and pending behavior with the state data in a queryable way. */ -private data class SharedState constructor(val data: Map?, val version: Int, val isPending: Boolean) +internal data class SharedState constructor(val data: Map?, val status: Status) { + + /** + * Represents the status of an extensions shared state, typically associated with a version. + */ + internal enum class Status { + SET, + PENDING, + NOT_SET + } +} /** * Represents the types of shared state that are supported. diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainerTest.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainerTest.kt new file mode 100644 index 000000000..80f59cdbc --- /dev/null +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainerTest.kt @@ -0,0 +1,220 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.internal.eventhub + +import java.util.concurrent.Callable +import java.util.concurrent.ExecutorService +import java.util.concurrent.Future +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.`when` +import org.mockito.Mockito.doAnswer +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import org.mockito.stubbing.Answer +import org.powermock.core.classloader.annotations.PrepareForTest +import org.powermock.modules.junit4.PowerMockRunner + +@RunWith(PowerMockRunner::class) +@PrepareForTest(ExtensionRuntime::class) +class ExtensionContainerTest { + + @Mock + private lateinit var mockExtensionRuntime: ExtensionRuntime + @Mock + private lateinit var mockExecutorService: ExecutorService + @Mock + private lateinit var mockErrorCallback: (EventHubError) -> Unit + + private lateinit var extensionContainer: ExtensionContainer + private val xdmStateManager: SharedStateManager = SharedStateManager() + private val standardStateManager: SharedStateManager = SharedStateManager() + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + val stateManagerMap: Map = mapOf( + SharedStateType.XDM to xdmStateManager, + SharedStateType.STANDARD to standardStateManager + ) + + doAnswer(Answer { + // Create a mock Future to return + val mockFuture: Future<*> = Mockito.mock(Future::class.java) + // Make it so that the Callable passed to the ExecutorService is run when future result is queried via get() + val callableArgument = it.getArgument>(0) + `when`(mockFuture.get()).thenReturn(callableArgument.call()) + return@Answer mockFuture + }).`when`(mockExecutorService).submit(any(Callable::class.java)) + + extensionContainer = ExtensionContainer(MockExtension::class.java, + mockExtensionRuntime, stateManagerMap, mockExecutorService, mockErrorCallback) + } + + @Test + fun testSetSharedState_NonPending_NoPreviousState() { + val ret: SharedState.Status = extensionContainer.setSharedState(SharedStateType.STANDARD, mutableMapOf(), 0) + + verify(mockExecutorService).submit(any(Callable::class.java)) + assertEquals(SharedState.Status.SET, ret) + } + + @Test + fun testSetSharedState_Pending_NoPreviousState() { + val ret: SharedState.Status = extensionContainer.setSharedState(SharedStateType.STANDARD, null, 0) + + verify(mockExecutorService).submit(any(Callable::class.java)) + assertEquals(SharedState.Status.PENDING, ret) + } + + @Test + fun testSetSharedState_PendingStateExists() { + var ret: SharedState.Status = extensionContainer.setSharedState(SharedStateType.STANDARD, null, 0) + assertEquals(SharedState.Status.PENDING, ret) + + val data = mutableMapOf ("One" to 1, "Yes" to true) + ret = extensionContainer.setSharedState(SharedStateType.STANDARD, data, 0) + verify(mockExecutorService, times(2)).submit(any(Callable::class.java)) + assertEquals(SharedState.Status.SET, ret) + } + + @Test + fun testSetSharedState_PendingStateDoesNotExist() { + var ret: SharedState.Status = extensionContainer.setSharedState(SharedStateType.STANDARD, mutableMapOf(), 0) + assertEquals(SharedState.Status.SET, ret) + + val data = mutableMapOf ("One" to 1, "Yes" to true) + ret = extensionContainer.setSharedState(SharedStateType.STANDARD, data, 0) + verify(mockExecutorService, times(2)).submit(any(Callable::class.java)) + assertEquals(SharedState.Status.NOT_SET, ret) + } + + @Test + fun testSetSharedState_ExecutorShutDown() { + `when`(mockExecutorService.isShutdown).thenReturn(true) + + val ret: SharedState.Status = extensionContainer.setSharedState(SharedStateType.STANDARD, mutableMapOf(), 0) + verify(mockExecutorService, times(0)).submit(any(Callable::class.java)) + assertEquals(SharedState.Status.NOT_SET, ret) + } + + @Test + fun testGetSharedState_StateExistsAtVersion() { + val dataAtV1 = mutableMapOf ("One" to 1, "Yes" to true) + val dataAtV4 = mutableMapOf ("Three" to 3, "No" to false) + assertEquals(SharedState.Status.SET, extensionContainer.setSharedState(SharedStateType.STANDARD, dataAtV1, 1)) + assertEquals(SharedState.Status.SET, extensionContainer.setSharedState(SharedStateType.STANDARD, dataAtV4, 4)) + + val ret = extensionContainer.getSharedState(SharedStateType.STANDARD, 4) + assertEquals(dataAtV4, ret?.data) + } + + @Test + fun testGetSharedState_PendingStateExistsAtVersion() { + val dataAtV1 = mutableMapOf ("One" to 1, "Yes" to true) + val dataAtV4 = null + assertEquals(SharedState.Status.SET, extensionContainer.setSharedState(SharedStateType.STANDARD, dataAtV1, 1)) + assertEquals(SharedState.Status.PENDING, extensionContainer.setSharedState(SharedStateType.STANDARD, null, 4)) + + val ret = extensionContainer.getSharedState(SharedStateType.STANDARD, 4) + assertEquals(dataAtV4, ret?.data) + assertEquals(SharedState.Status.PENDING, ret?.status) + } + + @Test + fun testGetSharedState_PendingStateExistsAtOlderVersion() { + // Create shared states at Version 1 and Version 4 + val dataAtV1 = mutableMapOf ("One" to 1, "Yes" to true) + val dataAtV4 = null + assertEquals(SharedState.Status.SET, extensionContainer.setSharedState(SharedStateType.STANDARD, dataAtV1, 1)) + assertEquals(SharedState.Status.PENDING, extensionContainer.setSharedState(SharedStateType.STANDARD, dataAtV4, 4)) + + var ret = extensionContainer.getSharedState(SharedStateType.STANDARD, 7) + assertEquals(dataAtV4, ret?.data) + assertEquals(SharedState.Status.PENDING, ret?.status) + } + + @Test + fun testGetSharedState_StateExistsAtOlderVersion() { + val dataAtV1 = mutableMapOf ("One" to 1, "Yes" to true) + val dataAtV4 = mutableMapOf ("Three" to 3, "No" to false) + assertEquals(SharedState.Status.SET, extensionContainer.setSharedState(SharedStateType.STANDARD, dataAtV1, 1)) + assertEquals(SharedState.Status.SET, extensionContainer.setSharedState(SharedStateType.STANDARD, dataAtV4, 4)) + + var ret = extensionContainer.getSharedState(SharedStateType.STANDARD, 7) + assertEquals(dataAtV4, ret?.data) + assertEquals(SharedState.Status.SET, ret?.status) + + ret = extensionContainer.getSharedState(SharedStateType.STANDARD, 3) + assertEquals(dataAtV1, ret?.data) + assertEquals(SharedState.Status.SET, ret?.status) + } + + @Test + fun testGetSharedState_StateDoesNotAtVersion() { + // Create shared states at Version 3 and Version 4 + val dataAtV3 = mutableMapOf ("One" to 1, "Yes" to true) + val dataAtV4 = mutableMapOf ("Three" to 3, "No" to false) + assertEquals(SharedState.Status.SET, extensionContainer.setSharedState(SharedStateType.STANDARD, dataAtV3, 3)) + assertEquals(SharedState.Status.SET, extensionContainer.setSharedState(SharedStateType.STANDARD, dataAtV4, 4)) + + // Fetch state at version 2 + val ret = extensionContainer.getSharedState(SharedStateType.STANDARD, 2) + assertNull(ret) + } + + @Test + fun testGetSharedState_ExecutorShutdown() { + // Create shared states at Version 3 and Version 4 + val dataAtV3 = mutableMapOf ("One" to 1, "Yes" to true) + val dataAtV4 = mutableMapOf ("Three" to 3, "No" to false) + assertEquals(SharedState.Status.SET, extensionContainer.setSharedState(SharedStateType.STANDARD, dataAtV3, 3)) + assertEquals(SharedState.Status.SET, extensionContainer.setSharedState(SharedStateType.STANDARD, dataAtV4, 4)) + + // Simulate shutdown + `when`(mockExecutorService.isShutdown).thenReturn(true) + + // Fetch state at version 4 + val ret = extensionContainer.getSharedState(SharedStateType.STANDARD, 4) + assertNull(ret) + } + + @Test + fun testClearSharedState() { + // Create shared states at Version 3 and Version 4 + val dataAtV3 = mutableMapOf ("One" to 1, "Yes" to true) + val dataAtV4 = mutableMapOf ("Three" to 3, "No" to false) + assertEquals(SharedState.Status.SET, extensionContainer.setSharedState(SharedStateType.STANDARD, dataAtV3, 3)) + assertEquals(SharedState.Status.SET, extensionContainer.setSharedState(SharedStateType.STANDARD, dataAtV4, 4)) + + assertEquals(SharedState.Status.SET, extensionContainer.setSharedState(SharedStateType.XDM, dataAtV3, 3)) + assertEquals(SharedState.Status.SET, extensionContainer.setSharedState(SharedStateType.XDM, dataAtV4, 4)) + + // Clear STANDARD shared state + assertTrue(extensionContainer.clearSharedState(SharedStateType.STANDARD)) + assertNull(extensionContainer.getSharedState(SharedStateType.STANDARD, 3)) + assertNull(extensionContainer.getSharedState(SharedStateType.STANDARD, 4)) + + // Verify nothing affects XDM state + assertEquals(dataAtV3, extensionContainer.getSharedState(SharedStateType.XDM, 3)?.data) + assertEquals(dataAtV4, extensionContainer.getSharedState(SharedStateType.XDM, 4)?.data) + } +} diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/SharedStateManagerTest.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/SharedStateManagerTest.kt index ec4626dc7..492684985 100644 --- a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/SharedStateManagerTest.kt +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/SharedStateManagerTest.kt @@ -12,10 +12,8 @@ package com.adobe.marketing.mobile.internal.eventhub import kotlin.test.assertEquals -import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertNull -import kotlin.test.assertTrue import org.junit.Before import org.junit.Test @@ -30,22 +28,22 @@ internal class SharedStateManagerTest { @Test fun testCreateSharedState_NonNullData() { - val data = mutableMapOf ("One" to 1, "Yes" to true) - assertTrue { sharedStateManager.createSharedState(data, 1, false) } + val data = mapOf ("One" to 1, "Yes" to true) + assertEquals(SharedState.Status.SET, sharedStateManager.createSharedState(data, 1, false)) } @Test fun testCreateSharedState_NullData() { - assertTrue { sharedStateManager.createSharedState(null, 1, false) } + assertEquals(SharedState.Status.SET, sharedStateManager.createSharedState(null, 1, false)) } @Test fun testCreateSharedState_PendingDataAssumptions() { - val data = mutableMapOf ("One" to 1, "Yes" to true) + val data = mapOf ("One" to 1, "Yes" to true) // Verify that SharedStateManager does not make assumptions of pending based on data - assertTrue { sharedStateManager.createSharedState(data, 1, true) } - assertTrue { sharedStateManager.createSharedState(null, 3, true) } + assertEquals(SharedState.Status.PENDING, sharedStateManager.createSharedState(data, 1, true)) + assertEquals(SharedState.Status.PENDING, sharedStateManager.createSharedState(null, 3, true)) } @Test @@ -54,7 +52,7 @@ internal class SharedStateManagerTest { sharedStateManager.createSharedState(null, 1, true) // Overwrite with another pending data - assertFalse { sharedStateManager.createSharedState(mapOf(), 1, true) } + assertEquals(SharedState.Status.NOT_SET, sharedStateManager.createSharedState(mapOf(), 1, true)) } @Test @@ -62,8 +60,8 @@ internal class SharedStateManagerTest { // Create pending state at version 1 sharedStateManager.createSharedState(null, 1, true) - // Overwrite with another pending data - assertFalse { sharedStateManager.createSharedState(mapOf(), 1, false) } + // Overwrite with non pending data + assertEquals(SharedState.Status.NOT_SET, sharedStateManager.createSharedState(mapOf(), 1, false)) } @Test @@ -74,10 +72,8 @@ internal class SharedStateManagerTest { sharedStateManager.createSharedState(mapOf(), 5, false) // Verify that state greater than 5 can be created irrespective of pending status - assertTrue { - sharedStateManager.createSharedState(mapOf(), 10, false) - sharedStateManager.createSharedState(mapOf(), 11, true) - } + assertEquals(SharedState.Status.SET, sharedStateManager.createSharedState(mapOf(), 10, false)) + assertEquals(SharedState.Status.PENDING, sharedStateManager.createSharedState(mapOf(), 11, true)) } @Test @@ -86,10 +82,8 @@ internal class SharedStateManagerTest { sharedStateManager.createSharedState(mapOf(), 5, false) // Verify that no state less than or equal to 5 can be created irrespective of pending status - assertFalse { - sharedStateManager.createSharedState(mapOf(), 3, false) - sharedStateManager.createSharedState(mapOf(), 5, true) - } + assertEquals(SharedState.Status.NOT_SET, sharedStateManager.createSharedState(mapOf(), 3, false)) + assertEquals(SharedState.Status.NOT_SET, sharedStateManager.createSharedState(mapOf(), 5, true)) } @Test @@ -98,16 +92,14 @@ internal class SharedStateManagerTest { sharedStateManager.updateSharedState(null, 1, true) // Verify that pending state cannot be updated with another pending state - assertFalse { - sharedStateManager.updateSharedState(null, 1, true) - sharedStateManager.updateSharedState(mapOf(), 1, true) - } + assertEquals(SharedState.Status.NOT_SET, sharedStateManager.updateSharedState(null, 1, true)) + assertEquals(SharedState.Status.NOT_SET, sharedStateManager.updateSharedState(mapOf(), 1, true)) } @Test fun testUpdateSharedState_NoStateAtVersion() { - // Verify that pending state cannot be updated when no state exists at the version - assertFalse { sharedStateManager.updateSharedState(mapOf(), 7, false) } + // Verify that a state cannot be updated when no state exists at the version + assertEquals(SharedState.Status.NOT_SET, sharedStateManager.updateSharedState(mapOf(), 7, false)) } @Test @@ -116,16 +108,18 @@ internal class SharedStateManagerTest { sharedStateManager.createSharedState(mapOf(), 7, false) // Verify that pending state cannot be updated when no pending state exists at the version - assertFalse { sharedStateManager.updateSharedState(mapOf("One" to 1, "Yes" to true), 7, false) } + assertEquals(SharedState.Status.NOT_SET, + sharedStateManager.updateSharedState(mapOf("One" to 1, "Yes" to true), 7, false)) } @Test fun testUpdateSharedState_ValidPendingStateAtVersion() { - // Create a non pending state at version 7 + // Create a pending state at version 7 sharedStateManager.createSharedState(null, 7, true) - // Verify that pending state cannot be updated when no pending state exists at the version - assertTrue { sharedStateManager.updateSharedState(mapOf("One" to 1, "Yes" to true), 7, false) } + // Verify that pending state can be updated when pending state exists at the version + assertEquals(SharedState.Status.SET, + sharedStateManager.updateSharedState(mapOf("One" to 1, "Yes" to true), 7, false)) } @Test @@ -138,24 +132,35 @@ internal class SharedStateManagerTest { val data = mapOf("One" to 1, "Yes" to true) sharedStateManager.createSharedState(mapOf("One" to 1, "Yes" to true), 3, false) - assertEquals(data, sharedStateManager.getSharedState(3)) + val actualState: SharedState? = sharedStateManager.getSharedState(3) + assertNotNull(actualState) + assertEquals(data, actualState.data) + assertEquals(SharedState.Status.SET, actualState.status) } @Test fun testGetSharedState_PendingStateExistsAtQueriedVersion() { sharedStateManager.createSharedState(null, 3, true) - assertNull(sharedStateManager.getSharedState(3)) + val actualState: SharedState? = sharedStateManager.getSharedState(3) + assertNotNull(actualState) + assertNull(actualState.data) + assertEquals(SharedState.Status.PENDING, actualState.status) } @Test fun testGetSharedState_StateExistsAtOlderVersion() { + // Create shared states at Version 3 and Version 4 val dataAtV3 = mapOf("One" to 1, "Yes" to true) val dataAtV4 = mapOf("Two" to 2, "No" to false) sharedStateManager.createSharedState(dataAtV3, 3, false) sharedStateManager.createSharedState(dataAtV4, 4, false) - assertEquals(dataAtV4, sharedStateManager.getSharedState(8)) + // Fetch state at version 8 + val actualState: SharedState? = sharedStateManager.getSharedState(8) + assertNotNull(actualState) + assertEquals(dataAtV4, actualState.data) + assertEquals(SharedState.Status.SET, actualState.status) } @Test @@ -166,7 +171,10 @@ internal class SharedStateManagerTest { sharedStateManager.createSharedState(dataAtV3, 3, false) sharedStateManager.createSharedState(dataAtV4, 4, true) - assertNull(sharedStateManager.getSharedState(8)) + val actualState: SharedState? = sharedStateManager.getSharedState(8) + assertNotNull(actualState) + assertEquals(dataAtV4, actualState.data) + assertEquals(SharedState.Status.PENDING, actualState.status) } @Test From 6b927722bd4ff19ec85ab80e82a7634d1926bbb8 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Wed, 11 May 2022 09:42:01 -0700 Subject: [PATCH 044/476] [#29] Address review comments - Moved SharedState to a separate class - Added a name memebr to SharedStateManager - Include state name in logs - Add default values for isPending parameter - Added missing copyright header --- .../mobile/internal/eventhub/EventHub.kt | 7 +-- .../internal/eventhub/ExtensionContainer.kt | 8 ++- .../mobile/internal/eventhub/SharedState.kt | 36 +++++++++++++ .../internal/eventhub/SharedStateManager.kt | 53 ++++++++----------- .../eventhub/ExtensionContainerTest.kt | 9 +--- .../eventhub/SharedStateManagerTest.kt | 2 +- 6 files changed, 66 insertions(+), 49 deletions(-) create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedState.kt diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index b2cac87c6..fe2238275 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -80,12 +80,7 @@ internal class EventHub { } val executor = Executors.newSingleThreadExecutor() - val stateManagers: Map = mapOf( - SharedStateType.STANDARD to SharedStateManager(), - SharedStateType.XDM to SharedStateManager() - ) - - val container = ExtensionContainer(extensionClass, ExtensionRuntime(), stateManagers, executor, completion) + val container = ExtensionContainer(extensionClass, ExtensionRuntime(), executor, completion) registeredExtensions[extensionName] = container } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt index 4f9e26e66..941c5f379 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt @@ -67,7 +67,7 @@ internal class ExtensionRuntime() : ExtensionApi() { errorCallback: ExtensionErrorCallback? ): Boolean { try { - return EventHub.shared.setSharedState(SharedStateType.XDM, extensionName, state, event, errorCallback) + return EventHub.shared.setSharedState(SharedStateType.STANDARD, extensionName, state, event, errorCallback) } catch (exception: Exception) { MobileCore.log(LoggingMode.ERROR, getTag(), "Failed to set shared state at EventID: ${event?.uniqueIdentifier}. $exception") @@ -164,7 +164,6 @@ internal class ExtensionRuntime() : ExtensionApi() { internal class ExtensionContainer constructor( private val extensionClass: Class, private val extensionRuntime: ExtensionRuntime, - private val sharedStateManagers: Map, private val taskExecutor: ExecutorService, callback: (EventHubError) -> Unit ) { @@ -178,6 +177,11 @@ internal class ExtensionContainer constructor( val version: String? get() = extensionRuntime.extensionVersion + private val sharedStateManagers: Map = mapOf( + SharedStateType.XDM to SharedStateManager(sharedStateName ?: ""), + SharedStateType.STANDARD to SharedStateManager(sharedStateName ?: "") + ) + init { taskExecutor.submit { val extension = extensionClass.initWith(extensionRuntime) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedState.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedState.kt new file mode 100644 index 000000000..41ebe6d08 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedState.kt @@ -0,0 +1,36 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.internal.eventhub + +/** + * Internal representation of a shared event state. + * Allows associating version and pending behavior with the state data in a queryable way. + */ +internal data class SharedState constructor(val data: Map?, val status: Status) { + + /** + * Represents the status of an extensions shared state, typically associated with a version. + */ + internal enum class Status { + SET, + PENDING, + NOT_SET + } +} + +/** + * Represents the types of shared state that are supported. + */ +internal enum class SharedStateType { + STANDARD, + XDM +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedStateManager.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedStateManager.kt index 5a3a31725..f22f40441 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedStateManager.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedStateManager.kt @@ -1,3 +1,14 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + package com.adobe.marketing.mobile.internal.eventhub import android.util.LruCache @@ -9,14 +20,13 @@ import java.util.TreeMap * Responsible for managing the shared state operations for an extension via [ExtensionRuntime]. * Employs a red-black tree to store the state data and their versions while also using a cache * to make a O(1) best effort retrieval. - * It is intentionally agnostic of the type ([SharedStateType]) of the state it deals with. * The knowledge of whether or not a state is pending is deferred to the caller to ensure this class * is decoupled from the rules for a pending state. * * Note that the methods in this class fall on the public ExtensionApi path and, changes to method * behaviors may impact the shared state API behavior. */ -internal class SharedStateManager { +internal class SharedStateManager(private val name: String) { /** * A mapping between the version of the state to the state. @@ -30,7 +40,7 @@ internal class SharedStateManager { private val cache: LruCache = LruCache(10) companion object { - const val LOG_TAG = "SharedStateManager" + private const val LOG_TAG = "SharedStateManager" const val VERSION_LATEST: Int = Int.MAX_VALUE } @@ -48,12 +58,13 @@ internal class SharedStateManager { fun createSharedState( data: Map?, version: Int, - isPending: Boolean + isPending: Boolean = false ): SharedState.Status { // Check if there exists a state at a version equal to, or higher than the one provided. if (states.ceilingEntry(version) != null) { - MobileCore.log(LoggingMode.VERBOSE, LOG_TAG, "Cannot create state st version $version. More recent state exists.") + MobileCore.log(LoggingMode.VERBOSE, LOG_TAG, "Cannot create $name shared state at version $version. " + + "More recent state exists.") // If such a state exists a new state cannot be created, it can be updated, if pending, // via SharedStateManager#updateSharedState(..) return SharedState.Status.NOT_SET @@ -86,12 +97,13 @@ internal class SharedStateManager { fun updateSharedState( data: Map?, version: Int, - isPending: Boolean + isPending: Boolean = false ): SharedState.Status { // Check if new state is pending. A pending state cannot be overwritten by another pending state if (isPending) { - MobileCore.log(LoggingMode.VERBOSE, LOG_TAG, "Cannot update pending state at version $version with a pending state.") + MobileCore.log(LoggingMode.VERBOSE, LOG_TAG, "Cannot update pending $name shared state at " + + "version $version with a pending state.") return SharedState.Status.NOT_SET } @@ -100,7 +112,8 @@ internal class SharedStateManager { // Check there is a valid pending state for updating. if (stateAtVersion.status != SharedState.Status.PENDING) { - MobileCore.log(LoggingMode.WARNING, LOG_TAG, "Cannot update a non pending state state version $version.") + MobileCore.log(LoggingMode.WARNING, LOG_TAG, "Cannot update a non pending $name shared state " + + "at version $version.") return SharedState.Status.NOT_SET } @@ -163,27 +176,3 @@ internal class SharedStateManager { cache.evictAll() } } - -/** - * Internal representation of a shared event state. - * Allows associating version and pending behavior with the state data in a queryable way. - */ -internal data class SharedState constructor(val data: Map?, val status: Status) { - - /** - * Represents the status of an extensions shared state, typically associated with a version. - */ - internal enum class Status { - SET, - PENDING, - NOT_SET - } -} - -/** - * Represents the types of shared state that are supported. - */ -internal enum class SharedStateType { - STANDARD, - XDM -} diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainerTest.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainerTest.kt index 80f59cdbc..4aeda4a84 100644 --- a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainerTest.kt +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainerTest.kt @@ -44,18 +44,11 @@ class ExtensionContainerTest { private lateinit var mockErrorCallback: (EventHubError) -> Unit private lateinit var extensionContainer: ExtensionContainer - private val xdmStateManager: SharedStateManager = SharedStateManager() - private val standardStateManager: SharedStateManager = SharedStateManager() @Before fun setUp() { MockitoAnnotations.initMocks(this) - val stateManagerMap: Map = mapOf( - SharedStateType.XDM to xdmStateManager, - SharedStateType.STANDARD to standardStateManager - ) - doAnswer(Answer { // Create a mock Future to return val mockFuture: Future<*> = Mockito.mock(Future::class.java) @@ -66,7 +59,7 @@ class ExtensionContainerTest { }).`when`(mockExecutorService).submit(any(Callable::class.java)) extensionContainer = ExtensionContainer(MockExtension::class.java, - mockExtensionRuntime, stateManagerMap, mockExecutorService, mockErrorCallback) + mockExtensionRuntime, mockExecutorService, mockErrorCallback) } @Test diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/SharedStateManagerTest.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/SharedStateManagerTest.kt index 492684985..6b45b7fa3 100644 --- a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/SharedStateManagerTest.kt +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/SharedStateManagerTest.kt @@ -19,7 +19,7 @@ import org.junit.Test internal class SharedStateManagerTest { - private val sharedStateManager: SharedStateManager = SharedStateManager() + private val sharedStateManager: SharedStateManager = SharedStateManager("SampleStateName") @Before fun setUp() { From 96cfa696d47a6cadebbf3f4c0e516e1661ca571f Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Thu, 12 May 2022 14:18:32 -0700 Subject: [PATCH 045/476] launch token finder implementation --- .../mobile/ConfigurationExtension.java | 2 + .../marketing/mobile/LaunchTokenFinder.kt | 162 ------- .../marketing/mobile/RuleTokenParser.java | 1 + .../com/adobe/marketing/mobile/TimeUtil.java | 70 --- .../mobile/internal/utility/MapExtensions.kt | 103 +++++ .../mobile/internal/utility/TimeUtil.kt | 71 +++ .../launch/rulesengine/LaunchTokenFinder.kt | 163 +++++++ .../marketing/mobile/LaunchTokenFinderTest.kt | 429 ++++++++---------- .../adobe/marketing/mobile/TestHelper.java | 1 + .../internal/utility/MapExtensionsTests.kt | 134 ++++++ .../mobile/internal/utility/TestHelper.java | 51 +++ .../internal/utility}/TimeUtilTest.java | 3 +- 12 files changed, 728 insertions(+), 462 deletions(-) delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/LaunchTokenFinder.kt delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/TimeUtil.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/TimeUtil.kt create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt create mode 100644 code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/TestHelper.java rename code/android-core-library/src/test/{java/com/adobe/marketing/mobile => kotlin/com/adobe/marketing/mobile/internal/utility}/TimeUtilTest.java (97%) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ConfigurationExtension.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ConfigurationExtension.java index 855165ff8..c79ac627c 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ConfigurationExtension.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ConfigurationExtension.java @@ -11,6 +11,8 @@ package com.adobe.marketing.mobile; +import com.adobe.marketing.mobile.internal.utility.TimeUtil; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/LaunchTokenFinder.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/LaunchTokenFinder.kt deleted file mode 100644 index 55b191e17..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/LaunchTokenFinder.kt +++ /dev/null @@ -1,162 +0,0 @@ -package com.adobe.marketing.mobile - -import java.security.SecureRandom - -internal class LaunchTokenFinder(val event: Event, val module: Module, val platformServices: PlatformServices) { - - companion object { - private const val LOG_TAG = "LaunchTokenFinder" - private const val KEY_EVENT_TYPE = "~type" - private const val KEY_EVENT_SOURCE = "~source" - private const val KEY_TIMESTAMP_UNIX = "~timestampu" - private const val KEY_TIMESTAMP_ISO8601 = "~timestampz" - private const val KEY_TIMESTAMP_PLATFORM = "~timestampp" - private const val KEY_SDK_VERSION = "~sdkver" - private const val KEY_CACHEBUST = "~cachebust" - private const val KEY_ALL_URL = "~all_url" - private const val KEY_ALL_JSON = "~all_json" - private const val KEY_SHARED_STATE = "~state." - private const val EMPTY_STRING = "" - private const val RANDOM_INT_BOUNDARY = 100000000 - private const val SHARED_STATE_KEY_DELIMITER = "/" - } - - // ======================================================== - // public methods - // ======================================================== - - /** - * Returns the value for the `key` provided as input. - * - * - * If the `key` is a special key recognized by SDK, the value is determined based on incoming `Event`, - * or `EventHub#moduleSharedStates` data. Otherwise the key is searched in the current `Event`'s data - * and the corresponding value is returned. - * - * @param key `String` containing the key whose value needs to be determined - * - * @return `Object` containing value to be substituted for the `key` - */ - fun get(key: String): Any? { - if (StringUtils.isNullOrEmpty(key)) { - return null - } - - return when (key) { - KEY_EVENT_TYPE -> event.getEventType().getName() - KEY_EVENT_SOURCE -> event.getEventSource().getName() - KEY_TIMESTAMP_UNIX -> TimeUtil.getUnixTimeInSeconds().toString() - KEY_TIMESTAMP_ISO8601 -> TimeUtil.getIso8601Date() - KEY_TIMESTAMP_PLATFORM -> TimeUtil.getIso8601DateTimeZoneISO8601() - KEY_SDK_VERSION -> { - MobileCore.extensionVersion() - } - KEY_CACHEBUST -> SecureRandom().nextInt(RANDOM_INT_BOUNDARY).toString() - KEY_ALL_URL -> { - if (event.getData() == null) { - Log.debug(LOG_TAG, "Triggering event data is null, can not use it to generate an url query string") - return EMPTY_STRING - } - val eventDataAsObjectMap = EventDataFlattener.getFlattenedDataMap(event.getData()) - UrlUtilities.serializeToQueryString(eventDataAsObjectMap) - } - KEY_ALL_JSON -> { - if (event.getData() == null) { - Log.debug(LOG_TAG, "Triggering event data is null, can not use it to generate a json string") - return EMPTY_STRING - } - generateJsonString(event.getData()) - } - else -> { - if (key.startsWith(KEY_SHARED_STATE)) { - getValueFromSharedState(key) - } else getValueFromEvent(key) - } - } - } - - // ======================================================== - // private getter methods - // ======================================================== - - /** - * Returns the value for shared state key specified by the `key`. - * - * - * The key is provided in the format ~state.valid_shared_state_name/key - * For example: ~state.com.adobe.marketing.mobile.Identity/mid - * - * @param key `String` containing the key to search for in `EventHub#moduleSharedStates` - * - * @return `Object` containing the value for the shared state key if valid, null otherwise - */ - private fun getValueFromSharedState(key: String): Any? { - val sharedStateKeyString = key.substring(KEY_SHARED_STATE.length) - if (StringUtils.isNullOrEmpty(sharedStateKeyString)) { - return null - } - val index = sharedStateKeyString.indexOf(SHARED_STATE_KEY_DELIMITER) - if (index == -1) { - return null - } - val sharedStateName = sharedStateKeyString.substring(0, index) - val dataKeyName = sharedStateKeyString.substring(index + 1) - val sharedStateMap = EventDataFlattener.getFlattenedDataMap(module.getSharedEventState( - sharedStateName, event)) - if (sharedStateMap.isEmpty() || StringUtils.isNullOrEmpty(dataKeyName) || !sharedStateMap.containsKey(dataKeyName)) { - return null - } - val variant = sharedStateMap[dataKeyName] - return try { - PermissiveVariantSerializer.DEFAULT_INSTANCE.deserialize(variant) - } catch (ex: VariantException) { - null - } - } - - /** - * Returns the value for the `key` provided as input by searching in the current `Event`'s data. - * - * @param key `String` containing the key whose value needs to be determined - * - * @return `Object` containing value to be substituted for the `key` from the `Event`'s data - */ - private fun getValueFromEvent(key: String): Any? { - if (event.getData() == null) { - Log.debug(LOG_TAG, String.format("Unable to replace the token %s, triggering event data is null", key)) - return EMPTY_STRING - } - val eventDataMap = EventDataFlattener.getFlattenedDataMap(event.getData()) - if (!eventDataMap.containsKey(key)) { - return null - } - val value = eventDataMap[key] - return if (value == null || value is NullVariant) { - null - } else try { - PermissiveVariantSerializer.DEFAULT_INSTANCE.deserialize(value) - } catch (ex: VariantException) { - EMPTY_STRING - } - } - - /** - * Returns the `EventData` in json format - * - * @param eventData `EventData` which needs to be encoded - * @return `String` containing `event`'s data encoded in json format - */ - private fun generateJsonString(eventData: EventData): String { - val jsonUtilityService = platformServices.getJsonUtilityService() - ?: return EMPTY_STRING - val jsonObject = try { - val dataMap = eventData.asMapCopy() - val variant = Variant.fromVariantMap(dataMap) - variant.getTypedObject(JsonObjectVariantSerializer( - jsonUtilityService)) - } catch (exception: Exception) { - return EMPTY_STRING - } - return jsonObject?.toString() ?: EMPTY_STRING - } -} \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleTokenParser.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleTokenParser.java index 325d5e401..c0e639b2b 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleTokenParser.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleTokenParser.java @@ -12,6 +12,7 @@ package com.adobe.marketing.mobile; import com.adobe.marketing.mobile.internal.utility.UrlUtilities; +import com.adobe.marketing.mobile.internal.utility.TimeUtil; import java.lang.reflect.Method; import java.security.SecureRandom; diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/TimeUtil.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/TimeUtil.java deleted file mode 100644 index 9ff45fb4d..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/TimeUtil.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - -final class TimeUtil { - - private static final long MILLISECONDS_PER_SECOND = 1000L; - private static final String ISO8601_DATE_FORMATTER_TIMEZONE_RFC822 = "yyyy-MM-dd'T'HH:mm:ssZZZ"; - private static final String ISO8601_DATE_FORMATTER_TIMEZONE_ISO8601 = "yyyy-MM-dd'T'HH:mm:ssXXX"; - - private TimeUtil() {} - - /** - * Gets current unix timestamp in seconds. - * - * @return {code long} current timestamp - */ - static long getUnixTimeInSeconds() { - return System.currentTimeMillis() / MILLISECONDS_PER_SECOND; - } - - /** - * Gets the the ISO 8601 formatted date {@code String} for the current date. - * TimeZone format used is RFC822 using ZZZ formatting letter. ex: 9th July 2020 PDT will be formatted as 2020-07-09T15:09:18-0700. - * - * @return Iso8601 formatted date {@link String} - */ - static String getIso8601Date() { - return getIso8601Date(new Date(), ISO8601_DATE_FORMATTER_TIMEZONE_RFC822); - } - - /** - * Gets the the ISO 8601 formatted date {@code String} for the current date. - * TimeZone format used is ISO8601 using XXX formatting letters. ex: 9th July 2020 PDT will be formatted as 2020-07-09T15:09:18-07:00. - * AMSDK-10273 :: ExEdge requires time zone offset formatted in form [+-]HH:MM. - * - * @return Iso8601 formatted date {@link String} - */ - static String getIso8601DateTimeZoneISO8601() { - return getIso8601Date(new Date(), ISO8601_DATE_FORMATTER_TIMEZONE_ISO8601); - } - - /** - * Gets the the ISO 8601 formatted date {@code String} for the passed in date - "yyyy-MM-dd'T'HH:mm:ssZZZ" - * - * @param date the {@link Date} to generate the {@link String} for - * @return ISO 8601 formatted date {@link String} - */ - static String getIso8601Date(final Date date, final String format) { - // AMSDK-8374 - - // we should explicitly ignore the device's locale when formatting an ISO 8601 timestamp - final Locale posixLocale = new Locale(Locale.US.getLanguage(), Locale.US.getCountry(), "POSIX"); - final DateFormat iso8601Format = new SimpleDateFormat(format, posixLocale); - return iso8601Format.format(date != null ? date : new Date()); - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt index ef7f3f0ad..94f85d73b 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt @@ -11,6 +11,8 @@ package com.adobe.marketing.mobile.internal.utility +import com.adobe.marketing.mobile.internal.utility.UrlUtilities.urlEncode + /** * Convert map to a decimal FNV1a 32-bit hash. If a mask is provided, only use keys in the provided mask and alphabetize their order. * @@ -65,9 +67,110 @@ internal fun Map.flattening(prefix: String = ""): Map.getFlattenedDataMap(prefix: String = ""): Map { + val keyPrefix = if (prefix.isNotEmpty()) "$prefix." else prefix + val flattenedMap = mutableMapOf() + this.forEach { entry -> + val expandedKey = keyPrefix + entry.key + val value = entry.value + flattenedMap[expandedKey] = value + if (value is Map<*, *> && value.keys.isAllString()) { + @Suppress("UNCHECKED_CAST") + flattenedMap.putAll((value as Map).flattening(expandedKey)) + } + } + return flattenedMap +} + +/** + * Serializes a map to key value pairs for url string. + * This method is recursive to handle the nested data objects. + * + * @return resulted serialized query parameters as [String] + */ +@JvmSynthetic +internal fun Map.serializeToQueryString(): String { + val builder = StringBuilder() + for ((key, value) in this.entries) { + val encodedKey = urlEncode(key) ?: continue + var encodedValue: String? = null + + // TODO add serializing for custom objects + if (value is List<*>) { + encodedValue = urlEncode(join(value, ",")) + } else { + encodedValue = urlEncode(value?.toString()) + } + + val serializedKVP = serializeKeyValuePair(encodedKey, encodedValue) + if (serializedKVP != null) { + builder.append(serializedKVP) + } + } + + return builder.toString() +} + private fun Set<*>.isAllString(): Boolean { this.forEach { if (it !is String) return false } return true } + +/** + * Encodes the key/value pair and prepares it in the URL format. + * If the value is a List, it will create a join string with the "," delimiter before encoding, + * otherwise it will use toString method on other objects. + * + * @param key the string value that we want to append to the builder + * @param value the object value that we want to encode and append to the builder + * @return [String] containing key/value pair encoded in URL format + */ +private fun serializeKeyValuePair(key: String?, value: String?): String? { + if (key == null || value == null || key.isEmpty()) { + return null + } + + val builder = StringBuilder() + builder.append("&") + builder.append(key) + builder.append("=") + builder.append(value) + + return builder.toString() +} + +/** + * Returns a [String] containing the elements joined by delimiters. + * + * @param elements an array objects to be joined. A [String] will be formed from the objects + * by calling object.toString(). + * @param delimiter the `String` to be used as the delimiter between all elements + * @return [String] containing the elements joined by delimiters + */ +fun join(elements: Iterable<*>, delimiter: String?): String { + val sBuilder = java.lang.StringBuilder() + val iterator = elements.iterator() + + // TODO: consider breaking on null items, otherwise we end up with sample1,null,sample3 instead of sample1,sample3 + while (iterator.hasNext()) { + sBuilder.append(iterator.next()) + if (iterator.hasNext()) { + sBuilder.append(delimiter) + } + } + return sBuilder.toString() +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/TimeUtil.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/TimeUtil.kt new file mode 100644 index 000000000..3b16f9677 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/TimeUtil.kt @@ -0,0 +1,71 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.internal.utility + +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +internal object TimeUtil { + private const val MILLISECONDS_PER_SECOND = 1000L + private const val ISO8601_DATE_FORMATTER_TIMEZONE_RFC822 = "yyyy-MM-dd'T'HH:mm:ssZZZ" + private const val ISO8601_DATE_FORMATTER_TIMEZONE_ISO8601 = "yyyy-MM-dd'T'HH:mm:ssXXX" + + /** + * Gets current unix timestamp in seconds. + * + * @return {code long} current timestamp + */ + @JvmStatic + fun getUnixTimeInSeconds(): Long { + return System.currentTimeMillis() / MILLISECONDS_PER_SECOND + } + + /** + * Gets the the ISO 8601 formatted date `String` for the current date. + * TimeZone format used is RFC822 using ZZZ formatting letter. ex: 9th July 2020 PDT will be formatted as 2020-07-09T15:09:18-0700. + * + * @return Iso8601 formatted date [String] + */ + @JvmStatic + fun getIso8601Date(): String? { + return getIso8601Date(Date(), ISO8601_DATE_FORMATTER_TIMEZONE_RFC822) + } + + /** + * Gets the the ISO 8601 formatted date `String` for the current date. + * TimeZone format used is ISO8601 using XXX formatting letters. ex: 9th July 2020 PDT will be formatted as 2020-07-09T15:09:18-07:00. + * AMSDK-10273 :: ExEdge requires time zone offset formatted in form [+-]HH:MM. + * + * @return Iso8601 formatted date [String] + */ + @JvmStatic + fun getIso8601DateTimeZoneISO8601(): String? { + return getIso8601Date(Date(), ISO8601_DATE_FORMATTER_TIMEZONE_ISO8601) + } + + /** + * Gets the the ISO 8601 formatted date `String` for the passed in date - "yyyy-MM-dd'T'HH:mm:ssZZZ" + * + * @param date the [Date] to generate the [String] for + * @return ISO 8601 formatted date [String] + */ + @JvmStatic + fun getIso8601Date(date: Date?, format: String?): String? { + // AMSDK-8374 - + // we should explicitly ignore the device's locale when formatting an ISO 8601 timestamp + val posixLocale = Locale(Locale.US.language, Locale.US.country, "POSIX") + val iso8601Format: DateFormat = SimpleDateFormat(format, posixLocale) + return iso8601Format.format(date ?: Date()) + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt new file mode 100644 index 000000000..a82feb204 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt @@ -0,0 +1,163 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.launch.rulesengine + +import com.adobe.marketing.mobile.Event +import com.adobe.marketing.mobile.ExtensionApi +import com.adobe.marketing.mobile.LoggingMode +import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.internal.utility.StringUtils +import com.adobe.marketing.mobile.internal.utility.TimeUtil +import com.adobe.marketing.mobile.internal.utility.flattening +import com.adobe.marketing.mobile.internal.utility.getFlattenedDataMap +import com.adobe.marketing.mobile.internal.utility.serializeToQueryString +import java.security.SecureRandom +import org.json.JSONObject + +internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionApi) { + + private val LOG_TAG = "LaunchTokenFinder" + private val KEY_EVENT_TYPE = "~type" + private val KEY_EVENT_SOURCE = "~source" + private val KEY_TIMESTAMP_UNIX = "~timestampu" + private val KEY_TIMESTAMP_ISO8601 = "~timestampz" + private val KEY_TIMESTAMP_PLATFORM = "~timestampp" + private val KEY_SDK_VERSION = "~sdkver" + private val KEY_CACHEBUST = "~cachebust" + private val KEY_ALL_URL = "~all_url" + private val KEY_ALL_JSON = "~all_json" + private val KEY_SHARED_STATE = "~state." + private val EMPTY_STRING = "" + private val RANDOM_INT_BOUNDARY = 100000000 + private val SHARED_STATE_KEY_DELIMITER = "/" + + // ======================================================== + // public methods + // ======================================================== + + /** + * Returns the value for the [key] provided as input. + * + * + * If the `key` is a special key recognized by SDK, the value is determined based on incoming `Event`, + * or `EventHub#moduleSharedStates` data. Otherwise the key is searched in the current `Event`'s data + * and the corresponding value is returned. + * + * @param key [String] containing the key whose value needs to be determined + * + * @return [Any] containing value to be substituted for the [key], null if the key does not exist + */ + fun get(key: String): Any? { + if (StringUtils.isNullOrEmpty(key)) { + return null + } + + return when (key) { + KEY_EVENT_TYPE -> event.type + KEY_EVENT_SOURCE -> event.source + KEY_TIMESTAMP_UNIX -> TimeUtil.getUnixTimeInSeconds().toString() + KEY_TIMESTAMP_ISO8601 -> TimeUtil.getIso8601Date() + KEY_TIMESTAMP_PLATFORM -> TimeUtil.getIso8601DateTimeZoneISO8601() + KEY_SDK_VERSION -> { + MobileCore.extensionVersion() + } + KEY_CACHEBUST -> SecureRandom().nextInt(RANDOM_INT_BOUNDARY).toString() + KEY_ALL_URL -> { + if (event.eventData == null) { + MobileCore.log( + LoggingMode.DEBUG, + LOG_TAG, + "Triggering event data is null, can not use it to generate an url query string" + ) + return EMPTY_STRING + } + val eventDataAsObjectMap = event.eventData.flattening() + eventDataAsObjectMap.serializeToQueryString() + } + KEY_ALL_JSON -> { + if (event.eventData == null) { + MobileCore.log(LoggingMode.DEBUG, + LOG_TAG, + "Triggering event data is null, can not use it to generate a json string" + ) + return EMPTY_STRING + } + JSONObject(event.eventData).toString() + } + else -> { + if (key.startsWith(KEY_SHARED_STATE)) { + getValueFromSharedState(key) + } else getValueFromEvent(key) + } + } + } + + // ======================================================== + // private getter methods + // ======================================================== + + /** + * Returns the value for shared state key specified by the [key]. + * + * + * The [key] is provided in the format ~state.valid_shared_state_name/key + * For example: ~state.com.adobe.marketing.mobile.Identity/mid + * + * @param key [String] containing the key to search for in `EventHub#moduleSharedStates` + * + * @return [Any] containing the value for the shared state key if valid, null otherwise + */ + private fun getValueFromSharedState(key: String): Any? { + val sharedStateKeyString = key.substring(KEY_SHARED_STATE.length) + if (StringUtils.isNullOrEmpty(sharedStateKeyString)) { + return null + } + val index = sharedStateKeyString.indexOf(SHARED_STATE_KEY_DELIMITER) + if (index == -1) { + return null + } + val sharedStateName = sharedStateKeyString.substring(0, index) + val dataKeyName = sharedStateKeyString.substring(index + 1) + val sharedStateMap = extensionApi.getSharedEventState(sharedStateName, event) { + MobileCore.log(LoggingMode.DEBUG, + LOG_TAG, + String.format("Unable to replace the token %s, token not found in shared state for the event", key) + ) + }?.getFlattenedDataMap() + if (sharedStateMap == null || sharedStateMap.isEmpty() || StringUtils.isNullOrEmpty(dataKeyName) || !sharedStateMap.containsKey(dataKeyName)) { + return null + } + return sharedStateMap[dataKeyName] + } + + /** + * Returns the value for the [key] provided as input by searching in the current [Event]'s data. + * + * @param key [String] containing the key whose value needs to be determined + * + * @return [Any] containing value to be substituted for the [key] from the [Event]'s data if [key] is present, null otherwise + */ + private fun getValueFromEvent(key: String): Any? { + if (event.eventData == null) { + MobileCore.log(LoggingMode.DEBUG, + LOG_TAG, + String.format("Unable to replace the token %s, triggering event data is null", key) + ) + return EMPTY_STRING + } + val eventDataMap = event.eventData.getFlattenedDataMap() + if (!eventDataMap.containsKey(key)) { + return null + } + return eventDataMap[key] + } +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/LaunchTokenFinderTest.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/LaunchTokenFinderTest.kt index 96d28df2e..3ebfa47f3 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/LaunchTokenFinderTest.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/LaunchTokenFinderTest.kt @@ -1,221 +1,210 @@ -package com.adobe.marketing.mobile +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ -import org.json.JSONException; -import org.json.JSONObject; -import org.junit.Before; -import org.junit.Test; +package com.adobe.marketing.mobile -import org.junit.Assert.* +import com.adobe.marketing.mobile.internal.utility.TimeUtil +import com.adobe.marketing.mobile.launch.rulesengine.LaunchTokenFinder +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test -class LaunchTokenFinderTest: BaseTest() { +class LaunchTokenFinderTest : BaseTest() { - private lateinit var configuration: TestableConfigurationExtension + private lateinit var extensionApi: ExtensionApi @Before - @Throws(MissingPlatformServicesException::class) fun setup() { super.beforeEach() - configuration = TestableConfigurationExtension(eventHub, platformServices) + extensionApi = ExtensionApi(eventHub) } @Test - fun get_ReturnsNull_When_KeyIsEmpty() { - //setup + fun `LaunchTokenFinder should return null on empty input string`() { + // setup val testEvent = getDefaultEvent() - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("") - //verify - assertNull("get should return null on empty input string", result) + // verify + assertNull(result) } @Test - fun get_ReturnsEventType_When_KeyIsType() { - //setup + fun `get should return Event Type on valid Event`() { + // setup val testEvent = getDefaultEvent() - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("~type") - //verify - assertEquals("get should return Event Type on valid Event", "com.adobe.eventtype.analytics", result) + // verify + assertEquals( "com.adobe.eventtype.analytics", result) } @Test - fun get_ReturnsEventSource_When_KeyIsSource() { - //setup + fun `get should return Event Source on valid Event`() { + // setup val testEvent = getDefaultEvent() - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("~source") - //verify - assertEquals("get should return Event Source on valid Event", "com.adobe.eventsource.requestcontent", result) + // verify + assertEquals("com.adobe.eventsource.requestcontent", result) } @Test - fun get_ReturnsCurrentUnixTimestamp_When_KeyPrefixIsTimestampu() { - //setup + fun `get should return current unix timestamp on valid event`() { + // setup val testEvent = getDefaultEvent()!! - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration!!, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("~timestampu") - //verify - assertEquals("get should return current unix timestamp on valid event", TimeUtil.getUnixTimeInSeconds().toString(), result) + // verify + assertEquals(TimeUtil.getUnixTimeInSeconds().toString(), result) } @Test - fun get_ReturnsCurrentISO8601Timestamp_When_KeyPrefixIsTimestampz() { - //setup + fun `get should return current ISO8601 timestamp on valid event`() { + // setup val testEvent = getDefaultEvent()!! - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration!!, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("~timestampz") - //verify - assertEquals("get should return current ISO8601 timestamp on valid event", TimeUtil.getIso8601Date(), result) + // verify + assertEquals(TimeUtil.getIso8601Date(), result) } @Test - fun get_ReturnsCurrentIso8601DateTimeZone_When_KeyPrefixIsTimestampp() { - //setup + fun `get should return current ISO8601 date timezone on valid event`() { + // setup val testEvent = getDefaultEvent()!! - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration!!, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("~timestampp") - //verify - assertEquals("get should return current ISO8601 date timezone on valid event", - TimeUtil.getIso8601DateTimeZoneISO8601(), result) + // verify + assertEquals(TimeUtil.getIso8601DateTimeZoneISO8601(), result) } @Test - fun expandKey_ReturnsCurrentSdkVersion_When_KeyPrefixIsSdkVersion() { - //setup + fun `get should return current sdk version on valid event`() { + // setup val testEvent = getDefaultEvent()!! - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration!!, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("~sdkver") - //verify - assertEquals("get should return current sdk version on valid event", "mockSdkVersion", result) + // verify + assertEquals(MobileCore.extensionVersion(), result) } @Test - fun get_ReturnsRandomNumber_When_KeyPrefixIsCachebust() { - //setup + fun `get should return random cachebust on valid event`() { + // setup val testEvent = getDefaultEvent() - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("~cachebust") as? String - //verify - try { - if (result != null) { - assertTrue("get should return random cachebust on valid event", result.toInt() < 100000000) - } - } catch (ex: VariantException) { + // verify + if (result != null) { + assertTrue(result.toInt() < 100000000) } } @Test - fun get_ReturnsUrlEncoded_When_KeyPrefixIsAllUrlStringOrNull() { - //setup + fun `get should return all string variables on valid event encoded in url format`() { + // setup val testEventData = EventData() testEventData.putString("key1", "value 1") testEventData.putNull("key8") val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("~all_url") - //verify - assertEquals("get should return all variables on valid event encoded in url format", - "&key1=value%201", - result) + // verify + assertEquals("&key1=value%201", result) } @Test - fun get_ReturnsUrlEncoded_When_KeyPrefixIsAllUrlAndEventDataIsIntOrLong() { - //setup + fun `get should return all numeric variables on valid event encoded in url format`() { + // setup val testEventData = EventData() testEventData.putInteger("key3", 123) testEventData.putLong("key4", -456L) val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("~all_url") - //verify - assertTrue("get should return all list variables on valid event encoded in url format", - "&key3=123&key4=-456" == result || "&key4=-456&key3=123" == result) + // verify + assertTrue("&key3=123&key4=-456" == result || "&key4=-456&key3=123" == result) } @Test - fun get_ReturnsUrlEncoded_When_KeyPrefixIsAllUrlAndEventDataIsDoubleOrBoolean() { - //setup + fun `get should return all boolean variables on valid event encoded in url format`() { + // setup val testEventData = EventData() testEventData.putBoolean("key2", true) testEventData.putDouble("key5", -123.456) val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("~all_url") - //verify - assertTrue("get should return all list variables on valid event encoded in url format", - "&key2=true&key5=-123.456" == result || "&key5=-123.456&key2=true" == result) + // verify + assertTrue("&key2=true&key5=-123.456" == result || "&key5=-123.456&key2=true" == result) } @Test - fun get_ReturnsUrlEncoded_When_KeyPrefixIsAllUrlAndEventDataIsList() { - //setup + fun `get should return all list variables on valid event encoded in url format`() { + // setup val testEventData = EventData() val stringList: MutableList = ArrayList() stringList.add("String1") stringList.add("String2") testEventData.putStringList("key6", stringList) val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("~all_url") - //verify - assertEquals("get should return all list variables on valid event encoded in url format", - "&key6=String1%2CString2", - result) + // verify + assertEquals("&key6=String1%2CString2", result) } @Test - fun get_ReturnsUrlEncoded_When_KeyPrefixIsAllUrlAndEventDataIsMap() { - //setup + fun `get should return all map variables on valid event encoded in url format`() { + // setup val testEventData = EventData() val stringMap: MutableMap = HashMap() stringMap["innerKey1"] = "inner val1" stringMap["innerKey2"] = "innerVal2" testEventData.putStringMap("key7", stringMap) val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("~all_url") - //verify - assertTrue("get should return all map variables on valid event encoded in url format", - "&key7.innerKey1=inner%20val1&key7.innerKey2=innerVal2" == result || "&key7.innerKey2=innerVal2&key7.innerKey1=inner%20val1" == result) + // verify + assertTrue("&key7.innerKey1=inner%20val1&key7.innerKey2=innerVal2" == result || "&key7.innerKey2=innerVal2&key7.innerKey1=inner%20val1" == result) } @Test - fun get_ReturnsEmptyString_When_KeyPrefixIsAllUrlEventDataIsNull() { - //setup + fun `get should return empty string on event with no event data for url`() { + // setup val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("~all_url") - //verify - assertEquals("get should return empty string on event with no event data", "", result) + // verify + assertEquals("", result) } - /* @Test + /*@Test @Throws(JSONException::class) fun get_ReturnsJson_When_KeyPrefixIsAllJson() { //setup @@ -234,50 +223,46 @@ class LaunchTokenFinderTest: BaseTest() { stringMap["key22"] = "22" testEventData.putStringMap("key8", stringMap) val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) - val launchTokenFinder = LaunchTokenFinder(testEvent!!, configuration!!, - platformServices) + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) //test val result = launchTokenFinder.get("~all_json") val resultObj = JSONObject(result as String) val expectedObj = JSONObject("{\"key1\":\"value1\",\"key2\":true,\"key5\":-123.456,\"key6\":null,\"key3\":123,\"key4\":-456,\"key7\":[\"String1\",\"String2\"],\"key8\":{\"key22\":\"22\"}}") //verify - assertTrue("get should return all variables on valid event encoded in json format", - expectedObj.similar(resultObj)) + assertEquals(expectedObj, resultObj) } */ @Test - fun get_ReturnsEmptyString_When_KeyPrefixIsAllJsonEventDataIsNull() { - //setup + fun `get should return empty string on event with no event data for json`() { + // setup val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("~all_json") - //verify - assertEquals("get should return empty string on event with no event data", "", result) + // verify + assertEquals("", result) } @Test - fun get_ReturnsSharedStateKey_When_KeyPrefixIsState() { - //setup + fun `get should return nested value from shared state of the module on valid event`() { + // setup val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) val lcdata = EventData() val lifecycleSharedState: MutableMap = HashMap() lifecycleSharedState["akey"] = "avalue" lcdata.putStringMap("analytics.contextData", lifecycleSharedState) eventHub.setSharedState("com.adobe.marketing.mobile.Analytics", lcdata) - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("~state.com.adobe.marketing.mobile.Analytics/analytics.contextData.akey") - //verify - assertEquals("get should return shared state of the module on valid event", "avalue", result) + // verify + assertEquals("avalue", result) } @Test - fun get_ReturnsSharedStateList_When_KeyPrefixIsStateAndValueIsList() { - //setup + fun `get should return shared state list of the module on valid event`() { + // setup val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) val lcdata = EventData() val identitySharedState: MutableList = ArrayList() @@ -285,145 +270,135 @@ class LaunchTokenFinderTest: BaseTest() { identitySharedState.add("vid2") lcdata.putStringList("visitoridslist", identitySharedState) eventHub.setSharedState("com.adobe.marketing.mobile.identity", lcdata) - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("~state.com.adobe.marketing.mobile.identity/visitoridslist") - //verify - assertEquals("get should return shared state list of the module on valid event", identitySharedState, result) + // verify + assertEquals(identitySharedState, result) } @Test - fun get_ReturnsSharedStateMap_When_KeyPrefixIsStateAndValueIsMap() { - //setup + fun `get should return shared state of the module on valid event`() { + // setup val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) val lcdata = EventData() val lifecycleSharedState: MutableMap = HashMap() lifecycleSharedState["akey"] = "avalue" lcdata.putStringMap("analytics.contextData", lifecycleSharedState) eventHub.setSharedState("com.adobe.marketing.mobile.Analytics", lcdata) - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("~state.com.adobe.marketing.mobile.Analytics/analytics.contextData") - //verify - assertEquals("get should return shared state of the module on valid event", lifecycleSharedState, result) + // verify + assertEquals(lifecycleSharedState, result) } @Test - fun get_ReturnsNull_When_KeyPrefixIsStateAndMissingSharedStateKeyName() { - //setup + fun `get should return null when key does not have shared state name`() { + // setup val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("~state.") - //verify - assertNull("get should return null when key does not have shared state name", result) + // verify + assertNull(result) } @Test - fun get_ReturnsNull_When_KeyPrefixIsStateAndMissingKeyName() { - //setup + fun `get should return null when key does not have shared state key name`() { + // setup val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("~state.com.adobe.marketing.mobile.Analytics/") - //verify - assertNull("get should return null when key does not have shared state key name", result) + // verify + assertNull(result) } @Test - fun get_ReturnsNull_When_KeyPrefixIsStateAndIncorrectFormat() { - //setup + fun `get should return null when key does not have valid format`() { + // setup val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("~state.com.adobe/.marketing.mobile.Analytics/analytics.contextData.akey") - //verify - assertNull("get should return null when key does not have valid format", result) + // verify + assertNull(result) } @Test - fun get_ReturnsNull_When_KeyPrefixIsStateAndKeyNotExist() { - //setup + fun `get should return null when key does not exist in shared state`() { + // setup val testEventData = EventData() val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("~state.com.adobe.marketing.mobile.Analytics/analytics.contextData.akey") - //verify - assertNull("get should return null when key does not exist in shared state", result) + // verify + assertNull(result) } @Test - fun get_ReturnsEventDataValue_When_KeyIsNotSpecialKey() { - //setup + fun `get should return value of the key from event data on valid event`() { + // setup val testEvent = getDefaultEvent() - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("key1") - //verify - assertEquals("get should return value of the key from event data on valid event", "value1", result) + // verify + assertEquals("value1", result) } @Test - fun get_ReturnsEmptyString_When_KeyIsNotSpecialKeyAndEventDataIsNull() { - //setup + fun `get should return empty string when event data is null on valid event`() { + // setup val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("key1") - //verify - assertEquals("get should return empty string when event data is null on valid event", "", result) + // verify + assertEquals("", result) + } @Test - fun get_ReturnsNull_When_KeyIsNotSpecialKeyAndDoesNotExist() { - //setup + fun `get should return null when key does not exist in event data on valid event`() { + // setup val testEvent = getDefaultEvent() - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("abc") - //verify - assertNull("get should return null when key does not exist in event data on valid event", result) + // verify + assertNull(result) } @Test - fun get_ReturnsNull_When_KeyIsNotSpecialKeyAndValueIsNull() { - //setup + fun `get should return null when value for the key in event data is null on valid event`() { + // setup val testEventData = EventData() testEventData.putNull("key1") val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("key1") - //verify - assertNull("get should return null when value for the key in event data is null on valid event", result) + // verify + assertNull(result) } @Test - fun get_ReturnsList_When_KeyIsNotSpecialKeyAndValueIsList() { - //setup + fun `get should return empty string on list`() { + // setup val testEventData = EventData() val stringList: MutableList = ArrayList() stringList.add("String1") stringList.add("String2") testEventData.putStringList("key6", stringList) val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("key6") - //verify - assertEquals("get should return empty string on list variant", stringList, result) + // verify + assertEquals(stringList, result) } /* @Test @@ -441,36 +416,34 @@ class LaunchTokenFinderTest: BaseTest() { } */ @Test - fun get_ReturnsMap_When_KeyIsNotSpecialKeyAndValueIsMap() { - //setup + fun `get should return map on map`() { + // setup val testEventData = EventData() val stringMap: MutableMap = HashMap() stringMap["innerKey1"] = "inner val1" testEventData.putStringMap("key1", stringMap) val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("key1") - //verify - assertEquals("get should return map on map variant", stringMap, result) + // verify + assertEquals(stringMap, result) } @Test - fun get_ReturnsNestedValue_When_KeyIsFlattenedNestedKey() { - //setup + fun `get should return nested value for valid flattened key on a valid event`() { + // setup val testEventData = EventData() val stringMap: MutableMap = HashMap() stringMap["innerKey1"] = "inner val1" stringMap["innerKey2"] = "innerVal2" testEventData.putStringMap("key7", stringMap) val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) - val launchTokenFinder = LaunchTokenFinder(testEvent, configuration, - platformServices) - //test + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) + // test val result = launchTokenFinder.get("key7.innerKey1") - //verify - assertEquals("get should return nested value for valid flattened key on a valid event", "inner val1", result) + // verify + assertEquals("inner val1", result) } private fun getEvent(type: EventType?, source: EventSource?, eventData: EventData?): Event { @@ -483,4 +456,4 @@ class LaunchTokenFinderTest: BaseTest() { testEventData.putString("key1", "value1") return getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) } -} \ No newline at end of file +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/TestHelper.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/TestHelper.java index 9e6edf064..03ad67c19 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/TestHelper.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/TestHelper.java @@ -18,6 +18,7 @@ import static org.junit.Assert.*; +//TODO Delete this class once all utils and tests are moved to internal package public class TestHelper { /** diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt index a13d5f276..fa88e302e 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt @@ -11,6 +11,9 @@ package com.adobe.marketing.mobile.internal.utility import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue import org.junit.Test class MapExtensionsTests { @@ -120,4 +123,135 @@ class MapExtensionsTests { ) assertEquals(expectedMap, flattenedMap) } + + @Test + @Throws(Exception::class) + fun getFlattenedMap_ReturnsFlattenedMap_WhenEventDataNotNull() { + val map = mapOf( + "boolKey" to "true", + "intKey" to 1, + "longKey" to 100L, + "stringKey" to "stringValue", + "mapStrKey" to mapOf( + "mapKey" to "mapValue" + ) + ) + val flattenedMap = map.getFlattenedDataMap() + val expectedMap = mapOf( + "boolKey" to "true", + "intKey" to 1, + "longKey" to 100L, + "stringKey" to "stringValue", + "mapStrKey" to mapOf( + "mapKey" to "mapValue" + ), + "mapStrKey.mapKey" to "mapValue" + ) + assertEquals(expectedMap, flattenedMap) + } + + @Test + fun testSerializeToQueryString() { + val dict = HashMap() + dict["key1"] = "val1" + dict["key2"] = "val2" + dict["key3"] = "val3" + val valueUnderTest = dict.serializeToQueryString() + assertTrue(valueUnderTest.contains("&key3=val3")) + assertTrue(valueUnderTest.contains("&key2=val2")) + assertTrue(valueUnderTest.contains("&key1=val1")) + } + + @Test + fun testSerializeToQueryStringNullInput() { + val dict = null + val valueUnderTest = dict?.serializeToQueryString() + assertNull(valueUnderTest) + } + + @Test + fun testSerializeToQueryStringNullValueParameter() { + val dict = HashMap() + dict["key1"] = "val1" + dict["key2"] = null + val valueUnderTest = dict.serializeToQueryString() + assertTrue(valueUnderTest.contains("&key1=val1")) + assertFalse(valueUnderTest.contains("&key2=val2")) + } + + @Test + fun testSerializeToQueryStringEmptyKeyParameter() { + val dict = HashMap() + dict["key1"] = "val1" + dict[""] = "val2" + val valueUnderTest = dict.serializeToQueryString() + assertTrue(valueUnderTest.contains("&key1=val1")) + assertFalse(valueUnderTest.contains("&key2=val2")) + } + + @Test + fun testSerializeToQueryStringEmptyValueParameter() { + val dict = HashMap() + dict["key1"] = "val1" + dict["key2"] = "" + val valueUnderTest = dict.serializeToQueryString() + assertTrue(valueUnderTest.contains("&key1=val1")) + assertTrue(valueUnderTest.contains("&key2=")) + } + + @Test + fun testSerializeToQueryStringNonString() { + val dict = HashMap() + dict["key1"] = 5 + val valueUnderTest = dict.serializeToQueryString() + assertEquals("&key1=5", valueUnderTest) + } + + @Test + fun testSerializeToQueryStringArrayList() { + val list = ArrayList() + list.add("TestArrayList1") + list.add("TestArrayList2") + list.add("TestArrayList3") + list.add("TestArrayList4") + val dict = HashMap() + dict["key1"] = list + val valueUnderTest = dict.serializeToQueryString() + assertEquals( + "&key1=TestArrayList1%2CTestArrayList2%2CTestArrayList3%2CTestArrayList4", + valueUnderTest + ) + } + + @Test + fun testSerializeToQueryStringArrayListNullObject() { + val list = ArrayList() + list.add("TestArrayList1") + list.add("TestArrayList2") + list.add(null) + list.add("TestArrayList4") + val dict = HashMap() + dict["key1"] = list + val valueUnderTest = dict.serializeToQueryString() + assertEquals( + "&key1=TestArrayList1%2CTestArrayList2%2Cnull%2CTestArrayList4", + valueUnderTest + ) + } + + @Test + fun testSerializeToQueryStringArrayListEmptyObject() { + val list = ArrayList() + list.add("TestArrayList1") + list.add("TestArrayList2") + list.add("") + list.add("TestArrayList4") + val dict = HashMap() + dict["key1"] = list + val valueUnderTest = dict.serializeToQueryString() + assertEquals( + "&key1=TestArrayList1%2CTestArrayList2%2C%2CTestArrayList4", + valueUnderTest + ) + } } diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/TestHelper.java b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/TestHelper.java new file mode 100644 index 000000000..0d6d7e430 --- /dev/null +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/TestHelper.java @@ -0,0 +1,51 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.internal.utility; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +class TestHelper { + + /** + * Verifies that an utility class is well defined. + * + * @param clazz utility class to verify. + */ + static void assertUtilityClassWellDefined(final Class clazz) throws NoSuchMethodException, InvocationTargetException, + InstantiationException, IllegalAccessException { + assertTrue("Class must be final", Modifier.isFinal(clazz.getModifiers())); + assertEquals("There must be only one constructor", 1, clazz.getDeclaredConstructors().length); + + final Constructor constructor = clazz.getDeclaredConstructor(); + + if (constructor.isAccessible() || !Modifier.isPrivate(constructor.getModifiers())) { + fail("Constructor is not private"); + } + + constructor.setAccessible(true); + constructor.newInstance(); + constructor.setAccessible(false); + + for (final Method method : clazz.getMethods()) { + if (!Modifier.isStatic(method.getModifiers()) && method.getDeclaringClass().equals(clazz)) { + fail("There exists a non-static method:" + method); + } + } + } +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/TimeUtilTest.java b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/TimeUtilTest.java similarity index 97% rename from code/android-core-library/src/test/java/com/adobe/marketing/mobile/TimeUtilTest.java rename to code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/TimeUtilTest.java index b41288d3b..df20f3e4a 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/TimeUtilTest.java +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/TimeUtilTest.java @@ -9,13 +9,12 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile; +package com.adobe.marketing.mobile.internal.utility; import org.junit.Before; import org.junit.Test; import java.util.Date; -import java.util.Locale; import java.util.TimeZone; import static org.junit.Assert.*; From d9f6376d8ab69db713598ebee24b396d33cefd07 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Thu, 12 May 2022 14:27:17 -0700 Subject: [PATCH 046/476] fixed formatting issues --- .../java/com/adobe/marketing/mobile/LaunchTokenFinderTest.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/LaunchTokenFinderTest.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/LaunchTokenFinderTest.kt index 3ebfa47f3..d6cc9cc8d 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/LaunchTokenFinderTest.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/LaunchTokenFinderTest.kt @@ -48,7 +48,7 @@ class LaunchTokenFinderTest : BaseTest() { // test val result = launchTokenFinder.get("~type") // verify - assertEquals( "com.adobe.eventtype.analytics", result) + assertEquals("com.adobe.eventtype.analytics", result) } @Test @@ -358,7 +358,6 @@ class LaunchTokenFinderTest : BaseTest() { val result = launchTokenFinder.get("key1") // verify assertEquals("", result) - } @Test From 3e03ba5779c84989ef91932021ec0527e50f41f4 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Fri, 13 May 2022 14:54:57 -0700 Subject: [PATCH 047/476] define constants inside companion object --- .../launch/rulesengine/LaunchTokenFinder.kt | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt index a82feb204..786508fb4 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt @@ -25,20 +25,22 @@ import org.json.JSONObject internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionApi) { - private val LOG_TAG = "LaunchTokenFinder" - private val KEY_EVENT_TYPE = "~type" - private val KEY_EVENT_SOURCE = "~source" - private val KEY_TIMESTAMP_UNIX = "~timestampu" - private val KEY_TIMESTAMP_ISO8601 = "~timestampz" - private val KEY_TIMESTAMP_PLATFORM = "~timestampp" - private val KEY_SDK_VERSION = "~sdkver" - private val KEY_CACHEBUST = "~cachebust" - private val KEY_ALL_URL = "~all_url" - private val KEY_ALL_JSON = "~all_json" - private val KEY_SHARED_STATE = "~state." - private val EMPTY_STRING = "" - private val RANDOM_INT_BOUNDARY = 100000000 - private val SHARED_STATE_KEY_DELIMITER = "/" + companion object { + private const val LOG_TAG = "LaunchTokenFinder" + private const val KEY_EVENT_TYPE = "~type" + private const val KEY_EVENT_SOURCE = "~source" + private const val KEY_TIMESTAMP_UNIX = "~timestampu" + private const val KEY_TIMESTAMP_ISO8601 = "~timestampz" + private const val KEY_TIMESTAMP_PLATFORM = "~timestampp" + private const val KEY_SDK_VERSION = "~sdkver" + private const val KEY_CACHEBUST = "~cachebust" + private const val KEY_ALL_URL = "~all_url" + private const val KEY_ALL_JSON = "~all_json" + private const val KEY_SHARED_STATE = "~state." + private const val EMPTY_STRING = "" + private const val RANDOM_INT_BOUNDARY = 100000000 + private const val SHARED_STATE_KEY_DELIMITER = "/" + } // ======================================================== // public methods From c38e6832890374681f455af8e8cad9db0080cbc7 Mon Sep 17 00:00:00 2001 From: Yansong Yang Date: Mon, 16 May 2022 12:34:10 -0600 Subject: [PATCH 048/476] Add Signal extension to the root project (#71) --- .gitignore | 1 + Makefile | 1 + code/android-signal-library/build.gradle | 75 ++++++ code/android-signal-library/dummy | 0 .../signal/ExampleInstrumentedTest.java | 37 +++ .../src/main/AndroidManifest.xml | 2 + ...nerConfigurationResponseContentSignal.java | 28 ++ ...tenerRulesEngineResponseContentSignal.java | 62 +++++ .../marketing/mobile/SignalConstants.java | 48 ++++ .../adobe/marketing/mobile/SignalCore.java | 58 ++++ .../marketing/mobile/SignalExtension.java | 212 +++++++++++++++ .../com/adobe/marketing/mobile/SignalHit.java | 31 +++ .../marketing/mobile/SignalHitSchema.java | 98 +++++++ .../marketing/mobile/SignalHitsDatabase.java | 151 +++++++++++ .../marketing/mobile/SignalTemplate.java | 139 ++++++++++ .../src/main/res/values/strings.xml | 3 + .../com/adobe/marketing/mobile/Signal.java | 43 +++ .../marketing/mobile/SignalModuleDetails.java | 30 +++ .../adobe/marketing/mobile/MockSignal.java | 46 ++++ .../mobile/MockSignalHitsDatabase.java | 34 +++ .../adobe/marketing/mobile/SignalCoreAPI.java | 40 +++ .../marketing/mobile/SignalHitSchemaTest.java | 54 ++++ .../mobile/SignalHitsDatabaseTest.java | 192 ++++++++++++++ ...enerConfigurationResponseContentTests.java | 55 ++++ ...stenerRulesEngineResponseContentTests.java | 74 ++++++ .../marketing/mobile/SignalModuleTest.java | 59 ++++ .../marketing/mobile/SignalTemplateTest.java | 251 ++++++++++++++++++ .../adobe/marketing/mobile/SignalTest.java | 230 ++++++++++++++++ code/settings.gradle | 2 +- 29 files changed, 2055 insertions(+), 1 deletion(-) create mode 100644 code/android-signal-library/build.gradle delete mode 100644 code/android-signal-library/dummy create mode 100644 code/android-signal-library/src/androidTest/java/com/adobe/marketing/mobile/signal/ExampleInstrumentedTest.java create mode 100644 code/android-signal-library/src/main/AndroidManifest.xml create mode 100644 code/android-signal-library/src/main/java/com/adobe/marketing/mobile/ListenerConfigurationResponseContentSignal.java create mode 100644 code/android-signal-library/src/main/java/com/adobe/marketing/mobile/ListenerRulesEngineResponseContentSignal.java create mode 100644 code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalConstants.java create mode 100644 code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalCore.java create mode 100644 code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalExtension.java create mode 100644 code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHit.java create mode 100644 code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHitSchema.java create mode 100644 code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHitsDatabase.java create mode 100644 code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalTemplate.java create mode 100644 code/android-signal-library/src/main/res/values/strings.xml create mode 100644 code/android-signal-library/src/phone/java/com/adobe/marketing/mobile/Signal.java create mode 100644 code/android-signal-library/src/phone/java/com/adobe/marketing/mobile/SignalModuleDetails.java create mode 100644 code/android-signal-library/src/test/java/com/adobe/marketing/mobile/MockSignal.java create mode 100644 code/android-signal-library/src/test/java/com/adobe/marketing/mobile/MockSignalHitsDatabase.java create mode 100644 code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalCoreAPI.java create mode 100644 code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalHitSchemaTest.java create mode 100644 code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalHitsDatabaseTest.java create mode 100644 code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalListenerConfigurationResponseContentTests.java create mode 100644 code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalListenerRulesEngineResponseContentTests.java create mode 100644 code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalModuleTest.java create mode 100644 code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalTemplateTest.java create mode 100644 code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalTest.java diff --git a/.gitignore b/.gitignore index 9fe199f71..e514f4766 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,4 @@ lint/tmp/ # Ignore Gradle build output directory build +jacoco.exec diff --git a/Makefile b/Makefile index 0509a88d4..964e1889a 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,7 @@ assemble-phone-release: unit-test: (./code/gradlew -p code/android-core-library testPhoneDebugUnitTest) + (./code/gradlew -p code/android-signal-library testPhoneDebugUnitTest) functional-test: (./code/gradlew -p code/android-core-library uninstallPhoneDebugAndroidTest) diff --git a/code/android-signal-library/build.gradle b/code/android-signal-library/build.gradle new file mode 100644 index 000000000..97bb7cdcf --- /dev/null +++ b/code/android-signal-library/build.gradle @@ -0,0 +1,75 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'org.jlleitschuh.gradle.ktlint' +apply plugin: 'org.jetbrains.dokka' + +android { + compileSdkVersion 30 + + defaultConfig { + minSdkVersion 19 + //noinspection OldTargetApi + targetSdkVersion 30 + //Include the Proguard rules for Core Extension in the aar + consumerProguardFiles 'lib-proguard-rules.pro' + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + } + + sourceSets { + } + + testOptions { + unitTests.returnDefaultValues = true + unitTests.includeAndroidResources = true + } + + flavorDimensions "target" + + productFlavors { + phone { + dimension "target" + } + } + + buildTypes { + debug { + testCoverageEnabled true + debuggable true + } + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } +} + +dependencies { + //noinspection GradleCompatible + implementation 'androidx.appcompat:appcompat:1.0.0' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation project(path: ':android-core-library') + // unit tests + testImplementation "junit:junit:4.13.2" + testImplementation project(path: ':android-signal-library') + //noinspection GradleDependency + testImplementation "org.mockito:mockito-core:2.22.0" + testImplementation 'org.powermock:powermock-api-mockito2:2.0.0' + testImplementation 'org.powermock:powermock-module-junit4:2.0.0' + testImplementation 'commons-codec:commons-codec:1.15' + testImplementation 'org.robolectric:robolectric:3.6.2' + //noinspection GradleDependency + testImplementation 'org.json:json:20160810' + testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" + // instrumentation tests + androidTestImplementation 'androidx.test:rules:1.1.1' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' +} \ No newline at end of file diff --git a/code/android-signal-library/dummy b/code/android-signal-library/dummy deleted file mode 100644 index e69de29bb..000000000 diff --git a/code/android-signal-library/src/androidTest/java/com/adobe/marketing/mobile/signal/ExampleInstrumentedTest.java b/code/android-signal-library/src/androidTest/java/com/adobe/marketing/mobile/signal/ExampleInstrumentedTest.java new file mode 100644 index 000000000..4a852c7f0 --- /dev/null +++ b/code/android-signal-library/src/androidTest/java/com/adobe/marketing/mobile/signal/ExampleInstrumentedTest.java @@ -0,0 +1,37 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.signal; + +import static org.junit.Assert.assertEquals; + +import android.content.Context; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + assertEquals("com.adobe.marketing.mobile.signal.test", appContext.getPackageName()); + } +} diff --git a/code/android-signal-library/src/main/AndroidManifest.xml b/code/android-signal-library/src/main/AndroidManifest.xml new file mode 100644 index 000000000..ea07a6460 --- /dev/null +++ b/code/android-signal-library/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/ListenerConfigurationResponseContentSignal.java b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/ListenerConfigurationResponseContentSignal.java new file mode 100644 index 000000000..9bdaf92ba --- /dev/null +++ b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/ListenerConfigurationResponseContentSignal.java @@ -0,0 +1,28 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile; + +/** + * Listens for {@code EventType.CONFIGURATION} - {@code EventSource.RESPONSE_CONTENT} {@code Event}. + */ +class ListenerConfigurationResponseContentSignal extends ModuleEventListener { + + ListenerConfigurationResponseContentSignal(final SignalExtension module, final EventType type, + final EventSource source) { + super(module, type, source); + } + + public void hear(final Event e) { + parentModule.updatePrivacyStatus(MobilePrivacyStatus.fromString(e.getData().optString( + SignalConstants.EventDataKeys.Configuration.GLOBAL_CONFIG_PRIVACY, ""))); + } +} diff --git a/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/ListenerRulesEngineResponseContentSignal.java b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/ListenerRulesEngineResponseContentSignal.java new file mode 100644 index 000000000..12a5f477d --- /dev/null +++ b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/ListenerRulesEngineResponseContentSignal.java @@ -0,0 +1,62 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile; + +import java.util.Map; + +/** + * Listens for {@code EventType.RULES_ENGINE} - {@code EventSource.RESPONSE_CONTENT} {@code Event}. + */ +class ListenerRulesEngineResponseContentSignal extends ModuleEventListener { + + private static final String LOGTAG = "ListenerRulesEngineResponseContentSignal"; + + ListenerRulesEngineResponseContentSignal(final SignalExtension module, final EventType type, final EventSource source) { + super(module, type, source); + } + + public void hear(final Event event) { + final EventData eventData = event == null ? null : event.getData(); + + if (eventData == null) { + Log.debug(LOGTAG, "%s (Event Data)", Log.UNEXPECTED_NULL_VALUE); + return; + } + + final Map triggeredConsequence = eventData.optVariantMap( + SignalConstants.EventDataKeys.RuleEngine.CONSEQUENCE_TRIGGERED, null); + + if (triggeredConsequence == null || triggeredConsequence.isEmpty()) { + Log.debug(LOGTAG, "Not a triggered rule. Return."); + return; + } + + //type + final String consequenceType = Variant.optVariantFromMap(triggeredConsequence, + SignalConstants.EventDataKeys.RuleEngine.RULES_RESPONSE_CONSEQUENCE_KEY_TYPE).optString(null); + + if (StringUtils.isNullOrEmpty(consequenceType)) { + Log.debug(LOGTAG, "Triggered rule is not Signal type. Return."); + return; + } + + if (SignalConstants.EventDataKeys.Signal.RULES_RESPONSE_CONSEQUENCE_TYPE_POSTBACKS.equals(consequenceType) + || SignalConstants.EventDataKeys.Signal.RULES_RESPONSE_CONSEQUENCE_TYPE_PII.equals(consequenceType)) { + parentModule.handleSignalConsequenceEvent(event); + } else if (SignalConstants.EventDataKeys.Signal.RULES_RESPONSE_CONSEQUENCE_TYPE_OPEN_URL.equals(consequenceType)) { + parentModule.handleOpenURLConsequenceEvent(event); + } else { + Log.debug(LOGTAG, "Triggered rule is not a valid Signal type. Cannot handle."); + } + + } +} diff --git a/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalConstants.java b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalConstants.java new file mode 100644 index 000000000..3a3a1d67a --- /dev/null +++ b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalConstants.java @@ -0,0 +1,48 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile; + +class SignalConstants { + private SignalConstants() {} + + static final class EventDataKeys { + + private EventDataKeys() {} + + static final class Configuration { + static final String MODULE_NAME = "com.adobe.module.configuration"; + static final String GLOBAL_CONFIG_PRIVACY = "global.privacy"; + + private Configuration() {} + } + + static final class RuleEngine { + static final String RULES_RESPONSE_CONSEQUENCE_KEY_TYPE = "type"; + static final String RULES_RESPONSE_CONSEQUENCE_KEY_ID = "id"; + static final String RULES_RESPONSE_CONSEQUENCE_KEY_DETAIL = "detail"; + static final String CONSEQUENCE_TRIGGERED = "triggeredconsequence"; + + private RuleEngine() {} + } + + static final class Signal { + static final String MODULE_NAME = "com.adobe.module.signal"; + static final String RULES_RESPONSE_CONSEQUENCE_TYPE_POSTBACKS = "pb"; + static final String RULES_RESPONSE_CONSEQUENCE_TYPE_PII = "pii"; + static final String RULES_RESPONSE_CONSEQUENCE_TYPE_OPEN_URL = "url"; + static final String RULES_RESPONSE_CONTENT_OPENURL_URLS = "url"; + static final String SIGNAL_CONTEXT_DATA = "contextdata"; + + private Signal() {} + } + } +} diff --git a/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalCore.java b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalCore.java new file mode 100644 index 000000000..9d8594388 --- /dev/null +++ b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalCore.java @@ -0,0 +1,58 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile; + +import java.util.Map; + +/** + * Signal Core code + */ +class SignalCore { + + private static final String LOG_TAG = SignalCore.class.getSimpleName(); + + EventHub eventHub; + + SignalCore(final EventHub eventHub, final ModuleDetails moduleDetails) { + if (eventHub == null) { + Log.debug(LOG_TAG, "%s (EventHub) while initializing Signal Core", Log.UNEXPECTED_NULL_VALUE); + return; + } + + this.eventHub = eventHub; + + try { + eventHub.registerModule(SignalExtension.class, moduleDetails); + Log.trace(LOG_TAG, "Registered %s extension", SignalExtension.class.getSimpleName()); + } catch (InvalidModuleException e) { + Log.debug(LOG_TAG, "Failed to register %s module (%s)", SignalExtension.class.getSimpleName(), e); + } + } + + /** + * Create collect PII event, which is listened by Rules Engine module to determine if the data matches any PII request. + * + * @param data the PII data to be collected, which will be used in Rules Engine comparison and request token replacement. + */ + void collectPii(final Map data) { + if (data == null || data.isEmpty()) { + Log.debug(LOG_TAG, "Could not trigger PII, the data is null or empty."); + return; + } + + final EventData eventData = new EventData() + .putStringMap(SignalConstants.EventDataKeys.Signal.SIGNAL_CONTEXT_DATA, data); + eventHub.dispatch(new Event.Builder("CollectPII", EventType.SIGNAL, + EventSource.REQUEST_CONTENT).setData(eventData).build()); + } + + +} diff --git a/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalExtension.java b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalExtension.java new file mode 100644 index 000000000..51cd43ea1 --- /dev/null +++ b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalExtension.java @@ -0,0 +1,212 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile; + +import java.util.Map; +import java.util.concurrent.ConcurrentLinkedQueue; + +class SignalExtension extends InternalModule { + + private static final String LOGTAG = "SignalExtension"; + private final ConcurrentLinkedQueue unprocessedEvents; + private SignalHitsDatabase signalHitsDatabase; + + /** + * Constructor for an internal module, must be called by inheritors. + * + * @param hub com.adobe.marketing.mobile.EventHub instance of eventhub that owns this module + * @param services PlatformServices instance + */ + SignalExtension(final EventHub hub, final PlatformServices services) { + super(SignalConstants.EventDataKeys.Signal.MODULE_NAME, hub, services); + registerListener(EventType.RULES_ENGINE, EventSource.RESPONSE_CONTENT, ListenerRulesEngineResponseContentSignal.class); + registerListener(EventType.CONFIGURATION, EventSource.RESPONSE_CONTENT, + ListenerConfigurationResponseContentSignal.class); + this.signalHitsDatabase = new SignalHitsDatabase(services); + this.unprocessedEvents = new ConcurrentLinkedQueue(); + } + + /** + * Constructor for testing purposes. + * + * @param hub com.adobe.marketing.mobile.EventHub instance of eventhub that owns this module + * @param services PlatformServices instance + * @param database SignalHitsDatabase instance + */ + SignalExtension(final EventHub hub, final PlatformServices services, final SignalHitsDatabase database) { + this(hub, services); + this.signalHitsDatabase = database; + } + + /** + * queue the signal event (postback or pii), triggered by Rules Engine, and try to process the events in queue. + * + * @param event original signal event{@code event} containing consequence data. + */ + void handleSignalConsequenceEvent(final Event event) { + getExecutor().execute(new Runnable() { + @Override + public void run() { + Log.trace(LOGTAG, "Handling signal consequence event, number: %s", event.getEventNumber()); + unprocessedEvents.add(event); + tryProcessQueuedEvent(); + } + }); + } + + /** + * Removes the URL from the {@code event}'s {@code EventData} and passes them to the {@code UIService} to open. + *

+ * Calling this method does nothing if there is no {@value SignalConstants.EventDataKeys.Signal#RULES_RESPONSE_CONTENT_OPENURL_URLS} + * key in the {@link EventData}, or if the {@link UIService} is unavailable. + * + * @param event {@link Event} containing the URLs to process + */ + void handleOpenURLConsequenceEvent(final Event event) { + getExecutor().execute(new Runnable() { + @SuppressWarnings("unchecked") + @Override + public void run() { + final EventData eventData = event == null ? null : event.getData(); + + if (eventData == null) { + return; + } + + Log.trace(LOGTAG, "Handling signal open url consequence event, number: %s", event.getEventNumber()); + + final Map signalConsequence = eventData.optVariantMap( + SignalConstants.EventDataKeys.RuleEngine.CONSEQUENCE_TRIGGERED, + null); + + if (signalConsequence == null || signalConsequence.isEmpty()) { + Log.debug(LOGTAG, "Null or empty signal consequence. Return"); + return; + } + + + Map consequenceDetail = Variant.optVariantFromMap(signalConsequence, + SignalConstants.EventDataKeys.RuleEngine.RULES_RESPONSE_CONSEQUENCE_KEY_DETAIL).optVariantMap(null); + + if (consequenceDetail == null || consequenceDetail.isEmpty()) { + Log.debug(LOGTAG, "Null or empty signal consequence detail. Return"); + return; + } + + final String url = Variant.optVariantFromMap(consequenceDetail, + SignalConstants.EventDataKeys.Signal.RULES_RESPONSE_CONTENT_OPENURL_URLS).optString(""); + + if (StringUtils.isNullOrEmpty(url)) { + Log.debug(LOGTAG, "Tried to process an OpenURL event, but no URL were found in EventData."); + return; + } + + if (getPlatformServices() == null) { + Log.debug(LOGTAG, "%s (Platform Services), Unable to process an OpenURL event.", Log.UNEXPECTED_NULL_VALUE); + return; + } + + final UIService uiService = getPlatformServices().getUIService(); + + if (uiService == null) { + Log.debug(LOGTAG, "%s (UIService), Unable to process OpenURL event.", Log.UNEXPECTED_NULL_VALUE); + return; + } + + uiService.showUrl(url); + } + }); + } + + /** + * Clear the queue if opt-out, and pass the status to database. + * + * @param privacyStatus the new privacy status + */ + void updatePrivacyStatus(final MobilePrivacyStatus privacyStatus) { + getExecutor().execute(new Runnable() { + @Override + public void run() { + if (privacyStatus == MobilePrivacyStatus.OPT_OUT) { + unprocessedEvents.clear(); + } + + signalHitsDatabase.updatePrivacyStatus(privacyStatus); + tryProcessQueuedEvent(); + } + }); + } + + /** + * try to process the event in queue + */ + void tryProcessQueuedEvent() { + while (!unprocessedEvents.isEmpty()) { + final Event currentEvent = unprocessedEvents.peek(); + boolean isEventProcessed = processSignalEvent(currentEvent); + + if (isEventProcessed) { + unprocessedEvents.poll(); + } else { + break; + } + } + } + + /** + * check if configuration shared state is ready, if so go ahead and process the signal event. Otherwise just return false. + * + * @param event the signal event + * @return true if the event has been processed, otherwise false. + */ + boolean processSignalEvent(final Event event) { + EventData configurationSharedState = getSharedEventState(SignalConstants.EventDataKeys.Configuration.MODULE_NAME, + event); + + //configuration + if (configurationSharedState == EventHub.SHARED_STATE_PENDING) { + Log.debug(LOGTAG, "Can not handle signal consequence. Shared state for Configuration module is not ready."); + return false; + } + + + final MobilePrivacyStatus privacyStatus = MobilePrivacyStatus.fromString( + configurationSharedState.optString(SignalConstants.EventDataKeys.Configuration.GLOBAL_CONFIG_PRIVACY, + MobilePrivacyStatus.UNKNOWN.getValue())); + + if (privacyStatus == MobilePrivacyStatus.OPT_OUT) { + Log.debug(LOGTAG, "Privacy status is OPT OUT. Signal processed without queuing the hit."); + return true; + } + + // all good to go. + final EventData eventData = event == null ? null : event.getData(); + + if (eventData == null) { + return true; + } + + final Map signalConsequence = eventData.optVariantMap( + SignalConstants.EventDataKeys.RuleEngine.CONSEQUENCE_TRIGGERED, null); + + if (signalConsequence == null || signalConsequence.isEmpty()) { + Log.debug(LOGTAG, "Null or empty signal consequence. Return"); + } else { + SignalTemplate signalTemplate = SignalTemplate.createSignalTemplateFromConsequence(signalConsequence); + + if (signalTemplate != null) { + signalHitsDatabase.queue(signalTemplate.getSignalHit(), event.getTimestamp(), privacyStatus); + } + } + + return true; + } +} diff --git a/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHit.java b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHit.java new file mode 100644 index 000000000..f55528979 --- /dev/null +++ b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHit.java @@ -0,0 +1,31 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile; + +/* + Define the fields for signal request + */ +class SignalHit extends AbstractHit { + String url; + String body; + String contentType; + int timeout; + + /** + * Determine the Http command based off the request body + * + * @return HttpCommand.POST if the body has content, otherwise HttpCommand.GET + */ + NetworkService.HttpCommand getHttpCommand() { + return StringUtils.isNullOrEmpty(body) ? NetworkService.HttpCommand.GET : NetworkService.HttpCommand.POST; + } +} diff --git a/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHitSchema.java b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHitSchema.java new file mode 100644 index 000000000..ff6da3ede --- /dev/null +++ b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHitSchema.java @@ -0,0 +1,98 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + Define the database schema for signal request + */ +class SignalHitSchema extends AbstractHitSchema { + private static final String LOG_TAG = "SignalHitType"; + + private static final String HIT_ID_COL_NAME = "ID"; + private static final int HIT_ID_COL_INDEX = 0; + private static final String HIT_URL_COL_NAME = "URL"; + private static final int HIT_URL_COL_INDEX = 1; + private static final String HIT_TIMESTAMP_COL_NAME = "TIMESTAMP"; + private static final int HIT_TIMESTAMP_COL_INDEX = 2; + private static final String HIT_POSTBODY_COL_NAME = "POSTBODY"; + private static final int HIT_POSTBODY_COL_INDEX = 3; + private static final String HIT_CONTENTTYPE_COL_NAME = "CONTENTTYPE"; + private static final int HIT_CONTENTTYPE_COL_INDEX = 4; + private static final String HIT_TIMEOUT_COL_NAME = "TIMEOUT"; + private static final int HIT_TIMEOUT_COL_INDEX = 5; + + SignalHitSchema() { + this.columnConstraints = + new ArrayList>(); + List idColumnConstraints = + new ArrayList(); + idColumnConstraints.add(DatabaseService.Database.ColumnConstraint.PRIMARY_KEY); + idColumnConstraints.add(DatabaseService.Database.ColumnConstraint.AUTOINCREMENT); + this.columnConstraints.add(idColumnConstraints); + this.columnConstraints.add(new ArrayList()); + this.columnConstraints.add(new ArrayList()); + this.columnConstraints.add(new ArrayList()); + this.columnConstraints.add(new ArrayList()); + this.columnConstraints.add(new ArrayList()); + + this.columnNames = new String[] {HIT_ID_COL_NAME, HIT_URL_COL_NAME, HIT_TIMESTAMP_COL_NAME + , HIT_POSTBODY_COL_NAME, HIT_CONTENTTYPE_COL_NAME, HIT_TIMEOUT_COL_NAME + }; + + this.columnDataTypes = new DatabaseService.Database.ColumnDataType[] { + DatabaseService.Database.ColumnDataType.INTEGER, + DatabaseService.Database.ColumnDataType.TEXT, + DatabaseService.Database.ColumnDataType.INTEGER, + DatabaseService.Database.ColumnDataType.TEXT, + DatabaseService.Database.ColumnDataType.TEXT, + DatabaseService.Database.ColumnDataType.INTEGER, + }; + } + + @Override + SignalHit generateHit(final DatabaseService.QueryResult queryResult) { + try { + + SignalHit signalHit = new SignalHit(); + signalHit.identifier = queryResult.getString(HIT_ID_COL_INDEX); + signalHit.url = queryResult.getString(HIT_URL_COL_INDEX); + signalHit.timestamp = queryResult.getLong(HIT_TIMESTAMP_COL_INDEX); + signalHit.body = queryResult.getString(HIT_POSTBODY_COL_INDEX); + signalHit.contentType = queryResult.getString(HIT_CONTENTTYPE_COL_INDEX); + signalHit.timeout = queryResult.getInt(HIT_TIMEOUT_COL_INDEX); + return signalHit; + } catch (Exception e) { + Log.error(LOG_TAG, "Unable to read from database. Query failed with error %s", e); + return null; + } finally { + if (queryResult != null) { + queryResult.close(); + } + } + } + + @Override + Map generateDataMap(final SignalHit hit) { + Map dataMap = new HashMap(); + dataMap.put(HIT_URL_COL_NAME, hit.url); + dataMap.put(HIT_TIMESTAMP_COL_NAME, hit.timestamp); + dataMap.put(HIT_POSTBODY_COL_NAME, hit.body); + dataMap.put(HIT_CONTENTTYPE_COL_NAME, hit.contentType); + dataMap.put(HIT_TIMEOUT_COL_NAME, hit.timeout); + return dataMap; + } + +} diff --git a/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHitsDatabase.java b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHitsDatabase.java new file mode 100644 index 000000000..aed954416 --- /dev/null +++ b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHitsDatabase.java @@ -0,0 +1,151 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile; + +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * The database queue for signal requests + */ +class SignalHitsDatabase implements HitQueue.IHitProcessor { + + private static final String LOG_TAG = "SignalHitsDatabase"; + private static final String SIGNAL_FILENAME = "ADBMobileSignalDataCache.sqlite"; + private static final String SIGNAL_TABLE_NAME = "HITS"; + private final NetworkService networkService; + private final SystemInfoService systemInfoService; + private final HitQueue hitQueue; + private final static int HTTP_SUCCESS_RESPONSE_CODE_LOWER_LIMIT = 200; + private final static int HTTP_SUCCESS_RESPONSE_CODE_UPPER_LIMIT = 299; + + /** + * Default Constructor + * @param services PlatformServices + */ + SignalHitsDatabase(final PlatformServices services) { + this(services, null); + } + + /** + * Constructor for test + * @param services PlatformServices + * @param hitQueue HitQueue for test + */ + SignalHitsDatabase(final PlatformServices services, final HitQueue hitQueue) { + + this.networkService = services.getNetworkService(); + this.systemInfoService = services.getSystemInfoService(); + + if (hitQueue != null) { // for unit test + this.hitQueue = hitQueue; + } else { + final File directory = systemInfoService != null ? systemInfoService.getApplicationCacheDir() : null; + final File dbFilePath = new File(directory, SIGNAL_FILENAME); + this.hitQueue = new HitQueue(services, dbFilePath, + SIGNAL_TABLE_NAME, new SignalHitSchema(), this); + } + } + + @Override + public HitQueue.RetryType process(final SignalHit hit) { + HitQueue.RetryType retryType = HitQueue.RetryType.NO; + + try { + byte[] outputBytes = null; + + if (hit.body != null) { + outputBytes = hit.body.getBytes(StringUtils.CHARSET_UTF_8); + } + + Map headers = NetworkConnectionUtil.getHeaders(false, hit.contentType); + NetworkService.HttpConnection connection = networkService.connectUrl(hit.url, hit.getHttpCommand(), outputBytes, + headers, hit.timeout, hit.timeout); + + if (connection == null) { + Log.warning(LOG_TAG, "Could not process a request because it was invalid. Discarding request"); + return retryType; + } + + if ((connection.getResponseCode() >= HTTP_SUCCESS_RESPONSE_CODE_LOWER_LIMIT) + && (connection.getResponseCode() <= HTTP_SUCCESS_RESPONSE_CODE_UPPER_LIMIT)) { + + try { + // have to read the stream in order to keep the SSL connection alive + NetworkConnectionUtil.readFromInputStream(connection.getInputStream()); + } catch (IOException e) { + //do nothing + } + + Log.debug(LOG_TAG, "Request (%s)was sent", hit.url); + } else if (!NetworkConnectionUtil.recoverableNetworkErrorCodes.contains(connection.getResponseCode())) { + Log.debug(LOG_TAG, "Un-recoverable network error: (%s) while processing requests. Discarding request.", + connection.getResponseCode()); + } else { + Log.debug(LOG_TAG, "Recoverable network error: (%s) while processing requests, will retry.", + connection.getResponseCode()); + retryType = HitQueue.RetryType.YES; + } + + connection.close(); + + } catch (UnsupportedEncodingException e) { + Log.debug(LOG_TAG, "Unable to encode the post body (%s) for the signal request, %s", hit.body, e); + } + + return retryType; + } + + /** + * update the privacy status. Resume the queue when optin. Clear the queue when optout. Suspend the queue when unkonwn. + * + * @param privacyStatus the new privacy status + */ + void updatePrivacyStatus(final MobilePrivacyStatus privacyStatus) { + switch (privacyStatus) { + case OPT_IN: + this.hitQueue.bringOnline(); + break; + + case OPT_OUT: + this.hitQueue.suspend(); + this.hitQueue.deleteAllHits(); + break; + + case UNKNOWN: + this.hitQueue.suspend(); + break; + } + } + + /** + * Add a signal hit to the queue + * @param signalHit the signal hit + * @param timestampMillis event timestamp to be associated with the signal hit + * @param privacyStatus the current privacy status + */ + void queue(final SignalHit signalHit, final long timestampMillis, final MobilePrivacyStatus privacyStatus) { + if (signalHit != null) { + signalHit.timestamp = TimeUnit.MILLISECONDS.toSeconds(timestampMillis); + } + + this.hitQueue.queue(signalHit); + + if (privacyStatus == MobilePrivacyStatus.OPT_IN) { + this.hitQueue.bringOnline(); + } + } + +} diff --git a/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalTemplate.java b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalTemplate.java new file mode 100644 index 000000000..105bb826d --- /dev/null +++ b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalTemplate.java @@ -0,0 +1,139 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; + +/** + * Signal template. Each signal consequence as received in Rules Response event will be converted to a signal template. + * This class also generates a signal hit object which can be saved int database. + */ +class SignalTemplate { + + private static final String LOGTAG = SignalTemplate.class.getSimpleName(); + private static final String ADB_TEMPLATE_CALLBACK_URL = "templateurl"; + private static final String ADB_TEMPLATE_CALLBACK_BODY = "templatebody"; + private static final String ADB_TEMPLATE_CALLBACK_CONTENT_TYPE = "contenttype"; + private static final String ADB_TEMPLATE_CALLBACK_TIMEOUT = "timeout"; + private static final int ADB_TEMPLATE_TIMEOUT_DEFAULT = 2; + + private String signalId; + private String urlTemplate; + private String bodyTemplate; + private String contentType; + private int timeout; + + /** + * create a new signal hit. + * @return a new signal hit object + */ + SignalHit getSignalHit() { + SignalHit signalHit = new SignalHit(); + signalHit.url = this.urlTemplate; + signalHit.body = this.bodyTemplate; + signalHit.contentType = this.contentType; + signalHit.timeout = this.timeout; + + return signalHit; + } + + /** + * return the id of the signal template + * @return the id of the signal template + */ + String getSignalTemplateId() { + return this.signalId; + } + + /** + * A static method to create the signal template from consequence object which is a Map of String,Object. + * @param signalConsequence the signal detail String,String Map + * @return the new SignalTemplate object converted from input consequence map. + */ + static SignalTemplate createSignalTemplateFromConsequence(final Map signalConsequence) { + + if (signalConsequence == null || signalConsequence.isEmpty()) { + return null; + } + + // id + final String consequenceId = Variant.optVariantFromMap(signalConsequence, + SignalConstants.EventDataKeys.RuleEngine.RULES_RESPONSE_CONSEQUENCE_KEY_ID).optString(null); + + if (StringUtils.isNullOrEmpty(consequenceId)) { + Log.debug(LOGTAG, "Triggered rule does not have ID. Return."); + return null; + } + + //detail + Map consequenceDetail = Variant.optVariantFromMap(signalConsequence, + SignalConstants.EventDataKeys.RuleEngine.RULES_RESPONSE_CONSEQUENCE_KEY_DETAIL).optVariantMap(null); + + if (consequenceDetail == null || consequenceDetail.isEmpty()) { + Log.debug(LOGTAG, "No detail found for the consequence with id %s", consequenceId); + return null; + } + + final String signalTemplateUrl = Variant.optVariantFromMap(consequenceDetail, + ADB_TEMPLATE_CALLBACK_URL).optString(null); + + // Per requirements, if this is a pii signal, url must be https. + String consequenceType = Variant.optVariantFromMap(signalConsequence, + SignalConstants.EventDataKeys.RuleEngine.RULES_RESPONSE_CONSEQUENCE_KEY_TYPE).optString(null); + + if (StringUtils.isNullOrEmpty(signalTemplateUrl) || !isValidTemplateUrl(signalTemplateUrl, consequenceType)) { + Log.warning(LOGTAG, "Unable to create signal template, \"templateUrl\" is invalid \n"); + return null; + } + + // all good to go! + final SignalTemplate signalTemplate = new SignalTemplate(); + + signalTemplate.signalId = consequenceId; + signalTemplate.urlTemplate = signalTemplateUrl; + signalTemplate.timeout = Variant.optVariantFromMap(consequenceDetail, + ADB_TEMPLATE_CALLBACK_TIMEOUT).optInteger(ADB_TEMPLATE_TIMEOUT_DEFAULT); + signalTemplate.bodyTemplate = Variant.optVariantFromMap(consequenceDetail, ADB_TEMPLATE_CALLBACK_BODY).optString(""); + + if (!StringUtils.isNullOrEmpty(signalTemplate.bodyTemplate)) { + signalTemplate.contentType = Variant.optVariantFromMap(consequenceDetail, + ADB_TEMPLATE_CALLBACK_CONTENT_TYPE).optString(""); + } + + return signalTemplate; + } + + /** + * A static method to evaluate if the template url is a valid url for the given consequence type. + * + * @param url The URL to check for + * @param type The {@link SignalConstants.EventDataKeys.RuleEngine#RULES_RESPONSE_CONSEQUENCE_KEY_TYPE} from the {@code RulesEngine} event + * @return false if the input url is invalid OR if it is a non HTTPS url for pii type consequence, true otherwise. + * + */ + static boolean isValidTemplateUrl(final String url, final String type) { + + try { + URL templateUrl = new URL(url); + + if (SignalConstants.EventDataKeys.Signal.RULES_RESPONSE_CONSEQUENCE_TYPE_PII.equals(type) + && !("https".equalsIgnoreCase(templateUrl.getProtocol()))) { + return false; + } + } catch (MalformedURLException e) { + return false; + } + + return true; + } +} diff --git a/code/android-signal-library/src/main/res/values/strings.xml b/code/android-signal-library/src/main/res/values/strings.xml new file mode 100644 index 000000000..b709bc509 --- /dev/null +++ b/code/android-signal-library/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + signallibrary + diff --git a/code/android-signal-library/src/phone/java/com/adobe/marketing/mobile/Signal.java b/code/android-signal-library/src/phone/java/com/adobe/marketing/mobile/Signal.java new file mode 100644 index 000000000..d63a43e55 --- /dev/null +++ b/code/android-signal-library/src/phone/java/com/adobe/marketing/mobile/Signal.java @@ -0,0 +1,43 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile; + +public class Signal { + //Suppressing the Unused warning for now, since Signals now does not have any public APIs of its own - + //making the SingalCore instance not being used. + @SuppressWarnings("unused") + private static SignalCore signalCore; + private final static String EXTENSION_VERSION = "1.0.4"; + + private Signal() { + + } + + public static String extensionVersion() { + return EXTENSION_VERSION; + } + + public static void registerExtension() throws InvalidInitException { + Core core = MobileCore.getCore(); + + if (core == null) { + throw new InvalidInitException(); + } + + try { + //MobileCore may not be loaded or present (because may be Core extension was not + //available). In that case, the Signal extension will not initialize itself + signalCore = new SignalCore(core.eventHub, new SignalModuleDetails()); + } catch (Exception e) { + throw new InvalidInitException(); + } + } +} diff --git a/code/android-signal-library/src/phone/java/com/adobe/marketing/mobile/SignalModuleDetails.java b/code/android-signal-library/src/phone/java/com/adobe/marketing/mobile/SignalModuleDetails.java new file mode 100644 index 000000000..b12da144a --- /dev/null +++ b/code/android-signal-library/src/phone/java/com/adobe/marketing/mobile/SignalModuleDetails.java @@ -0,0 +1,30 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile; + +import java.util.HashMap; +import java.util.Map; + +final class SignalModuleDetails implements ModuleDetails { + private final String FRIENDLY_NAME = "Signal"; + + public String getName() { + return FRIENDLY_NAME; + } + + public String getVersion() { + return Signal.extensionVersion(); + } + + public Map getAdditionalInfo() { + return new HashMap<>(); + } +} \ No newline at end of file diff --git a/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/MockSignal.java b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/MockSignal.java new file mode 100644 index 000000000..4cec105c8 --- /dev/null +++ b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/MockSignal.java @@ -0,0 +1,46 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile; + +public class MockSignal extends SignalExtension { + + public MockSignal(EventHub hub, PlatformServices services) { + super(hub, services); + } + + Event onReceivePostbackConsequenceEvent; + @Override + void handleSignalConsequenceEvent(final Event event) { + this.onReceivePostbackConsequenceEvent = event; + } + + Event onReceiveOpenUrlEventParameterEvent; + @Override + void handleOpenURLConsequenceEvent(final Event event) { + this.onReceiveOpenUrlEventParameterEvent = event; + } + + boolean tryProcessQueuedEventWasCalled; + + @Override + void tryProcessQueuedEvent() { + this.tryProcessQueuedEventWasCalled = true; + } + + boolean updatePrivacyStatusWasCalled; + MobilePrivacyStatus updatePrivacyStatusParameter; + @Override + void updatePrivacyStatus(MobilePrivacyStatus privacyStatus) { + updatePrivacyStatusWasCalled = true; + updatePrivacyStatusParameter = privacyStatus; + } +} \ No newline at end of file diff --git a/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/MockSignalHitsDatabase.java b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/MockSignalHitsDatabase.java new file mode 100644 index 000000000..7c36867dc --- /dev/null +++ b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/MockSignalHitsDatabase.java @@ -0,0 +1,34 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile; + +public class MockSignalHitsDatabase extends SignalHitsDatabase { + MockSignalHitsDatabase(PlatformServices services) { + super(services); + } + + @Override + void updatePrivacyStatus(final MobilePrivacyStatus privacyStatus) { + } + + boolean queueWasCalled; + SignalHit queueParametersSignalHit; + MobilePrivacyStatus queueParametersMobilePrivacyStatus; + long queueParametersTimestamp; + + @Override + void queue(final SignalHit signalHit, final long timestamp, final MobilePrivacyStatus privacyStatus) { + this.queueParametersSignalHit = signalHit; + this.queueParametersTimestamp = timestamp; + this.queueParametersMobilePrivacyStatus = privacyStatus; + this.queueWasCalled = true; + } +} diff --git a/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalCoreAPI.java b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalCoreAPI.java new file mode 100644 index 000000000..15f5b4b0c --- /dev/null +++ b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalCoreAPI.java @@ -0,0 +1,40 @@ +///**************************************************************************** +// * +// * ADOBE CONFIDENTIAL +// * ___________________ +// * +// * Copyright 2018 Adobe Systems Incorporated +// * All Rights Reserved. +// * +// * NOTICE: All information contained herein is, and remains +// * the property of Adobe Systems Incorporated and its suppliers, +// * if any. The intellectual and technical concepts contained +// * herein are proprietary to Adobe Systems Incorporated and its +// * suppliers and are protected by trade secret or copyright law. +// * Dissemination of this information or reproduction of this material +// * is strictly forbidden unless prior written permission is obtained +// * from Adobe Systems Incorporated. +// * +// ***************************************************************************/ +// +//package com.adobe.marketing.mobile; +//import java.util.Map; +// +//class SignalCoreAPI { +// private MockEventHubModuleTest eventHub; +// SignalCoreAPI(final MockEventHubModuleTest eventHub) { +// this.eventHub = eventHub; +// } +// +// void collectPii(final Map data) { +// if (data == null || data.isEmpty()) { +// return; +// } +// +// final EventData eventData = new EventData() +// .putStringMap(SignalConstants.EventDataKeys.Signal.SIGNAL_CONTEXT_DATA, data); +// eventHub.dispatch(new Event.Builder("CollectPII", EventType.SIGNAL, +// EventSource.REQUEST_CONTENT).setData(eventData).build()); +// +// } +//} diff --git a/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalHitSchemaTest.java b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalHitSchemaTest.java new file mode 100644 index 000000000..4ce81525d --- /dev/null +++ b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalHitSchemaTest.java @@ -0,0 +1,54 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile; + +import org.junit.Test; + +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class SignalHitSchemaTest { + private static final String HIT_ID_COL_NAME = "ID"; + private static final int HIT_ID_COL_INDEX = 0; + private static final String HIT_URL_COL_NAME = "URL"; + private static final int HIT_URL_COL_INDEX = 1; + private static final String HIT_TIMESTAMP_COL_NAME = "TIMESTAMP"; + private static final int HIT_TIMESTAMP_COL_INDEX = 2; + private static final String HIT_POSTBODY_COL_NAME = "POSTBODY"; + private static final int HIT_POSTBODY_COL_INDEX = 3; + private static final String HIT_CONTENTTYPE_COL_NAME = "CONTENTTYPE"; + private static final int HIT_CONTENTTYPE_COL_INDEX = 4; + private static final String HIT_TIMEOUT_COL_NAME = "TIMEOUT"; + private static final int HIT_TIMEOUT_COL_INDEX = 5; + + @Test + public void testGenerateDataMap() { + SignalHitSchema schema = new SignalHitSchema(); + SignalHit newHit = new SignalHit(); + newHit.identifier = "id"; + newHit.body = "body"; + newHit.contentType = "Get"; + newHit.url = "url"; + newHit.timestamp = 123; + newHit.timeout = 5; + Map values = schema.generateDataMap(newHit); + assertFalse(values.containsKey(HIT_ID_COL_NAME)); + assertEquals("body", values.get(HIT_POSTBODY_COL_NAME)); + assertEquals(123L, values.get(HIT_TIMESTAMP_COL_NAME)); + assertEquals(5, values.get(HIT_TIMEOUT_COL_NAME)); + assertEquals("Get", values.get(HIT_CONTENTTYPE_COL_NAME)); + assertEquals("url", values.get(HIT_URL_COL_NAME)); + } +} diff --git a/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalHitsDatabaseTest.java b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalHitsDatabaseTest.java new file mode 100644 index 000000000..10d94ab33 --- /dev/null +++ b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalHitsDatabaseTest.java @@ -0,0 +1,192 @@ +///* *********************************************************************** +// * ADOBE CONFIDENTIAL +// * ___________________ +// * +// * Copyright 2018 Adobe Systems Incorporated +// * All Rights Reserved. +// * +// * NOTICE: All information contained herein is, and remains +// * the property of Adobe Systems Incorporated and its suppliers, +// * if any. The intellectual and technical concepts contained +// * herein are proprietary to Adobe Systems Incorporated and its +// * suppliers and are protected by trade secret or copyright law. +// * Dissemination of this information or reproduction of this material +// * is strictly forbidden unless prior written permission is obtained +// * from Adobe Systems Incorporated. +// **************************************************************************/ +// +//package com.adobe.marketing.mobile; +// +//import org.junit.Before; +//import org.junit.Test; +// +//import java.net.HttpURLConnection; +//import java.util.concurrent.TimeUnit; +// +//import static org.junit.Assert.*; +// +// +//public class SignalHitsDatabaseTest extends BaseTest { +// +// private SignalHitsDatabase signalHitsDatabase; +// private MockNetworkService networkService; +// private MockHitQueue hitQueue; +// +// @Before +// public void setup() { +// super.beforeEach(); +// hitQueue = new MockHitQueue(platformServices); +// networkService = platformServices.getMockNetworkService(); +// signalHitsDatabase = new SignalHitsDatabase(platformServices, hitQueue); +// } +// +// @Test +// public void testProcess_NotRetry_When_ConnectionIsNull() throws Exception { +// SignalHit signalHit = createHit("id", 3000, "serverName2.com", "body", "GET", 5); +// networkService.connectUrlReturnValue = null; +// +// HitQueue.RetryType retryType = signalHitsDatabase.process(signalHit); +// assertEquals(HitQueue.RetryType.NO, retryType); +// } +// +// +// @Test +// public void testProcess_NotRetry_When_ResponseIsValid200OK() throws Exception { +// SignalHit signalHit = createHit("id", 3000, "serverName2.com", "body", "GET", 5); +// MockConnection mockConnection = new MockConnection("", 200, null, +// null); +// networkService.connectUrlReturnValue = mockConnection; +// +// HitQueue.RetryType retryType = signalHitsDatabase.process(signalHit); +// assertEquals(HitQueue.RetryType.NO, retryType); +// } +// +// @Test +// public void testProcess_NotRetry_When_ResponseIsValid2xx() throws Exception { +// SignalHit signalHit = createHit("id", 3000, "serverName2.com", "body", "GET", 5); +// MockConnection mockConnection = new MockConnection("", 206, null, +// null); +// networkService.connectUrlReturnValue = mockConnection; +// +// HitQueue.RetryType retryType = signalHitsDatabase.process(signalHit); +// assertEquals(HitQueue.RetryType.NO, retryType); +// } +// +// @Test +// public void testProcess_Retry_When_ConnectionTimeOout() throws Exception { +// SignalHit signalHit = createHit("id", 3000, "serverName2.com", "body", "GET", 5); +// MockConnection mockConnection = new MockConnection("", HttpURLConnection.HTTP_CLIENT_TIMEOUT, null, +// null); +// networkService.connectUrlReturnValue = mockConnection; +// +// HitQueue.RetryType retryType = signalHitsDatabase.process(signalHit); +// assertEquals(HitQueue.RetryType.YES, retryType); +// } +// +// @Test +// public void testProcess_Retry_When_GateWayOout() throws Exception { +// SignalHit signalHit = createHit("id", 3000, "serverName2.com", "body", "GET", 5); +// MockConnection mockConnection = new MockConnection("", HttpURLConnection.HTTP_GATEWAY_TIMEOUT, null, +// null); +// networkService.connectUrlReturnValue = mockConnection; +// +// HitQueue.RetryType retryType = signalHitsDatabase.process(signalHit); +// assertEquals(HitQueue.RetryType.YES, retryType); +// } +// +// +// @Test +// public void testProcess_Retry_When_HttpUnavailable() throws Exception { +// SignalHit signalHit = createHit("id", 3000, "serverName2.com", "body", "GET", 5); +// MockConnection mockConnection = new MockConnection("", HttpURLConnection.HTTP_UNAVAILABLE, null, +// null); +// networkService.connectUrlReturnValue = mockConnection; +// +// HitQueue.RetryType retryType = signalHitsDatabase.process(signalHit); +// assertEquals(HitQueue.RetryType.YES, retryType); +// } +// +// @Test +// public void testProcess_NotRetry_When_OtherResponseCode() throws Exception { +// SignalHit signalHit = createHit("id", 3000, "serverName2.com", "body", "GET", 5); +// MockConnection mockConnection = new MockConnection("", 301, null, +// null); +// networkService.connectUrlReturnValue = mockConnection; +// +// HitQueue.RetryType retryType = signalHitsDatabase.process(signalHit); +// assertEquals(HitQueue.RetryType.NO, retryType); +// } +// +// @Test +// public void testQueue_Happy_PrivacyStatusIsOptIn() throws Exception { +// SignalHit signalHit = createHit("id", 3000, "serverName2.com", "body", "GET", 5); +// SignalHitsDatabase hitsDatabase = new SignalHitsDatabase(platformServices, hitQueue); +// long currentTimestamp = System.currentTimeMillis(); +// +// // test +// hitsDatabase.queue(signalHit, currentTimestamp, MobilePrivacyStatus.OPT_IN); +// +// // verify +// assertTrue(hitQueue.queueWasCalled); +// assertEquals("id", hitQueue.queueParametersHit.identifier); +// assertEquals(TimeUnit.MILLISECONDS.toSeconds(currentTimestamp), hitQueue.queueParametersHit.timestamp); +// assertEquals("body", hitQueue.queueParametersHit.body); +// assertEquals("serverName2.com", hitQueue.queueParametersHit.url); +// assertEquals(5, hitQueue.queueParametersHit.timeout); +// assertTrue(hitQueue.bringOnlineWasCalled); +// } +// +// @Test +// public void testQueue_NullHit() throws Exception { +// SignalHitsDatabase hitsDatabase = new SignalHitsDatabase(platformServices, hitQueue); +// long currentTimestamp = System.currentTimeMillis(); +// +// // test +// hitsDatabase.queue(null, currentTimestamp, MobilePrivacyStatus.OPT_IN); +// +// // verify +// assertTrue(hitQueue.queueWasCalled); +// assertNull(hitQueue.queueParametersHit); +// assertTrue(hitQueue.bringOnlineWasCalled); +// } +// +// @Test +// public void testQueue_When_PrivacyStatusUnknown() throws Exception { +// SignalHit signalHit = createHit("id", 3000, "serverName2.com", "body", "GET", 5); +// SignalHitsDatabase hitsDatabase = new SignalHitsDatabase(platformServices, hitQueue); +// long currentTimestamp = System.currentTimeMillis(); +// +// // test +// hitsDatabase.queue(signalHit, currentTimestamp, MobilePrivacyStatus.UNKNOWN); +// +// // verify +// assertTrue(hitQueue.queueWasCalled); +// assertFalse(hitQueue.bringOnlineWasCalled); +// } +// +// @Test +// public void testQueue_When_PrivacyStatusOptOut() throws Exception { +// SignalHit signalHit = createHit("id", 3000, "serverName2.com", "body", "GET", 5); +// SignalHitsDatabase hitsDatabase = new SignalHitsDatabase(platformServices, hitQueue); +// long currentTimestamp = System.currentTimeMillis(); +// +// // test +// hitsDatabase.queue(signalHit, currentTimestamp, MobilePrivacyStatus.OPT_OUT); +// +// // verify +// assertTrue(hitQueue.queueWasCalled); +// assertFalse(hitQueue.bringOnlineWasCalled); +// } +// +// private SignalHit createHit(String identifier, long timeStamp, String url, String body, +// String contentType, int timeout) { +// SignalHit newHit = new SignalHit(); +// newHit.identifier = identifier; +// newHit.url = url; +// newHit.body = body; +// newHit.contentType = contentType; +// newHit.timestamp = timeStamp; +// newHit.timeout = timeout; +// return newHit; +// } +//} \ No newline at end of file diff --git a/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalListenerConfigurationResponseContentTests.java b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalListenerConfigurationResponseContentTests.java new file mode 100644 index 000000000..d5d30d7a0 --- /dev/null +++ b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalListenerConfigurationResponseContentTests.java @@ -0,0 +1,55 @@ +///* *********************************************************************** +// * ADOBE CONFIDENTIAL +// * ___________________ +// * +// * Copyright 2018 Adobe Systems Incorporated +// * All Rights Reserved. +// * +// * NOTICE: All information contained herein is, and remains +// * the property of Adobe Systems Incorporated and its suppliers, +// * if any. The intellectual and technical concepts contained +// * herein are proprietary to Adobe Systems Incorporated and its +// * suppliers and are protected by trade secret or copyright law. +// * Dissemination of this information or reproduction of this material +// * is strictly forbidden unless prior written permission is obtained +// * from Adobe Systems Incorporated. +// **************************************************************************/ +// +//package com.adobe.marketing.mobile; +// +// +//import org.junit.Assert; +//import org.junit.Before; +//import org.junit.Test; +// +//public class SignalListenerConfigurationResponseContentTests { +// MockSignal module; +// ListenerConfigurationResponseContentSignal listener; +// FakePlatformServices fakePlatformServices; +// +// @Before() +// public void beforeEach() { +// fakePlatformServices = new FakePlatformServices(); +// module = new MockSignal(new MockEventHubUnitTest("UnitTest", fakePlatformServices), fakePlatformServices); +// listener = new ListenerConfigurationResponseContentSignal(module, null, null); +// } +// +// @Test +// public void updatePrivacyStatusWithUnknown_When_EventDataNotContainPrivacyStatus() { +// Event e = new Event.Builder("Test", EventType.CONFIGURATION, EventSource.RESPONSE_CONTENT) +// .setData(new EventData()).build();; +// listener.hear(e); +// Assert.assertEquals(MobilePrivacyStatus.UNKNOWN, module.updatePrivacyStatusParameter); +// } +// +// @Test +// public void updatePrivacyStatusWithOptout_When_EventDataContainPrivacyStatusWithOptout() { +// Event e = new Event.Builder("Test", EventType.CONFIGURATION, EventSource.RESPONSE_CONTENT) +// .setData(new EventData().putString(SignalConstants.EventDataKeys.Configuration.GLOBAL_CONFIG_PRIVACY, +// MobilePrivacyStatus.OPT_OUT.getValue())).build();; +// listener.hear(e); +// Assert.assertEquals(MobilePrivacyStatus.OPT_OUT, module.updatePrivacyStatusParameter); +// +// } +// +//} \ No newline at end of file diff --git a/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalListenerRulesEngineResponseContentTests.java b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalListenerRulesEngineResponseContentTests.java new file mode 100644 index 000000000..b7744a928 --- /dev/null +++ b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalListenerRulesEngineResponseContentTests.java @@ -0,0 +1,74 @@ +///* *********************************************************************** +// * ADOBE CONFIDENTIAL +// * ___________________ +// * +// * Copyright 2018 Adobe Systems Incorporated +// * All Rights Reserved. +// * +// * NOTICE: All information contained herein is, and remains +// * the property of Adobe Systems Incorporated and its suppliers, +// * if any. The intellectual and technical concepts contained +// * herein are proprietary to Adobe Systems Incorporated and its +// * suppliers and are protected by trade secret or copyright law. +// * Dissemination of this information or reproduction of this material +// * is strictly forbidden unless prior written permission is obtained +// * from Adobe Systems Incorporated. +// **************************************************************************/ +// +//package com.adobe.marketing.mobile; +// +//import org.junit.Assert; +//import org.junit.Before; +//import org.junit.Test; +// +//import java.util.ArrayList; +//import java.util.HashMap; +// +//public class SignalListenerRulesEngineResponseContentTests { +// MockSignal module; +// ListenerRulesEngineResponseContentSignal listener; +// FakePlatformServices fakePlatformServices; +// +// private Event createEmptyDataSignalEvent() { +// EventData data = new EventData(); +// return new Event.Builder("Test", EventType.RULES_ENGINE, EventSource.RESPONSE_CONTENT).setData(data).build(); +// } +// +// private Event createValidSignalEvent() { +// HashMap triggeredConsequence = new HashMap(); +// HashMap consequenceDetail = new HashMap(); +// +// consequenceDetail.put("templateurl", Variant.fromString("my-url")); +// +// triggeredConsequence.put("id", Variant.fromString("my-id")); +// triggeredConsequence.put("type", Variant.fromString("pb")); +// triggeredConsequence.put("detail", Variant.fromVariantMap(consequenceDetail)); +// +// EventData data = new EventData(); +// data.putVariantMap("triggeredconsequence", triggeredConsequence); +// +// return new Event.Builder("Test", EventType.RULES_ENGINE, EventSource.RESPONSE_CONTENT).setData(data).build(); +// } +// +// @Before() +// public void beforeEach() { +// fakePlatformServices = new FakePlatformServices(); +// module = new MockSignal(new MockEventHubUnitTest("UnitTest", fakePlatformServices), fakePlatformServices); +// listener = new ListenerRulesEngineResponseContentSignal(module, null, null); +// } +// +// @Test +// public void notCallOnReceiveSignalEvent_When_EventDoesNotContainSignalOrPiiIds() { +// Event e = createEmptyDataSignalEvent(); +// listener.hear(e); +// Assert.assertNull(module.onReceivePostbackConsequenceEvent); +// } +// +// @Test +// public void callOnReceiveSignalEvent_When_EventContainSignalId() { +// Event e = createValidSignalEvent(); +// listener.hear(e); +// Assert.assertEquals(e, module.onReceivePostbackConsequenceEvent); +// } +// +//} \ No newline at end of file diff --git a/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalModuleTest.java b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalModuleTest.java new file mode 100644 index 000000000..1a1cb3d5c --- /dev/null +++ b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalModuleTest.java @@ -0,0 +1,59 @@ +///**************************************************************************** +// * +// * ADOBE CONFIDENTIAL +// * ___________________ +// * +// * Copyright 2018 Adobe Systems Incorporated +// * All Rights Reserved. +// * +// * NOTICE: All information contained herein is, and remains +// * the property of Adobe Systems Incorporated and its suppliers, +// * if any. The intellectual and technical concepts contained +// * herein are proprietary to Adobe Systems Incorporated and its +// * suppliers and are protected by trade secret or copyright law. +// * Dissemination of this information or reproduction of this material +// * is strictly forbidden unless prior written permission is obtained +// * from Adobe Systems Incorporated. +// * +// ***************************************************************************/ +// +//package com.adobe.marketing.mobile; +// +//import org.junit.After; +//import org.junit.Before; +//import org.junit.Ignore; +//import org.junit.Test; +//import org.junit.runner.RunWith; +//import org.junit.runners.JUnit4; +// +//import java.util.HashMap; +//import java.util.List; +//import java.util.Map; +// +//import static org.junit.Assert.assertEquals; +//import static org.junit.Assert.assertTrue; +// +// +//@RunWith(JUnit4.class) +//public class SignalModuleTest extends SystemTest { +// private TestableNetworkService testableNetworkService; +// private SignalCoreAPI testableCore; +// +// @Before +// public void beforeEach() throws Exception { +// testableNetworkService = platformServices.getTestableNetworkService(); +// eventHub.registerModule(SignalExtension.class); +// testableCore = new SignalCoreAPI(eventHub); +// eventHub.clearEvents(); +// } +// +// @After +// public void afterEach() { +// eventHub.clearEvents(); +// } +// +// @Test +// public void testSignal_Registration() { +// assertTrue(testableCore != null); +// } +//} diff --git a/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalTemplateTest.java b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalTemplateTest.java new file mode 100644 index 000000000..3e230e042 --- /dev/null +++ b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalTemplateTest.java @@ -0,0 +1,251 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class SignalTemplateTest { + + @Before() + public void beforeEach() throws Exception { + } + + @Test + public void returnNullSignalTemplate_when_JsonObjectIsNull() { + Assert.assertNull(SignalTemplate.createSignalTemplateFromConsequence(null)); + } + + @Test + public void returnNullSignalTemplate_when_NoMessageIdInJson() { + Assert.assertNull(SignalTemplate.createSignalTemplateFromConsequence(new HashMap())); + } + + @Test + public void returnNullSignalTemplate_when_MessageIdIsEmptyInJson() { + Map map = new HashMap(); + map.put("id", Variant.fromString("")); + Assert.assertNull(SignalTemplate.createSignalTemplateFromConsequence(map)); + } + + @Test + public void returnNullSignalTemplate_when_DetailIsEmpty() { + Map map = new HashMap(); + map.put("id", Variant.fromString("id")); + map.put("detail", Variant.fromVariantMap(new HashMap())); + map.put("type", Variant.fromString("pb")); + + Assert.assertNull(SignalTemplate.createSignalTemplateFromConsequence(map)); + } + + @Test + public void returnNullSignalTemplate_when_UrlIsEmptyInJson() { + Map detail = new HashMap(); + detail.put("templateurl", ""); + + Map map = new HashMap(); + map.put("id", Variant.fromString("what-id")); + map.put("detail", Variant.fromStringMap(detail)); + + Assert.assertNull(SignalTemplate.createSignalTemplateFromConsequence(map)); + } + + @Test + public void returnNullSignalTemplate_when_UrlIsHttpWithPiiType() { + Map detail = new HashMap(); + detail.put("templateurl", "http://xyz.com"); + + Map map = new HashMap(); + map.put("id", Variant.fromString("what-id")); + map.put("type", Variant.fromString("pii")); + map.put("detail", Variant.fromStringMap(detail)); + + Assert.assertNull(SignalTemplate.createSignalTemplateFromConsequence(map)); + } + + @Test + public void returnValidSignalTemplate_when_UrlIsHttpsWithPiiType() { + Map detail = new HashMap(); + detail.put("templateurl", "https://xyz.com"); + + Map map = new HashMap(); + map.put("id", Variant.fromString("what-id")); + map.put("type", Variant.fromString("pii")); + map.put("detail", Variant.fromStringMap(detail)); + + SignalTemplate signal = SignalTemplate.createSignalTemplateFromConsequence(map); + Assert.assertNotNull(signal); + Assert.assertEquals("https://xyz.com", signal.getSignalHit().url); + } + + @Test + public void returnValidSignalTemplate_when_UrlIsHttpWithPostbackType() { + Map detail = new HashMap(); + detail.put("templateurl", "http://xyz.com"); + + Map map = new HashMap(); + map.put("id", Variant.fromString("what-id")); + map.put("type", Variant.fromString("pb")); + map.put("detail", Variant.fromStringMap(detail)); + + SignalTemplate signal = SignalTemplate.createSignalTemplateFromConsequence(map); + Assert.assertNotNull(signal); + Assert.assertEquals("http://xyz.com", signal.getSignalHit().url); + } + + @Test + public void returnValidSignalTemplate_when_UrlIsValidInJson() { + Map detail = new HashMap(); + detail.put("templateurl", "http://my-url"); + + Map map = new HashMap(); + map.put("id", Variant.fromString("what-id")); + map.put("detail", Variant.fromStringMap(detail)); + + SignalTemplate signal = SignalTemplate.createSignalTemplateFromConsequence(map); + Assert.assertEquals("what-id", signal.getSignalTemplateId()); + } + + @Test + public void signalHitHasCorrectUrl_when_UrlIsValidInJson() { + Map detail = new HashMap(); + detail.put("templateurl", "http://my-url"); + + Map map = new HashMap(); + map.put("id", Variant.fromString("what-id")); + map.put("detail", Variant.fromStringMap(detail)); + + SignalTemplate signal = SignalTemplate.createSignalTemplateFromConsequence(map); + Assert.assertEquals("what-id", signal.getSignalTemplateId()); + Assert.assertEquals("http://my-url", signal.getSignalHit().url); + } + + @Test + public void signalHitHasNullPostBody_when_TemplatebodyIsEmptyInJson() { + Map detail = new HashMap(); + detail.put("templateurl", "http://my-url"); + detail.put("templatebody", ""); + + Map map = new HashMap(); + map.put("id", Variant.fromString("what-id")); + map.put("detail", Variant.fromStringMap(detail)); + + SignalTemplate signal = SignalTemplate.createSignalTemplateFromConsequence(map); + Assert.assertEquals("", signal.getSignalHit().body); + } + + @Test + public void signalHitHasNullPostBody_when_TemplatebodyIsNullInJson() { + Map detail = new HashMap(); + detail.put("templateurl", "http://my-url"); + detail.put("templatebody", null); + + Map map = new HashMap(); + map.put("id", Variant.fromString("what-id")); + map.put("detail", Variant.fromStringMap(detail)); + + SignalTemplate signal = SignalTemplate.createSignalTemplateFromConsequence(map); + Assert.assertEquals("", signal.getSignalHit().body); + } + + @Test + public void signalHitHasValidPostBody_when_DecodedTemplatebodyIsValid() throws Exception { + Map detail = new HashMap(); + detail.put("templateurl", "http://my-url"); + detail.put("templatebody", "body"); + + Map map = new HashMap(); + map.put("id", Variant.fromString("what-id")); + map.put("detail", Variant.fromStringMap(detail)); + + SignalTemplate signal = SignalTemplate.createSignalTemplateFromConsequence(map); + Assert.assertEquals("body", signal.getSignalHit().body); + } + + @Test + public void signalHitHasNullContentType_when_DecodedTemplatebodyIsInvalid() throws Exception { + Map detail = new HashMap(); + detail.put("templateurl", "http://my-url"); + detail.put("templatebody", ""); + detail.put("contenttype", "json"); + + Map map = new HashMap(); + map.put("id", Variant.fromString("what-id"));; + map.put("detail", Variant.fromStringMap(detail)); + + SignalTemplate signal = SignalTemplate.createSignalTemplateFromConsequence(map); + Assert.assertNull(signal.getSignalHit().contentType); + } + + @Test + public void signalHitHasNullPostBody_when_TemplatebodyIsMissingInJson() { + Map detail = new HashMap(); + detail.put("templateurl", "http://my-url"); + + Map map = new HashMap(); + map.put("id", Variant.fromString("what-id"));; + map.put("detail", Variant.fromStringMap(detail)); + + SignalTemplate signal = SignalTemplate.createSignalTemplateFromConsequence(map); + Assert.assertEquals("", signal.getSignalHit().body); + } + + @Test + public void signalHitHasValidContentType_when_DecodedTemplatebodyIsValid() throws Exception { + Map detail = new HashMap(); + detail.put("templateurl", "http://my-url"); + detail.put("templatebody", "body"); + detail.put("contenttype", "json"); + + Map map = new HashMap(); + map.put("id", Variant.fromString("what-id"));; + map.put("detail", Variant.fromStringMap(detail)); + + SignalTemplate signal = SignalTemplate.createSignalTemplateFromConsequence(map); + Assert.assertEquals("json", signal.getSignalHit().contentType); + } + + @Test + public void signalHitUseTheDefaultTimeout_when_NoTimeoutInJson() throws Exception { + Map detail = new HashMap(); + detail.put("templateurl", "http://myurl"); + + Map map = new HashMap(); + map.put("id", Variant.fromString("what-id"));; + map.put("detail", Variant.fromStringMap(detail)); + + SignalTemplate signal = SignalTemplate.createSignalTemplateFromConsequence(map); + Assert.assertEquals(2, signal.getSignalHit().timeout); + } + + @Test + public void signalHitUseConfiguredTimeout_when_ContainsTimeoutInJson() throws Exception { + Map detail = new HashMap(); + detail.put("templateurl", Variant.fromString("http://myurl")); + detail.put("timeout", Variant.fromInteger(5)); + + Map map = new HashMap(); + map.put("id", Variant.fromString("what-id"));; + map.put("detail", Variant.fromVariantMap(detail)); + + SignalTemplate signal = SignalTemplate.createSignalTemplateFromConsequence(map); + Assert.assertEquals(5, signal.getSignalHit().timeout); + } +} diff --git a/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalTest.java b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalTest.java new file mode 100644 index 000000000..5a0f5ca60 --- /dev/null +++ b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalTest.java @@ -0,0 +1,230 @@ +///* *********************************************************************** +// * ADOBE CONFIDENTIAL +// * ___________________ +// * +// * Copyright 2018 Adobe Systems Incorporated +// * All Rights Reserved. +// * +// * NOTICE: All information contained herein is, and remains +// * the property of Adobe Systems Incorporated and its suppliers, +// * if any. The intellectual and technical concepts contained +// * herein are proprietary to Adobe Systems Incorporated and its +// * suppliers and are protected by trade secret or copyright law. +// * Dissemination of this information or reproduction of this material +// * is strictly forbidden unless prior written permission is obtained +// * from Adobe Systems Incorporated. +// **************************************************************************/ +// +//package com.adobe.marketing.mobile; +// +//import org.junit.Before; +//import org.junit.Ignore; +//import org.junit.Test; +// +//import java.util.HashMap; +// +//import static org.junit.Assert.*; +// +//public class SignalTest extends BaseTest { +// +// private static final String CONFIGURATION_STATE_NAME = "com.adobe.module.configuration"; +// SignalExtension signalModule; +// FakePlatformServices fakePlatformServices; +// MockEventHubUnitTest eventHub; +// MockSignalHitsDatabase mockSignalHitsDatabase; +// +// +// @Before() +// public void beforeEach() { +// fakePlatformServices = new FakePlatformServices(); +// eventHub = new MockEventHubUnitTest("Test", fakePlatformServices); +// mockSignalHitsDatabase = new MockSignalHitsDatabase(fakePlatformServices); +// signalModule = new SignalExtension(eventHub, fakePlatformServices, mockSignalHitsDatabase); +// } +// +// @Test +// public void testDefaultConstructor() { +// SignalExtension module = new SignalExtension(eventHub, fakePlatformServices); +// assertNotNull(module); +// } +// +// private Event createSignalEvent() { +// HashMap triggeredConsequence = new HashMap(); +// HashMap consequenceDetail = new HashMap(); +// +// consequenceDetail.put("templateurl", Variant.fromString("http://my-url")); +// +// triggeredConsequence.put("id", Variant.fromString("my-id")); +// triggeredConsequence.put("type", Variant.fromString("pb")); +// triggeredConsequence.put("detail", Variant.fromVariantMap(consequenceDetail)); +// +// EventData data = new EventData(); +// data.putVariantMap("triggeredconsequence", triggeredConsequence); +// +// return new Event.Builder("Test", EventType.RULES_ENGINE, EventSource.RESPONSE_CONTENT).setData(data).build(); +// } +// +// private Event createPIIEvent() { +// HashMap triggeredConsequence = new HashMap(); +// HashMap consequenceDetail = new HashMap(); +// +// consequenceDetail.put("templateurl", Variant.fromString("https://my-url")); +// +// triggeredConsequence.put("id", Variant.fromString("my-id")); +// triggeredConsequence.put("type", Variant.fromString("pii")); +// triggeredConsequence.put("detail", Variant.fromVariantMap(consequenceDetail)); +// +// EventData data = new EventData(); +// data.putVariantMap("triggeredconsequence", triggeredConsequence); +// +// return new Event.Builder("Test", EventType.RULES_ENGINE, EventSource.RESPONSE_CONTENT).setData(data).build(); +// } +// +// private Event createOpenUrlEvent(final String url) { +// HashMap triggeredConsequence = new HashMap(); +// HashMap consequenceDetail = new HashMap(); +// +// consequenceDetail.put("url", Variant.fromString(url)); +// +// triggeredConsequence.put("id", Variant.fromString("my-id")); +// triggeredConsequence.put("type", Variant.fromString("url")); +// triggeredConsequence.put("detail", Variant.fromVariantMap(consequenceDetail)); +// +// EventData data = new EventData(); +// data.putVariantMap("triggeredconsequence", triggeredConsequence); +// +// return new Event.Builder("Test", EventType.RULES_ENGINE, EventSource.RESPONSE_CONTENT).setData(data).build(); +// } +// +// @Test +// public void processEventReturnsNo_When_ConfigurationStateIsNull() { +// eventHub.setSharedState(CONFIGURATION_STATE_NAME, null); +// boolean isProcessed = signalModule.processSignalEvent(createSignalEvent()); +// assertEquals(false, isProcessed); +// } +// +// @Test +// public void processEventReturnsYes_When_ConfigurationStateAreValid_EventDataContainsSignalId() throws Exception { +// eventHub.setSharedState(CONFIGURATION_STATE_NAME, new EventData().putString("key", "value")); +// Event signalEvent = createSignalEvent(); +// boolean isProcessed = signalModule.processSignalEvent(signalEvent); +// assertEquals(true, isProcessed); +// assertTrue(mockSignalHitsDatabase.queueWasCalled); +// +// SignalHit hit = mockSignalHitsDatabase.queueParametersSignalHit; +// assertEquals("http://my-url", hit.url); +// assertEquals(signalEvent.getTimestamp(), mockSignalHitsDatabase.queueParametersTimestamp); +// } +// +// @Test +// public void canProcessEvent_When_ConfigurationAndIdentityStateAreValid_EventDataContainsPiiId() throws Exception { +// eventHub.setSharedState(CONFIGURATION_STATE_NAME, new EventData().putString("key", "value")); +// boolean isProcessed = signalModule.processSignalEvent(createPIIEvent()); +// assertEquals(true, isProcessed); +// assertTrue(mockSignalHitsDatabase.queueWasCalled); +// +// SignalHit hit = mockSignalHitsDatabase.queueParametersSignalHit; +// assertEquals("https://my-url", hit.url); +// } +// +// @Test +// public void holdTheEvents_UntilConfigurationSharedStateIsReady() throws Exception { +// eventHub.setSharedState(CONFIGURATION_STATE_NAME, null); +// signalModule.handleSignalConsequenceEvent(createSignalEvent()); +// +// waitForExecutor(signalModule.getExecutor()); +// assertEquals(false, mockSignalHitsDatabase.queueWasCalled); +// +// eventHub.setSharedState(CONFIGURATION_STATE_NAME, new EventData()); +// signalModule.tryProcessQueuedEvent(); +// +// waitForExecutor(signalModule.getExecutor()); +// assertEquals(true, mockSignalHitsDatabase.queueWasCalled); +// } +// +// @Test +// public void callLogicMethodUpdatePrivacyStatus_When_UpdatePrivacyStatus() throws Exception { +// signalModule.updatePrivacyStatus(MobilePrivacyStatus.OPT_IN); +// waitForExecutor(signalModule.getExecutor()); +// mockSignalHitsDatabase.queueParametersMobilePrivacyStatus = MobilePrivacyStatus.OPT_IN; +// } +// +// +// //ToDo (module test??) +// @Ignore +// @Test +// public void clearTheEventsInQueue_When_SetPrivacyToOptOut() throws Exception { +// +// eventHub.setSharedState(CONFIGURATION_STATE_NAME, null); +// signalModule.handleSignalConsequenceEvent(createSignalEvent()); +// signalModule.handleSignalConsequenceEvent(createSignalEvent()); +// +// waitForExecutor(signalModule.getExecutor()); +// signalModule.updatePrivacyStatus(MobilePrivacyStatus.OPT_OUT); +// +// eventHub.setSharedState(CONFIGURATION_STATE_NAME, new EventData()); +// signalModule.handleSignalConsequenceEvent(createSignalEvent()); +// +// waitForExecutor(signalModule.getExecutor()); +// //assertEquals(1, mockSignalLogic.queueSignalRequestCalledTimes); +// } +// +// @Test +// public void testOnReceiveOpenUrlEvent_When_Happy_Then_CallOpenURL() throws Exception { +// +// // execute +// // setup +// final Event openUrlEvent = createOpenUrlEvent("mytest://deeplink"); +// +// signalModule.handleOpenURLConsequenceEvent(openUrlEvent); +// waitForExecutor(signalModule.getExecutor()); +// +// // verify +// final MockUIService uiService = fakePlatformServices.getMockUIService(); +// assertTrue(uiService.showUrlWasCalled); +// assertEquals("mytest://deeplink", uiService.showUrlUrl); +// } +// +// @Test +// public void testOnReceiveOpenUrlEvent_When_EmptyUrls_Then_DoNothing() throws Exception { +// // setup +// final Event openUrlEvent = createOpenUrlEvent(""); +// +// // execute +// signalModule.handleOpenURLConsequenceEvent(openUrlEvent); +// waitForExecutor(signalModule.getExecutor()); +// +// // verify +// final MockUIService uiService = fakePlatformServices.getMockUIService(); +// assertFalse(uiService.showUrlWasCalled); +// } +// +// @Test +// public void testOnReceiveOpenUrlEvent_When_NullUrls_Then_DoNothing() throws Exception { +// // setup +// final Event openUrlEvent = createOpenUrlEvent(null); +// +// // execute +// signalModule.handleOpenURLConsequenceEvent(openUrlEvent); +// waitForExecutor(signalModule.getExecutor()); +// +// // verify +// final MockUIService uiService = fakePlatformServices.getMockUIService(); +// assertFalse(uiService.showUrlWasCalled); +// } +// +// @Test +// public void testOnReceiveOpenUrlEvent_When_NullUiService_Then_DontCrash() throws Exception { +// // setup +// final Event openUrlEvent = createOpenUrlEvent("mytest://deeplink"); +// +// fakePlatformServices.mockUIService = null; +// +// // execute +// signalModule.handleOpenURLConsequenceEvent(openUrlEvent); +// waitForExecutor(signalModule.getExecutor()); +// +// // verify +// // nothing to verify cause there's no ui service to validate against, just make sure we don't crash +// } +//} diff --git a/code/settings.gradle b/code/settings.gradle index 0154b6104..7fd24ab17 100644 --- a/code/settings.gradle +++ b/code/settings.gradle @@ -11,7 +11,7 @@ rootProject.name = 'aepsdk-core-android' include ':android-core-library', // ':android-lifecycle-library', // ':android-identity-library', - // ':android-signal-library', + ':android-signal-library', ':android-core-library-maven-root', ':test-third-party-extension', ':testapp' From fc47f7a59f6a184416fddf9666ce01bccd8ea3db Mon Sep 17 00:00:00 2001 From: Yansong Yang Date: Mon, 16 May 2022 12:41:50 -0600 Subject: [PATCH 049/476] Integrate rules engine with EventHub (Part I) (#68) --- code/android-core-library/build.gradle | 4 +- .../mobile/MockEventHubModuleTest.java | 11 - .../mobile/ConfigurationModuleTest.java | 38 +- .../mobile/ConfigurationExtension.java | 2419 +++++++------- .../com/adobe/marketing/mobile/EventHub.java | 2966 ++++++++--------- .../marketing/mobile/EventPreprocessor.java} | 24 +- .../com/adobe/marketing/mobile/Matcher.java | 214 -- .../marketing/mobile/MatcherContains.java | 66 - .../marketing/mobile/MatcherEndsWith.java | 65 - .../adobe/marketing/mobile/MatcherEquals.java | 98 - .../adobe/marketing/mobile/MatcherExists.java | 36 - .../marketing/mobile/MatcherGreaterThan.java | 65 - .../mobile/MatcherGreaterThanOrEqual.java | 66 - .../marketing/mobile/MatcherLessThan.java | 65 - .../mobile/MatcherLessThanOrEqual.java | 66 - .../marketing/mobile/MatcherNotContains.java | 48 - .../marketing/mobile/MatcherNotEquals.java | 48 - .../marketing/mobile/MatcherNotExists.java | 34 - .../marketing/mobile/MatcherStartsWith.java | 66 - .../com/adobe/marketing/mobile/Module.java | 45 - .../java/com/adobe/marketing/mobile/Rule.java | 69 - .../adobe/marketing/mobile/RuleCondition.java | 80 - .../mobile/RuleConditionAndGroup.java | 77 - .../marketing/mobile/RuleConditionGroup.java | 97 - .../mobile/RuleConditionMatcher.java | 94 - .../mobile/RuleConditionOrGroup.java | 76 - .../marketing/mobile/RuleConsequence.java | 92 - .../adobe/marketing/mobile/RulesEngine.java | 577 ---- .../mobile/RulesEngineConstants.java | 56 - .../mobile/launch/rulesengine/LaunchRule.kt | 7 +- .../launch/rulesengine/LaunchRulesEngine.java | 45 + .../rulesengine/LaunchRulesEvaluator.kt | 98 + .../rulesengine/LaunchTokenFinder.kt} | 31 +- .../launch/rulesengine/json/GroupCondition.kt | 2 +- .../rulesengine/json/HistoricalCondition.kt | 2 +- .../rulesengine/json/MatcherCondition.kt | 2 +- .../rulesengine/ConditionEvaluator.java | 5 + .../mobile/rulesengine/RulesEngine.java | 61 +- .../adobe/marketing/mobile/EventHubTest.java | 67 - .../adobe/marketing/mobile/MatcherTests.java | 2485 -------------- .../mobile/RuleConditionAndGroupTests.java | 163 - .../mobile/RuleConditionGroupTests.java | 82 - .../mobile/RuleConditionOrGroupTests.java | 157 - .../marketing/mobile/RuleConditionTests.java | 515 --- .../mobile/RuleConsequenceTests.java | 155 - .../com/adobe/marketing/mobile/RuleTests.java | 88 - .../mobile/RuleTokenParserTests.java | 352 -- .../marketing/mobile/RulesEngineTests.java | 1506 --------- .../rulesengine/LaunchRulesEvaluatorTests.kt | 145 + .../rulesengine/json/JSONRulesParserTests.kt | 5 +- 50 files changed, 2934 insertions(+), 10701 deletions(-) rename code/android-core-library/src/{test/java/com/adobe/marketing/mobile/InAccessibleMatcher.java => main/java/com/adobe/marketing/mobile/EventPreprocessor.java} (65%) delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/Matcher.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherContains.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherEndsWith.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherEquals.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherExists.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherGreaterThan.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherGreaterThanOrEqual.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherLessThan.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherLessThanOrEqual.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherNotContains.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherNotEquals.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherNotExists.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherStartsWith.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/Rule.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleCondition.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConditionAndGroup.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConditionGroup.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConditionMatcher.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConditionOrGroup.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConsequence.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/RulesEngine.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/RulesEngineConstants.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngine.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt rename code/android-core-library/src/main/java/com/adobe/marketing/mobile/{MatcherUnknown.java => launch/rulesengine/LaunchTokenFinder.kt} (51%) delete mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/MatcherTests.java delete mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleConditionAndGroupTests.java delete mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleConditionGroupTests.java delete mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleConditionOrGroupTests.java delete mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleConditionTests.java delete mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleConsequenceTests.java delete mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleTests.java delete mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleTokenParserTests.java delete mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/RulesEngineTests.java create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index 5c7216e2b..f9518a2ae 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -52,7 +52,7 @@ android { buildTypes { debug { - testCoverageEnabled true + testCoverageEnabled false debuggable true } release { @@ -107,7 +107,6 @@ android.libraryVariants.all { variant -> apply from: 'checkStyle.gradle' apply from: 'release.gradle' -//apply from: 'jacoco.gradle' dependencies { //noinspection GradleCompatible @@ -124,6 +123,7 @@ dependencies { //noinspection GradleDependency testImplementation 'org.json:json:20160810' testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" + testImplementation project(path: ':android-core-library') // instrumentation tests androidTestImplementation 'androidx.test:rules:1.1.1' androidTestImplementation 'androidx.test.ext:junit:1.1.3' diff --git a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockEventHubModuleTest.java b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockEventHubModuleTest.java index 5c2791dff..4013839da 100755 --- a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockEventHubModuleTest.java +++ b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockEventHubModuleTest.java @@ -206,17 +206,6 @@ public List getEvents() { return events; } - public void replaceRulesAndEvaluateEvents(final Module module, final List rules, - final ReprocessEventsHandler reprocessEventsHandler) { - this.reprocessEventsHandler = reprocessEventsHandler; - this.replaceRulesAndEvaluateEventsHasBeenCalled = true; - super.replaceRulesAndEvaluateEvents(module, rules, reprocessEventsHandler); - } - - - - - public void ignoreStateChangeEvents(final String stateName) { ignoredStateNames.add(stateName); } diff --git a/code/android-core-library/src/legacy/test-module/java/com/adobe/marketing/mobile/ConfigurationModuleTest.java b/code/android-core-library/src/legacy/test-module/java/com/adobe/marketing/mobile/ConfigurationModuleTest.java index 747120953..f6c0289a1 100644 --- a/code/android-core-library/src/legacy/test-module/java/com/adobe/marketing/mobile/ConfigurationModuleTest.java +++ b/code/android-core-library/src/legacy/test-module/java/com/adobe/marketing/mobile/ConfigurationModuleTest.java @@ -13,6 +13,7 @@ import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.rules.TestRule; @@ -1204,6 +1205,7 @@ public void configEvent_WithValidRulesUrl_ShouldTriggerNetworkRequest() throws E } @Test + @Ignore public void ruleMatch_WithValidRuleWithOneConsequenceSetup_ShouldDispatchOneEventIfRuleMatches() throws Exception { //Setup eventHub.setExpectedEventCount(1); @@ -1248,6 +1250,7 @@ public void ruleMatch_WithValidRuleWithOneConsequenceSetup_ShouldDispatchOneEven } @Test + @Ignore public void ruleMatch_WithValidRuleWithTwoConsequenceSetup_ShouldDispatchTwoEventsIfRuleMatches() throws Exception { //Setup eventHub.setExpectedEventCount(2); @@ -1392,6 +1395,7 @@ public void rules_with_same_url_not_download_within_time_sec() throws Exception assertEquals(0, count); } @Test + @Ignore public void rules_with_same_url_will_download_when_timeout() throws Exception { setupNetWorkService("RulesEngineTest_Rules_ModuleTest1.zip", new Date(), 1, HttpURLConnection.HTTP_OK); setupSystemInfoService(temporaryFolder.newFolder()); @@ -1412,6 +1416,7 @@ public void rules_with_same_url_will_download_when_timeout() throws Exception { @Test + @Ignore public void configEvent_When_HttpNotFound_ShouldClearCorrespondingRules() throws Exception { //Setup @@ -1471,6 +1476,7 @@ public void configEvent_When_HttpNotFound_ShouldClearCorrespondingRules() } @Test + @Ignore public void configEvent_shouldNotClearRulesData_WhenRemoteDownloadReturnsUnknownError() throws Exception { //Setup @@ -1539,37 +1545,7 @@ public void reprocessEvent_When_FirstAppLaunch() throws Exception { eventHub.dispatch(new Event.Builder("Test3", EventType.ACQUISITION, EventSource.NONE).build()); ConfigurationExtension configuration = (ConfigurationExtension) eventHub.getActiveModules().iterator().next(); super.sleep(EVENTHUB_WAIT_MS); - configuration.replaceRulesAndReprocessEvents(new ArrayList()); - List events = eventHub.reprocessEventsHandler.getEvents(); - int size = 0; - - for (Event event : events) { - if (event.getEventType() == EventType.ACQUISITION) { - size ++; - } - } - - assertEquals(3, size); - } - - @Test - public void reprocessEvent_When_SecondAppLaunch() throws Exception { - eventHub.registerModule(ConfigurationExtension.class); - super.sleep(EVENTHUB_WAIT_MS); - eventHub.finishModulesRegistration(null); - eventHub.dispatch(new Event.Builder("Test1", EventType.ACQUISITION, EventSource.NONE).build()); - eventHub.dispatch(new Event.Builder("Test2", EventType.ACQUISITION, EventSource.NONE).build()); - eventHub.dispatch(new Event.Builder("Test3", EventType.ACQUISITION, EventSource.NONE).build()); - ConfigurationExtension configuration = (ConfigurationExtension) eventHub.getActiveModules().iterator().next(); - int size = eventHub.getModuleListeners(configuration).size(); - super.sleep(EVENTHUB_WAIT_MS); - configuration.replaceRulesAndReprocessEvents(new ArrayList()); - super.sleep(EVENTHUB_WAIT_MS); - eventHub.replaceRulesAndEvaluateEventsHasBeenCalled = false; - configuration.replaceRulesAndReprocessEvents(new ArrayList()); - assertEquals(0, eventHub.reprocessEventsHandler.getEvents().size()); - assertFalse(eventHub.replaceRulesAndEvaluateEventsHasBeenCalled); - assertEquals(size - 1, eventHub.getModuleListeners(configuration).size()); + assertEquals(4, eventHub.getAllEventsCount()); } @After diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ConfigurationExtension.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ConfigurationExtension.java index 855165ff8..8393b6283 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ConfigurationExtension.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ConfigurationExtension.java @@ -11,6 +11,9 @@ package com.adobe.marketing.mobile; +import com.adobe.marketing.mobile.launch.rulesengine.LaunchRulesEvaluator; +import com.adobe.marketing.mobile.launch.rulesengine.json.JSONRulesParser; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -23,7 +26,6 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicBoolean; /** * ConfigurationExtension module is responsible to retrieve the configuration for each module of the SDK, update @@ -39,22 +41,22 @@ * *

* Each method will have their own public API, which customers can use to specify how and from where the configuration can be initialized. - * + *

* The ConfigurationExtension module listens for the following {@link Event}s: *

    *
  1. {@link EventType#CONFIGURATION} - {@link EventSource#REQUEST_CONTENT}
  2. *
  3. {@code EventType.CONFIGURATION} - {@link EventSource#REQUEST_IDENTITY}
  4. *
  5. {@link EventType#LIFECYCLE} - {@link EventSource#RESPONSE_CONTENT}
  6. - *
  7. {@link EventType#HUB} - {@link EventSource#BOOTED}
  8. - *
- * + *
  • {@link EventType#HUB} - {@link EventSource#BOOTED}
  • + * + *

    * The ConfigurationExtension module dispatches the following {@code Event}s: *

      *
    1. {@code EventType.CONFIGURATION} - {@code EventSource.RESPONSE_CONTENT}
    2. *
    3. {@code EventType.CONFIGURATION} - {@code EventSource.REQUEST_CONTENT}
    4. *
    5. {@code EventType.CONFIGURATION} - {@link EventSource#RESPONSE_IDENTITY}
    6. *
    - * + *

    * The ConfigurationExtension module has dependencies on the following {@link PlatformServices}: *

      *
    1. {@link LocalStorageService}
    2. @@ -64,1266 +66,1147 @@ *
    */ class ConfigurationExtension extends InternalModule { - static final String LOG_SOURCE = ConfigurationExtension.class.getSimpleName(); - - private static final String CONFIGURATION_URL_BASE = "https://assets.adobedtm.com/%s.json"; - - private static final String CONFIG_BUNDLED_FILE_NAME = "ADBMobileConfig.json"; - private static final String CONFIG_MANIFEST_APPID_KEY = "ADBMobileAppID"; - private static final String CONFIG_REMOTE_SERVER = "com.adobe.marketing.mobile.RemoteConfigServer"; - - private static final String DATASTORE_KEY = "AdobeMobile_ConfigState"; - private static final String PERSISTED_OVERRIDDEN_CONFIG = "config.overridden.map"; - private static final String PERSISTED_APPID = "config.appID"; - private static final String PERSISTED_RULES_URL = "config.last.rules.url"; - - private static final String RULES_CACHE_FOLDER = "configRules"; - private static final String RULES_JSON_FILE_NAME = "rules.json"; - private static final String RULES_JSON_KEY = "rules"; - private static final String RULES_JSON_CONDITION_KEY = "condition"; - private static final String RULES_JSON_CONSEQUENCES_KEY = "consequences"; - - private static final int DEFAULT_NOT_DOWNLOAD_RULES_WITHIN_TIME_SEC = 15; - static int NOT_DOWNLOAD_RULES_WITHIN_TIME_SEC = DEFAULT_NOT_DOWNLOAD_RULES_WITHIN_TIME_SEC; - - - private final ConfigurationDispatcherConfigurationRequestContent requestDispatcher; - private final ConfigurationDispatcherConfigurationResponseContent responseDispatcher; - private final ConfigurationDispatcherConfigurationResponseIdentity responseIdentityDispatcher; - final ConcurrentLinkedQueue getsdkIdsEventQueue; - private ConfigurationData currentConfig; - /// The persisted programmatic config or an empty config if none is found - private ConfigurationData programmaticConfig; - // The configuration without a merge from programmaticConfig, needed for clearing the config - private ConfigurationData unmergedConfiguration; - private boolean isUnregistered; - private ConcurrentHashMap cachedRulesDownloadTime; - final private List cachedEvents; - private AtomicBoolean needToProcessEvents = new AtomicBoolean(true); - - private final ExecutorService rulesDownloadExecutor; - /** - * Returns an instance of the ConfigurationExtension Module. Adds a RequestContentListener to the provided EventHub - * - * @param eventHub an EventHub to be used by the module - * @param services an instance of PlatformServices to be used by the module - * - * @see EventHub - * @see PlatformServices - */ - public ConfigurationExtension(final EventHub eventHub, final PlatformServices services) { - super(ConfigurationConstants.EventDataKeys.Configuration.MODULE_NAME, eventHub, services); - getsdkIdsEventQueue = new ConcurrentLinkedQueue(); - cachedRulesDownloadTime = new ConcurrentHashMap(); - - // register listeners - registerListener(EventType.CONFIGURATION, EventSource.REQUEST_CONTENT, ConfigurationListenerRequestContent - .class); - registerListener(EventType.HUB, EventSource.BOOTED, ConfigurationListenerBootEvent.class); - registerListener(EventType.CONFIGURATION, EventSource.REQUEST_IDENTITY, ConfigurationListenerRequestIdentity.class); - - registerWildcardListener(ConfigurationWildCardListener.class); - - // create Dispatcher - - requestDispatcher = createRequestDispatcher(); - responseDispatcher = createResponseDispatcher(); - responseIdentityDispatcher = createResponseIdentityDispatcher(); - - rulesDownloadExecutor = Executors.newSingleThreadExecutor(); - this.cachedEvents = Collections.synchronizedList(new ArrayList()); - } - - ConfigurationDispatcherConfigurationResponseIdentity createResponseIdentityDispatcher() { - return createDispatcher(ConfigurationDispatcherConfigurationResponseIdentity.class); - } - - ConfigurationDispatcherConfigurationRequestContent createRequestDispatcher() { - return createDispatcher(ConfigurationDispatcherConfigurationRequestContent.class); - } - - ConfigurationDispatcherConfigurationResponseContent createResponseDispatcher() { - return createDispatcher(ConfigurationDispatcherConfigurationResponseContent.class); - } - - // ======================================================== - // Event Handlers - // ======================================================== - - /** - * Handler for {@code EventType.CONFIGURATION} {@code EventSource.REQUEST_IDENTITY} {@code Event}. - *

    - * This event is generated when the {@code getSDKIdentities} public API is called. - * - *

    - * ConfigurationExtension module attempts to read all the identities known to the SDK. It then generates - * a {@code EventType.CONFIGURATION} {@code EventSource.RESPONSE_IDENTITY} response {@code Event} with all - * the obtained identities in a JSON {@code String} format. - * - * @param event An Configuration Request Identity event - * @see MobileIdentities - */ - void handleGetSdkIdentitiesEvent(final Event event) { - getExecutor().execute(new Runnable() { - @Override - public void run() { - getsdkIdsEventQueue.add(event); - processGetSdkIdsEvent(); - } - }); - } - - /** - * Handler for the Boot event created by EventHub. - * - * If AppId present, create a configureWithAppId request content event and then attempt to load configuration in the following - * order - * 1. cachedFile - * 2. Bundled - * 3. Overridden Config - * - * If No AppId, attempt to load the bundled/overridden configuration. - * - * @param event boot configuration event, which is generated when the module is initialized. - */ - void handleBootEvent(final Event event) { - retrieveCachedRules(retrieveRulesURLFromPersistence()); - processBootEvent(event); - } - - void handleWildcardEvent(final Event event) { - this.cachedEvents.add(event); - } - - /** - * Processes the Events generated by {@code getSDKIdentities} public API. - *

    - * Calls the callback with empty {@code String} if {@link JsonUtilityService} is not available. - * Queues the {@link Event} in the {@link #getsdkIdsEventQueue} if the one of the requires shared state is in {@link EventHub#SHARED_STATE_PENDING}. - * Dispatches the paired {@code EventType.CONFIGURATION}, {@code EventSource.RESPONSE_IDENTITY} event into the {@link EventHub} - * with {@link String} identities JSON. - */ - void processGetSdkIdsEvent() { - while (!getsdkIdsEventQueue.isEmpty()) { - final Event event = getsdkIdsEventQueue.peek(); - - - // check if jsonUtility service is available to process the event. - // If unavailable call the callback with empty string and remove the event from the queue - JsonUtilityService jsonUtilityService = getJSONUtilityService(); - - if (jsonUtilityService == null) { - Log.debug(LOG_SOURCE, "%s (JSON Utility Service), unable to retrieve sdk identities", Log.UNEXPECTED_NULL_VALUE); - responseIdentityDispatcher.dispatchAllIdentities("{}", event.getResponsePairID()); - getsdkIdsEventQueue.poll(); - continue; - } - - // verify that if all the required shared state is not in pending. - // If not, break out of the loop and wait for the shared state update event to happen. - if (!MobileIdentities.areAllSharedStatesReady(event, this)) { - break; - } - - final String allIds = MobileIdentities.getAllIdentifiers(jsonUtilityService, event, this); - responseIdentityDispatcher.dispatchAllIdentities(allIds, event.getResponsePairID()); - - getsdkIdsEventQueue.poll(); - } - } - - private void processBootEvent(final Event event) { - Log.trace(ConfigurationExtension.LOG_SOURCE, "Processing boot configuration event"); - - // programmed config from the persistence - retrieveProgrammaticConfigFromPersistence(); - - String appId = getValidAppID(); - - // If appID is present from any source. - if (!StringUtils.isNullOrEmpty(appId)) { - - // dispatch an configureWithAppID internal event if we have a valid appId(From manifest or persistence). - // We dispatch this during initialization just to prevent other configureWithAppId calls occurring before this event. - requestDispatcher.dispatchInternalConfigureWithAppIdEvent(appId); - - // Create a shared state with cached config, if present - if (loadCachedConfig(appId, event)) { - return; - } - } - - // If the app is not configured with an AppID (OR) if the cachedConfig for the appID is missing - // attempt to load the bundled configuration - if (loadBundledConfig(event, CONFIG_BUNDLED_FILE_NAME)) { - return; - } - - // If the application is not configured with an AppID and has no bundled configuration. - // Just load the programmatic config - if (loadProgrammaticConfig(event)) { - return; - } - } - - /** - * Unpacks the configuration request content event and processes them according to their event data key. - * - * @param event a configuration request content event. - */ - void handleEvent(final Event event) { - Log.trace(ConfigurationExtension.LOG_SOURCE, "Handling the configuration event: %s", event.getEventNumber()); - EventData eventData = event.getData(); - - // if the event has an app id. try to fetch from the remote - if (eventData.containsKey( - ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID)) { - getExecutor().execute(new Runnable() { - @Override - public void run() { - processConfigureWithAppIDEvent(event); - } - }); - } - // if the event has a path to a file in asset folder, try to load configuration from the file - else if (eventData.containsKey( - ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_ASSET_FILE)) { - getExecutor().execute(new Runnable() { - @Override - public void run() { - - final String fileName = event.getData().optString( - ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_ASSET_FILE, null); - - loadBundledConfig(event, fileName); - } - }); - } - // if the event has a path to a file, try to load configuration from the file - else if (eventData.containsKey( - ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_FILE_PATH)) { - getExecutor().execute(new Runnable() { - @Override - public void run() { - processConfigWithFilePathEvent(event); - } - }); - } - // if the event has a manually configured information, try to override with existing configuration - else if (event.getData().containsKey( - ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_UPDATE_CONFIG)) { - getExecutor().execute(new Runnable() { - @Override - public void run() { - processUpdateConfigEvent(event); - } - }); - - - } else if (event.getData().containsKey( - ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_CLEAR_UPDATED_CONFIG)) { - getExecutor().execute(new Runnable() { - @Override - public void run() { - processClearUpdatedConfigEvent(event); - } - }); - } else if (event.getData().containsKey( - ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_RETRIEVE_CONFIG)) { - // do not create shared state here, this is just a request for information - getExecutor().execute(new Runnable() { - @Override - public void run() { - processPublishConfigurationEvent(event); - } - }); - } - - } - - /** - * Attempts to load a configuration from the provide file path - * - * @param event event which triggered the configuration request - */ - void processConfigWithFilePathEvent(final Event event) { - final String filePath = event.getData().optString( - ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_FILE_PATH, null); - - // make sure our filePath is valid - if (StringUtils.isNullOrEmpty(filePath)) { - Log.warning(ConfigurationExtension.LOG_SOURCE, - "Unable to read config from provided file (filePath is invalid)"); - return; - } - - Log.trace(ConfigurationExtension.LOG_SOURCE, "Processing configWithFilePath Event. \n %s", filePath); - final String jsonConfigString = FileUtil.readStringFromFile(new File(filePath)); - - Log.trace(ConfigurationExtension.LOG_SOURCE, "Configuration obtained from filePath %s is \n %s", filePath, - jsonConfigString); - configureWithJsonString(jsonConfigString, event, true); // state already created with null data - - } - - /** - * Unpacks the update configuration request content event. The newConfiguration data is added over the existing - * configuration and a Configuration response event is created. - * - * @param event an update Configuration event. - */ - @SuppressWarnings("unchecked") - void processUpdateConfigEvent(final Event event) { - // update the programmaticConfig with the new config from API and persist them in disk - Map newProgrammaticConfig = event.getData().optVariantMap( - ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_UPDATE_CONFIG, null); - - // if update config is not provided or is empty, abort update config request. - if (newProgrammaticConfig == null || newProgrammaticConfig.isEmpty()) { - Log.debug(LOG_SOURCE, "Configuration update data was either not provided in event or is empty."); - return; - } - - Log.trace(ConfigurationExtension.LOG_SOURCE, "Processing updateConfiguration Event. \n %s", newProgrammaticConfig); - // Retrieve the latest programmatic config value from the persistence. - retrieveProgrammaticConfigFromPersistence(); - - programmaticConfig.put(newProgrammaticConfig); - - saveProgrammaticConfigToPersistence(programmaticConfig); - - if (currentConfig == null) { - if (getJSONUtilityService() == null) { - return; - } - - currentConfig = new ConfigurationData(getJSONUtilityService()); - } - - currentConfig.put(programmaticConfig); - - changeConfiguration(event, currentConfig, true); - } - - void processClearUpdatedConfigEvent(final Event event) { - Log.trace(ConfigurationExtension.LOG_SOURCE, "Processing clear updated configuration event"); - - if (unmergedConfiguration == null) { - if (getJSONUtilityService() == null) { - return; - } - - unmergedConfiguration = new ConfigurationData(getJSONUtilityService()); - } - - // remove overridden configuration from persistence - removeProgrammaticConfigFromPersistence(); - - // clear value stored in programmaticConfig variable - retrieveProgrammaticConfigFromPersistence(); - - // set currentConfig value to unmergedConfig - currentConfig = unmergedConfiguration; - - // Update the shared state and dispatch a event clearing updated configuration - changeConfiguration(event, unmergedConfiguration, true); - } - - /** - * Process the configuration request content events with appId. Tries to retrieve the latest configuration - * from the remote. - * - * @param event the configureWithAppIDEvent - */ - void processConfigureWithAppIDEvent(final Event event) { - final EventData eventData = event.getData(); - - if (eventData == null) { - Log.trace(LOG_SOURCE, "%s (event data), for ConfigureWithAppID event, Ignoring event", Log.UNEXPECTED_NULL_VALUE); - return; - } - - final String newAppId = event.getData().getString( - ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID); - - if (StringUtils.isNullOrEmpty(newAppId)) { - Log.trace(LOG_SOURCE, "App ID was null or empty while processing ConfigureWithAppID event"); - removeAppIdFromPersistence(); - return; - } - - if (!validateForInternalEventAppIDChange(eventData, newAppId)) { - Log.trace(LOG_SOURCE, "App ID is changed. Ignoring the setAppID Internal event %s", newAppId); - return; - } - - Log.trace(ConfigurationExtension.LOG_SOURCE, "Processing configureWithAppID event. AppID -(%s)", newAppId); - saveAppIdToPersistence(newAppId); - - // get a downloader instance - ConfigurationDownloader configurationDownloader = getConfigDownloader(newAppId); - - if (configurationDownloader == null) { - Log.trace(LOG_SOURCE, "%s (Configuration Downloader).", Log.UNEXPECTED_NULL_VALUE); - return; - } - - // first, just blindly try to download the config - String jsonString = configurationDownloader.downloadConfig(); - - if (StringUtils.isNullOrEmpty(jsonString)) { - // download failed - // try to get a cached config - jsonString = configurationDownloader.loadCachedConfig(); - } - - if (StringUtils.isNullOrEmpty(jsonString)) { - // no cached config - // if the network is down, wait for it to come back up - final PlatformServices platformServices = getPlatformServices(); - final SystemInfoService systemInfoService = platformServices == null ? null : platformServices.getSystemInfoService(); - final boolean networkIsDown = ( - systemInfoService != null && - systemInfoService.getNetworkConnectionStatus() != SystemInfoService.ConnectionStatus.CONNECTED); - - if (networkIsDown && waitForNetworkConnection()) { - // try once more after network is back up - jsonString = configurationDownloader.downloadConfig(); - } - } - - if (StringUtils.isNullOrEmpty(jsonString)) { - Log.warning(LOG_SOURCE, "Unable to fetch config. Rolling back to previous configuration."); - return; - } - - - // Update the shared state and dispatch a event with the received jsonString. - configureWithJsonString(jsonString, event, true); - } - - boolean waitForNetworkConnection() { - final int WAIT_FOR_NETWORK_POLL_MS = 1000; - final PlatformServices platformServices = getPlatformServices(); - - if (platformServices == null) { - return false; - } - - final SystemInfoService systemInfoService = platformServices.getSystemInfoService(); - - if (systemInfoService == null) { - return false; - } - - class State extends Object { - public boolean isListenerRegistered = false; - }; - - final State state = new State(); - - while (true) { - synchronized (this) { - if (isUnregistered) { - return false; - } - } - - final SystemInfoService.ConnectionStatus connectionStatus = systemInfoService.getNetworkConnectionStatus(); - - if (connectionStatus == SystemInfoService.ConnectionStatus.CONNECTED) { - return true; - } - - // DISCONNECTED - - synchronized (state) { - if (!state.isListenerRegistered) { - state.isListenerRegistered = true; - systemInfoService.registerOneTimeNetworkConnectionActiveListener(new - SystemInfoService.NetworkConnectionActiveListener() { - @Override - public final void onActive() { - synchronized (state) { - state.notifyAll(); - state.isListenerRegistered = false; - } - } - }); - } - - try { - state.wait(WAIT_FOR_NETWORK_POLL_MS); - } catch (final InterruptedException e) { - continue; - } - } - } - } - - @Override - protected void onUnregistered() { - synchronized (this) { - isUnregistered = true; - } - } - - /** - * Retrieves current configuration through a new configuration response event. - * - * @param event the publish configuration event - */ - void processPublishConfigurationEvent(final Event event) { - Log.trace(ConfigurationExtension.LOG_SOURCE, "Processing publish configuration event"); - - - if (getJSONUtilityService() == null) { - return; - } - - ConfigurationData newConfigData = new ConfigurationData(getJSONUtilityService()) - .put(currentConfig).put(programmaticConfig); - - responseDispatcher.dispatchConfigResponseWithEventData(newConfigData.getEventData(), - event.getResponsePairID()); - } - - /** - * Dispatches a ConfigurationResponseContent event with the provided JSON string. - * Overrides with the programmed configuration if they exist. - * - * @param jsonConfigString a String containing the JSON configuration - * @param event event which triggered the configuration request - * @param loadRules if set to true, will try to load the remote rules - */ - void configureWithJsonString(final String jsonConfigString, - final Event event, - final boolean loadRules) { - - if (getJSONUtilityService() == null) { - return; - } - - // Convert the JSONString into an EventData and save it in memory - ConfigurationData newConfig = new ConfigurationData(getJSONUtilityService()) - .put(jsonConfigString); - - if (newConfig.isEmpty()) { - Log.debug(LOG_SOURCE, "Empty configuration found when processing JSON string."); - return; - } - - retrieveProgrammaticConfigFromPersistence(); - - unmergedConfiguration = new ConfigurationData(getJSONUtilityService()).put(jsonConfigString); - currentConfig = newConfig; - - // Before dispatching override the primary configuration with the programmatic configuration. - currentConfig.put(programmaticConfig); - - changeConfiguration(event, currentConfig, loadRules); - - } - - - // ======================================================== - // Other Helper Methods - // ======================================================== - - /** - * Performs modifications to configuration - * - * @param triggerEvent {@code Event} that the configuration change should be valid for - * @param configurationData {@code ConfigurationData} object containing the new configuration - * @param loadRules {@code boolean} if we should load the rules - */ - private void changeConfiguration(final Event triggerEvent, final ConfigurationData configurationData, - final boolean loadRules) { - final EventData configEventData = configurationData.getEventData(); - - - createSharedState(triggerEvent.getEventNumber(), configEventData); - Log.trace(LOG_SOURCE, "Shared state is created for event number %d with data \n %s", triggerEvent.getEventNumber(), - configEventData); - - if (loadRules) { - final String remoteRulesURL = configurationData.getEventData().optString( - ConfigurationConstants.EventDataKeys.Configuration.RULES_CONFIG_URL, ""); - this.rulesDownloadExecutor.execute(new Runnable() { - @Override - public void run() { - downloadRules(remoteRulesURL); - } - }); - } - - responseDispatcher.dispatchConfigResponseWithEventData(configEventData, triggerEvent.getPairID()); - } - - /** - * Sort the preference and returns the valid app Id used - * - * @return {@code String} application identifier - */ - private String getValidAppID() { - // return the persisted appId if we have - String appId = retrieveAppIdFromPersistence(); - - if (!StringUtils.isNullOrEmpty(appId)) { - Log.trace(ConfigurationExtension.LOG_SOURCE, "Valid AppID is retrieved from persistence - %s", appId); - return appId; - } - - // else try to fetch the appID from manifest - return getAppIDFromManifest(); - } - - /** - * Attempts to configure with the bundled configuration. - * - * @param event event which triggered the configruation request. - * @param fileName the name of the asset file - * - * @return {@code boolean} true if the configuration is successfully loaded, false otherwise - */ - protected boolean loadBundledConfig(final Event event, final String fileName) { - String bundledConfigContent = readContentFromAsset(fileName); - - if (bundledConfigContent == null) { - Log.trace(ConfigurationExtension.LOG_SOURCE, "%s (Failed to read bundled config file content from asset file %s)", - Log.UNEXPECTED_NULL_VALUE, fileName); - return false; - } - - Log.debug(ConfigurationExtension.LOG_SOURCE, "Bundled configuration loaded from asset file (%s). \n %s", fileName, - bundledConfigContent); - configureWithJsonString(bundledConfigContent, event, true); - return true; - } - - - /** - * Attempts to configure with the cached file. - * - * @param appID associated appID of that cache file. - * @param event event which triggered the configruation request. - * - * @return {@code boolean} true if the cached file is successfully loaded, false otherwise - */ - private boolean loadCachedConfig(final String appID, final Event event) { - ConfigurationDownloader configurationDownloader = getConfigDownloader(appID); - - if (configurationDownloader == null) { - return false; - } - - String cachedConfigJSON = configurationDownloader.loadCachedConfig(); - - if (StringUtils.isNullOrEmpty(cachedConfigJSON)) { - Log.trace(ConfigurationExtension.LOG_SOURCE, "Nothing is loaded from cached file"); - return false; - } - - Log.debug(ConfigurationExtension.LOG_SOURCE, "Cached configuration loaded. \n %s", cachedConfigJSON); - configureWithJsonString(cachedConfigJSON, event, false); - return true; - - } - - /** - * Attempts to configure with the programmatic configuration. - * - * @param event event which triggered the configuration request. - * - * @return {@code boolean} true if succeeded, false otherwise - */ - private boolean loadProgrammaticConfig(final Event event) { - if (programmaticConfig.isEmpty()) { - return false; - } - - changeConfiguration(event, programmaticConfig, true); - return true; - } - - - /** - * Provides you the instance of the configuration downloader with the specified url. - * - * @param appId the appID required to build the url for the downloader. - * - * @return {@code ConfigurationDownloader} configuration downloader instance, or null if an error occurs - * attempting to create the ConfigurationDownloader instance. - */ - ConfigurationDownloader getConfigDownloader(final String appId) { - - if (getPlatformServices() == null) { - Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (Platform services)", Log.UNEXPECTED_NULL_VALUE); - return null; - } - - if (getPlatformServices().getSystemInfoService() == null) { - Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (System Info services)", Log.UNEXPECTED_NULL_VALUE); - return null; - } - - // check whether manifest overrides the config endpoint - String requestUrlString = String.format(CONFIGURATION_URL_BASE, appId); - final SystemInfoService systemInfoService = getPlatformServices().getSystemInfoService(); - - if (systemInfoService != null) { - String remoteConfigServer = systemInfoService.getProperty(CONFIG_REMOTE_SERVER); - - if (!StringUtils.isNullOrEmpty(remoteConfigServer)) { - requestUrlString = String.format(remoteConfigServer, appId); - } - } - - if (getPlatformServices().getNetworkService() == null) { - Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (Network services)", Log.UNEXPECTED_NULL_VALUE); - return null; - } - - // Build a Configuration Downloader - try { - return new ConfigurationDownloader(getPlatformServices().getNetworkService(), - getPlatformServices().getSystemInfoService(), requestUrlString); - } catch (MissingPlatformServicesException exp) { - Log.warning(LOG_SOURCE, "Unable to Initialize Downloader (%s)", exp); - return null; - } - } - - // ======================================================== - // Platform resource getters - // ======================================================== - - /** - * Reads the AppId from the manifest file. - * Returns null if appId is not found. - * - * @return {@code String} application identifier from manifest file - */ - private String getAppIDFromManifest() { - - if (getPlatformServices() == null) { - Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (Platform services)", Log.UNEXPECTED_NULL_VALUE); - return null; - } - - if (getPlatformServices().getSystemInfoService() == null) { - Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (System Info services)", Log.UNEXPECTED_NULL_VALUE); - return null; - } - - final SystemInfoService systemInfoService = getPlatformServices().getSystemInfoService(); - - if (systemInfoService == null) { - Log.debug(ConfigurationExtension.LOG_SOURCE, - "%s (System info service), unable to read AppID from manifest", Log.UNEXPECTED_NULL_VALUE); - return null; - } - - final String manifestAppId = systemInfoService.getProperty(CONFIG_MANIFEST_APPID_KEY); - - if (StringUtils.isNullOrEmpty(manifestAppId)) { - return null; - } - - Log.trace(ConfigurationExtension.LOG_SOURCE, " Valid AppID is retrieved from manifest - %s", manifestAppId); - saveAppIdToPersistence(manifestAppId); - return manifestAppId; - } - - - /** - * Reads the bundled ADBMobileConfig.json file from the assets folder. - * Returns null if the Bundled file is not found. - * - * @return {@code String} bundled file content - */ - private String readContentFromAsset(final String fileName) { - if (StringUtils.isNullOrEmpty(fileName)) { - Log.debug(ConfigurationExtension.LOG_SOURCE, "Invalid asset file name."); - return null; - } - - if (getPlatformServices() == null) { - Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (Platform services)", Log.UNEXPECTED_NULL_VALUE); - return null; - } - - final SystemInfoService systemInfoService = getPlatformServices().getSystemInfoService(); - - if (systemInfoService == null) { - Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (System info services), unable to read bundled configuration", - Log.UNEXPECTED_NULL_VALUE); - return null; - } - - final InputStream configStream = systemInfoService.getAsset(fileName); - - if (configStream == null) { - return null; - } - - return StringUtils.streamToString(configStream); - } - - // ======================================================== - // private methods - // ======================================================== - - - /** - * Load the overriddenConfiguration from persistent storage. - */ - private void retrieveProgrammaticConfigFromPersistence() { - - if (getJSONUtilityService() == null) { - return; - } - - this.programmaticConfig = new ConfigurationData(getJSONUtilityService()); - LocalStorageService.DataStore configStore = getDataStore(); - - if (configStore != null) { - String jsonString = configStore.getString(PERSISTED_OVERRIDDEN_CONFIG, null); - Log.trace(ConfigurationExtension.LOG_SOURCE, "Loading overridden configuration from persistence - \n %s", jsonString); - this.programmaticConfig = new ConfigurationData(getJSONUtilityService()).put(jsonString); - } else { - Log.debug(ConfigurationExtension.LOG_SOURCE, - "%s (Local storage service), unable to load overridden config from persistence", Log.UNEXPECTED_NULL_VALUE); - } - } - - - /** - * Saves the overriddenConfig map in the persistence under the Configuration DataStore. - * Used to persist the programmed Configuration between launches - * - * @param overriddenConfig new overriddenConfig map that needs to be saved - */ - private void saveProgrammaticConfigToPersistence(final ConfigurationData overriddenConfig) { - LocalStorageService.DataStore configStore = getDataStore(); - - if (configStore != null) { - Log.trace(ConfigurationExtension.LOG_SOURCE, "Saving the overridden configuration to persistence - \n %s", - overriddenConfig); - configStore.setString(PERSISTED_OVERRIDDEN_CONFIG, overriddenConfig.getJSONString()); - } else { - Log.debug(ConfigurationExtension.LOG_SOURCE, - "%s (Local storage service), unable to save overridden config to persistence", Log.UNEXPECTED_NULL_VALUE); - } - } - - /** - * Removes the overriddenConfig map from the persistence under the Configuration Datastore - */ - private void removeProgrammaticConfigFromPersistence() { - LocalStorageService.DataStore configStore = getDataStore(); - - if (configStore != null) { - Log.trace(ConfigurationExtension.LOG_SOURCE, "Removing overridden configuration from persistence"); - configStore.remove(PERSISTED_OVERRIDDEN_CONFIG); - } else { - Log.debug(ConfigurationExtension.LOG_SOURCE, - "%s (Storage Service), unable to remove overridden configuration from persistence", - Log.UNEXPECTED_NULL_VALUE); - } - } - - - - /** - * Load the appId from persistent storage. - * - * @return {@code String} application identifier from persistence, or null - */ - private String retrieveAppIdFromPersistence() { - LocalStorageService.DataStore configStore = getDataStore(); - - if (configStore != null) { - final String persistedAppID = configStore.getString(PERSISTED_APPID, null); - Log.trace(ConfigurationExtension.LOG_SOURCE, "AppID loaded from persistence - %s", persistedAppID); - return persistedAppID; - } else { - Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (Storage Service), unable to load appId from persistence", - Log.UNEXPECTED_NULL_VALUE); - return null; - } - } - - - /** - * Saves the appID in the persistence under the Configuration DataStore. - * Used to persist the appID between launches to load the cached configuration. - * - * @param appID appID that needs to be saved - */ - private void saveAppIdToPersistence(final String appID) { - LocalStorageService.DataStore configStore = getDataStore(); - - if (configStore != null) { - Log.trace(ConfigurationExtension.LOG_SOURCE, "Saving appID to persistence - %s", appID); - configStore.setString(PERSISTED_APPID, appID); - } else { - Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (Storage Service), unable to save appId to persistence", - Log.UNEXPECTED_NULL_VALUE); - } - } - - /** - * Removes the appId from the persistence under the Configuration Datastore - */ - private void removeAppIdFromPersistence() { - LocalStorageService.DataStore configStore = getDataStore(); - - if (configStore != null) { - Log.trace(ConfigurationExtension.LOG_SOURCE, "Removing appID from persistence"); - configStore.remove(PERSISTED_APPID); - } else { - Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (Storage Service), unable to remove appId from persistence", - Log.UNEXPECTED_NULL_VALUE); - } - } - - /** - * Load the last known rules URL from persistent storage. - * - * @return {@code String} last known rules URL from persistence, or null - */ - private String retrieveRulesURLFromPersistence() { - LocalStorageService.DataStore configStore = getDataStore(); - - if (configStore != null) { - final String persistedRulesUrl = configStore.getString(PERSISTED_RULES_URL, null); - Log.trace(ConfigurationExtension.LOG_SOURCE, "Last known rules URL loaded from persistence - %s", persistedRulesUrl); - return persistedRulesUrl; - } else { - Log.debug(ConfigurationExtension.LOG_SOURCE, - "%s (Storage Service), unable to load the last known rules URL from persistence", Log.UNEXPECTED_NULL_VALUE); - return null; - } - } - - - /** - * Saves the last known rules URL in the persistence under the Configuration DataStore. - * - * @param rulesURL the last known rules URL that needs to be saved - */ - private void saveRulesURLToPersistence(final String rulesURL) { - LocalStorageService.DataStore configStore = getDataStore(); - - if (configStore != null) { - Log.trace(ConfigurationExtension.LOG_SOURCE, "Saving last known rules URL to persistence - %s", rulesURL); - configStore.setString(PERSISTED_RULES_URL, rulesURL); - } else { - Log.debug(ConfigurationExtension.LOG_SOURCE, - "%s (Storage Service), unable to save the last known rules URL to persistence", Log.UNEXPECTED_NULL_VALUE); - } - } - - /** - * The purpose of the SetAppIDInternalEvent is to refresh the existing with the persisted appId - * This method validates the appId for the SetAppIDInternalEvent - * It return true, if the persisted appId is same as the internalEvent appId present in the eventData - * It return false, if the persisted appId is different from the internalEvent appId present in the eventData - * - * @param eventData The {@link EventData} associated to the set internal appId event - * @param newAppID A {@link String} appId associated with set internal appId event - * @return A {@code boolean} indicating if there is a change in appID for the SetAppIDInternalEvent - */ - private boolean validateForInternalEventAppIDChange(final EventData eventData, final String newAppID) { - boolean isInternalEvent = eventData.optBoolean( - ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT, false); - - if (isInternalEvent && (!newAppID.equals(getValidAppID()))) { - return false; - } - - return true; - } - - - /** - * Returns the configuration datastore - * - * @return {@code LocalStorageService.DataStore} configuration datastore or null - */ - private LocalStorageService.DataStore getDataStore() { - - if (getPlatformServices() == null) { - Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (Platform services)", Log.UNEXPECTED_NULL_VALUE); - return null; - } - - if (getPlatformServices(). getLocalStorageService() == null) { - Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (Local Storage services)", Log.UNEXPECTED_NULL_VALUE); - return null; - } - - return getPlatformServices().getLocalStorageService().getDataStore(DATASTORE_KEY); - } - - /** - * Method to obtain the {@code JsonUtilityService} instance. - * - *

    - * Returns null if the {@code PlatformServices} is null or the {@code JsonUtilityService} obtained from the - * {@code PlatformServices} is null. - * - * @return {@link JsonUtilityService} instance - */ - private JsonUtilityService getJSONUtilityService() { - if (getPlatformServices() == null) { - Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (Platform services)", Log.UNEXPECTED_NULL_VALUE); - return null; - } - - if (getPlatformServices().getJsonUtilityService() == null) { - Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (JSON Utility services)", Log.UNEXPECTED_NULL_VALUE); - return null; - } - - return getPlatformServices().getJsonUtilityService(); - } - - // ======================================================== - // rules retrieval stuff - // ======================================================== - - /** - * Downloads and registers remote rules (asynchronously via getExecutor) - * - * @param remoteRulesURL the url of remote rules - */ - private void downloadRules(final String remoteRulesURL) { - - long currentTimeSec = TimeUtil.getUnixTimeInSeconds(); - Long lastDownloadTimeSec = cachedRulesDownloadTime.get(remoteRulesURL); - - if ((lastDownloadTimeSec != null) && (currentTimeSec - lastDownloadTimeSec < NOT_DOWNLOAD_RULES_WITHIN_TIME_SEC)) { - Log.debug(ConfigurationExtension.LOG_SOURCE, "Will not download rules from same url in 30 sec. "); - return; - } - - cachedRulesDownloadTime.put(remoteRulesURL, currentTimeSec); - - saveRulesURLToPersistence(remoteRulesURL); - - if (StringUtils.isNullOrEmpty(remoteRulesURL)) { - return; - } - - final PlatformServices platformServices = getPlatformServices(); - - if (platformServices == null) { - return; - } - - try { - final RulesRemoteDownloader remoteDownloader = new RulesRemoteDownloader( - platformServices.getNetworkService(), - platformServices.getSystemInfoService(), - platformServices.getCompressedFileService(), - remoteRulesURL, RULES_CACHE_FOLDER); - final File outputFile = remoteDownloader.startDownloadSync(); - onRulesDownloaded(outputFile); - } catch (final MissingPlatformServicesException e) { - Log.debug(LOG_SOURCE, "Unable to download remote rules. Exception: %s", e); - } - } - - /** - * Called when rules have completed downloading - * - * @param rulesDirectory File object containing the directory that the rules bundle is in - */ - private void onRulesDownloaded(final File rulesDirectory) { - - - // check if we downloaded a valid file - if (rulesDirectory == null || !rulesDirectory.isDirectory()) { - // clear out existing rules - unregisterAllRules(); - return; - } - - final String rulesFilePath = rulesDirectory.getPath() + File.separator + RULES_JSON_FILE_NAME; - final File rulesFile = new File(rulesFilePath); - final String jsonString = readFromFile(rulesFile); - - // read new object - final JsonUtilityService.JSONObject rulesJsonObject = getPlatformServices() - .getJsonUtilityService() - .createJSONObject(jsonString); - - replaceRulesAndReprocessEvents(parseRulesFromJsonObject(rulesJsonObject)); - - } - - - /** - * Call this method to replace module rules in Event hub, then notify Event hub to reprocess - * (cached) custom events with given rules if it's the fist time you call this method. - * - * @param rules loaded from remote or local files - */ - void replaceRulesAndReprocessEvents(final List rules) { - if (this.needToProcessEvents.getAndSet(false)) { - replaceRulesAndEvaluateEvents(rules, new ReprocessEventsHandler() { - @Override - public List getEvents() { - return new ArrayList(cachedEvents); - } - - @Override - public void onEventReprocessingComplete() { - unregisterWildcardListener(); - cachedEvents.clear(); - } - }); - } else { - replaceRules(rules); - } - } - - /** - * Reads a file from disk and returns its contents as {@code String}. - * - * @param rulesJsonFile the file object to read - * - * @return the file contents as {@code String} - */ - private String readFromFile(final File rulesJsonFile) { - String json = null; - - if (rulesJsonFile != null) { - FileInputStream rulesJsonIS = null; - - try { - rulesJsonIS = new FileInputStream(rulesJsonFile); - json = StringUtils.streamToString(rulesJsonIS); - } catch (IOException ex) { - Log.debug(LOG_SOURCE, "Could not read the rules json file! Exception: (%s)", ex); - } finally { - try { - if (rulesJsonIS != null) { - rulesJsonIS.close(); - } - } catch (Exception e) { - Log.trace(LOG_SOURCE, "Failed to close stream for %s, with Exception: %s", rulesJsonFile, e); - } - } - } - - return json; - } - - /** - * Parses all rules and resulting consequence events from the jsonObject - * - * @param jsonObject {@code JSONObject} containing the list of rules and consequences - * - * @return a {@code List} of {@code Rule} objects that were parsed from the input object - */ - private List parseRulesFromJsonObject(final JsonUtilityService.JSONObject jsonObject) { - final List parsedRules = new ArrayList(); - - if (jsonObject == null) { - return parsedRules; - } - - JsonUtilityService.JSONArray rulesJsonArray; - - try { - rulesJsonArray = jsonObject.getJSONArray(RULES_JSON_KEY); - } catch (final JsonException e) { - Log.debug(LOG_SOURCE, "Unable to parse rules. Exception: (%s)", e); - return parsedRules; - } - - // loop through each rule definition - for (int i = 0; i < rulesJsonArray.length(); i++) { - try { - // get individual rule json object - final JsonUtilityService.JSONObject ruleObject = rulesJsonArray.getJSONObject(i); - // get rule condition - final JsonUtilityService.JSONObject ruleConditionJsonObject = ruleObject.getJSONObject(RULES_JSON_CONDITION_KEY); - final RuleCondition condition = RuleCondition.ruleConditionFromJson(ruleConditionJsonObject); - // get consequences - final List consequences = generateConsequenceEvents(ruleObject.getJSONArray(RULES_JSON_CONSEQUENCES_KEY)); - - parsedRules.add(new Rule(condition, consequences)); - } catch (final JsonException e) { - Log.debug(LOG_SOURCE, "Unable to parse individual rule json. Exception: (%s)", e); - } catch (final UnsupportedConditionException e) { - Log.debug(LOG_SOURCE, "Unable to parse individual rule conditions. Exception: (%s)", e); - } catch (final IllegalArgumentException e) { - Log.debug(LOG_SOURCE, "Unable to create rule object. Exception: (%s)", e); - } - } - - return parsedRules; - } - - /** - * Parses consequence objects from rules definition and converts them into a list of Events - * - * @param consequenceJsonArray {@code JSONArray} object containing 1 or more rule consequence definitions - * - * @return a {@code List} of consequence {@code Event} objects. - * - * @throws JsonException if errors occur during parsing - */ - private List generateConsequenceEvents(final JsonUtilityService.JSONArray consequenceJsonArray) throws - JsonException { - final List parsedEvents = new ArrayList(); - - if (consequenceJsonArray == null) { - return parsedEvents; - } - - for (int i = 0; i < consequenceJsonArray.length(); i++) { - final RuleConsequence consequence = RuleConsequence.consequenceFromJson(consequenceJsonArray.getJSONObject(i), - getPlatformServices().getJsonUtilityService()); - - if (consequence != null) { - final Event event = new Event.Builder("Rules Event", EventType.RULES_ENGINE, EventSource.RESPONSE_CONTENT) - .setData(consequence.generateEventData()) - .build(); - - parsedEvents.add(event); - } - } - - return parsedEvents; - } - - private void retrieveCachedRules(final String remoteRulesURL) { - - if (StringUtils.isNullOrEmpty(remoteRulesURL)) { - return; - } - - final PlatformServices platformServices = getPlatformServices(); - - if (platformServices == null) { - return; - } - - try { - final RulesRemoteDownloader remoteDownloader = new RulesRemoteDownloader( - platformServices.getNetworkService(), - platformServices.getSystemInfoService(), - platformServices.getCompressedFileService(), - remoteRulesURL, RULES_CACHE_FOLDER); - final File outputFile = remoteDownloader.getCachedRulesFile(); - onRulesDownloaded(outputFile); - } catch (final MissingPlatformServicesException e) { - Log.debug(LOG_SOURCE, "Unable to read cached remote rules. Exception: (%s)", e); - } - } + static final String LOG_SOURCE = ConfigurationExtension.class.getSimpleName(); + + private static final String CONFIGURATION_URL_BASE = "https://assets.adobedtm.com/%s.json"; + + private static final String CONFIG_BUNDLED_FILE_NAME = "ADBMobileConfig.json"; + private static final String CONFIG_MANIFEST_APPID_KEY = "ADBMobileAppID"; + private static final String CONFIG_REMOTE_SERVER = "com.adobe.marketing.mobile.RemoteConfigServer"; + + private static final String DATASTORE_KEY = "AdobeMobile_ConfigState"; + private static final String PERSISTED_OVERRIDDEN_CONFIG = "config.overridden.map"; + private static final String PERSISTED_APPID = "config.appID"; + private static final String PERSISTED_RULES_URL = "config.last.rules.url"; + + private static final String RULES_CACHE_FOLDER = "configRules"; + private static final String RULES_JSON_FILE_NAME = "rules.json"; + private static final String RULES_JSON_KEY = "rules"; + private static final String RULES_JSON_CONDITION_KEY = "condition"; + private static final String RULES_JSON_CONSEQUENCES_KEY = "consequences"; + private static final String LAUNCH_RULES_ENGINE = "LaunchRulesEngine"; + + private static final int DEFAULT_NOT_DOWNLOAD_RULES_WITHIN_TIME_SEC = 15; + static int NOT_DOWNLOAD_RULES_WITHIN_TIME_SEC = DEFAULT_NOT_DOWNLOAD_RULES_WITHIN_TIME_SEC; + + + private final ConfigurationDispatcherConfigurationRequestContent requestDispatcher; + private final ConfigurationDispatcherConfigurationResponseContent responseDispatcher; + private final ConfigurationDispatcherConfigurationResponseIdentity responseIdentityDispatcher; + final ConcurrentLinkedQueue getsdkIdsEventQueue; + private ConfigurationData currentConfig; + /// The persisted programmatic config or an empty config if none is found + private ConfigurationData programmaticConfig; + // The configuration without a merge from programmaticConfig, needed for clearing the config + private ConfigurationData unmergedConfiguration; + private boolean isUnregistered; + private ConcurrentHashMap cachedRulesDownloadTime; + final private List cachedEvents; + private final ExecutorService rulesDownloadExecutor; + private final LaunchRulesEvaluator launchRulesEvaluator; + + + /** + * Returns an instance of the ConfigurationExtension Module. Adds a RequestContentListener to the provided EventHub + * + * @param eventHub an EventHub to be used by the module + * @param services an instance of PlatformServices to be used by the module + * @see EventHub + * @see PlatformServices + */ + public ConfigurationExtension(final EventHub eventHub, final PlatformServices services) { + super(ConfigurationConstants.EventDataKeys.Configuration.MODULE_NAME, eventHub, services); + getsdkIdsEventQueue = new ConcurrentLinkedQueue(); + cachedRulesDownloadTime = new ConcurrentHashMap(); + + // register listeners + registerListener(EventType.CONFIGURATION, EventSource.REQUEST_CONTENT, ConfigurationListenerRequestContent + .class); + registerListener(EventType.HUB, EventSource.BOOTED, ConfigurationListenerBootEvent.class); + registerListener(EventType.CONFIGURATION, EventSource.REQUEST_IDENTITY, ConfigurationListenerRequestIdentity.class); + + registerWildcardListener(ConfigurationWildCardListener.class); + + // create Dispatcher + + requestDispatcher = createRequestDispatcher(); + responseDispatcher = createResponseDispatcher(); + responseIdentityDispatcher = createResponseIdentityDispatcher(); + + rulesDownloadExecutor = Executors.newSingleThreadExecutor(); + this.cachedEvents = Collections.synchronizedList(new ArrayList()); + launchRulesEvaluator = new LaunchRulesEvaluator(LAUNCH_RULES_ENGINE); + eventHub.registerPreprocessor(launchRulesEvaluator); + } + + ConfigurationDispatcherConfigurationResponseIdentity createResponseIdentityDispatcher() { + return createDispatcher(ConfigurationDispatcherConfigurationResponseIdentity.class); + } + + ConfigurationDispatcherConfigurationRequestContent createRequestDispatcher() { + return createDispatcher(ConfigurationDispatcherConfigurationRequestContent.class); + } + + ConfigurationDispatcherConfigurationResponseContent createResponseDispatcher() { + return createDispatcher(ConfigurationDispatcherConfigurationResponseContent.class); + } + + // ======================================================== + // Event Handlers + // ======================================================== + + /** + * Handler for {@code EventType.CONFIGURATION} {@code EventSource.REQUEST_IDENTITY} {@code Event}. + *

    + * This event is generated when the {@code getSDKIdentities} public API is called. + * + *

    + * ConfigurationExtension module attempts to read all the identities known to the SDK. It then generates + * a {@code EventType.CONFIGURATION} {@code EventSource.RESPONSE_IDENTITY} response {@code Event} with all + * the obtained identities in a JSON {@code String} format. + * + * @param event An Configuration Request Identity event + * @see MobileIdentities + */ + void handleGetSdkIdentitiesEvent(final Event event) { + getExecutor().execute(new Runnable() { + @Override + public void run() { + getsdkIdsEventQueue.add(event); + processGetSdkIdsEvent(); + } + }); + } + + /** + * Handler for the Boot event created by EventHub. + *

    + * If AppId present, create a configureWithAppId request content event and then attempt to load configuration in the following + * order + * 1. cachedFile + * 2. Bundled + * 3. Overridden Config + *

    + * If No AppId, attempt to load the bundled/overridden configuration. + * + * @param event boot configuration event, which is generated when the module is initialized. + */ + void handleBootEvent(final Event event) { + retrieveCachedRules(retrieveRulesURLFromPersistence()); + processBootEvent(event); + } + + void handleWildcardEvent(final Event event) { + this.cachedEvents.add(event); + } + + /** + * Processes the Events generated by {@code getSDKIdentities} public API. + *

    + * Calls the callback with empty {@code String} if {@link JsonUtilityService} is not available. + * Queues the {@link Event} in the {@link #getsdkIdsEventQueue} if the one of the requires shared state is in {@link EventHub#SHARED_STATE_PENDING}. + * Dispatches the paired {@code EventType.CONFIGURATION}, {@code EventSource.RESPONSE_IDENTITY} event into the {@link EventHub} + * with {@link String} identities JSON. + */ + void processGetSdkIdsEvent() { + while (!getsdkIdsEventQueue.isEmpty()) { + final Event event = getsdkIdsEventQueue.peek(); + + + // check if jsonUtility service is available to process the event. + // If unavailable call the callback with empty string and remove the event from the queue + JsonUtilityService jsonUtilityService = getJSONUtilityService(); + + if (jsonUtilityService == null) { + Log.debug(LOG_SOURCE, "%s (JSON Utility Service), unable to retrieve sdk identities", Log.UNEXPECTED_NULL_VALUE); + responseIdentityDispatcher.dispatchAllIdentities("{}", event.getResponsePairID()); + getsdkIdsEventQueue.poll(); + continue; + } + + // verify that if all the required shared state is not in pending. + // If not, break out of the loop and wait for the shared state update event to happen. + if (!MobileIdentities.areAllSharedStatesReady(event, this)) { + break; + } + + final String allIds = MobileIdentities.getAllIdentifiers(jsonUtilityService, event, this); + responseIdentityDispatcher.dispatchAllIdentities(allIds, event.getResponsePairID()); + + getsdkIdsEventQueue.poll(); + } + } + + private void processBootEvent(final Event event) { + Log.trace(ConfigurationExtension.LOG_SOURCE, "Processing boot configuration event"); + + // programmed config from the persistence + retrieveProgrammaticConfigFromPersistence(); + + String appId = getValidAppID(); + + // If appID is present from any source. + if (!StringUtils.isNullOrEmpty(appId)) { + + // dispatch an configureWithAppID internal event if we have a valid appId(From manifest or persistence). + // We dispatch this during initialization just to prevent other configureWithAppId calls occurring before this event. + requestDispatcher.dispatchInternalConfigureWithAppIdEvent(appId); + + // Create a shared state with cached config, if present + if (loadCachedConfig(appId, event)) { + return; + } + } + + // If the app is not configured with an AppID (OR) if the cachedConfig for the appID is missing + // attempt to load the bundled configuration + if (loadBundledConfig(event, CONFIG_BUNDLED_FILE_NAME)) { + return; + } + + // If the application is not configured with an AppID and has no bundled configuration. + // Just load the programmatic config + if (loadProgrammaticConfig(event)) { + return; + } + } + + /** + * Unpacks the configuration request content event and processes them according to their event data key. + * + * @param event a configuration request content event. + */ + void handleEvent(final Event event) { + Log.trace(ConfigurationExtension.LOG_SOURCE, "Handling the configuration event: %s", event.getEventNumber()); + EventData eventData = event.getData(); + + // if the event has an app id. try to fetch from the remote + if (eventData.containsKey( + ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID)) { + getExecutor().execute(new Runnable() { + @Override + public void run() { + processConfigureWithAppIDEvent(event); + } + }); + } + // if the event has a path to a file in asset folder, try to load configuration from the file + else if (eventData.containsKey( + ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_ASSET_FILE)) { + getExecutor().execute(new Runnable() { + @Override + public void run() { + + final String fileName = event.getData().optString( + ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_ASSET_FILE, null); + + loadBundledConfig(event, fileName); + } + }); + } + // if the event has a path to a file, try to load configuration from the file + else if (eventData.containsKey( + ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_FILE_PATH)) { + getExecutor().execute(new Runnable() { + @Override + public void run() { + processConfigWithFilePathEvent(event); + } + }); + } + // if the event has a manually configured information, try to override with existing configuration + else if (event.getData().containsKey( + ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_UPDATE_CONFIG)) { + getExecutor().execute(new Runnable() { + @Override + public void run() { + processUpdateConfigEvent(event); + } + }); + + + } else if (event.getData().containsKey( + ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_CLEAR_UPDATED_CONFIG)) { + getExecutor().execute(new Runnable() { + @Override + public void run() { + processClearUpdatedConfigEvent(event); + } + }); + } else if (event.getData().containsKey( + ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_RETRIEVE_CONFIG)) { + // do not create shared state here, this is just a request for information + getExecutor().execute(new Runnable() { + @Override + public void run() { + processPublishConfigurationEvent(event); + } + }); + } + + } + + /** + * Attempts to load a configuration from the provide file path + * + * @param event event which triggered the configuration request + */ + void processConfigWithFilePathEvent(final Event event) { + final String filePath = event.getData().optString( + ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_FILE_PATH, null); + + // make sure our filePath is valid + if (StringUtils.isNullOrEmpty(filePath)) { + Log.warning(ConfigurationExtension.LOG_SOURCE, + "Unable to read config from provided file (filePath is invalid)"); + return; + } + + Log.trace(ConfigurationExtension.LOG_SOURCE, "Processing configWithFilePath Event. \n %s", filePath); + final String jsonConfigString = FileUtil.readStringFromFile(new File(filePath)); + + Log.trace(ConfigurationExtension.LOG_SOURCE, "Configuration obtained from filePath %s is \n %s", filePath, + jsonConfigString); + configureWithJsonString(jsonConfigString, event, true); // state already created with null data + + } + + /** + * Unpacks the update configuration request content event. The newConfiguration data is added over the existing + * configuration and a Configuration response event is created. + * + * @param event an update Configuration event. + */ + @SuppressWarnings("unchecked") + void processUpdateConfigEvent(final Event event) { + // update the programmaticConfig with the new config from API and persist them in disk + Map newProgrammaticConfig = event.getData().optVariantMap( + ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_UPDATE_CONFIG, null); + + // if update config is not provided or is empty, abort update config request. + if (newProgrammaticConfig == null || newProgrammaticConfig.isEmpty()) { + Log.debug(LOG_SOURCE, "Configuration update data was either not provided in event or is empty."); + return; + } + + Log.trace(ConfigurationExtension.LOG_SOURCE, "Processing updateConfiguration Event. \n %s", newProgrammaticConfig); + // Retrieve the latest programmatic config value from the persistence. + retrieveProgrammaticConfigFromPersistence(); + + programmaticConfig.put(newProgrammaticConfig); + + saveProgrammaticConfigToPersistence(programmaticConfig); + + if (currentConfig == null) { + if (getJSONUtilityService() == null) { + return; + } + + currentConfig = new ConfigurationData(getJSONUtilityService()); + } + + currentConfig.put(programmaticConfig); + + changeConfiguration(event, currentConfig, true); + } + + void processClearUpdatedConfigEvent(final Event event) { + Log.trace(ConfigurationExtension.LOG_SOURCE, "Processing clear updated configuration event"); + + if (unmergedConfiguration == null) { + if (getJSONUtilityService() == null) { + return; + } + + unmergedConfiguration = new ConfigurationData(getJSONUtilityService()); + } + + // remove overridden configuration from persistence + removeProgrammaticConfigFromPersistence(); + + // clear value stored in programmaticConfig variable + retrieveProgrammaticConfigFromPersistence(); + + // set currentConfig value to unmergedConfig + currentConfig = unmergedConfiguration; + + // Update the shared state and dispatch a event clearing updated configuration + changeConfiguration(event, unmergedConfiguration, true); + } + + /** + * Process the configuration request content events with appId. Tries to retrieve the latest configuration + * from the remote. + * + * @param event the configureWithAppIDEvent + */ + void processConfigureWithAppIDEvent(final Event event) { + final EventData eventData = event.getData(); + + if (eventData == null) { + Log.trace(LOG_SOURCE, "%s (event data), for ConfigureWithAppID event, Ignoring event", Log.UNEXPECTED_NULL_VALUE); + return; + } + + final String newAppId = event.getData().getString( + ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID); + + if (StringUtils.isNullOrEmpty(newAppId)) { + Log.trace(LOG_SOURCE, "App ID was null or empty while processing ConfigureWithAppID event"); + removeAppIdFromPersistence(); + return; + } + + if (!validateForInternalEventAppIDChange(eventData, newAppId)) { + Log.trace(LOG_SOURCE, "App ID is changed. Ignoring the setAppID Internal event %s", newAppId); + return; + } + + Log.trace(ConfigurationExtension.LOG_SOURCE, "Processing configureWithAppID event. AppID -(%s)", newAppId); + saveAppIdToPersistence(newAppId); + + // get a downloader instance + ConfigurationDownloader configurationDownloader = getConfigDownloader(newAppId); + + if (configurationDownloader == null) { + Log.trace(LOG_SOURCE, "%s (Configuration Downloader).", Log.UNEXPECTED_NULL_VALUE); + return; + } + + // first, just blindly try to download the config + String jsonString = configurationDownloader.downloadConfig(); + + if (StringUtils.isNullOrEmpty(jsonString)) { + // download failed + // try to get a cached config + jsonString = configurationDownloader.loadCachedConfig(); + } + + if (StringUtils.isNullOrEmpty(jsonString)) { + // no cached config + // if the network is down, wait for it to come back up + final PlatformServices platformServices = getPlatformServices(); + final SystemInfoService systemInfoService = platformServices == null ? null : platformServices.getSystemInfoService(); + final boolean networkIsDown = ( + systemInfoService != null && + systemInfoService.getNetworkConnectionStatus() != SystemInfoService.ConnectionStatus.CONNECTED); + + if (networkIsDown && waitForNetworkConnection()) { + // try once more after network is back up + jsonString = configurationDownloader.downloadConfig(); + } + } + + if (StringUtils.isNullOrEmpty(jsonString)) { + Log.warning(LOG_SOURCE, "Unable to fetch config. Rolling back to previous configuration."); + return; + } + + + // Update the shared state and dispatch a event with the received jsonString. + configureWithJsonString(jsonString, event, true); + } + + boolean waitForNetworkConnection() { + final int WAIT_FOR_NETWORK_POLL_MS = 1000; + final PlatformServices platformServices = getPlatformServices(); + + if (platformServices == null) { + return false; + } + + final SystemInfoService systemInfoService = platformServices.getSystemInfoService(); + + if (systemInfoService == null) { + return false; + } + + class State extends Object { + public boolean isListenerRegistered = false; + } + + final State state = new State(); + + while (true) { + synchronized (this) { + if (isUnregistered) { + return false; + } + } + + final SystemInfoService.ConnectionStatus connectionStatus = systemInfoService.getNetworkConnectionStatus(); + + if (connectionStatus == SystemInfoService.ConnectionStatus.CONNECTED) { + return true; + } + + // DISCONNECTED + + synchronized (state) { + if (!state.isListenerRegistered) { + state.isListenerRegistered = true; + systemInfoService.registerOneTimeNetworkConnectionActiveListener(new + SystemInfoService.NetworkConnectionActiveListener() { + @Override + public final void onActive() { + synchronized (state) { + state.notifyAll(); + state.isListenerRegistered = false; + } + } + }); + } + + try { + state.wait(WAIT_FOR_NETWORK_POLL_MS); + } catch (final InterruptedException e) { + continue; + } + } + } + } + + @Override + protected void onUnregistered() { + synchronized (this) { + isUnregistered = true; + } + } + + /** + * Retrieves current configuration through a new configuration response event. + * + * @param event the publish configuration event + */ + void processPublishConfigurationEvent(final Event event) { + Log.trace(ConfigurationExtension.LOG_SOURCE, "Processing publish configuration event"); + + + if (getJSONUtilityService() == null) { + return; + } + + ConfigurationData newConfigData = new ConfigurationData(getJSONUtilityService()) + .put(currentConfig).put(programmaticConfig); + + responseDispatcher.dispatchConfigResponseWithEventData(newConfigData.getEventData(), + event.getResponsePairID()); + } + + /** + * Dispatches a ConfigurationResponseContent event with the provided JSON string. + * Overrides with the programmed configuration if they exist. + * + * @param jsonConfigString a String containing the JSON configuration + * @param event event which triggered the configuration request + * @param loadRules if set to true, will try to load the remote rules + */ + void configureWithJsonString(final String jsonConfigString, + final Event event, + final boolean loadRules) { + + if (getJSONUtilityService() == null) { + return; + } + + // Convert the JSONString into an EventData and save it in memory + ConfigurationData newConfig = new ConfigurationData(getJSONUtilityService()) + .put(jsonConfigString); + + if (newConfig.isEmpty()) { + Log.debug(LOG_SOURCE, "Empty configuration found when processing JSON string."); + return; + } + + retrieveProgrammaticConfigFromPersistence(); + + unmergedConfiguration = new ConfigurationData(getJSONUtilityService()).put(jsonConfigString); + currentConfig = newConfig; + + // Before dispatching override the primary configuration with the programmatic configuration. + currentConfig.put(programmaticConfig); + + changeConfiguration(event, currentConfig, loadRules); + + } + + + // ======================================================== + // Other Helper Methods + // ======================================================== + + /** + * Performs modifications to configuration + * + * @param triggerEvent {@code Event} that the configuration change should be valid for + * @param configurationData {@code ConfigurationData} object containing the new configuration + * @param loadRules {@code boolean} if we should load the rules + */ + private void changeConfiguration(final Event triggerEvent, final ConfigurationData configurationData, + final boolean loadRules) { + final EventData configEventData = configurationData.getEventData(); + + + createSharedState(triggerEvent.getEventNumber(), configEventData); + Log.trace(LOG_SOURCE, "Shared state is created for event number %d with data \n %s", triggerEvent.getEventNumber(), + configEventData); + + if (loadRules) { + final String remoteRulesURL = configurationData.getEventData().optString( + ConfigurationConstants.EventDataKeys.Configuration.RULES_CONFIG_URL, ""); + this.rulesDownloadExecutor.execute(new Runnable() { + @Override + public void run() { + downloadRules(remoteRulesURL); + } + }); + } + + responseDispatcher.dispatchConfigResponseWithEventData(configEventData, triggerEvent.getPairID()); + } + + /** + * Sort the preference and returns the valid app Id used + * + * @return {@code String} application identifier + */ + private String getValidAppID() { + // return the persisted appId if we have + String appId = retrieveAppIdFromPersistence(); + + if (!StringUtils.isNullOrEmpty(appId)) { + Log.trace(ConfigurationExtension.LOG_SOURCE, "Valid AppID is retrieved from persistence - %s", appId); + return appId; + } + + // else try to fetch the appID from manifest + return getAppIDFromManifest(); + } + + /** + * Attempts to configure with the bundled configuration. + * + * @param event event which triggered the configruation request. + * @param fileName the name of the asset file + * @return {@code boolean} true if the configuration is successfully loaded, false otherwise + */ + protected boolean loadBundledConfig(final Event event, final String fileName) { + String bundledConfigContent = readContentFromAsset(fileName); + + if (bundledConfigContent == null) { + Log.trace(ConfigurationExtension.LOG_SOURCE, "%s (Failed to read bundled config file content from asset file %s)", + Log.UNEXPECTED_NULL_VALUE, fileName); + return false; + } + + Log.debug(ConfigurationExtension.LOG_SOURCE, "Bundled configuration loaded from asset file (%s). \n %s", fileName, + bundledConfigContent); + configureWithJsonString(bundledConfigContent, event, true); + return true; + } + + + /** + * Attempts to configure with the cached file. + * + * @param appID associated appID of that cache file. + * @param event event which triggered the configruation request. + * @return {@code boolean} true if the cached file is successfully loaded, false otherwise + */ + private boolean loadCachedConfig(final String appID, final Event event) { + ConfigurationDownloader configurationDownloader = getConfigDownloader(appID); + + if (configurationDownloader == null) { + return false; + } + + String cachedConfigJSON = configurationDownloader.loadCachedConfig(); + + if (StringUtils.isNullOrEmpty(cachedConfigJSON)) { + Log.trace(ConfigurationExtension.LOG_SOURCE, "Nothing is loaded from cached file"); + return false; + } + + Log.debug(ConfigurationExtension.LOG_SOURCE, "Cached configuration loaded. \n %s", cachedConfigJSON); + configureWithJsonString(cachedConfigJSON, event, false); + return true; + + } + + /** + * Attempts to configure with the programmatic configuration. + * + * @param event event which triggered the configuration request. + * @return {@code boolean} true if succeeded, false otherwise + */ + private boolean loadProgrammaticConfig(final Event event) { + if (programmaticConfig.isEmpty()) { + return false; + } + + changeConfiguration(event, programmaticConfig, true); + return true; + } + + + /** + * Provides you the instance of the configuration downloader with the specified url. + * + * @param appId the appID required to build the url for the downloader. + * @return {@code ConfigurationDownloader} configuration downloader instance, or null if an error occurs + * attempting to create the ConfigurationDownloader instance. + */ + ConfigurationDownloader getConfigDownloader(final String appId) { + + if (getPlatformServices() == null) { + Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (Platform services)", Log.UNEXPECTED_NULL_VALUE); + return null; + } + + if (getPlatformServices().getSystemInfoService() == null) { + Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (System Info services)", Log.UNEXPECTED_NULL_VALUE); + return null; + } + + // check whether manifest overrides the config endpoint + String requestUrlString = String.format(CONFIGURATION_URL_BASE, appId); + final SystemInfoService systemInfoService = getPlatformServices().getSystemInfoService(); + + if (systemInfoService != null) { + String remoteConfigServer = systemInfoService.getProperty(CONFIG_REMOTE_SERVER); + + if (!StringUtils.isNullOrEmpty(remoteConfigServer)) { + requestUrlString = String.format(remoteConfigServer, appId); + } + } + + if (getPlatformServices().getNetworkService() == null) { + Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (Network services)", Log.UNEXPECTED_NULL_VALUE); + return null; + } + + // Build a Configuration Downloader + try { + return new ConfigurationDownloader(getPlatformServices().getNetworkService(), + getPlatformServices().getSystemInfoService(), requestUrlString); + } catch (MissingPlatformServicesException exp) { + Log.warning(LOG_SOURCE, "Unable to Initialize Downloader (%s)", exp); + return null; + } + } + + // ======================================================== + // Platform resource getters + // ======================================================== + + /** + * Reads the AppId from the manifest file. + * Returns null if appId is not found. + * + * @return {@code String} application identifier from manifest file + */ + private String getAppIDFromManifest() { + + if (getPlatformServices() == null) { + Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (Platform services)", Log.UNEXPECTED_NULL_VALUE); + return null; + } + + if (getPlatformServices().getSystemInfoService() == null) { + Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (System Info services)", Log.UNEXPECTED_NULL_VALUE); + return null; + } + + final SystemInfoService systemInfoService = getPlatformServices().getSystemInfoService(); + + if (systemInfoService == null) { + Log.debug(ConfigurationExtension.LOG_SOURCE, + "%s (System info service), unable to read AppID from manifest", Log.UNEXPECTED_NULL_VALUE); + return null; + } + + final String manifestAppId = systemInfoService.getProperty(CONFIG_MANIFEST_APPID_KEY); + + if (StringUtils.isNullOrEmpty(manifestAppId)) { + return null; + } + + Log.trace(ConfigurationExtension.LOG_SOURCE, " Valid AppID is retrieved from manifest - %s", manifestAppId); + saveAppIdToPersistence(manifestAppId); + return manifestAppId; + } + + + /** + * Reads the bundled ADBMobileConfig.json file from the assets folder. + * Returns null if the Bundled file is not found. + * + * @return {@code String} bundled file content + */ + private String readContentFromAsset(final String fileName) { + if (StringUtils.isNullOrEmpty(fileName)) { + Log.debug(ConfigurationExtension.LOG_SOURCE, "Invalid asset file name."); + return null; + } + + if (getPlatformServices() == null) { + Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (Platform services)", Log.UNEXPECTED_NULL_VALUE); + return null; + } + + final SystemInfoService systemInfoService = getPlatformServices().getSystemInfoService(); + + if (systemInfoService == null) { + Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (System info services), unable to read bundled configuration", + Log.UNEXPECTED_NULL_VALUE); + return null; + } + + final InputStream configStream = systemInfoService.getAsset(fileName); + + if (configStream == null) { + return null; + } + + return StringUtils.streamToString(configStream); + } + + // ======================================================== + // private methods + // ======================================================== + + + /** + * Load the overriddenConfiguration from persistent storage. + */ + private void retrieveProgrammaticConfigFromPersistence() { + + if (getJSONUtilityService() == null) { + return; + } + + this.programmaticConfig = new ConfigurationData(getJSONUtilityService()); + LocalStorageService.DataStore configStore = getDataStore(); + + if (configStore != null) { + String jsonString = configStore.getString(PERSISTED_OVERRIDDEN_CONFIG, null); + Log.trace(ConfigurationExtension.LOG_SOURCE, "Loading overridden configuration from persistence - \n %s", jsonString); + this.programmaticConfig = new ConfigurationData(getJSONUtilityService()).put(jsonString); + } else { + Log.debug(ConfigurationExtension.LOG_SOURCE, + "%s (Local storage service), unable to load overridden config from persistence", Log.UNEXPECTED_NULL_VALUE); + } + } + + + /** + * Saves the overriddenConfig map in the persistence under the Configuration DataStore. + * Used to persist the programmed Configuration between launches + * + * @param overriddenConfig new overriddenConfig map that needs to be saved + */ + private void saveProgrammaticConfigToPersistence(final ConfigurationData overriddenConfig) { + LocalStorageService.DataStore configStore = getDataStore(); + + if (configStore != null) { + Log.trace(ConfigurationExtension.LOG_SOURCE, "Saving the overridden configuration to persistence - \n %s", + overriddenConfig); + configStore.setString(PERSISTED_OVERRIDDEN_CONFIG, overriddenConfig.getJSONString()); + } else { + Log.debug(ConfigurationExtension.LOG_SOURCE, + "%s (Local storage service), unable to save overridden config to persistence", Log.UNEXPECTED_NULL_VALUE); + } + } + + /** + * Removes the overriddenConfig map from the persistence under the Configuration Datastore + */ + private void removeProgrammaticConfigFromPersistence() { + LocalStorageService.DataStore configStore = getDataStore(); + + if (configStore != null) { + Log.trace(ConfigurationExtension.LOG_SOURCE, "Removing overridden configuration from persistence"); + configStore.remove(PERSISTED_OVERRIDDEN_CONFIG); + } else { + Log.debug(ConfigurationExtension.LOG_SOURCE, + "%s (Storage Service), unable to remove overridden configuration from persistence", + Log.UNEXPECTED_NULL_VALUE); + } + } + + + /** + * Load the appId from persistent storage. + * + * @return {@code String} application identifier from persistence, or null + */ + private String retrieveAppIdFromPersistence() { + LocalStorageService.DataStore configStore = getDataStore(); + + if (configStore != null) { + final String persistedAppID = configStore.getString(PERSISTED_APPID, null); + Log.trace(ConfigurationExtension.LOG_SOURCE, "AppID loaded from persistence - %s", persistedAppID); + return persistedAppID; + } else { + Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (Storage Service), unable to load appId from persistence", + Log.UNEXPECTED_NULL_VALUE); + return null; + } + } + + + /** + * Saves the appID in the persistence under the Configuration DataStore. + * Used to persist the appID between launches to load the cached configuration. + * + * @param appID appID that needs to be saved + */ + private void saveAppIdToPersistence(final String appID) { + LocalStorageService.DataStore configStore = getDataStore(); + + if (configStore != null) { + Log.trace(ConfigurationExtension.LOG_SOURCE, "Saving appID to persistence - %s", appID); + configStore.setString(PERSISTED_APPID, appID); + } else { + Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (Storage Service), unable to save appId to persistence", + Log.UNEXPECTED_NULL_VALUE); + } + } + + /** + * Removes the appId from the persistence under the Configuration Datastore + */ + private void removeAppIdFromPersistence() { + LocalStorageService.DataStore configStore = getDataStore(); + + if (configStore != null) { + Log.trace(ConfigurationExtension.LOG_SOURCE, "Removing appID from persistence"); + configStore.remove(PERSISTED_APPID); + } else { + Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (Storage Service), unable to remove appId from persistence", + Log.UNEXPECTED_NULL_VALUE); + } + } + + /** + * Load the last known rules URL from persistent storage. + * + * @return {@code String} last known rules URL from persistence, or null + */ + private String retrieveRulesURLFromPersistence() { + LocalStorageService.DataStore configStore = getDataStore(); + + if (configStore != null) { + final String persistedRulesUrl = configStore.getString(PERSISTED_RULES_URL, null); + Log.trace(ConfigurationExtension.LOG_SOURCE, "Last known rules URL loaded from persistence - %s", persistedRulesUrl); + return persistedRulesUrl; + } else { + Log.debug(ConfigurationExtension.LOG_SOURCE, + "%s (Storage Service), unable to load the last known rules URL from persistence", Log.UNEXPECTED_NULL_VALUE); + return null; + } + } + + + /** + * Saves the last known rules URL in the persistence under the Configuration DataStore. + * + * @param rulesURL the last known rules URL that needs to be saved + */ + private void saveRulesURLToPersistence(final String rulesURL) { + LocalStorageService.DataStore configStore = getDataStore(); + + if (configStore != null) { + Log.trace(ConfigurationExtension.LOG_SOURCE, "Saving last known rules URL to persistence - %s", rulesURL); + configStore.setString(PERSISTED_RULES_URL, rulesURL); + } else { + Log.debug(ConfigurationExtension.LOG_SOURCE, + "%s (Storage Service), unable to save the last known rules URL to persistence", Log.UNEXPECTED_NULL_VALUE); + } + } + + /** + * The purpose of the SetAppIDInternalEvent is to refresh the existing with the persisted appId + * This method validates the appId for the SetAppIDInternalEvent + * It return true, if the persisted appId is same as the internalEvent appId present in the eventData + * It return false, if the persisted appId is different from the internalEvent appId present in the eventData + * + * @param eventData The {@link EventData} associated to the set internal appId event + * @param newAppID A {@link String} appId associated with set internal appId event + * @return A {@code boolean} indicating if there is a change in appID for the SetAppIDInternalEvent + */ + private boolean validateForInternalEventAppIDChange(final EventData eventData, final String newAppID) { + boolean isInternalEvent = eventData.optBoolean( + ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_IS_INTERNAL_EVENT, false); + + if (isInternalEvent && (!newAppID.equals(getValidAppID()))) { + return false; + } + + return true; + } + + + /** + * Returns the configuration datastore + * + * @return {@code LocalStorageService.DataStore} configuration datastore or null + */ + private LocalStorageService.DataStore getDataStore() { + + if (getPlatformServices() == null) { + Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (Platform services)", Log.UNEXPECTED_NULL_VALUE); + return null; + } + + if (getPlatformServices().getLocalStorageService() == null) { + Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (Local Storage services)", Log.UNEXPECTED_NULL_VALUE); + return null; + } + + return getPlatformServices().getLocalStorageService().getDataStore(DATASTORE_KEY); + } + + /** + * Method to obtain the {@code JsonUtilityService} instance. + * + *

    + * Returns null if the {@code PlatformServices} is null or the {@code JsonUtilityService} obtained from the + * {@code PlatformServices} is null. + * + * @return {@link JsonUtilityService} instance + */ + private JsonUtilityService getJSONUtilityService() { + if (getPlatformServices() == null) { + Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (Platform services)", Log.UNEXPECTED_NULL_VALUE); + return null; + } + + if (getPlatformServices().getJsonUtilityService() == null) { + Log.debug(ConfigurationExtension.LOG_SOURCE, "%s (JSON Utility services)", Log.UNEXPECTED_NULL_VALUE); + return null; + } + + return getPlatformServices().getJsonUtilityService(); + } + + // ======================================================== + // rules retrieval stuff + // ======================================================== + + /** + * Downloads and registers remote rules (asynchronously via getExecutor) + * + * @param remoteRulesURL the url of remote rules + */ + private void downloadRules(final String remoteRulesURL) { + + long currentTimeSec = TimeUtil.getUnixTimeInSeconds(); + Long lastDownloadTimeSec = cachedRulesDownloadTime.get(remoteRulesURL); + + if ((lastDownloadTimeSec != null) && (currentTimeSec - lastDownloadTimeSec < NOT_DOWNLOAD_RULES_WITHIN_TIME_SEC)) { + Log.debug(ConfigurationExtension.LOG_SOURCE, "Will not download rules from same url in 30 sec. "); + return; + } + + cachedRulesDownloadTime.put(remoteRulesURL, currentTimeSec); + + saveRulesURLToPersistence(remoteRulesURL); + + if (StringUtils.isNullOrEmpty(remoteRulesURL)) { + return; + } + + final PlatformServices platformServices = getPlatformServices(); + + if (platformServices == null) { + return; + } + + try { + final RulesRemoteDownloader remoteDownloader = new RulesRemoteDownloader( + platformServices.getNetworkService(), + platformServices.getSystemInfoService(), + platformServices.getCompressedFileService(), + remoteRulesURL, RULES_CACHE_FOLDER); + final File outputFile = remoteDownloader.startDownloadSync(); + onRulesDownloaded(outputFile); + } catch (final MissingPlatformServicesException e) { + Log.debug(LOG_SOURCE, "Unable to download remote rules. Exception: %s", e); + } + } + + /** + * Called when rules have completed downloading + * + * @param rulesDirectory File object containing the directory that the rules bundle is in + */ + private void onRulesDownloaded(final File rulesDirectory) { + + // check if we downloaded a valid file + if (rulesDirectory == null || !rulesDirectory.isDirectory()) { + return; + } + + final String rulesFilePath = rulesDirectory.getPath() + File.separator + RULES_JSON_FILE_NAME; + final File rulesFile = new File(rulesFilePath); + final String jsonString = readFromFile(rulesFile); + this.launchRulesEvaluator.replaceRules(JSONRulesParser.parse(jsonString)); + } + + /** + * Reads a file from disk and returns its contents as {@code String}. + * + * @param rulesJsonFile the file object to read + * @return the file contents as {@code String} + */ + private String readFromFile(final File rulesJsonFile) { + String json = null; + + if (rulesJsonFile != null) { + FileInputStream rulesJsonIS = null; + + try { + rulesJsonIS = new FileInputStream(rulesJsonFile); + json = StringUtils.streamToString(rulesJsonIS); + } catch (IOException ex) { + Log.debug(LOG_SOURCE, "Could not read the rules json file! Exception: (%s)", ex); + } finally { + try { + if (rulesJsonIS != null) { + rulesJsonIS.close(); + } + } catch (Exception e) { + Log.trace(LOG_SOURCE, "Failed to close stream for %s, with Exception: %s", rulesJsonFile, e); + } + } + } + + return json; + } + + private void retrieveCachedRules(final String remoteRulesURL) { + + if (StringUtils.isNullOrEmpty(remoteRulesURL)) { + return; + } + + final PlatformServices platformServices = getPlatformServices(); + + if (platformServices == null) { + return; + } + + try { + final RulesRemoteDownloader remoteDownloader = new RulesRemoteDownloader( + platformServices.getNetworkService(), + platformServices.getSystemInfoService(), + platformServices.getCompressedFileService(), + remoteRulesURL, RULES_CACHE_FOLDER); + final File outputFile = remoteDownloader.getCachedRulesFile(); + onRulesDownloaded(outputFile); + } catch (final MissingPlatformServicesException e) { + Log.debug(LOG_SOURCE, "Unable to read cached remote rules. Exception: (%s)", e); + } + } } \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java index bb682006d..34c0f3e89 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java @@ -14,8 +14,22 @@ import com.adobe.marketing.mobile.internal.eventhub.history.EventHistoryResultHandler; import java.lang.reflect.Constructor; -import java.util.*; -import java.util.concurrent.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** @@ -25,1540 +39,1416 @@ * @version 5.0 */ class EventHub { - // shared state markers - /** - * State that is "on the way" and will eventually be resolved. - */ - public static final EventData SHARED_STATE_PENDING = null; - /** - * Special state that indicates that the state is not valid. - */ - public static final EventData SHARED_STATE_INVALID = new EventData(); - /** - * Special "marker" state that indicates that this state is equal to the next DATA/PENDING/INVALID state. - */ - public static final EventData SHARED_STATE_NEXT = new EventData(); - /** - * Special "marker" state that indicates that this state is equal to the previous state. - */ - public static final EventData SHARED_STATE_PREV = new EventData(); - - private static final String STANDARD_STATE_CHANGE_EVENTNAME = "Shared state change"; - private static final String XDM_STATE_CHANGE_EVENTNAME = "Shared state change (XDM)"; - private static final String LOG_PROVIDED_MODULE_WAS_NULL = "Provided module was null"; - private static final String LOG_MODULE_WAS_NULL = "Module was null"; - private static final String LOG_CLASS_WAS_NULL = "Extension class was null"; - private static final String LOG_STATENAME_WAS_NULL = "StateName was null"; - private static final String LOG_MODULE_NOT_REGISTERED = "Module (%s) is not registered"; - private static final long DEFAULT_THREAD_POOL_KEEP_ALIVE_TIME_SECONDS = 60L; - private static final int ONE_TIME_LISTENER_TIMEOUT_DEFAULT_MILLISECONDS = 5000; - public static final int REPROCESS_EVENTS_AMOUNT_LIMIT = 100; - private static final String SDK_VERSION_DELIMITER = "-"; - - // instance variables - private final String logPrefix; - private final PlatformServices services; - private final ConcurrentHashMap activeModules; - private final ConcurrentHashMap> moduleListeners; - private final ConcurrentHashMap> moduleSharedStates; - private final ConcurrentHashMap> moduleXdmSharedStates; - private final ConcurrentHashMap sharedStateCircularCheck; - private final LinkedList preBootEvents; // locked on bootMutex - private final RulesEngine rulesEngine; - private final AtomicInteger currentEventNumber; - private final ExecutorService threadPool; - private final ExecutorService eventHubThreadService; - protected final EventData eventHubSharedState; - protected final String coreVersion; - private WrapperType wrapperType = WrapperType.NONE; - - private ScheduledExecutorService scheduledThreadPool; - private final Object scheduledThreadPoolMutex = new Object(); - protected boolean isBooted; // locked on bootMutex - private final Object bootMutex = new Object(); - - private final EventBus eventBus; - - - /** - * Returns an instance of the Event Hub - * - * @param name the name of the {@code EventHub} to be created - for logging purposes - * @param services instance of {@code PlatformServices} class to provide platform-specific functionality - * @throws IllegalArgumentException If platform services is null - */ - public EventHub(final String name, final PlatformServices services) { - this(name, services, "undefined"); - } - - /** - * Returns an instance of the Event Hub - * - * @param name the name of the {@code EventHub} to be created - for logging purposes - * @param services instance of {@code PlatformServices} class to provide platform-specific functionality - * @param coreVersion value passed from platform to indicate the running version of core - * @throws IllegalArgumentException If platform services is null - */ - public EventHub(final String name, final PlatformServices services, final String coreVersion) { - logPrefix = String.format("%s(%s)", this.getClass().getSimpleName(), name); - - if (services == null) { - throw new IllegalArgumentException("Cannot construct EventHub without a valid platform services instance"); - } - - this.coreVersion = coreVersion; - this.services = services; - this.activeModules = new ConcurrentHashMap(); - this.moduleListeners = new ConcurrentHashMap>(); - this.moduleSharedStates = new ConcurrentHashMap>(); - this.moduleXdmSharedStates = new ConcurrentHashMap>(); - this.currentEventNumber = new AtomicInteger(1); - this.preBootEvents = new LinkedList(); - this.sharedStateCircularCheck = new ConcurrentHashMap(); - this.threadPool = Executors.newCachedThreadPool(); - this.eventHubThreadService = new ThreadPoolExecutor(0, 1, - DEFAULT_THREAD_POOL_KEEP_ALIVE_TIME_SECONDS, TimeUnit.SECONDS, - new LinkedBlockingQueue()); - this.eventHubSharedState = getInitialEventHubSharedState(); - this.isBooted = false; - this.rulesEngine = new RulesEngine(this); - this.eventBus = new EventBus(); - } - - /** - * Let event hub know that all the modules have been registered, so it can dispatch a booted event as the first event. - * - * @param completionCallback An optional {@link AdobeCallback} invoked after registrations are completed - */ - void finishModulesRegistration(final AdobeCallback completionCallback) { - this.eventHubThreadService.submit(new Runnable() { - @Override - public void run() { - synchronized (bootMutex) { - if (isBooted) { - Log.trace(logPrefix, "Eventhub has already been booted"); - return; - } - - Event bootedEvent = new Event.Builder("EventHub", EventType.HUB, EventSource.BOOTED).build(); - bootedEvent.setEventNumber(0); - eventHubThreadService.submit(new EventRunnable(bootedEvent)); - - isBooted = true; - - // generate eventhub shared state prior to releasing "pre-boot" events. - createEventHubSharedState(0); - - while (preBootEvents.peek() != null) { - eventHubThreadService.submit(new EventRunnable(preBootEvents.poll())); - } - - if (completionCallback != null) { - eventHubThreadService.submit(new Runnable() { - @Override - public void run() { - completionCallback.call(null); - } - }); - } - } - } - }); - } - - /** - * Dispatches an event onto the Event queue - * - * @param e the event to be added to the queue - */ - void dispatch(final Event e) { - synchronized (bootMutex) { - e.setEventNumber(this.currentEventNumber.getAndIncrement()); - - if (!this.isBooted) { - Log.debug(logPrefix, "Event (%s, %s) was dispatched before module registration was finished", - e.getEventType().getName(), e.getEventSource().getName()); - preBootEvents.add(e); - } else { - this.eventHubThreadService.submit(new EventRunnable(e)); - } - - final EventHistory eventHistory = MobileCore.getEventHistory(); - - // record the event in the event history database if the event has a mask - if (eventHistory != null && e.getMask() != null) { - final EventHistoryResultHandler handler = new EventHistoryResultHandler() { - @Override - public void call(final Boolean value) { - Log.trace(logPrefix, value ? "Successfully inserted an Event into EventHistory database" : - "Failed to insert an Event into EventHistory database"); - } - }; - eventHistory.recordEvent(e, handler); - } - } - } - - /** - * Creates a shared state for this module, then sends an event ( the event and the state will have the same event number ) - * - * @param module module instance to create the shared state - * @param sharedState {@code EventData} object containing the state to save - * @param event the event to be dispatched to {@code EventHub} - */ - void createSharedStateAndDispatchEvent(final Module module, final EventData sharedState, - final Event event) throws InvalidModuleException { - createSharedStateAndDispatchEvent(module, sharedState, event, SharedStateType.STANDARD); - } - - /** - * Creates a shared state for this module, then sends an event ( the event and the state will have the same event number ) - * - * @param module module instance to create the shared state - * @param sharedState {@code EventData} object containing the state to save - * @param event the event to be dispatched to {@code EventHub} - * @param sharedStateType the type of shared state to be created - */ - void createSharedStateAndDispatchEvent(final Module module, final EventData sharedState, final Event event, - final SharedStateType sharedStateType) throws InvalidModuleException { - event.setEventNumber(this.currentEventNumber.getAndIncrement()); - createSharedState(module, event.getEventNumber(), sharedState, sharedStateType); - this.eventHubThreadService.submit(new EventRunnable(event)); - } - - /** - * Checks if a module with the provided name is already registered. The comparison is not case-sensitive, - * the names will be lower cased before the compare. - *

    - * This method iterates through the modules list without a mutex, and is supposed to be called from a thread safe method - * - * @param moduleName the {@link String} module name to search for - * @return the status of the search - */ - private boolean isRegisteredModule(final String moduleName) { - if (moduleName == null) { - return false; - } - - return activeModules.containsKey(normalizeName(moduleName)); - } - - /** - * Returns the lower cased name - * - * @param name {@link String} name to be normalized - * @return the lower case name - */ - private String normalizeName(final String name) { - return name != null ? name.toLowerCase() : null; - } - - /** - * Interface for receiving callbacks when a module is registered - */ - protected interface RegisterModuleCallback { - void registered(Module module); - } - - /** - * For testing - * - * @return all loaded rules - */ - protected ConcurrentHashMap> getModuleRuleAssociation() { - return this.rulesEngine.getModuleRuleAssociation(); - } - - /** - * Registers a module with the event hub. Modules must extend {@code Module} - * - * @param moduleClass a class that extends {@link Module} - * @param callback class implementing {@code RegisterModuleCallback} called if module successfully registers - * @throws InvalidModuleException will be thrown if the {@code moduleClass} is null - */ - protected void registerModuleWithCallback(final Class moduleClass, - final RegisterModuleCallback callback) throws InvalidModuleException { - registerModuleWithCallback(moduleClass, null, callback); - } - - - protected void registerModuleWithCallback(final Class moduleClass, - final ModuleDetails moduleDetails, - final RegisterModuleCallback callback) throws InvalidModuleException { - if (moduleClass == null) { - throw new InvalidModuleException(LOG_CLASS_WAS_NULL); - } - - // register the module on the event hub thread - final EventHub hub = this; - Future f = this.eventHubThreadService.submit(new Runnable() { - @Override - public void run() { - try { - Module module; - - // check, not to register modules have same class name - for (Module activeModule : hub.getActiveModules()) { - if (activeModule.getClass().getName().equalsIgnoreCase(moduleClass.getName())) { - Log.warning(logPrefix, "Failed to register extension, an extension with the same name (%s) already exists", - activeModule.getModuleName()); - return; - } - } - - if (InternalModule.class.isAssignableFrom(moduleClass)) { - Constructor moduleConstructor = moduleClass.getDeclaredConstructor(EventHub.class, - PlatformServices.class); - moduleConstructor.setAccessible(true); - module = moduleConstructor.newInstance(hub, services); - } else { - Constructor moduleConstructor = moduleClass.getDeclaredConstructor(EventHub.class); - moduleConstructor.setAccessible(true); - module = moduleConstructor.newInstance(hub); - } - - // check, not to register modules have same "module name" - if (isRegisteredModule(module.getModuleName())) { - Log.warning(logPrefix, "Failed to register extension, an extension with the same name (%s) already exists", - module.getModuleName()); - return; - } - - // set the module details that got passed in - module.setModuleDetails(moduleDetails); - addModuleToEventHubSharedState(module); - - activeModules.put(normalizeName(module.getModuleName()), module); - moduleListeners.put(module, new ConcurrentLinkedQueue()); - - if (callback != null) { - callback.registered(module); - } - - } catch (Exception e) { - Log.error(logPrefix, "Unable to create instance of provided extension %s: %s", moduleClass.getSimpleName(), e); - } - } - }); - } - - /** - * Registers a rule to a module - * - * @param module module instance to register rule for - * @param rule {@code Rule} to register - * @throws InvalidModuleException if module is null - */ - final void registerModuleRule(final Module module, final Rule rule) throws InvalidModuleException { - if (module == null) { - throw new InvalidModuleException(LOG_PROVIDED_MODULE_WAS_NULL); - } - - if (rule == null) { - throw new IllegalArgumentException("Cannot register a null rule"); - } - - - rulesEngine.addRule(module, rule); - } - - final void replaceModuleRules(final Module module, final List rules) throws InvalidModuleException { - if (module == null) { - throw new InvalidModuleException(LOG_PROVIDED_MODULE_WAS_NULL); - } - - if (rules == null) { - throw new IllegalArgumentException("Cannot register a null rule"); - } - - - rulesEngine.replaceRules(module, rules); - } - - - /** - * Evaluates given events with supplied rules, then registers rules for this module - * - * @param module module instance to register rules for - * @param rules {@code Rule} to register - * @param reprocessEventsHandler handler to return custom events - */ - protected void replaceRulesAndEvaluateEvents(final Module module, final List rules, - final ReprocessEventsHandler reprocessEventsHandler) { - - if (reprocessEventsHandler == null) { - Log.debug(logPrefix, "failed to reprocess events as is null "); - return; - } - - if (rules == null) { - Log.debug(logPrefix, "failed to reprocess events as is null "); - return; - } - - this.eventHubThreadService.submit(new ReprocessEventsWithRules(reprocessEventsHandler, rules, module)); - } - - /** - * Unregisters all rules registered by the given {@code Module} - * - * @param module module instance to unregister rules for - * @throws InvalidModuleException if module is null - */ - final void unregisterModuleRules(final Module module) throws InvalidModuleException { - if (module == null) { - throw new InvalidModuleException(LOG_PROVIDED_MODULE_WAS_NULL); - } - - rulesEngine.unregisterAllRules(module); - } - - - /** - * Registers a module with the event hub. Modules must extend {@code Module} - * - * @param moduleClass a class that extends {@link Module} - * @throws InvalidModuleException will be thrown if the {@code moduleClass} is null - */ - final void registerModule(final Class moduleClass) throws InvalidModuleException { - registerModuleWithCallback(moduleClass, null); - } - - final void registerModule(final Class moduleClass, - final ModuleDetails moduleDetails) throws InvalidModuleException { - registerModuleWithCallback(moduleClass, moduleDetails, null); - } - - /** - * Registers an extension with the event hub. Extensions must extend {@code Extension} - *

    - * When the registration is completed, the extension class will be initialized with the {@link ExtensionApi} - * - * @param extensionClass a class that extends {@link Extension} - * @param type of the extension class - * @throws InvalidModuleException will be thrown if the {@code extensionClass} is null - */ - final - void registerExtension(final Class extensionClass) throws InvalidModuleException { - if (extensionClass == null) { - throw new InvalidModuleException(LOG_CLASS_WAS_NULL); - } - - // register the module on the event hub thread - final EventHub hub = this; - this.eventHubThreadService.submit(new Runnable() { - @Override - public void run() { - try { - ExtensionApi module = new ExtensionApi(hub); - - Constructor moduleConstructor = extensionClass.getDeclaredConstructor(ExtensionApi.class); - moduleConstructor.setAccessible(true); - final Extension extension = moduleConstructor.newInstance(module); - - - if (StringUtils.isNullOrEmpty(extension.getName())) { - Log.error(logPrefix, "Failed to register extension, extension name should not be null or empty", - extension.getName()); - extension.onUnexpectedError(new ExtensionUnexpectedError( - String.format("Failed to register extension with name (%s), %s class", - extension.getName(), extensionClass.getSimpleName()), - ExtensionError.BAD_NAME)); - return; - } - - if (isRegisteredModule(extension.getName())) { - Log.error(logPrefix, "Failed to register extension, an extension with the same name (%s) already exists", - extension.getName()); - extension.onUnexpectedError(new ExtensionUnexpectedError( - String.format("Failed to register extension with name %s, %s class", - extension.getName(), extensionClass.getSimpleName()), - ExtensionError.DUPLICATE_NAME)); - return; - } - - activeModules.put(normalizeName(extension.getName()), module); - - moduleListeners.putIfAbsent(module, new ConcurrentLinkedQueue()); - - module.setExtension(extension); - - // add external module details to event hub shared state - module.setModuleDetails(new ModuleDetails() { - @Override - public String getName() { - return extension.getFriendlyName(); - } - - @Override - public String getVersion() { - return extension.getVersion(); - } - - @Override - public Map getAdditionalInfo() { - return new HashMap(); - } - }); - addModuleToEventHubSharedState(module); - - Log.debug(logPrefix, "Extension with name %s was registered successfully", module.getModuleName()); - } catch (Exception e) { - Log.error(logPrefix, "Unable to create instance of provided extension %s: %s", extensionClass.getSimpleName(), e); - } - } - }); - } - - /** - * Unregisters a Module - *

    - * This will remove all listeners, and will drop all references to the Module OneTime blocks that have been - * registered by the module will continue to exist until they execute - * If the {@code module} is a 3rd party extension, {@link ExtensionApi#onUnregistered()} will be called when the - * extension is unregistered - * - * @param module the instance of a Module class to unregister from the event loop - * @throws InvalidModuleException will be thrown if the module is null - */ - final void unregisterModule(final Module module) throws InvalidModuleException { - if (module == null) { - throw new InvalidModuleException(LOG_MODULE_WAS_NULL); - } - - // unregister the module on the event hub thread - this.eventHubThreadService.submit(new Runnable() { - @Override - public void run() { - - if (!isRegisteredModule(module.getModuleName())) { - Log.error(logPrefix, "Failed to unregister module, " + LOG_MODULE_NOT_REGISTERED, module.getModuleName()); - return; - } - - final Collection thisModulesListeners = moduleListeners.remove(module); - - if (thisModulesListeners != null) { - for (EventListener listener : thisModulesListeners) { - eventBus.removeListener(listener); - } - } - - activeModules.remove(normalizeName(module.getModuleName())); - - try { - module.onUnregistered(); - } catch (Exception e) { - Log.error(logPrefix, "%s.onUnregistered() threw %s", module.getClass().getSimpleName(), e); - } - } - }); - - removeModuleFromEventHubSharedState(module); - } - - /** - * Registers an event listener for a module - * - * @param module module instance to register listener with - * @param type {@code EventType} to listen for - * @param source {@code EventSource} to listen for - * @param pairID pairID for one-time event. May be {@code null} - * @param listenerClass class definition that extends {@code ModuleEventListener} - * @param type of the listener class - * @throws InvalidModuleException if module is null - */ - final > - void registerModuleListener(final Module module, - final EventType type, - final EventSource source, - final String pairID, - final Class listenerClass) throws InvalidModuleException { - - if (module == null) { - throw new InvalidModuleException(LOG_MODULE_WAS_NULL); - } - - if (listenerClass == null || type == null || source == null) { - Log.debug(logPrefix, "%s (listenerClass, type or source), failed to register listener", Log.UNEXPECTED_NULL_VALUE); - return; - } - - // register the module listener on the event hub thread - this.eventHubThreadService.submit(new Runnable() { - @Override - public void run() { - - // make sure module is registered - if (!isRegisteredModule(module.getModuleName())) { - Log.error(logPrefix, "Failed to register listener, " + LOG_MODULE_NOT_REGISTERED, module.getModuleName()); - return; - } - - // unregister listener if already registered - unregisterListenerIfPresent(module, type, source); - - // first try to find a constructor using the instance class (normal case) - Class expectedModuleClass = module.getClass(); - Constructor listenerConstructor = null; - - boolean isExtensionListener; - - try { - // try the extension listener case first - listenerConstructor = listenerClass.getDeclaredConstructor(expectedModuleClass, - String.class, String.class); - isExtensionListener = true; - } catch (NoSuchMethodException error) { - // not an extension listener, try to register an internal listener instead - isExtensionListener = false; - } - - if (!isExtensionListener) { - try { - // try the regular listener constructor - listenerConstructor = listenerClass.getDeclaredConstructor(expectedModuleClass, - EventType.class, - EventSource.class); - } catch (NoSuchMethodException firstTryError) { - - // next try using the instances classes parent class (mocking case) - expectedModuleClass = expectedModuleClass.getSuperclass(); - - try { - listenerConstructor = listenerClass.getDeclaredConstructor(expectedModuleClass, - EventType.class, - EventSource.class); - - } catch (NoSuchMethodException secondTryError) { - Log.error(logPrefix, "Failed to find a constructor for class %s (%s)", - listenerClass.getSimpleName(), secondTryError); - - // if this is an extension, call the error callback - if (ExtensionApi.class.isAssignableFrom(module.getClass())) { - ExtensionApi ext = (ExtensionApi) module; - ext.getExtension().onUnexpectedError(new ExtensionUnexpectedError("Failed to register listener", - ExtensionError.UNEXPECTED_ERROR)); - } - } - } - } - - if (listenerConstructor != null) { - try { - // construct the listener - listenerConstructor.setAccessible(true); - T listener; - - if (isExtensionListener) { - listener = listenerConstructor.newInstance(module, type.getName(), source.getName()); - } else { - listener = listenerConstructor.newInstance(module, type, source); - } - - // This allows EventHubTest tests to work without registering a module - moduleListeners.putIfAbsent(module, new ConcurrentLinkedQueue()); - ConcurrentLinkedQueue moduleSpecificListeners = moduleListeners.get(module); - - // add the listener to the modules queue of listeners - moduleSpecificListeners.add(listener); - - // add the listener to the event hub map of listeners - eventBus.addListener(listener, type, source, pairID); - - } catch (Exception e) { - Log.error(logPrefix, "Failed to register listener for class %s (%s)", - listenerClass.getSimpleName(), e); - - // if this is an extension, call the error callback - if (ExtensionApi.class.isAssignableFrom(module.getClass())) { - ExtensionApi ext = (ExtensionApi) module; - ext.getExtension().onUnexpectedError(new ExtensionUnexpectedError("Failed to register listener", e, - ExtensionError.UNEXPECTED_ERROR)); - } - } - } - } - }); - - } - - /** - * Registers a one-time block with the event hub - * a one-time block is an event handler that will cease to function after it successfully 'hears' one event - * - * @param type the {@code EventType} of an event to listen for - * @param source the {@code EventSource} of an event to listen for - * @param pairID optional pairID to listen for -- this is primarily used for request/response events - * @param block the block to call when the event is heard - * @see #registerOneTimeListener(String, Module.OneTimeListenerBlock) - * @deprecated this method is deprecated, please use {@link #registerOneTimeListener(String, Module.OneTimeListenerBlock)} instead - */ - @Deprecated - void registerOneTimeListener(final EventType type, - final EventSource source, - final String pairID, - final Module.OneTimeListenerBlock block) { - registerOneTimeListener(pairID, block); - } - - void registerOneTimeListener(final String pairID, - final Module.OneTimeListenerBlock block) { - registerOneTimeListener(pairID, block, null, 0); - } - - /** - * Registers a one-time block with the {@code EventHub}. An one-time block is an {@code Event} handler that will - * cease to function after it successfully 'hears' one event. The one-time listener will also be unregistered by - * the event hub after the event is received. Use the default timeout value of 5000ms. - * - * @param pairID optional pairID to listen for -- this is primarily used for request/response events - * @param block the block to call when the event is heard - * @param adobeCallbackWithError the block to call when there is error or timeout - */ - void registerOneTimeListener(final String pairID, - final Module.OneTimeListenerBlock block, - final AdobeCallbackWithError adobeCallbackWithError) { - registerOneTimeListener(pairID, block, adobeCallbackWithError, ONE_TIME_LISTENER_TIMEOUT_DEFAULT_MILLISECONDS); - - } - - /** - * Registers an event listener for the provided event type and source. - * - * @param eventType the type of the listened event - * @param eventSource the source of the listened event - * @param callback {@link AdobeCallbackWithError#call(Object)} will be called when the listened event is heard - */ - void registerEventListener(final EventType eventType, final EventSource eventSource, - final AdobeCallbackWithError callback) { - if (callback == null) { - Log.debug(logPrefix, "%s (callback), failed to register the event listener", Log.UNEXPECTED_NULL_VALUE); - return; - } - - this.eventHubThreadService.submit(new Runnable() { - @Override - public void run() { - try { - eventBus.addListener(new EventListener() { - @Override - public void hear(final Event e) { - callback.call(e); - } - - @Override - public void onUnregistered() {} - - @Override - public EventSource getEventSource() { - return eventSource; - } - - @Override - public EventType getEventType() { - return eventType; - } - }, eventType, eventSource, null); - - } catch (Exception e) { - Log.error(logPrefix, "Failed to register the event listener - (%s)", e); - } - } - }); - } - /** - * Registers a one-time block with the {@code EventHub}. An one-time block is an {@code Event} handler that will - * cease to function after it successfully 'hears' one event. The one-time listener will also be unregistered by - * the event hub after the event is received. - * - * @param pairID optional pairID to listen for -- this is primarily used for request/response events - * @param block the block to call when the event is heard - * @param adobeCallbackWithError the block to call when there is error or timeout - * @param timeoutInMilliSec the timeout value in milliseconds - */ - void registerOneTimeListener(final String pairID, - final Module.OneTimeListenerBlock block, - final AdobeCallbackWithError adobeCallbackWithError, - final int timeoutInMilliSec) { - - if (block == null) { - Log.debug(logPrefix, "%s (callback block), failed to register one-time listener", Log.UNEXPECTED_NULL_VALUE); - - if (adobeCallbackWithError != null) { - adobeCallbackWithError.fail(AdobeError.CALLBACK_NULL); - } - - return; - } - - final OneTimeListener oneTimeListener = new OneTimeListener(block); - // register the one-time listener on the event hub thread - this.eventHubThreadService.submit(new Runnable() { - @Override - public void run() { - - try { - eventBus.addListener(oneTimeListener, null, null, pairID); - - } catch (Exception e) { - Log.error(logPrefix, "Failed to register one-time listener", e); - } - } - }); - - if (timeoutInMilliSec > 0 && adobeCallbackWithError != null) { - ScheduledExecutorService scheduledThreadPool = getScheduledExecutorService(); - scheduledThreadPool.schedule(new Runnable() { - @Override - public void run() { - if (oneTimeListener.isCalled()) { - return; - } - - oneTimeListener.cancel(); - eventHubThreadService.submit(new Runnable() { - @Override - public void run() { - eventBus.removeListener(oneTimeListener, null, null, pairID); - } - }); - adobeCallbackWithError.fail(AdobeError.CALLBACK_TIMEOUT); - - } - }, timeoutInMilliSec, TimeUnit.MILLISECONDS); - } - - - } - - private ScheduledExecutorService getScheduledExecutorService() { - if (scheduledThreadPool == null) { - synchronized (scheduledThreadPoolMutex) { - if (scheduledThreadPool == null) { - scheduledThreadPool = Executors.newSingleThreadScheduledExecutor(); - } - } - } - - return scheduledThreadPool; - } - - /** - * Private method that unregisters a specific listener. Assumes that it is running on the event hub thread. - * - * @param module module instance to unregister listener with - * @param type the {@code EventType} of an event to listen for - * @param source the {@code EventSource} of an event to listen for - * @return {@code boolean} true if a listener was found and removed, false otherwise - */ - private boolean unregisterListenerIfPresent(final Module module, final EventType type, final EventSource source) { - - // check if listeners are registered for this module - boolean listenerExist = false; - - final ConcurrentLinkedQueue moduleSpecificListeners = moduleListeners.get(module); - - if (moduleSpecificListeners == null || moduleSpecificListeners.isEmpty()) { - return false; - } - - for (EventListener listener : moduleSpecificListeners) { - if (listener.getEventSource().equals(source) && listener.getEventType().equals(type)) { - listenerExist = true; - moduleSpecificListeners.remove(listener); - this.eventBus.removeListener(listener); - } - } - - return listenerExist; - } - - /** - * Unregisters all listeners that match a type/source pair from the {@code Module} and the - * {@code EventHub} - * - * @param module module instance to unregister listener for - * @param type the {@code EventType} of an event to listen for - * @param source the {@code EventSource} of an event to listen for - * @throws InvalidModuleException if module is null or not registered with this event hub - */ - final void unregisterModuleListener(final Module module, final EventType type, - final EventSource source) throws InvalidModuleException { - if (module == null) { - throw new InvalidModuleException(LOG_MODULE_WAS_NULL); - } - - // un-register the module listener on the event hub thread - this.eventHubThreadService.submit(new Runnable() { - @Override - public void run() { - if (!unregisterListenerIfPresent(module, type, source)) { - Log.debug(logPrefix, "Failed to unregister listener (no registered listener)"); - } - } - }); - - } - - /** - * For testing, get a collection of all the active modules - * - * @return the collection of active modules - */ - final Collection getActiveModules() { - return this.activeModules.values(); - } - - - /** - * @return instance of {@code PlatformServices} that is associated with this {@code EventHub} - */ - final PlatformServices getPlatformServices() { - return this.services; - } - - /** - * Creates a shared state object for the given module versioned at the current event for this hub - * - * @param module Module that owns this shared state - * @param version int version that this configuration should begin to be valid for - * @param state EventData object containing the state to share. Must be data, - * {@link EventHub#SHARED_STATE_PENDING}, or {@link EventHub#SHARED_STATE_INVALID} - * @throws InvalidModuleException if the provided module is null or the provided module has a null - * sharedStateName() - **/ - void createSharedState(final Module module, final int version, - final EventData state) throws InvalidModuleException { - createOrUpdateSharedStateCommon(module, version, state, true, false, SharedStateType.STANDARD); - } - - /** - * Updates an existing {@link EventHub#SHARED_STATE_PENDING} for the given module and version - * - * @param module Module to update the shared state for - * @param version int version to update - * @param state new state to replace with existing state. Must be data, {@link EventHub#SHARED_STATE_PENDING}, - * {@link EventHub#SHARED_STATE_INVALID}, {@link EventHub#SHARED_STATE_NEXT}, or {@link EventHub#SHARED_STATE_PREV}. - * @throws InvalidModuleException if the provided module is null or the provided module has a null - * sharedStateName() - **/ - void updateSharedState(final Module module, final int version, - final EventData state) throws InvalidModuleException { - createOrUpdateSharedStateCommon(module, version, state, false, true, SharedStateType.STANDARD); - } - - /** - * Creates or updates a shared state object for the given {@code module} and {@code version}. - * If no shared state exists for the module at the given version, then one is created with the given state. - * If a shared state already exists for the module at the given version and the state - * is {@link EventHub#SHARED_STATE_PENDING}, then the state is updated with the given state. - *

    - * Only for use by Module. - * - * @param module Module that owns this shared state - * @param version version of the existing shared state to add or replace - * @param state {@link EventData} object containing the state to share. Must be data, - * {@link EventHub#SHARED_STATE_PENDING}, or {@link EventHub#SHARED_STATE_INVALID} - * when creating or data, {@link EventHub#SHARED_STATE_PENDING}, {@link EventHub#SHARED_STATE_INVALID}, - * {@link EventHub#SHARED_STATE_NEXT}, or {@link EventHub#SHARED_STATE_PREV} when updating - * @throws InvalidModuleException if the provided module is null. - **/ - void createOrUpdateSharedState(final Module module, - final int version, - final EventData state) throws InvalidModuleException { - createOrUpdateSharedStateCommon(module, version, state, true, true, SharedStateType.STANDARD); - - } - - /** - * Creates a shared state object for the given module versioned at the current event number for this hub. - *

    - * NOTE: Do not call this method if the {@code module} also calls {@link #createSharedState(Module, int, EventData)} - * or {@link #createOrUpdateSharedState(Module, int, EventData)}. It may lead to data loss when attempting - * to create a shared state. - * - * @param module Module that owns this shared state - * @param state EventData object containing the state to share. Must be data, - * {@link EventHub#SHARED_STATE_PENDING}, or {@link EventHub#SHARED_STATE_INVALID} - * @throws InvalidModuleException if the provided module is null or the provided module has a null - * sharedStateName() - **/ - void createOrUpdateSharedState(final Module module, - final EventData state) throws InvalidModuleException { - int version = currentEventNumber.get(); - // as this is a new state version, only create and not update - createOrUpdateSharedStateCommon(module, version, state, true, false, SharedStateType.STANDARD); - - } - - /** - * Creates a shared state object for the given module versioned at the current event for this hub - * - * @param module Module that owns this shared state - * @param version int version that this configuration should begin to be valid for - * @param state EventData object containing the state to share. Must be data, - * {@link EventHub#SHARED_STATE_PENDING}, or {@link EventHub#SHARED_STATE_INVALID} - * @param sharedStateType the type of shared state to be created - * @throws InvalidModuleException if the provided module is null or the provided module has a null - * sharedStateName() - **/ - void createSharedState(final Module module, final int version, - final EventData state, final SharedStateType sharedStateType) throws InvalidModuleException { - createOrUpdateSharedStateCommon(module, version, state, true, false, sharedStateType); - } - - /** - * Updates an existing {@link EventHub#SHARED_STATE_PENDING} for the given module and version - * - * @param module Module to update the shared state for - * @param version int version to update - * @param state new state to replace with existing state. Must be data, {@link EventHub#SHARED_STATE_PENDING}, - * {@link EventHub#SHARED_STATE_INVALID}, {@link EventHub#SHARED_STATE_NEXT}, or {@link EventHub#SHARED_STATE_PREV}. - * @param sharedStateType the type of shared state to be updated - * @throws InvalidModuleException if the provided module is null or the provided module has a null - * sharedStateName() - **/ - void updateSharedState(final Module module, final int version, - final EventData state, final SharedStateType sharedStateType) throws InvalidModuleException { - createOrUpdateSharedStateCommon(module, version, state, false, true, sharedStateType); - } - - /** - * Creates or updates a shared state object for the given {@code module} and {@code version}. - * If no shared state exists for the module at the given version, then one is created with the given state. - * If an shared state already exists for the module at the given version and the state - * is {@link EventHub#SHARED_STATE_PENDING}, then the state is updated with the given state. - *

    - * Only for use by Module. - * - * @param module Module that owns this shared state - * @param version version of the existing shared state to add or replace - * @param state {@link EventData} object containing the state to share. Must be data, - * {@link EventHub#SHARED_STATE_PENDING}, or {@link EventHub#SHARED_STATE_INVALID} - * when creating or data, {@link EventHub#SHARED_STATE_PENDING}, {@link EventHub#SHARED_STATE_INVALID}, - * {@link EventHub#SHARED_STATE_NEXT}, or {@link EventHub#SHARED_STATE_PREV} when updating - * @param sharedStateType the type of shared state to be created - * @throws InvalidModuleException if the provided module is null. - **/ - void createOrUpdateSharedState(final Module module, - final int version, - final EventData state, - final SharedStateType sharedStateType) throws InvalidModuleException { - createOrUpdateSharedStateCommon(module, version, state, true, true, sharedStateType); - - } - - /** - * Creates a shared state object for the given module versioned at the current event number for this hub. - *

    - * NOTE: Do not call this method if the {@code module} also calls {@link #createSharedState(Module, int, EventData)} - * or {@link #createOrUpdateSharedState(Module, int, EventData)}. It may lead to data loss when attempting - * to create an shared state. - * - * @param module Module that owns this shared state - * @param state EventData object containing the state to share. Must be data, - * {@link EventHub#SHARED_STATE_PENDING}, or {@link EventHub#SHARED_STATE_INVALID} - * @param sharedStateType the type of shared state to be created or updated - * @throws InvalidModuleException if the provided module is null or the provided module has a null - * sharedStateName() - **/ - void createOrUpdateSharedState(final Module module, - final EventData state, final SharedStateType sharedStateType) throws InvalidModuleException { - int version = currentEventNumber.get(); - // as this is a new state version, only create and not update - createOrUpdateSharedStateCommon(module, version, state, true, false, sharedStateType); - - } - - /** - * Common helper method for creating and update shared states. - * - * @param module Module that owns this shared state - * @param version version of the existing shared state to add or replace - * @param state {@link EventData} object containing the state to share. Must be data, - * {@link EventHub#SHARED_STATE_PENDING}, or {@link EventHub#SHARED_STATE_INVALID} - * when creating or data, {@link EventHub#SHARED_STATE_PENDING}, {@link EventHub#SHARED_STATE_INVALID}, - * {@link EventHub#SHARED_STATE_NEXT}, or {@link EventHub#SHARED_STATE_PREV} when updating - * @param shouldCreate if this method should attempt to create a new shared state - * @param shouldUpdate if this method should attempt to update an existing shared state - * @param sharedStateType type of shared state - * @throws InvalidModuleException if the provided module is null or the provided module has a null - * sharedStateName() - */ - private void createOrUpdateSharedStateCommon(final Module module, final int version, - final EventData state, final boolean shouldCreate, final boolean shouldUpdate, final SharedStateType sharedStateType) - throws InvalidModuleException { - if (module == null) { - throw new InvalidModuleException(LOG_MODULE_WAS_NULL); - } - - final String stateName = module.getModuleName(); - - if (stateName == null) { - throw new InvalidModuleException(LOG_STATENAME_WAS_NULL); - } - - createOrUpdateSharedStateCommon(stateName, version, state, shouldCreate, shouldUpdate, sharedStateType); - } - - /** - * Common helper method for creating and update shared states. - * - * @param moduleName name of the Module that owns this shared state - * @param version version of the existing shared state to add or replace - * @param state {@link EventData} object containing the state to share. Must be data, - * {@link EventHub#SHARED_STATE_PENDING}, or {@link EventHub#SHARED_STATE_INVALID} - * when creating or data, {@link EventHub#SHARED_STATE_PENDING}, {@link EventHub#SHARED_STATE_INVALID}, - * {@link EventHub#SHARED_STATE_NEXT}, or {@link EventHub#SHARED_STATE_PREV} when updating - * @param shouldCreate if this method should attempt to create a new shared state - * @param shouldUpdate if this method should attempt to update an existing shared state - * @param sharedStateType type of shared state - * @throws InvalidModuleException if the provided module is null or the provided module has a null - * sharedStateName() - */ - private void createOrUpdateSharedStateCommon(final String moduleName, final int version, - final EventData state, final boolean shouldCreate, - final boolean shouldUpdate, final SharedStateType sharedStateType) { - boolean didUpdate = false; - boolean didCreate = false; - ConcurrentHashMap> sharedStates = sharedStateType == SharedStateType.XDM ? - moduleXdmSharedStates : moduleSharedStates; - - if (!sharedStates.containsKey(moduleName)) { - if (shouldCreate) { - // first shared state for this 'stateName' module - RangedResolver resolver = new RangedResolver( - SHARED_STATE_PENDING, - SHARED_STATE_INVALID, - SHARED_STATE_NEXT, - SHARED_STATE_PREV); - didCreate = resolver.add(version, state); - sharedStates.put(moduleName, resolver); - } - } else { - // module 'stateName' already shared state - if (shouldCreate) { - // attempt to add this 'version' - didCreate = sharedStates.get(moduleName).add(version, state); - } - - if (shouldUpdate && !didCreate) { - // attempt to update at 'version' if did not just create - didUpdate = sharedStates.get(moduleName).update(version, state); - } - } - - if (!didCreate && !didUpdate) { - Log.warning(logPrefix, "Unable to create or update shared state for %s with version %d.", moduleName, version); - return; - } - - if (state == SHARED_STATE_PENDING) { - Log.trace(logPrefix, "Will not fire shared state for %s with version %d, when this shared state is PENDING.", - moduleName, version); - } else { - fireStateChangeEvent(moduleName, sharedStateType); - - // we only want to run the expensive string manipulation code in the "prettyString" - // method when we know that the resulting string is going to be used in the log - if (Log.getLogLevel().id >= LoggingMode.VERBOSE.id) { - Log.trace(logPrefix, "New shared state data for '%s' at version '%d': \n%s", moduleName, version, - state.prettyString(1)); - } - } - } - - /** - * Retrieves shared state by name that is valid for the given event. If {@code event} is null, retrieves the - * latest shared state for {@code stateName}. - * - * @param stateName String identifier for the module that shared the state - * @param event {@link Event}'s version used to retrieve corresponding state, or the latest state if null - * @param callingModule the module calling this method - * @return EventData object containing the valid state, - * {@link EventHub#SHARED_STATE_PENDING}, or {@link EventHub#SHARED_STATE_INVALID} - **/ - EventData getSharedEventState(final String stateName, final Event event, final Module callingModule) { - return getSharedEventStateCommon(stateName, event, callingModule, SharedStateType.STANDARD); - } - - /** - * Retrieves shared state by name that is valid for the given event. If {@code event} is null, retrieves the - * latest shared state for {@code stateName}. - * - * @param stateName String identifier for the module that shared the state - * @param event {@link Event}'s version used to retrieve corresponding state, or the latest state if null - * @param callingModule the module calling this method - * @param sharedStateType the type of shared state to be read - * @return EventData object containing the valid state, - * {@link EventHub#SHARED_STATE_PENDING}, or {@link EventHub#SHARED_STATE_INVALID} - **/ - EventData getSharedEventState(final String stateName, final Event event, final Module callingModule, - final SharedStateType sharedStateType) { - return getSharedEventStateCommon(stateName, event, callingModule, sharedStateType); - } - - private EventData getSharedEventStateCommon(final String stateName, final Event event, final Module callingModule, - final SharedStateType sharedStateType) { - if (stateName == null) { - throw new IllegalArgumentException(LOG_STATENAME_WAS_NULL); - } - - int eventVersion = Event.SHARED_STATE_NEWEST.getEventNumber(); // default to getting latest - - if (event != null) { - eventVersion = event.getEventNumber(); - } - - // check for circular dependency (live lock risk) - // this can occur if you have two modules (m1, m2) that are inter-dependent on shared states (m1->m2 and - // m2->m1). - // this code provides detection and logging, we can't avoid the livelock but we should at least detect it. - if (Log.getLogLevel().id >= LoggingMode.DEBUG.id && callingModule != null) { - final String callingModuleStateName = callingModule.getModuleName(); - this.sharedStateCircularCheck.put(callingModuleStateName + stateName, true); - - if (this.sharedStateCircularCheck.get(stateName + callingModuleStateName) != null) { - Log.warning(logPrefix, "Circular shared-state dependency between %s and %s, you may have a " + - "live-lock.", - callingModuleStateName, stateName); - } - } - - final RangedResolver sharedState = sharedStateType == SharedStateType.XDM ? this.moduleXdmSharedStates.get( - stateName) : this.moduleSharedStates.get(stateName); - - if (sharedState != null) { - return sharedState.resolve(eventVersion); - } - - return SHARED_STATE_PENDING; - } - - /** - * Determine if there are any shared states for a specified module. - * A module is considered to have a valid shared state if any state contains data or is {@link #SHARED_STATE_PENDING}. - * States which are {@link #SHARED_STATE_INVALID}, {@link #SHARED_STATE_NEXT}, or {@link #SHARED_STATE_PREV} - * are not considered valid. - * - * @param stateName {@link String} identifier for the module which shared a state - * @return true if the specified module has shared a state which either contains data or is {@link #SHARED_STATE_PENDING} - */ - boolean hasSharedEventState(final String stateName) { - return hasSharedEventState(stateName, SharedStateType.STANDARD); - } - - /** - * Determine if there are any shared states for a specified module. - * A module is considered to have a valid shared state if any state contains data or is {@link #SHARED_STATE_PENDING}. - * States which are {@link #SHARED_STATE_INVALID}, {@link #SHARED_STATE_NEXT}, or {@link #SHARED_STATE_PREV} - * are not considered valid. - * - * @param stateName {@link String} identifier for the module which shared a state - * @param sharedStateType the type of shared state checked - * @return true if the specified module has shared a state which either contains data or is {@link #SHARED_STATE_PENDING} - */ - boolean hasSharedEventState(final String stateName, final SharedStateType sharedStateType) { - if (stateName == null) { - throw new IllegalArgumentException(LOG_STATENAME_WAS_NULL); - } - - final RangedResolver sharedStates = sharedStateType == SharedStateType.XDM ? this.moduleXdmSharedStates.get( - stateName) : this.moduleSharedStates.get(stateName); - - return sharedStates != null && sharedStates.containsValidState(); - } - - /** - * Clears all the shared states for the given module. - *

    - * Only for use by Module. - * - * @param module {@link Module} to clear the shared states for - * @throws InvalidModuleException if the provided module is null or the provided module has a - * null {@link Module#getModuleName()} - **/ - void clearSharedStates(final Module module) throws InvalidModuleException { - clearSharedStateCommon(module, SharedStateType.STANDARD); - } - - /** - * Clears all the shared states for the given module. - *

    - * Only for use by Module. - * - * @param module {@link Module} to clear the shared states for - * @param sharedStateType the type of shared state to be created - * @throws InvalidModuleException if the provided module is null or the provided module has a - * null {@link Module#getModuleName()} - **/ - void clearSharedStates(final Module module, final SharedStateType sharedStateType) throws InvalidModuleException { - clearSharedStateCommon(module, sharedStateType); - } - - private void clearSharedStateCommon(final Module module, - final SharedStateType sharedStateType) throws InvalidModuleException { - if (module == null) { - throw new InvalidModuleException(LOG_MODULE_WAS_NULL); - } - - final String stateName = module.getModuleName(); - - if (stateName == null) { - throw new InvalidModuleException(LOG_STATENAME_WAS_NULL); - } - - if (sharedStateType == SharedStateType.XDM) { - this.moduleXdmSharedStates.remove(stateName); - } else { - this.moduleSharedStates.remove(stateName); - } - - fireStateChangeEvent(stateName, sharedStateType); - } - - /** - * For test, need to shutdown the threadPoll after each test - */ - void shutdown() { - this.eventHubThreadService.shutdownNow(); - this.threadPool.shutdownNow(); - } - - /** - * For platform tests, need to clear data from memory. - */ - void resetSharedStates() { - this.moduleSharedStates.clear(); - this.moduleXdmSharedStates.clear(); - } - - /** - * For extensions tests, to check that a listener was registered/unregistered - * - * @param module the module for which to retrieve the listeners - * @return the list of listeners for the provided module - */ - ConcurrentLinkedQueue getModuleListeners(final Module module) { - return this.moduleListeners.get(module); - } - - /** - * Creates and publishes a state change event to this event hub - * - * @param stateName stateName state-name of the module that published the change - * @param sharedStateType the type of the shared state updated - **/ - private void fireStateChangeEvent(final String stateName, final SharedStateType sharedStateType) { - final String eventName = sharedStateType == SharedStateType.STANDARD ? STANDARD_STATE_CHANGE_EVENTNAME : - XDM_STATE_CHANGE_EVENTNAME; - final Event updateEvent = new Event.Builder(eventName, EventType.HUB, EventSource.SHARED_STATE) - .setData(new EventData().putString(EventHubConstants.EventDataKeys.Configuration.EVENT_STATE_OWNER, stateName)) - .build(); - this.dispatch(updateEvent); - } - - protected void createEventHubSharedState(final int eventNumber) { - createOrUpdateSharedStateCommon(EventHubConstants.EventDataKeys.EventHub.SHARED_STATE_NAME, - eventNumber, eventHubSharedState, true, false, SharedStateType.STANDARD); - } - - protected void addModuleToEventHubSharedState(final Module module) { - if (module == null) { - return; - } - - final ModuleDetails details = module.getModuleDetails(); - - final String moduleName = module.getModuleName(); - final String friendlyName = details == null ? module.getModuleName() : details.getName(); - final String moduleVersion = details == null ? module.getModuleVersion() : details.getVersion(); - - if (StringUtils.isNullOrEmpty(moduleName)) { - return; - } - - Log.trace(logPrefix, "Registering extension '%s' with version '%s'", moduleName, moduleVersion); - - final Map extensionsMap = eventHubSharedState.optVariantMap( - EventHubConstants.EventDataKeys.EventHub.EXTENSIONS, new HashMap()); - final Map moduleMap = new HashMap(); - moduleMap.put(EventHubConstants.EventDataKeys.EventHub.VERSION, - Variant.fromString(moduleVersion != null ? moduleVersion : "")); - moduleMap.put(EventHubConstants.EventDataKeys.EventHub.FRIENDLY_NAME, - Variant.fromString(friendlyName != null ? friendlyName : moduleName)); - extensionsMap.put(moduleName, Variant.fromVariantMap(moduleMap)); - eventHubSharedState.putVariantMap(EventHubConstants.EventDataKeys.EventHub.EXTENSIONS, - extensionsMap); - - // only update the shared state if we're beyond the boot event - synchronized (bootMutex) { - if (isBooted) { - createEventHubSharedState(currentEventNumber.get()); - } - } - } - - protected void removeModuleFromEventHubSharedState(final Module module) { - // quick out if the shared state doesn't exist yet - if (module == null) { - return; - } - - final String moduleName = module.getModuleName(); - - if (StringUtils.isNullOrEmpty(moduleName)) { - return; - } - - final Map extensionsMap = eventHubSharedState.optVariantMap( - EventHubConstants.EventDataKeys.EventHub.EXTENSIONS, new HashMap()); - extensionsMap.remove(moduleName); - eventHubSharedState.putVariantMap(EventHubConstants.EventDataKeys.EventHub.EXTENSIONS, - extensionsMap); - - // only update the shared state if we're beyond the boot event - synchronized (bootMutex) { - if (isBooted) { - createEventHubSharedState(currentEventNumber.get()); - } - } - } - - protected EventData getInitialEventHubSharedState() { - final EventData state = new EventData(); - - state.putString(EventHubConstants.EventDataKeys.EventHub.VERSION, coreVersion); - state.putVariantMap(EventHubConstants.EventDataKeys.EventHub.EXTENSIONS, new HashMap()); - state.putStringMap(EventHubConstants.EventDataKeys.EventHub.WRAPPER, getWrapperInfo()); - - return state; - } - - /** - * Gets the SDK's current version with wrapper type. - */ - String getSdkVersion() { - String version = coreVersion; - - if (wrapperType != WrapperType.NONE) { - version += SDK_VERSION_DELIMITER + wrapperType.getWrapperTag(); - } - - return version; - } - - /** - * Sets the SDK's current wrapper type. This API should only be used if - * being developed on platforms such as React Native. - * - * @param wrapperType the type of wrapper being used. - */ - void setWrapperType(final WrapperType wrapperType) { - synchronized (bootMutex) { - if (isBooted) { - Log.warning(logPrefix, "Cannot set wrapper type to (%s) - (%s) as event hub has already booted.", - wrapperType.getWrapperTag(), wrapperType.getFriendlyName()); - return; - } - - this.wrapperType = wrapperType; - // update wrapperType info in hub shared state - eventHubSharedState.putStringMap(EventHubConstants.EventDataKeys.EventHub.WRAPPER, getWrapperInfo()); - } - } - - /** - * Returns the SDK's current wrapper type. - * - * @return {@link WrapperType} enum. - */ - WrapperType getWrapperType() { - return wrapperType; - } - - /** - * Returns the wrapper info with wrapper tag and friendlyName. - * - * @return map containing wrapper tag and friendlyName. - */ - private HashMap getWrapperInfo() { - HashMap wrapperInfo = new HashMap(); - wrapperInfo.put(EventHubConstants.EventDataKeys.EventHub.TYPE, wrapperType.getWrapperTag()); - wrapperInfo.put(EventHubConstants.EventDataKeys.EventHub.FRIENDLY_NAME, wrapperType.getFriendlyName()); - - return wrapperInfo; - } - - private final class ReprocessEventsWithRules implements Runnable { - - final ReprocessEventsHandler reprocessEventsHandler; - final List rules; - final List consequenceEvents; - final Module module; - - ReprocessEventsWithRules(final ReprocessEventsHandler reprocessEventsHandler, final List rules, - final Module module) { - this.reprocessEventsHandler = reprocessEventsHandler; - this.rules = rules; - this.module = module; - this.consequenceEvents = new ArrayList(); - } - - @Override - public void run() { - try { - List events = reprocessEventsHandler.getEvents(); - - if (events.size() > REPROCESS_EVENTS_AMOUNT_LIMIT) { - Log.debug(logPrefix, "Failed to reprocess cached events, since the amount of events (%s) reach the limits (%s)", - events.size(), - REPROCESS_EVENTS_AMOUNT_LIMIT); - } else { - for (final Event e : events) { - List resultEvents = rulesEngine.evaluateEventWithRules(e, rules); - consequenceEvents.addAll(resultEvents); - } - } - - reprocessEventsHandler.onEventReprocessingComplete(); - - replaceModuleRules(module, rules); - - for (final Event e : consequenceEvents) { - dispatch(e); - } - } catch (Exception e) { - Log.debug(logPrefix, "Failed to reprocess cached events (%s)", e); - } - } - } - - /** - * Class implementing the Runnable for event - */ - private final class EventRunnable implements Runnable { - final Event event; - - EventRunnable(final Event event) { - this.event = event; - } - - @Override - public void run() { - // run rules - final long preRulesTime = System.currentTimeMillis(); - final List resultEvents = rulesEngine.evaluateRules(event); - - for (final Event e : resultEvents) { - dispatch(e); - } - - final long totalRulesTime = System.currentTimeMillis() - preRulesTime; - Log.trace(logPrefix, "Event (%s) #%d (%s) resulted in %d consequence events. Time in rules was %d milliseconds.", - event.getUniqueIdentifier(), event.getEventNumber(), event.getName(), resultEvents.size(), totalRulesTime); - eventBus.dispatch(event); - } - } + // shared state markers + /** + * State that is "on the way" and will eventually be resolved. + */ + public static final EventData SHARED_STATE_PENDING = null; + /** + * Special state that indicates that the state is not valid. + */ + public static final EventData SHARED_STATE_INVALID = new EventData(); + /** + * Special "marker" state that indicates that this state is equal to the next DATA/PENDING/INVALID state. + */ + public static final EventData SHARED_STATE_NEXT = new EventData(); + /** + * Special "marker" state that indicates that this state is equal to the previous state. + */ + public static final EventData SHARED_STATE_PREV = new EventData(); + + private static final String STANDARD_STATE_CHANGE_EVENTNAME = "Shared state change"; + private static final String XDM_STATE_CHANGE_EVENTNAME = "Shared state change (XDM)"; + private static final String LOG_MODULE_WAS_NULL = "Module was null"; + private static final String LOG_CLASS_WAS_NULL = "Extension class was null"; + private static final String LOG_STATENAME_WAS_NULL = "StateName was null"; + private static final String LOG_MODULE_NOT_REGISTERED = "Module (%s) is not registered"; + private static final long DEFAULT_THREAD_POOL_KEEP_ALIVE_TIME_SECONDS = 60L; + private static final int ONE_TIME_LISTENER_TIMEOUT_DEFAULT_MILLISECONDS = 5000; + private static final String SDK_VERSION_DELIMITER = "-"; + + // instance variables + private final String logPrefix; + private final PlatformServices services; + private final ConcurrentHashMap activeModules; + private final ConcurrentHashMap> moduleListeners; + private final ConcurrentHashMap> moduleSharedStates; + private final ConcurrentHashMap> moduleXdmSharedStates; + private final ConcurrentHashMap sharedStateCircularCheck; + private final LinkedList preBootEvents; // locked on bootMutex + private final AtomicInteger currentEventNumber; + private final ExecutorService threadPool; + private final ExecutorService eventHubThreadService; + protected final EventData eventHubSharedState; + protected final String coreVersion; + private WrapperType wrapperType = WrapperType.NONE; + private ScheduledExecutorService scheduledThreadPool; + private final Object scheduledThreadPoolMutex = new Object(); + protected boolean isBooted; // locked on bootMutex + private final Object bootMutex = new Object(); + private final EventBus eventBus; + private final List preprocessors = Collections.synchronizedList(new ArrayList<>()); + + + /** + * Returns an instance of the Event Hub + * + * @param name the name of the {@code EventHub} to be created - for logging purposes + * @param services instance of {@code PlatformServices} class to provide platform-specific functionality + * @throws IllegalArgumentException If platform services is null + */ + public EventHub(final String name, final PlatformServices services) { + this(name, services, "undefined"); + } + + /** + * Returns an instance of the Event Hub + * + * @param name the name of the {@code EventHub} to be created - for logging purposes + * @param services instance of {@code PlatformServices} class to provide platform-specific functionality + * @param coreVersion value passed from platform to indicate the running version of core + * @throws IllegalArgumentException If platform services is null + */ + public EventHub(final String name, final PlatformServices services, final String coreVersion) { + logPrefix = String.format("%s(%s)", this.getClass().getSimpleName(), name); + + if (services == null) { + throw new IllegalArgumentException("Cannot construct EventHub without a valid platform services instance"); + } + + this.coreVersion = coreVersion; + this.services = services; + this.activeModules = new ConcurrentHashMap(); + this.moduleListeners = new ConcurrentHashMap>(); + this.moduleSharedStates = new ConcurrentHashMap>(); + this.moduleXdmSharedStates = new ConcurrentHashMap>(); + this.currentEventNumber = new AtomicInteger(1); + this.preBootEvents = new LinkedList(); + this.sharedStateCircularCheck = new ConcurrentHashMap(); + this.threadPool = Executors.newCachedThreadPool(); + this.eventHubThreadService = new ThreadPoolExecutor(0, 1, + DEFAULT_THREAD_POOL_KEEP_ALIVE_TIME_SECONDS, TimeUnit.SECONDS, + new LinkedBlockingQueue()); + this.eventHubSharedState = getInitialEventHubSharedState(); + this.isBooted = false; + this.eventBus = new EventBus(); + } + + /** + * Let event hub know that all the modules have been registered, so it can dispatch a booted event as the first event. + * + * @param completionCallback An optional {@link AdobeCallback} invoked after registrations are completed + */ + void finishModulesRegistration(final AdobeCallback completionCallback) { + this.eventHubThreadService.submit(new Runnable() { + @Override + public void run() { + synchronized (bootMutex) { + if (isBooted) { + Log.trace(logPrefix, "Eventhub has already been booted"); + return; + } + + Event bootedEvent = new Event.Builder("EventHub", EventType.HUB, EventSource.BOOTED).build(); + bootedEvent.setEventNumber(0); + eventHubThreadService.submit(new EventRunnable(bootedEvent)); + + isBooted = true; + + // generate eventhub shared state prior to releasing "pre-boot" events. + createEventHubSharedState(0); + + while (preBootEvents.peek() != null) { + eventHubThreadService.submit(new EventRunnable(preBootEvents.poll())); + } + + if (completionCallback != null) { + eventHubThreadService.submit(new Runnable() { + @Override + public void run() { + completionCallback.call(null); + } + }); + } + } + } + }); + } + + /** + * Register an event preprocessor + * @param preprocessor a {@link EventPreprocessor} that processes each event before dispatching it to the EventHub + */ + void registerPreprocessor(final EventPreprocessor preprocessor) { + synchronized (bootMutex) { + preprocessors.add(preprocessor); + } + } + + + /** + * Dispatches an event onto the Event queue + * + * @param e the event to be added to the queue + */ + void dispatch(final Event e) { + synchronized (bootMutex) { + e.setEventNumber(this.currentEventNumber.getAndIncrement()); + + if (!this.isBooted) { + Log.debug(logPrefix, "Event (%s, %s) was dispatched before module registration was finished", + e.getEventType().getName(), e.getEventSource().getName()); + preBootEvents.add(e); + } else { + this.eventHubThreadService.submit(new EventRunnable(e)); + } + + final EventHistory eventHistory = MobileCore.getEventHistory(); + + // record the event in the event history database if the event has a mask + if (eventHistory != null && e.getMask() != null) { + final EventHistoryResultHandler handler = new EventHistoryResultHandler() { + @Override + public void call(final Boolean value) { + Log.trace(logPrefix, value ? "Successfully inserted an Event into EventHistory database" : + "Failed to insert an Event into EventHistory database"); + } + }; + eventHistory.recordEvent(e, handler); + } + } + } + + /** + * Creates a shared state for this module, then sends an event ( the event and the state will have the same event number ) + * + * @param module module instance to create the shared state + * @param sharedState {@code EventData} object containing the state to save + * @param event the event to be dispatched to {@code EventHub} + */ + void createSharedStateAndDispatchEvent(final Module module, final EventData sharedState, + final Event event) throws InvalidModuleException { + createSharedStateAndDispatchEvent(module, sharedState, event, SharedStateType.STANDARD); + } + + /** + * Creates a shared state for this module, then sends an event ( the event and the state will have the same event number ) + * + * @param module module instance to create the shared state + * @param sharedState {@code EventData} object containing the state to save + * @param event the event to be dispatched to {@code EventHub} + * @param sharedStateType the type of shared state to be created + */ + void createSharedStateAndDispatchEvent(final Module module, final EventData sharedState, final Event event, + final SharedStateType sharedStateType) throws InvalidModuleException { + event.setEventNumber(this.currentEventNumber.getAndIncrement()); + createSharedState(module, event.getEventNumber(), sharedState, sharedStateType); + this.eventHubThreadService.submit(new EventRunnable(event)); + } + + /** + * Checks if a module with the provided name is already registered. The comparison is not case-sensitive, + * the names will be lower cased before the compare. + *

    + * This method iterates through the modules list without a mutex, and is supposed to be called from a thread safe method + * + * @param moduleName the {@link String} module name to search for + * @return the status of the search + */ + private boolean isRegisteredModule(final String moduleName) { + if (moduleName == null) { + return false; + } + + return activeModules.containsKey(normalizeName(moduleName)); + } + + /** + * Returns the lower cased name + * + * @param name {@link String} name to be normalized + * @return the lower case name + */ + private String normalizeName(final String name) { + return name != null ? name.toLowerCase() : null; + } + + /** + * Interface for receiving callbacks when a module is registered + */ + protected interface RegisterModuleCallback { + void registered(Module module); + } + + /** + * Registers a module with the event hub. Modules must extend {@code Module} + * + * @param moduleClass a class that extends {@link Module} + * @param callback class implementing {@code RegisterModuleCallback} called if module successfully registers + * @throws InvalidModuleException will be thrown if the {@code moduleClass} is null + */ + protected void registerModuleWithCallback(final Class moduleClass, + final RegisterModuleCallback callback) throws InvalidModuleException { + registerModuleWithCallback(moduleClass, null, callback); + } + + + protected void registerModuleWithCallback(final Class moduleClass, + final ModuleDetails moduleDetails, + final RegisterModuleCallback callback) throws InvalidModuleException { + if (moduleClass == null) { + throw new InvalidModuleException(LOG_CLASS_WAS_NULL); + } + + // register the module on the event hub thread + final EventHub hub = this; + Future f = this.eventHubThreadService.submit(new Runnable() { + @Override + public void run() { + try { + Module module; + + // check, not to register modules have same class name + for (Module activeModule : hub.getActiveModules()) { + if (activeModule.getClass().getName().equalsIgnoreCase(moduleClass.getName())) { + Log.warning(logPrefix, "Failed to register extension, an extension with the same name (%s) already exists", + activeModule.getModuleName()); + return; + } + } + + if (InternalModule.class.isAssignableFrom(moduleClass)) { + Constructor moduleConstructor = moduleClass.getDeclaredConstructor(EventHub.class, + PlatformServices.class); + moduleConstructor.setAccessible(true); + module = moduleConstructor.newInstance(hub, services); + } else { + Constructor moduleConstructor = moduleClass.getDeclaredConstructor(EventHub.class); + moduleConstructor.setAccessible(true); + module = moduleConstructor.newInstance(hub); + } + + // check, not to register modules have same "module name" + if (isRegisteredModule(module.getModuleName())) { + Log.warning(logPrefix, "Failed to register extension, an extension with the same name (%s) already exists", + module.getModuleName()); + return; + } + + // set the module details that got passed in + module.setModuleDetails(moduleDetails); + addModuleToEventHubSharedState(module); + + activeModules.put(normalizeName(module.getModuleName()), module); + moduleListeners.put(module, new ConcurrentLinkedQueue()); + + if (callback != null) { + callback.registered(module); + } + + } catch (Exception e) { + Log.error(logPrefix, "Unable to create instance of provided extension %s: %s", moduleClass.getSimpleName(), e); + } + } + }); + } + + /** + * Registers a module with the event hub. Modules must extend {@code Module} + * + * @param moduleClass a class that extends {@link Module} + * @throws InvalidModuleException will be thrown if the {@code moduleClass} is null + */ + final void registerModule(final Class moduleClass) throws InvalidModuleException { + registerModuleWithCallback(moduleClass, null); + } + + final void registerModule(final Class moduleClass, + final ModuleDetails moduleDetails) throws InvalidModuleException { + registerModuleWithCallback(moduleClass, moduleDetails, null); + } + + /** + * Registers an extension with the event hub. Extensions must extend {@code Extension} + *

    + * When the registration is completed, the extension class will be initialized with the {@link ExtensionApi} + * + * @param extensionClass a class that extends {@link Extension} + * @param type of the extension class + * @throws InvalidModuleException will be thrown if the {@code extensionClass} is null + */ + final + void registerExtension(final Class extensionClass) throws InvalidModuleException { + if (extensionClass == null) { + throw new InvalidModuleException(LOG_CLASS_WAS_NULL); + } + + // register the module on the event hub thread + final EventHub hub = this; + this.eventHubThreadService.submit(new Runnable() { + @Override + public void run() { + try { + ExtensionApi module = new ExtensionApi(hub); + + Constructor moduleConstructor = extensionClass.getDeclaredConstructor(ExtensionApi.class); + moduleConstructor.setAccessible(true); + final Extension extension = moduleConstructor.newInstance(module); + + + if (StringUtils.isNullOrEmpty(extension.getName())) { + Log.error(logPrefix, "Failed to register extension, extension name should not be null or empty", + extension.getName()); + extension.onUnexpectedError(new ExtensionUnexpectedError( + String.format("Failed to register extension with name (%s), %s class", + extension.getName(), extensionClass.getSimpleName()), + ExtensionError.BAD_NAME)); + return; + } + + if (isRegisteredModule(extension.getName())) { + Log.error(logPrefix, "Failed to register extension, an extension with the same name (%s) already exists", + extension.getName()); + extension.onUnexpectedError(new ExtensionUnexpectedError( + String.format("Failed to register extension with name %s, %s class", + extension.getName(), extensionClass.getSimpleName()), + ExtensionError.DUPLICATE_NAME)); + return; + } + + activeModules.put(normalizeName(extension.getName()), module); + + moduleListeners.putIfAbsent(module, new ConcurrentLinkedQueue()); + + module.setExtension(extension); + + // add external module details to event hub shared state + module.setModuleDetails(new ModuleDetails() { + @Override + public String getName() { + return extension.getFriendlyName(); + } + + @Override + public String getVersion() { + return extension.getVersion(); + } + + @Override + public Map getAdditionalInfo() { + return new HashMap(); + } + }); + addModuleToEventHubSharedState(module); + + Log.debug(logPrefix, "Extension with name %s was registered successfully", module.getModuleName()); + } catch (Exception e) { + Log.error(logPrefix, "Unable to create instance of provided extension %s: %s", extensionClass.getSimpleName(), e); + } + } + }); + } + + /** + * Unregisters a Module + *

    + * This will remove all listeners, and will drop all references to the Module OneTime blocks that have been + * registered by the module will continue to exist until they execute + * If the {@code module} is a 3rd party extension, {@link ExtensionApi#onUnregistered()} will be called when the + * extension is unregistered + * + * @param module the instance of a Module class to unregister from the event loop + * @throws InvalidModuleException will be thrown if the module is null + */ + final void unregisterModule(final Module module) throws InvalidModuleException { + if (module == null) { + throw new InvalidModuleException(LOG_MODULE_WAS_NULL); + } + + // unregister the module on the event hub thread + this.eventHubThreadService.submit(new Runnable() { + @Override + public void run() { + + if (!isRegisteredModule(module.getModuleName())) { + Log.error(logPrefix, "Failed to unregister module, " + LOG_MODULE_NOT_REGISTERED, module.getModuleName()); + return; + } + + final Collection thisModulesListeners = moduleListeners.remove(module); + + if (thisModulesListeners != null) { + for (EventListener listener : thisModulesListeners) { + eventBus.removeListener(listener); + } + } + + activeModules.remove(normalizeName(module.getModuleName())); + + try { + module.onUnregistered(); + } catch (Exception e) { + Log.error(logPrefix, "%s.onUnregistered() threw %s", module.getClass().getSimpleName(), e); + } + } + }); + + removeModuleFromEventHubSharedState(module); + } + + /** + * Registers an event listener for a module + * + * @param module module instance to register listener with + * @param type {@code EventType} to listen for + * @param source {@code EventSource} to listen for + * @param pairID pairID for one-time event. May be {@code null} + * @param listenerClass class definition that extends {@code ModuleEventListener} + * @param type of the listener class + * @throws InvalidModuleException if module is null + */ + final > + void registerModuleListener(final Module module, + final EventType type, + final EventSource source, + final String pairID, + final Class listenerClass) throws InvalidModuleException { + + if (module == null) { + throw new InvalidModuleException(LOG_MODULE_WAS_NULL); + } + + if (listenerClass == null || type == null || source == null) { + Log.debug(logPrefix, "%s (listenerClass, type or source), failed to register listener", Log.UNEXPECTED_NULL_VALUE); + return; + } + + // register the module listener on the event hub thread + this.eventHubThreadService.submit(new Runnable() { + @Override + public void run() { + + // make sure module is registered + if (!isRegisteredModule(module.getModuleName())) { + Log.error(logPrefix, "Failed to register listener, " + LOG_MODULE_NOT_REGISTERED, module.getModuleName()); + return; + } + + // unregister listener if already registered + unregisterListenerIfPresent(module, type, source); + + // first try to find a constructor using the instance class (normal case) + Class expectedModuleClass = module.getClass(); + Constructor listenerConstructor = null; + + boolean isExtensionListener; + + try { + // try the extension listener case first + listenerConstructor = listenerClass.getDeclaredConstructor(expectedModuleClass, + String.class, String.class); + isExtensionListener = true; + } catch (NoSuchMethodException error) { + // not an extension listener, try to register an internal listener instead + isExtensionListener = false; + } + + if (!isExtensionListener) { + try { + // try the regular listener constructor + listenerConstructor = listenerClass.getDeclaredConstructor(expectedModuleClass, + EventType.class, + EventSource.class); + } catch (NoSuchMethodException firstTryError) { + + // next try using the instances classes parent class (mocking case) + expectedModuleClass = expectedModuleClass.getSuperclass(); + + try { + listenerConstructor = listenerClass.getDeclaredConstructor(expectedModuleClass, + EventType.class, + EventSource.class); + + } catch (NoSuchMethodException secondTryError) { + Log.error(logPrefix, "Failed to find a constructor for class %s (%s)", + listenerClass.getSimpleName(), secondTryError); + + // if this is an extension, call the error callback + if (ExtensionApi.class.isAssignableFrom(module.getClass())) { + ExtensionApi ext = (ExtensionApi) module; + ext.getExtension().onUnexpectedError(new ExtensionUnexpectedError("Failed to register listener", + ExtensionError.UNEXPECTED_ERROR)); + } + } + } + } + + if (listenerConstructor != null) { + try { + // construct the listener + listenerConstructor.setAccessible(true); + T listener; + + if (isExtensionListener) { + listener = listenerConstructor.newInstance(module, type.getName(), source.getName()); + } else { + listener = listenerConstructor.newInstance(module, type, source); + } + + // This allows EventHubTest tests to work without registering a module + moduleListeners.putIfAbsent(module, new ConcurrentLinkedQueue()); + ConcurrentLinkedQueue moduleSpecificListeners = moduleListeners.get(module); + + // add the listener to the modules queue of listeners + moduleSpecificListeners.add(listener); + + // add the listener to the event hub map of listeners + eventBus.addListener(listener, type, source, pairID); + + } catch (Exception e) { + Log.error(logPrefix, "Failed to register listener for class %s (%s)", + listenerClass.getSimpleName(), e); + + // if this is an extension, call the error callback + if (ExtensionApi.class.isAssignableFrom(module.getClass())) { + ExtensionApi ext = (ExtensionApi) module; + ext.getExtension().onUnexpectedError(new ExtensionUnexpectedError("Failed to register listener", e, + ExtensionError.UNEXPECTED_ERROR)); + } + } + } + } + }); + + } + + /** + * Registers a one-time block with the event hub + * a one-time block is an event handler that will cease to function after it successfully 'hears' one event + * + * @param type the {@code EventType} of an event to listen for + * @param source the {@code EventSource} of an event to listen for + * @param pairID optional pairID to listen for -- this is primarily used for request/response events + * @param block the block to call when the event is heard + * @see #registerOneTimeListener(String, Module.OneTimeListenerBlock) + * @deprecated this method is deprecated, please use {@link #registerOneTimeListener(String, Module.OneTimeListenerBlock)} instead + */ + @Deprecated + void registerOneTimeListener(final EventType type, + final EventSource source, + final String pairID, + final Module.OneTimeListenerBlock block) { + registerOneTimeListener(pairID, block); + } + + void registerOneTimeListener(final String pairID, + final Module.OneTimeListenerBlock block) { + registerOneTimeListener(pairID, block, null, 0); + } + + /** + * Registers a one-time block with the {@code EventHub}. An one-time block is an {@code Event} handler that will + * cease to function after it successfully 'hears' one event. The one-time listener will also be unregistered by + * the event hub after the event is received. Use the default timeout value of 5000ms. + * + * @param pairID optional pairID to listen for -- this is primarily used for request/response events + * @param block the block to call when the event is heard + * @param adobeCallbackWithError the block to call when there is error or timeout + */ + void registerOneTimeListener(final String pairID, + final Module.OneTimeListenerBlock block, + final AdobeCallbackWithError adobeCallbackWithError) { + registerOneTimeListener(pairID, block, adobeCallbackWithError, ONE_TIME_LISTENER_TIMEOUT_DEFAULT_MILLISECONDS); + + } + + /** + * Registers an event listener for the provided event type and source. + * + * @param eventType the type of the listened event + * @param eventSource the source of the listened event + * @param callback {@link AdobeCallbackWithError#call(Object)} will be called when the listened event is heard + */ + void registerEventListener(final EventType eventType, final EventSource eventSource, + final AdobeCallbackWithError callback) { + if (callback == null) { + Log.debug(logPrefix, "%s (callback), failed to register the event listener", Log.UNEXPECTED_NULL_VALUE); + return; + } + + this.eventHubThreadService.submit(new Runnable() { + @Override + public void run() { + try { + eventBus.addListener(new EventListener() { + @Override + public void hear(final Event e) { + callback.call(e); + } + + @Override + public void onUnregistered() { + } + + @Override + public EventSource getEventSource() { + return eventSource; + } + + @Override + public EventType getEventType() { + return eventType; + } + }, eventType, eventSource, null); + + } catch (Exception e) { + Log.error(logPrefix, "Failed to register the event listener - (%s)", e); + } + } + }); + } + + /** + * Registers a one-time block with the {@code EventHub}. An one-time block is an {@code Event} handler that will + * cease to function after it successfully 'hears' one event. The one-time listener will also be unregistered by + * the event hub after the event is received. + * + * @param pairID optional pairID to listen for -- this is primarily used for request/response events + * @param block the block to call when the event is heard + * @param adobeCallbackWithError the block to call when there is error or timeout + * @param timeoutInMilliSec the timeout value in milliseconds + */ + void registerOneTimeListener(final String pairID, + final Module.OneTimeListenerBlock block, + final AdobeCallbackWithError adobeCallbackWithError, + final int timeoutInMilliSec) { + + if (block == null) { + Log.debug(logPrefix, "%s (callback block), failed to register one-time listener", Log.UNEXPECTED_NULL_VALUE); + + if (adobeCallbackWithError != null) { + adobeCallbackWithError.fail(AdobeError.CALLBACK_NULL); + } + + return; + } + + final OneTimeListener oneTimeListener = new OneTimeListener(block); + // register the one-time listener on the event hub thread + this.eventHubThreadService.submit(new Runnable() { + @Override + public void run() { + + try { + eventBus.addListener(oneTimeListener, null, null, pairID); + + } catch (Exception e) { + Log.error(logPrefix, "Failed to register one-time listener", e); + } + } + }); + + if (timeoutInMilliSec > 0 && adobeCallbackWithError != null) { + ScheduledExecutorService scheduledThreadPool = getScheduledExecutorService(); + scheduledThreadPool.schedule(new Runnable() { + @Override + public void run() { + if (oneTimeListener.isCalled()) { + return; + } + + oneTimeListener.cancel(); + eventHubThreadService.submit(new Runnable() { + @Override + public void run() { + eventBus.removeListener(oneTimeListener, null, null, pairID); + } + }); + adobeCallbackWithError.fail(AdobeError.CALLBACK_TIMEOUT); + + } + }, timeoutInMilliSec, TimeUnit.MILLISECONDS); + } + + + } + + private ScheduledExecutorService getScheduledExecutorService() { + if (scheduledThreadPool == null) { + synchronized (scheduledThreadPoolMutex) { + if (scheduledThreadPool == null) { + scheduledThreadPool = Executors.newSingleThreadScheduledExecutor(); + } + } + } + + return scheduledThreadPool; + } + + /** + * Private method that unregisters a specific listener. Assumes that it is running on the event hub thread. + * + * @param module module instance to unregister listener with + * @param type the {@code EventType} of an event to listen for + * @param source the {@code EventSource} of an event to listen for + * @return {@code boolean} true if a listener was found and removed, false otherwise + */ + private boolean unregisterListenerIfPresent(final Module module, final EventType type, final EventSource source) { + + // check if listeners are registered for this module + boolean listenerExist = false; + + final ConcurrentLinkedQueue moduleSpecificListeners = moduleListeners.get(module); + + if (moduleSpecificListeners == null || moduleSpecificListeners.isEmpty()) { + return false; + } + + for (EventListener listener : moduleSpecificListeners) { + if (listener.getEventSource().equals(source) && listener.getEventType().equals(type)) { + listenerExist = true; + moduleSpecificListeners.remove(listener); + this.eventBus.removeListener(listener); + } + } + + return listenerExist; + } + + /** + * Unregisters all listeners that match a type/source pair from the {@code Module} and the + * {@code EventHub} + * + * @param module module instance to unregister listener for + * @param type the {@code EventType} of an event to listen for + * @param source the {@code EventSource} of an event to listen for + * @throws InvalidModuleException if module is null or not registered with this event hub + */ + final void unregisterModuleListener(final Module module, final EventType type, + final EventSource source) throws InvalidModuleException { + if (module == null) { + throw new InvalidModuleException(LOG_MODULE_WAS_NULL); + } + + // un-register the module listener on the event hub thread + this.eventHubThreadService.submit(new Runnable() { + @Override + public void run() { + if (!unregisterListenerIfPresent(module, type, source)) { + Log.debug(logPrefix, "Failed to unregister listener (no registered listener)"); + } + } + }); + + } + + /** + * For testing, get a collection of all the active modules + * + * @return the collection of active modules + */ + final Collection getActiveModules() { + return this.activeModules.values(); + } + + + /** + * @return instance of {@code PlatformServices} that is associated with this {@code EventHub} + */ + final PlatformServices getPlatformServices() { + return this.services; + } + + /** + * Creates a shared state object for the given module versioned at the current event for this hub + * + * @param module Module that owns this shared state + * @param version int version that this configuration should begin to be valid for + * @param state EventData object containing the state to share. Must be data, + * {@link EventHub#SHARED_STATE_PENDING}, or {@link EventHub#SHARED_STATE_INVALID} + * @throws InvalidModuleException if the provided module is null or the provided module has a null + * sharedStateName() + **/ + void createSharedState(final Module module, final int version, + final EventData state) throws InvalidModuleException { + createOrUpdateSharedStateCommon(module, version, state, true, false, SharedStateType.STANDARD); + } + + /** + * Updates an existing {@link EventHub#SHARED_STATE_PENDING} for the given module and version + * + * @param module Module to update the shared state for + * @param version int version to update + * @param state new state to replace with existing state. Must be data, {@link EventHub#SHARED_STATE_PENDING}, + * {@link EventHub#SHARED_STATE_INVALID}, {@link EventHub#SHARED_STATE_NEXT}, or {@link EventHub#SHARED_STATE_PREV}. + * @throws InvalidModuleException if the provided module is null or the provided module has a null + * sharedStateName() + **/ + void updateSharedState(final Module module, final int version, + final EventData state) throws InvalidModuleException { + createOrUpdateSharedStateCommon(module, version, state, false, true, SharedStateType.STANDARD); + } + + /** + * Creates or updates a shared state object for the given {@code module} and {@code version}. + * If no shared state exists for the module at the given version, then one is created with the given state. + * If a shared state already exists for the module at the given version and the state + * is {@link EventHub#SHARED_STATE_PENDING}, then the state is updated with the given state. + *

    + * Only for use by Module. + * + * @param module Module that owns this shared state + * @param version version of the existing shared state to add or replace + * @param state {@link EventData} object containing the state to share. Must be data, + * {@link EventHub#SHARED_STATE_PENDING}, or {@link EventHub#SHARED_STATE_INVALID} + * when creating or data, {@link EventHub#SHARED_STATE_PENDING}, {@link EventHub#SHARED_STATE_INVALID}, + * {@link EventHub#SHARED_STATE_NEXT}, or {@link EventHub#SHARED_STATE_PREV} when updating + * @throws InvalidModuleException if the provided module is null. + **/ + void createOrUpdateSharedState(final Module module, + final int version, + final EventData state) throws InvalidModuleException { + createOrUpdateSharedStateCommon(module, version, state, true, true, SharedStateType.STANDARD); + + } + + /** + * Creates a shared state object for the given module versioned at the current event number for this hub. + *

    + * NOTE: Do not call this method if the {@code module} also calls {@link #createSharedState(Module, int, EventData)} + * or {@link #createOrUpdateSharedState(Module, int, EventData)}. It may lead to data loss when attempting + * to create a shared state. + * + * @param module Module that owns this shared state + * @param state EventData object containing the state to share. Must be data, + * {@link EventHub#SHARED_STATE_PENDING}, or {@link EventHub#SHARED_STATE_INVALID} + * @throws InvalidModuleException if the provided module is null or the provided module has a null + * sharedStateName() + **/ + void createOrUpdateSharedState(final Module module, + final EventData state) throws InvalidModuleException { + int version = currentEventNumber.get(); + // as this is a new state version, only create and not update + createOrUpdateSharedStateCommon(module, version, state, true, false, SharedStateType.STANDARD); + + } + + /** + * Creates a shared state object for the given module versioned at the current event for this hub + * + * @param module Module that owns this shared state + * @param version int version that this configuration should begin to be valid for + * @param state EventData object containing the state to share. Must be data, + * {@link EventHub#SHARED_STATE_PENDING}, or {@link EventHub#SHARED_STATE_INVALID} + * @param sharedStateType the type of shared state to be created + * @throws InvalidModuleException if the provided module is null or the provided module has a null + * sharedStateName() + **/ + void createSharedState(final Module module, final int version, + final EventData state, final SharedStateType sharedStateType) throws InvalidModuleException { + createOrUpdateSharedStateCommon(module, version, state, true, false, sharedStateType); + } + + /** + * Updates an existing {@link EventHub#SHARED_STATE_PENDING} for the given module and version + * + * @param module Module to update the shared state for + * @param version int version to update + * @param state new state to replace with existing state. Must be data, {@link EventHub#SHARED_STATE_PENDING}, + * {@link EventHub#SHARED_STATE_INVALID}, {@link EventHub#SHARED_STATE_NEXT}, or {@link EventHub#SHARED_STATE_PREV}. + * @param sharedStateType the type of shared state to be updated + * @throws InvalidModuleException if the provided module is null or the provided module has a null + * sharedStateName() + **/ + void updateSharedState(final Module module, final int version, + final EventData state, final SharedStateType sharedStateType) throws InvalidModuleException { + createOrUpdateSharedStateCommon(module, version, state, false, true, sharedStateType); + } + + /** + * Creates or updates a shared state object for the given {@code module} and {@code version}. + * If no shared state exists for the module at the given version, then one is created with the given state. + * If an shared state already exists for the module at the given version and the state + * is {@link EventHub#SHARED_STATE_PENDING}, then the state is updated with the given state. + *

    + * Only for use by Module. + * + * @param module Module that owns this shared state + * @param version version of the existing shared state to add or replace + * @param state {@link EventData} object containing the state to share. Must be data, + * {@link EventHub#SHARED_STATE_PENDING}, or {@link EventHub#SHARED_STATE_INVALID} + * when creating or data, {@link EventHub#SHARED_STATE_PENDING}, {@link EventHub#SHARED_STATE_INVALID}, + * {@link EventHub#SHARED_STATE_NEXT}, or {@link EventHub#SHARED_STATE_PREV} when updating + * @param sharedStateType the type of shared state to be created + * @throws InvalidModuleException if the provided module is null. + **/ + void createOrUpdateSharedState(final Module module, + final int version, + final EventData state, + final SharedStateType sharedStateType) throws InvalidModuleException { + createOrUpdateSharedStateCommon(module, version, state, true, true, sharedStateType); + + } + + /** + * Creates a shared state object for the given module versioned at the current event number for this hub. + *

    + * NOTE: Do not call this method if the {@code module} also calls {@link #createSharedState(Module, int, EventData)} + * or {@link #createOrUpdateSharedState(Module, int, EventData)}. It may lead to data loss when attempting + * to create an shared state. + * + * @param module Module that owns this shared state + * @param state EventData object containing the state to share. Must be data, + * {@link EventHub#SHARED_STATE_PENDING}, or {@link EventHub#SHARED_STATE_INVALID} + * @param sharedStateType the type of shared state to be created or updated + * @throws InvalidModuleException if the provided module is null or the provided module has a null + * sharedStateName() + **/ + void createOrUpdateSharedState(final Module module, + final EventData state, final SharedStateType sharedStateType) throws InvalidModuleException { + int version = currentEventNumber.get(); + // as this is a new state version, only create and not update + createOrUpdateSharedStateCommon(module, version, state, true, false, sharedStateType); + + } + + /** + * Common helper method for creating and update shared states. + * + * @param module Module that owns this shared state + * @param version version of the existing shared state to add or replace + * @param state {@link EventData} object containing the state to share. Must be data, + * {@link EventHub#SHARED_STATE_PENDING}, or {@link EventHub#SHARED_STATE_INVALID} + * when creating or data, {@link EventHub#SHARED_STATE_PENDING}, {@link EventHub#SHARED_STATE_INVALID}, + * {@link EventHub#SHARED_STATE_NEXT}, or {@link EventHub#SHARED_STATE_PREV} when updating + * @param shouldCreate if this method should attempt to create a new shared state + * @param shouldUpdate if this method should attempt to update an existing shared state + * @param sharedStateType type of shared state + * @throws InvalidModuleException if the provided module is null or the provided module has a null + * sharedStateName() + */ + private void createOrUpdateSharedStateCommon(final Module module, final int version, + final EventData state, final boolean shouldCreate, final boolean shouldUpdate, final SharedStateType sharedStateType) + throws InvalidModuleException { + if (module == null) { + throw new InvalidModuleException(LOG_MODULE_WAS_NULL); + } + + final String stateName = module.getModuleName(); + + if (stateName == null) { + throw new InvalidModuleException(LOG_STATENAME_WAS_NULL); + } + + createOrUpdateSharedStateCommon(stateName, version, state, shouldCreate, shouldUpdate, sharedStateType); + } + + /** + * Common helper method for creating and update shared states. + * + * @param moduleName name of the Module that owns this shared state + * @param version version of the existing shared state to add or replace + * @param state {@link EventData} object containing the state to share. Must be data, + * {@link EventHub#SHARED_STATE_PENDING}, or {@link EventHub#SHARED_STATE_INVALID} + * when creating or data, {@link EventHub#SHARED_STATE_PENDING}, {@link EventHub#SHARED_STATE_INVALID}, + * {@link EventHub#SHARED_STATE_NEXT}, or {@link EventHub#SHARED_STATE_PREV} when updating + * @param shouldCreate if this method should attempt to create a new shared state + * @param shouldUpdate if this method should attempt to update an existing shared state + * @param sharedStateType type of shared state + * @throws InvalidModuleException if the provided module is null or the provided module has a null + * sharedStateName() + */ + private void createOrUpdateSharedStateCommon(final String moduleName, final int version, + final EventData state, final boolean shouldCreate, + final boolean shouldUpdate, final SharedStateType sharedStateType) { + boolean didUpdate = false; + boolean didCreate = false; + ConcurrentHashMap> sharedStates = sharedStateType == SharedStateType.XDM ? + moduleXdmSharedStates : moduleSharedStates; + + if (!sharedStates.containsKey(moduleName)) { + if (shouldCreate) { + // first shared state for this 'stateName' module + RangedResolver resolver = new RangedResolver( + SHARED_STATE_PENDING, + SHARED_STATE_INVALID, + SHARED_STATE_NEXT, + SHARED_STATE_PREV); + didCreate = resolver.add(version, state); + sharedStates.put(moduleName, resolver); + } + } else { + // module 'stateName' already shared state + if (shouldCreate) { + // attempt to add this 'version' + didCreate = sharedStates.get(moduleName).add(version, state); + } + + if (shouldUpdate && !didCreate) { + // attempt to update at 'version' if did not just create + didUpdate = sharedStates.get(moduleName).update(version, state); + } + } + + if (!didCreate && !didUpdate) { + Log.warning(logPrefix, "Unable to create or update shared state for %s with version %d.", moduleName, version); + return; + } + + if (state == SHARED_STATE_PENDING) { + Log.trace(logPrefix, "Will not fire shared state for %s with version %d, when this shared state is PENDING.", + moduleName, version); + } else { + fireStateChangeEvent(moduleName, sharedStateType); + + // we only want to run the expensive string manipulation code in the "prettyString" + // method when we know that the resulting string is going to be used in the log + if (Log.getLogLevel().id >= LoggingMode.VERBOSE.id) { + Log.trace(logPrefix, "New shared state data for '%s' at version '%d': \n%s", moduleName, version, + state.prettyString(1)); + } + } + } + + /** + * Retrieves shared state by name that is valid for the given event. If {@code event} is null, retrieves the + * latest shared state for {@code stateName}. + * + * @param stateName String identifier for the module that shared the state + * @param event {@link Event}'s version used to retrieve corresponding state, or the latest state if null + * @param callingModule the module calling this method + * @return EventData object containing the valid state, + * {@link EventHub#SHARED_STATE_PENDING}, or {@link EventHub#SHARED_STATE_INVALID} + **/ + EventData getSharedEventState(final String stateName, final Event event, final Module callingModule) { + return getSharedEventStateCommon(stateName, event, callingModule, SharedStateType.STANDARD); + } + + /** + * Retrieves shared state by name that is valid for the given event. If {@code event} is null, retrieves the + * latest shared state for {@code stateName}. + * + * @param stateName String identifier for the module that shared the state + * @param event {@link Event}'s version used to retrieve corresponding state, or the latest state if null + * @param callingModule the module calling this method + * @param sharedStateType the type of shared state to be read + * @return EventData object containing the valid state, + * {@link EventHub#SHARED_STATE_PENDING}, or {@link EventHub#SHARED_STATE_INVALID} + **/ + EventData getSharedEventState(final String stateName, final Event event, final Module callingModule, + final SharedStateType sharedStateType) { + return getSharedEventStateCommon(stateName, event, callingModule, sharedStateType); + } + + private EventData getSharedEventStateCommon(final String stateName, final Event event, final Module callingModule, + final SharedStateType sharedStateType) { + if (stateName == null) { + throw new IllegalArgumentException(LOG_STATENAME_WAS_NULL); + } + + int eventVersion = Event.SHARED_STATE_NEWEST.getEventNumber(); // default to getting latest + + if (event != null) { + eventVersion = event.getEventNumber(); + } + + // check for circular dependency (live lock risk) + // this can occur if you have two modules (m1, m2) that are inter-dependent on shared states (m1->m2 and + // m2->m1). + // this code provides detection and logging, we can't avoid the livelock but we should at least detect it. + if (Log.getLogLevel().id >= LoggingMode.DEBUG.id && callingModule != null) { + final String callingModuleStateName = callingModule.getModuleName(); + this.sharedStateCircularCheck.put(callingModuleStateName + stateName, true); + + if (this.sharedStateCircularCheck.get(stateName + callingModuleStateName) != null) { + Log.warning(logPrefix, "Circular shared-state dependency between %s and %s, you may have a " + + "live-lock.", + callingModuleStateName, stateName); + } + } + + final RangedResolver sharedState = sharedStateType == SharedStateType.XDM ? this.moduleXdmSharedStates.get( + stateName) : this.moduleSharedStates.get(stateName); + + if (sharedState != null) { + return sharedState.resolve(eventVersion); + } + + return SHARED_STATE_PENDING; + } + + /** + * Determine if there are any shared states for a specified module. + * A module is considered to have a valid shared state if any state contains data or is {@link #SHARED_STATE_PENDING}. + * States which are {@link #SHARED_STATE_INVALID}, {@link #SHARED_STATE_NEXT}, or {@link #SHARED_STATE_PREV} + * are not considered valid. + * + * @param stateName {@link String} identifier for the module which shared a state + * @return true if the specified module has shared a state which either contains data or is {@link #SHARED_STATE_PENDING} + */ + boolean hasSharedEventState(final String stateName) { + return hasSharedEventState(stateName, SharedStateType.STANDARD); + } + + /** + * Determine if there are any shared states for a specified module. + * A module is considered to have a valid shared state if any state contains data or is {@link #SHARED_STATE_PENDING}. + * States which are {@link #SHARED_STATE_INVALID}, {@link #SHARED_STATE_NEXT}, or {@link #SHARED_STATE_PREV} + * are not considered valid. + * + * @param stateName {@link String} identifier for the module which shared a state + * @param sharedStateType the type of shared state checked + * @return true if the specified module has shared a state which either contains data or is {@link #SHARED_STATE_PENDING} + */ + boolean hasSharedEventState(final String stateName, final SharedStateType sharedStateType) { + if (stateName == null) { + throw new IllegalArgumentException(LOG_STATENAME_WAS_NULL); + } + + final RangedResolver sharedStates = sharedStateType == SharedStateType.XDM ? this.moduleXdmSharedStates.get( + stateName) : this.moduleSharedStates.get(stateName); + + return sharedStates != null && sharedStates.containsValidState(); + } + + /** + * Clears all the shared states for the given module. + *

    + * Only for use by Module. + * + * @param module {@link Module} to clear the shared states for + * @throws InvalidModuleException if the provided module is null or the provided module has a + * null {@link Module#getModuleName()} + **/ + void clearSharedStates(final Module module) throws InvalidModuleException { + clearSharedStateCommon(module, SharedStateType.STANDARD); + } + + /** + * Clears all the shared states for the given module. + *

    + * Only for use by Module. + * + * @param module {@link Module} to clear the shared states for + * @param sharedStateType the type of shared state to be created + * @throws InvalidModuleException if the provided module is null or the provided module has a + * null {@link Module#getModuleName()} + **/ + void clearSharedStates(final Module module, final SharedStateType sharedStateType) throws InvalidModuleException { + clearSharedStateCommon(module, sharedStateType); + } + + private void clearSharedStateCommon(final Module module, + final SharedStateType sharedStateType) throws InvalidModuleException { + if (module == null) { + throw new InvalidModuleException(LOG_MODULE_WAS_NULL); + } + + final String stateName = module.getModuleName(); + + if (stateName == null) { + throw new InvalidModuleException(LOG_STATENAME_WAS_NULL); + } + + if (sharedStateType == SharedStateType.XDM) { + this.moduleXdmSharedStates.remove(stateName); + } else { + this.moduleSharedStates.remove(stateName); + } + + fireStateChangeEvent(stateName, sharedStateType); + } + + /** + * For test, need to shutdown the threadPoll after each test + */ + void shutdown() { + this.eventHubThreadService.shutdownNow(); + this.threadPool.shutdownNow(); + } + + /** + * For platform tests, need to clear data from memory. + */ + void resetSharedStates() { + this.moduleSharedStates.clear(); + this.moduleXdmSharedStates.clear(); + } + + /** + * For extensions tests, to check that a listener was registered/unregistered + * + * @param module the module for which to retrieve the listeners + * @return the list of listeners for the provided module + */ + ConcurrentLinkedQueue getModuleListeners(final Module module) { + return this.moduleListeners.get(module); + } + + /** + * Creates and publishes a state change event to this event hub + * + * @param stateName stateName state-name of the module that published the change + * @param sharedStateType the type of the shared state updated + **/ + private void fireStateChangeEvent(final String stateName, final SharedStateType sharedStateType) { + final String eventName = sharedStateType == SharedStateType.STANDARD ? STANDARD_STATE_CHANGE_EVENTNAME : + XDM_STATE_CHANGE_EVENTNAME; + final Event updateEvent = new Event.Builder(eventName, EventType.HUB, EventSource.SHARED_STATE) + .setData(new EventData().putString(EventHubConstants.EventDataKeys.Configuration.EVENT_STATE_OWNER, stateName)) + .build(); + this.dispatch(updateEvent); + } + + protected void createEventHubSharedState(final int eventNumber) { + createOrUpdateSharedStateCommon(EventHubConstants.EventDataKeys.EventHub.SHARED_STATE_NAME, + eventNumber, eventHubSharedState, true, false, SharedStateType.STANDARD); + } + + protected void addModuleToEventHubSharedState(final Module module) { + if (module == null) { + return; + } + + final ModuleDetails details = module.getModuleDetails(); + + final String moduleName = module.getModuleName(); + final String friendlyName = details == null ? module.getModuleName() : details.getName(); + final String moduleVersion = details == null ? module.getModuleVersion() : details.getVersion(); + + if (StringUtils.isNullOrEmpty(moduleName)) { + return; + } + + Log.trace(logPrefix, "Registering extension '%s' with version '%s'", moduleName, moduleVersion); + + final Map extensionsMap = eventHubSharedState.optVariantMap( + EventHubConstants.EventDataKeys.EventHub.EXTENSIONS, new HashMap()); + final Map moduleMap = new HashMap(); + moduleMap.put(EventHubConstants.EventDataKeys.EventHub.VERSION, + Variant.fromString(moduleVersion != null ? moduleVersion : "")); + moduleMap.put(EventHubConstants.EventDataKeys.EventHub.FRIENDLY_NAME, + Variant.fromString(friendlyName != null ? friendlyName : moduleName)); + extensionsMap.put(moduleName, Variant.fromVariantMap(moduleMap)); + eventHubSharedState.putVariantMap(EventHubConstants.EventDataKeys.EventHub.EXTENSIONS, + extensionsMap); + + // only update the shared state if we're beyond the boot event + synchronized (bootMutex) { + if (isBooted) { + createEventHubSharedState(currentEventNumber.get()); + } + } + } + + protected void removeModuleFromEventHubSharedState(final Module module) { + // quick out if the shared state doesn't exist yet + if (module == null) { + return; + } + + final String moduleName = module.getModuleName(); + + if (StringUtils.isNullOrEmpty(moduleName)) { + return; + } + + final Map extensionsMap = eventHubSharedState.optVariantMap( + EventHubConstants.EventDataKeys.EventHub.EXTENSIONS, new HashMap()); + extensionsMap.remove(moduleName); + eventHubSharedState.putVariantMap(EventHubConstants.EventDataKeys.EventHub.EXTENSIONS, + extensionsMap); + + // only update the shared state if we're beyond the boot event + synchronized (bootMutex) { + if (isBooted) { + createEventHubSharedState(currentEventNumber.get()); + } + } + } + + protected EventData getInitialEventHubSharedState() { + final EventData state = new EventData(); + + state.putString(EventHubConstants.EventDataKeys.EventHub.VERSION, coreVersion); + state.putVariantMap(EventHubConstants.EventDataKeys.EventHub.EXTENSIONS, new HashMap()); + state.putStringMap(EventHubConstants.EventDataKeys.EventHub.WRAPPER, getWrapperInfo()); + + return state; + } + + /** + * Gets the SDK's current version with wrapper type. + */ + String getSdkVersion() { + String version = coreVersion; + + if (wrapperType != WrapperType.NONE) { + version += SDK_VERSION_DELIMITER + wrapperType.getWrapperTag(); + } + + return version; + } + + /** + * Sets the SDK's current wrapper type. This API should only be used if + * being developed on platforms such as React Native. + * + * @param wrapperType the type of wrapper being used. + */ + void setWrapperType(final WrapperType wrapperType) { + synchronized (bootMutex) { + if (isBooted) { + Log.warning(logPrefix, "Cannot set wrapper type to (%s) - (%s) as event hub has already booted.", + wrapperType.getWrapperTag(), wrapperType.getFriendlyName()); + return; + } + + this.wrapperType = wrapperType; + // update wrapperType info in hub shared state + eventHubSharedState.putStringMap(EventHubConstants.EventDataKeys.EventHub.WRAPPER, getWrapperInfo()); + } + } + + /** + * Returns the SDK's current wrapper type. + * + * @return {@link WrapperType} enum. + */ + WrapperType getWrapperType() { + return wrapperType; + } + + /** + * Returns the wrapper info with wrapper tag and friendlyName. + * + * @return map containing wrapper tag and friendlyName. + */ + private HashMap getWrapperInfo() { + HashMap wrapperInfo = new HashMap(); + wrapperInfo.put(EventHubConstants.EventDataKeys.EventHub.TYPE, wrapperType.getWrapperTag()); + wrapperInfo.put(EventHubConstants.EventDataKeys.EventHub.FRIENDLY_NAME, wrapperType.getFriendlyName()); + + return wrapperInfo; + } + + /** + * Class implementing the Runnable for event + */ + private final class EventRunnable implements Runnable { + final Event event; + + EventRunnable(final Event event) { + this.event = event; + } + + @Override + public void run() { + Event processedEvent = this.event; + for (EventPreprocessor processor : preprocessors) { + processedEvent = processor.process(processedEvent); + } + eventBus.dispatch(processedEvent); + } + } } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/InAccessibleMatcher.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventPreprocessor.java similarity index 65% rename from code/android-core-library/src/test/java/com/adobe/marketing/mobile/InAccessibleMatcher.java rename to code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventPreprocessor.java index 6cd29ebe7..c527e7a85 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/InAccessibleMatcher.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventPreprocessor.java @@ -8,27 +8,9 @@ OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - package com.adobe.marketing.mobile; -public class InAccessibleMatcher extends Matcher { - - private InAccessibleMatcher() { - - } - - @Override - protected boolean matches(Object value) { - return super.matches(value); - } - - @Override - protected Double tryParseDouble(Object value) { - return super.tryParseDouble(value); - } - - @Override - public String toString() { - return null; - } +@FunctionalInterface +public interface EventPreprocessor { + Event process(final Event event); } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Matcher.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Matcher.java deleted file mode 100644 index bc1159d87..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Matcher.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ -package com.adobe.marketing.mobile; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -/** - * Base Matcher class. Matcher classes evaluate given values to values mapped to a given key. Derived Matcher - * classes specialize in the match operation used to evaluate the values. - */ -abstract class Matcher { - // ================================================================================ - // protected members - // ================================================================================ - protected String key; - protected ArrayList values = new ArrayList(); - - // ================================================================================ - // private string constants - // ================================================================================ - private static final String LOG_TAG = Matcher.class.getSimpleName(); - - private static final String MATCHER_JSON_KEY = "key"; - private static final String MATCHER_JSON_MATCHES = "matcher"; - private static final String MATCHER_JSON_VALUES = "values"; - - private static final String MATCHER_STRING_EQUALS = "eq"; - private static final String MATCHER_STRING_NOT_EQUALS = "ne"; - private static final String MATCHER_STRING_GREATER_THAN = "gt"; - private static final String MATCHER_STRING_GREATER_THAN_OR_EQUALS = "ge"; - private static final String MATCHER_STRING_LESS_THAN = "lt"; - private static final String MATCHER_STRING_LESS_THAN_OR_EQUALS = "le"; - private static final String MATCHER_STRING_CONTAINS = "co"; - private static final String MATCHER_STRING_NOT_CONTAINS = "nc"; - private static final String MATCHER_STRING_STARTS_WITH = "sw"; - private static final String MATCHER_STRING_ENDS_WITH = "ew"; - private static final String MATCHER_STRING_EXISTS = "ex"; - private static final String MATCHER_STRING_NOT_EXISTS = "nx"; - - // ================================================================================ - // convenience constructor - // ================================================================================ - protected static final Map _matcherTypeDictionary = initializeMatcherTypeDictionary(); - - /** - * Creates a mapping of all supported rule condition matcher types to their class objects. - * - * @return a mapping of rule condition matcher types to their class object - */ - private static Map initializeMatcherTypeDictionary() { - HashMap matcherTypeDictionary = new HashMap(); - matcherTypeDictionary.put(MATCHER_STRING_EQUALS, MatcherEquals.class); - matcherTypeDictionary.put(MATCHER_STRING_NOT_EQUALS, MatcherNotEquals.class); - matcherTypeDictionary.put(MATCHER_STRING_GREATER_THAN, MatcherGreaterThan.class); - matcherTypeDictionary.put(MATCHER_STRING_GREATER_THAN_OR_EQUALS, MatcherGreaterThanOrEqual.class); - matcherTypeDictionary.put(MATCHER_STRING_LESS_THAN, MatcherLessThan.class); - matcherTypeDictionary.put(MATCHER_STRING_LESS_THAN_OR_EQUALS, MatcherLessThanOrEqual.class); - matcherTypeDictionary.put(MATCHER_STRING_CONTAINS, MatcherContains.class); - matcherTypeDictionary.put(MATCHER_STRING_NOT_CONTAINS, MatcherNotContains.class); - matcherTypeDictionary.put(MATCHER_STRING_STARTS_WITH, MatcherStartsWith.class); - matcherTypeDictionary.put(MATCHER_STRING_ENDS_WITH, MatcherEndsWith.class); - matcherTypeDictionary.put(MATCHER_STRING_EXISTS, MatcherExists.class); - matcherTypeDictionary.put(MATCHER_STRING_NOT_EXISTS, MatcherNotExists.class); - return matcherTypeDictionary; - } - - /** - * Creates a Matcher instance based on the given {@code JSONObject}. - * Searches the JSON object for a matcher operator, key, and values and returns a Matcher instance - * populated with those values. Returns null if an error occurs creating the Matcher instance. - * - * @param dictionary {@link JsonUtilityService.JSONObject} containing the definition for a {@code Matcher} instance - * @return a new {@code Matcher} instance, or null if a {@code Matcher} could not be created - */ - public static Matcher matcherWithJsonObject(final JsonUtilityService.JSONObject dictionary) { - Class matcherClass; - Matcher matcher = null; - final String matcherString = dictionary.optString(MATCHER_JSON_MATCHES, ""); - - if (matcherString.length() <= 0) { - Log.debug(LOG_TAG, "Messages - message matcher type is empty"); - } - - // get the correct class type and instantiate it - matcherClass = _matcherTypeDictionary.get(matcherString); - - if (matcherClass == null) { - matcherClass = MatcherUnknown.class; - Log.debug(LOG_TAG, "Messages - message matcher type \"%s\" is invalid", matcherString); - } - - try { - matcher = (Matcher) matcherClass.newInstance(); - } catch (InstantiationException ex) { - Log.error(LOG_TAG, "Messages - Error creating matcher (%s)", ex); - } catch (IllegalAccessException ex) { - Log.error(LOG_TAG, "Messages - Error creating matcher (%s)", ex); - } - - if (matcher != null) { - setMatcherKeyFromJson(dictionary, matcher); - - try { - // if this is an exists matcher, we know we don't have anything in the values array - if (matcher instanceof MatcherExists) { - return matcher; - } - - setMatcherValuesFromJson(dictionary, matcher); - } catch (JsonException ex) { - Log.warning(LOG_TAG, "Messages - error creating matcher, values is required (%s)", ex); - } - } - - return matcher; - } - - /** - * Searches the JSON object for matcher values and adds them to the given Matcher object. - * - * @param dictionary {@link JsonUtilityService.JSONObject} containing a matcher values array - * @param matcher the {@link Matcher} instance to add the found values - * @throws JsonException if no matcher values are found in the provided JSON object - */ - static void setMatcherValuesFromJson(final JsonUtilityService.JSONObject dictionary, final Matcher matcher) - throws JsonException { - - if (matcher == null) { - return; - } - - // loop through json array and put things in the values array - JsonUtilityService.JSONArray jsonArray = dictionary.getJSONArray(MATCHER_JSON_VALUES); - - if (jsonArray == null) { - return; - } - - int arrayLength = jsonArray.length(); - - - for (int i = 0; i < arrayLength; i++) { - matcher.values.add(jsonArray.get(i)); - } - - if (matcher.values.isEmpty()) { - Log.debug(LOG_TAG, "%s (matcher values), messages - error creating matcher", Log.UNEXPECTED_EMPTY_VALUE); - } - } - - /** - * Searches the JSON object for a matcher key and adds it to the given Matcher object. - * If an error occurs parsing the JSON {@code dictionary}, an error is logged and the - * {@code matcher}'s key is not set. - * - * @param dictionary {@link JsonUtilityService.JSONObject} containing the matcher key - * @param matcher the {@link Matcher} instance to add the found key - */ - static void setMatcherKeyFromJson(final JsonUtilityService.JSONObject dictionary, final Matcher matcher) { - if (matcher == null) { - return; - } - - String key = dictionary.optString(MATCHER_JSON_KEY, ""); - - // we toLowercase the key so we can have case insensitive matchers - if (key.length() > 0) { - matcher.key = key; - } else { - Log.debug(LOG_TAG, "%s (key), messages - error creating matcher", Log.UNEXPECTED_EMPTY_VALUE); - } - } - - /** - * Evaluates the given {@code value} against this {@code Matcher}'s values. - * This base class's implementation always returns false and should be overwritten by the derived class - * if the desired behavior is different. - * - * @param value the value to match. may be null - * @return true if {@code value} matches against this {@code Matcher}'s values - */ - @SuppressWarnings("squid:S1172")//Unused param value will need to exist here - protected boolean matches(final Object value) { - return false; - } - - /** - * Convenience method to parse a {@code Double} from the given {@code value} object. - * @param value an object to attempt to parse a {@code Double} - * @return a {@code Double} instance from the given {@code value}, or null if {@code value} could not - * be parsed to a {@code Double} - */ - protected Double tryParseDouble(final Object value) { - try { - return Double.valueOf(value.toString()); - } catch (Exception ex) { - Log.trace(LOG_TAG, "Could not parse into a Double (%s)", ex); - return null; - } - } - - @Override - public abstract String toString(); -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherContains.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherContains.java deleted file mode 100644 index 5e1490402..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherContains.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ -package com.adobe.marketing.mobile; - -import java.util.regex.Pattern; - -/** - * A rule condition matcher which evaluates if a given value contains any of this matcher's {@code values}. - */ -class MatcherContains extends Matcher { - - /** - * Evaluates the given {@code value} against this {@code Matcher}'s values. - * Valid data types for {@code value} are {@link String} and {@link Number}. All other data types - * will evaluate to false. - * - * @param value the value to match - * @return true if {@code value} contains any of this {@code Matcher}'s values - */ - @Override - protected boolean matches(final Object value) { - boolean valueIsString = value instanceof String; - boolean valueIsNumber = value instanceof Number; - - if (!valueIsString && !valueIsNumber) { - return false; - } - - String valueToSearchFor = value.toString(); - - for (Object v : values) { - if (v instanceof String && - Pattern.compile(Pattern.quote(v.toString()), Pattern.CASE_INSENSITIVE).matcher(valueToSearchFor).find()) { - return true; - } - } - - return false; - } - - @Override public String toString() { - StringBuilder matcherStringBuilder = new StringBuilder(); - - for (Object value : values) { - if (matcherStringBuilder.length() > 0) { - matcherStringBuilder.append(" OR "); - } - - matcherStringBuilder.append(key); - matcherStringBuilder.append(" CONTAINS "); - matcherStringBuilder.append(value.toString()); - } - - matcherStringBuilder.insert(0, "("); - matcherStringBuilder.append(")"); - return matcherStringBuilder.toString(); - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherEndsWith.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherEndsWith.java deleted file mode 100644 index a53680c38..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherEndsWith.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ -package com.adobe.marketing.mobile; - -import java.util.regex.Pattern; - -/** - * A rule condition matcher which evaluates if a given value ends with any of this matcher's {@code values}. - */ -final class MatcherEndsWith extends Matcher { - - /** - * Evaluates the given {@code value} against this {@code Matcher}'s values. - * Valid data types for {@code value} are {@link String} and {@link Number}. All other data types - * will evaluate to false. - * - * @param value the value to match - * @return true if {@code value} ends with any of this {@code Matcher}'s values - */ - @Override - protected boolean matches(final Object value) { - boolean valueIsString = value instanceof String; - boolean valueIsNumber = value instanceof Number; - - if (!valueIsString && !valueIsNumber) { - return false; - } - - String stringToMatch = value.toString(); - - for (Object v : values) { - if (v instanceof String && stringToMatch.matches("(?i).*" + Pattern.quote(v.toString()))) { - return true; - } - } - - return false; - } - - @Override public String toString() { - StringBuilder matcherStringBuilder = new StringBuilder(); - - for (Object value : values) { - if (matcherStringBuilder.length() > 0) { - matcherStringBuilder.append(" OR "); - } - - matcherStringBuilder.append(key); - matcherStringBuilder.append(" ENDS WITH "); - matcherStringBuilder.append(value.toString()); - } - - matcherStringBuilder.insert(0, "("); - matcherStringBuilder.append(")"); - return matcherStringBuilder.toString(); - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherEquals.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherEquals.java deleted file mode 100644 index e2216feb4..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherEquals.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ -package com.adobe.marketing.mobile; - -/** - * A rule condition matcher which evaluates if a given value equals any of this matcher's {@code values}. - */ -class MatcherEquals extends Matcher { - private final static String FALSE_STRING = "false"; - private final static String TRUE_STRING = "true"; - private final static String ONE_STRING = "1"; - private final static String ZERO_STRING = "0"; - - /** - * Evaluates the given {@code value} against this {@code Matcher}'s values. - * Valid data types for {@code value} are {@link String} and {@link Number}. All other data types - * will evaluate to false. - * - * @param value the value to match - * @return true if {@code value} equals any of this {@code Matcher}'s values, ignoring case - */ - @Override - @SuppressWarnings({ "squid:S3776"}) - //S3776 - Complexity warning. The below code is comprehensible enough that we can disable this warning here. - protected boolean matches(final Object value) { - for (Object potentialMatch : values) { - // string v string OR string v Number - if (potentialMatch instanceof String && value instanceof String || - potentialMatch instanceof String && value instanceof Number) { - if (potentialMatch.toString().compareToIgnoreCase(value.toString()) == 0) { - return true; - } - } - // number v number - else if (potentialMatch instanceof Number && value instanceof Number) { - if (Double.compare(((Number) potentialMatch).doubleValue(), ((Number) value).doubleValue()) == 0) { - return true; - } - } - // number v string - else if (potentialMatch instanceof Number && value instanceof String) { - Double valueAsDouble = tryParseDouble(value); - - if (valueAsDouble != null && Double.compare(((Number) potentialMatch).doubleValue(), valueAsDouble) == 0) { - return true; - } - } - // boolean v string (according to the current code where this method gets called, value can only be string) - else if (potentialMatch instanceof Boolean) { - return compareObjectWithBoolean(value, ((Boolean) potentialMatch)); - } - } - - return false; - } - - protected boolean compareObjectWithBoolean(final Object object, final boolean booleanValue) { - if (object instanceof Boolean) { - return (Boolean)object == booleanValue; - } else if (object instanceof Integer || object instanceof Long) { - long objectAsLong = ((Number)object).longValue(); - return (objectAsLong == 1 && booleanValue) || (objectAsLong == 0 && !booleanValue); - } else if (object instanceof String) { - String objectAsString = (String)object; - return booleanValue ? (ONE_STRING.equals(objectAsString) || TRUE_STRING.equalsIgnoreCase(objectAsString)) : - (ZERO_STRING.equals(objectAsString) || FALSE_STRING.equalsIgnoreCase(objectAsString)); - } - - return false; - } - - @Override - public String toString() { - StringBuilder matcherStringBuilder = new StringBuilder(); - - for (Object value : values) { - if (matcherStringBuilder.length() > 0) { - matcherStringBuilder.append(" OR "); - } - - matcherStringBuilder.append(key); - matcherStringBuilder.append(" EQUALS "); - matcherStringBuilder.append(value.toString()); - } - - matcherStringBuilder.insert(0, "("); - matcherStringBuilder.append(")"); - return matcherStringBuilder.toString(); - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherExists.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherExists.java deleted file mode 100644 index fe056760f..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherExists.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import java.util.Map; - -/** - * A rule condition matcher which evaluates if this matcher's {@code key} exists within a set of given values - */ -class MatcherExists extends Matcher { - - /** - * Evaluates if this matcher's {@code key} exists in the triggering {@code Event}'s data. - * - * @param value the value to match - * - * @return {@code boolean} indicating whether this matcher's {@code key} exists in the {@code Event} data - */ - @Override - public boolean matches(final Object value) { - return value != null; - } - - @Override public String toString() { - return "(" + key + " EXISTS)"; - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherGreaterThan.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherGreaterThan.java deleted file mode 100644 index fa027ebf3..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherGreaterThan.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ -package com.adobe.marketing.mobile; - -/** - * A rule condition matcher which evaluates if a given value is greater than any of this matcher's {@code values}. - */ -final class MatcherGreaterThan extends Matcher { - - /** - * Evaluates the given {@code value} against this {@code Matcher}'s values. - * Parses {@code value} as {@link Double} data type before evaluation. If {@code value} is not parsable - * to {@code Double}, then false is returned. - * - * @param value the value to match - * @return true if {@code value} is greater than any of this {@code Matcher}'s values - */ - @Override - protected boolean matches(final Object value) { - if (value == null) { - return false; - } - - // figure out if value is a number - Double valueAsDouble = tryParseDouble(value); - - if (valueAsDouble == null) { - return false; - } - - for (Object v : values) { - if (v instanceof Number && valueAsDouble > ((Number) v).doubleValue()) { - return true; - } - } - - return false; - } - - @Override public String toString() { - StringBuilder matcherStringBuilder = new StringBuilder(); - - for (Object value : values) { - if (matcherStringBuilder.length() > 0) { - matcherStringBuilder.append(" OR "); - } - - matcherStringBuilder.append(key); - matcherStringBuilder.append(" GREATER THAN "); - matcherStringBuilder.append(value.toString()); - } - - matcherStringBuilder.insert(0, "("); - matcherStringBuilder.append(")"); - return matcherStringBuilder.toString(); - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherGreaterThanOrEqual.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherGreaterThanOrEqual.java deleted file mode 100644 index 1260f2d9f..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherGreaterThanOrEqual.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ -package com.adobe.marketing.mobile; - -/** - * A rule condition matcher which evaluates if a given value is greater than or equal to - * any of this matcher's {@code values}. - */ -final class MatcherGreaterThanOrEqual extends Matcher { - - /** - * Evaluates the given {@code value} against this {@code Matcher}'s values. - * Parses {@code value} as {@link Double} data type before evaluation. If {@code value} is not parsable - * to {@code Double}, then false is returned. - * - * @param value the value to match - * @return true if {@code value} is greater than or equal to any of this {@code Matcher}'s values - */ - @Override - protected boolean matches(final Object value) { - if (value == null) { - return false; - } - - // figure out if value is a number - Double valueAsDouble = tryParseDouble(value); - - if (valueAsDouble == null) { - return false; - } - - for (Object v : values) { - if (v instanceof Number && valueAsDouble >= ((Number) v).doubleValue()) { - return true; - } - } - - return false; - } - - @Override public String toString() { - StringBuilder matcherStringBuilder = new StringBuilder(); - - for (Object value : values) { - if (matcherStringBuilder.length() > 0) { - matcherStringBuilder.append(" OR "); - } - - matcherStringBuilder.append(key); - matcherStringBuilder.append(" GREATER THAN OR EQUALS "); - matcherStringBuilder.append(value.toString()); - } - - matcherStringBuilder.insert(0, "("); - matcherStringBuilder.append(")"); - return matcherStringBuilder.toString(); - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherLessThan.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherLessThan.java deleted file mode 100644 index 7b670a723..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherLessThan.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ -package com.adobe.marketing.mobile; - -/** - * A rule condition matcher which evaluates if a given value is less than any of this matcher's {@code values}. - */ -final class MatcherLessThan extends Matcher { - - /** - * Evaluates the given {@code value} against this {@code Matcher}'s values. - * Parses {@code value} as {@link Double} data type before evaluation. If {@code value} is not parsable - * to {@code Double}, then false is returned. - * - * @param value the value to match - * @return true if {@code value} is less than any of this {@code Matcher}'s values - */ - @Override - protected boolean matches(final Object value) { - if (value == null) { - return false; - } - - // figure out if value is a number - Double valueAsDouble = tryParseDouble(value); - - if (valueAsDouble == null) { - return false; - } - - for (Object v : values) { - if (v instanceof Number && valueAsDouble < ((Number) v).doubleValue()) { - return true; - } - } - - return false; - } - - @Override public String toString() { - StringBuilder matcherStringBuilder = new StringBuilder(); - - for (Object value : values) { - if (matcherStringBuilder.length() > 0) { - matcherStringBuilder.append(" OR "); - } - - matcherStringBuilder.append(key); - matcherStringBuilder.append(" LESS THAN "); - matcherStringBuilder.append(value.toString()); - } - - matcherStringBuilder.insert(0, "("); - matcherStringBuilder.append(")"); - return matcherStringBuilder.toString(); - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherLessThanOrEqual.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherLessThanOrEqual.java deleted file mode 100644 index dd48166a8..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherLessThanOrEqual.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ -package com.adobe.marketing.mobile; - -/** - * A rule condition matcher which evaluates if a given value is less than or equal to - * any of this matcher's {@code values}. - */ -final class MatcherLessThanOrEqual extends Matcher { - - /** - * Evaluates the given {@code value} against this {@code Matcher}'s values. - * Parses {@code value} as {@link Double} data type before evaluation. If {@code value} is not parsable - * to {@code Double}, then false is returned. - * - * @param value the value to match - * @return true if {@code value} is less than or equal to any of this {@code Matcher}'s values - */ - @Override - protected boolean matches(final Object value) { - if (value == null) { - return false; - } - - // figure out if value is a number - Double valueAsDouble = tryParseDouble(value); - - if (valueAsDouble == null) { - return false; - } - - for (Object v : values) { - if (v instanceof Number && valueAsDouble <= ((Number) v).doubleValue()) { - return true; - } - } - - return false; - } - - @Override public String toString() { - StringBuilder matcherStringBuilder = new StringBuilder(); - - for (Object value : values) { - if (matcherStringBuilder.length() > 0) { - matcherStringBuilder.append(" OR "); - } - - matcherStringBuilder.append(key); - matcherStringBuilder.append(" LESS THAN OR EQUALS "); - matcherStringBuilder.append(value.toString()); - } - - matcherStringBuilder.insert(0, "("); - matcherStringBuilder.append(")"); - return matcherStringBuilder.toString(); - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherNotContains.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherNotContains.java deleted file mode 100644 index 10f6626fa..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherNotContains.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ -package com.adobe.marketing.mobile; - -/** - * A rule condition matcher which evaluates if a given value does not contain any of this matcher's {@code values}. - */ -final class MatcherNotContains extends MatcherContains { - - /** - * Evaluates the given {@code value} against this {@code Matcher}'s values. - * Valid data types for {@code value} are {@link String} and {@link Number}. All other data types - * will evaluate to false. - * - * @param value the value to match - * @return true if {@code value} does not contain any of this {@code Matcher}'s values - */ - @Override - protected boolean matches(final Object value) { - return value != null && !super.matches(value); - } - - @Override public String toString() { - StringBuilder matcherStringBuilder = new StringBuilder(); - - for (Object value : values) { - if (matcherStringBuilder.length() > 0) { - matcherStringBuilder.append(" OR "); - } - - matcherStringBuilder.append(key); - matcherStringBuilder.append(" NOT CONTAINS "); - matcherStringBuilder.append(value.toString()); - } - - matcherStringBuilder.insert(0, "("); - matcherStringBuilder.append(")"); - return matcherStringBuilder.toString(); - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherNotEquals.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherNotEquals.java deleted file mode 100644 index 0971e5459..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherNotEquals.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ -package com.adobe.marketing.mobile; - -/** - * A rule condition matcher which evaluates if a given value is not equal to any of this matcher's {@code values}. - */ -final class MatcherNotEquals extends MatcherEquals { - - /** - * Evaluates the given {@code value} against this {@code Matcher}'s values. - * Valid data types for {@code value} are {@link String} and {@link Number}. All other data types - * will evaluate to false. - * - * @param value the value to match - * @return true if {@code value} is not equal to any of this {@code Matcher}'s values, ignoring case - */ - @Override - protected boolean matches(final Object value) { - return value != null && !super.matches(value); - } - - @Override public String toString() { - StringBuilder matcherStringBuilder = new StringBuilder(); - - for (Object value : values) { - if (matcherStringBuilder.length() > 0) { - matcherStringBuilder.append(" OR "); - } - - matcherStringBuilder.append(key); - matcherStringBuilder.append(" NOT EQUALS "); - matcherStringBuilder.append(value.toString()); - } - - matcherStringBuilder.insert(0, "("); - matcherStringBuilder.append(")"); - return matcherStringBuilder.toString(); - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherNotExists.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherNotExists.java deleted file mode 100644 index 01ccdd447..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherNotExists.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -/** - * A rule condition matcher which evaluates if this matcher's {@code key} does not exist within a set of given values - */ -final class MatcherNotExists extends MatcherExists { - - /** - * Evaluates if this matcher's {@code key} does not exist in the triggering {@code Event}'s data. - * - * @param value the value to match - * - * @return {@code boolean} indicating whether this matcher's {@code key} does not exist in the {@code Event} data - */ - @Override - public boolean matches(final Object value) { - return !super.matches(value); - } - - @Override public String toString() { - return "(" + key + " NOT EXISTS)"; - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherStartsWith.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherStartsWith.java deleted file mode 100644 index cb513a841..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherStartsWith.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ -package com.adobe.marketing.mobile; - -import java.util.regex.Pattern; - -/** - * A rule condition matcher which evaluates if a given value starts with any of this matcher's {@code values}. - */ -final class MatcherStartsWith extends Matcher { - - /** - * Evaluates the given {@code value} against this {@code Matcher}'s values. - * Valid data types for {@code value} are {@link String} and {@link Number}. All other data types - * will evaluate to false. - * - * @param value the value to match - * @return true if {@code value} starts with any of this {@code Matcher}'s values - */ - @Override - protected boolean matches(final Object value) { - boolean valueIsString = value instanceof String; - boolean valueIsNumber = value instanceof Number; - - if (!valueIsString && !valueIsNumber) { - return false; - } - - String stringToMatch = value.toString(); - - for (Object v : values) { - if (v instanceof String && stringToMatch.matches("(?i)" + Pattern.quote(v.toString()) + ".*")) { - return true; - } - } - - return false; - } - - @Override - public String toString() { - StringBuilder matcherStringBuilder = new StringBuilder(); - - for (Object value : values) { - if (matcherStringBuilder.length() > 0) { - matcherStringBuilder.append(" OR "); - } - - matcherStringBuilder.append(key); - matcherStringBuilder.append(" STARTS WITH "); - matcherStringBuilder.append(value.toString()); - } - - matcherStringBuilder.insert(0, "("); - matcherStringBuilder.append(")"); - return matcherStringBuilder.toString(); - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Module.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Module.java index ed1fb9bbc..e5fbb548e 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Module.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Module.java @@ -131,39 +131,6 @@ protected final > void registerListener(final E } } - /** - * Registers a rule with the {@code EventHub} for this module. - * - * @param rule rule definition object containing the conditions to be met and the consequence events to be published. - */ - protected final void registerRule(final Rule rule) { - try { - parentHub.registerModuleRule(this, rule); - } catch (final InvalidModuleException e) { - Log.debug(moduleName, "Failed to register rule (%s)", e); - } - } - - protected final void replaceRules(final List rules) { - try { - parentHub.replaceModuleRules(this, rules); - } catch (final InvalidModuleException e) { - Log.debug(moduleName, "Failed to register rule (%s)", e); - } - } - - /** - * Re-evaluates custom events with the provided rules, then registers the rules for this module - * - * @param rules {@code Rule} to register - * @param reprocessEventsHandler handler to return custom events - */ - protected final void replaceRulesAndEvaluateEvents(final List rules, - final ReprocessEventsHandler reprocessEventsHandler) { - parentHub.replaceRulesAndEvaluateEvents(this, rules, reprocessEventsHandler); - } - - /** * Registers a wild card event listener for this module. * @@ -221,18 +188,6 @@ protected final void unregisterWildcardListener() { unregisterListener(EventType.WILDCARD, EventSource.WILDCARD); } - /** - * Unregisters all {@code Rule} objects that have been registered by this {@code Module} instance with the parent - * {@code EventHub} object. - */ - protected final void unregisterAllRules() { - try { - parentHub.unregisterModuleRules(this); - } catch (final InvalidModuleException e) { - Log.debug(moduleName, "Failed ot unregister rules for module (%s)", e); - } - } - /** * Creates a shared state for this module, then sends an event ( the event and the state will have the same event number ) * @param sharedState {@code EventData} object containing the state to save diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Rule.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Rule.java deleted file mode 100644 index 66fe2f837..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Rule.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import java.util.Collections; -import java.util.List; - -class Rule { - protected RuleCondition condition; - protected List consequenceEvents; - - /** - * Constructs a new Rule object with the given {@code RuleCondition} and list of consequence {@code Event} objects. - * - * @param condition a {@code RuleCondition} object defining the requirements for this rule - * @param ruleConsequenceEvents a list of consequence {@code Event} objects. - */ - Rule(final RuleCondition condition, final List ruleConsequenceEvents) throws IllegalArgumentException { - if (condition == null) { - throw new IllegalArgumentException("Cannot create rule with null condition"); - } - - if (ruleConsequenceEvents == null) { - throw new IllegalArgumentException("Cannot create rule with null consequence events"); - } - - this.condition = condition; - this.consequenceEvents = ruleConsequenceEvents; - } - - /** - * Evaluates this {@code Rule} condition against the given {@code Event}. - * - * @param ruleTokenParser a {@code RuleTokenParser} instance - * @param event the {@code Event} to evaluate conditions against - * - * @return true if condition is satisfied, false otherwise. - */ - public boolean evaluateCondition(final RuleTokenParser ruleTokenParser, final Event event) { - return condition.evaluate(ruleTokenParser, event); - } - - /** - * Returns an unmodifiable list of {@code Event} objects to trigger - * @return An unmodifiable list. Will not be null. - */ - public List getConsequenceEvents() { - return Collections.unmodifiableList(this.consequenceEvents); - } - - @Override - public String toString() { - return "{\n\tCondition: " + - (condition == null ? "null" : condition.toString()) + - "\n\tConsequences: " + - (consequenceEvents == null ? "null" : consequenceEvents.toString()) + - "\n}"; - } - -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleCondition.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleCondition.java deleted file mode 100644 index 4bcfd81cf..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleCondition.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -/** - * Base class for a {@code Rule}'s set of conditions used to evaluate a rule. - */ -abstract class RuleCondition { - private static final String RULE_CONDITION_TYPE_KEY_JSON = "type"; - private static final String RULE_CONDITION_TYPE_GROUP_JSON = "group"; - private static final String RULE_CONDITION_TYPE_MATCHER_JSON = "matcher"; - private static final String RULE_CONDITION_TYPE_HISTORICAL_JSON = "historical"; - - private static final String RULE_CONDITION_DEFINITION_KEY_JSON = "definition"; - - /** - * Evaluate the condition and return true if the condition holds, with the data supplied in triggering event. - * - * @param ruleTokenParser {@link RuleTokenParser} instance to process keys in rule condition - * @param event triggering {@link Event} instance - * @return true if the condition holds - */ - protected abstract boolean evaluate(final RuleTokenParser ruleTokenParser, final Event event); - - @Override - public abstract String toString(); - - /** - * Instantiate a Rule condition class. The condition types supported are "group" (Condition Group) and "matcher" (Condition Matcher). - * - *

    - * - * Required keys are
    - *

      - *
    • - * {@code type} Types supported are "group" (Condition Group) and "matcher" (Condition Matcher). - *
    • - *
    • - * {@code definition} Defines the group or matcher conditions - *
    • - *
    - * - * @param conditionJson The json representing the rule condition. - * @return A {@link RuleCondition} instance - * - * @throws JsonException if the json format is not supported. - * @throws UnsupportedConditionException If the condition json contains unsupported keys or type values - */ - protected static RuleCondition ruleConditionFromJson(final JsonUtilityService.JSONObject conditionJson) throws - JsonException, UnsupportedConditionException { - if (conditionJson == null || conditionJson.length() == 0) { - return null; - } - - RuleCondition ruleCondition = null; - - if (conditionJson.getString(RULE_CONDITION_TYPE_KEY_JSON).equals(RULE_CONDITION_TYPE_GROUP_JSON)) { - ruleCondition = RuleConditionGroup.ruleConditionGroupFromJson(conditionJson.getJSONObject( - RULE_CONDITION_DEFINITION_KEY_JSON)); - } else if (conditionJson.getString(RULE_CONDITION_TYPE_KEY_JSON).equals(RULE_CONDITION_TYPE_MATCHER_JSON)) { - ruleCondition = RuleConditionMatcher.ruleConditionMatcherFromJson(conditionJson.getJSONObject( - RULE_CONDITION_DEFINITION_KEY_JSON)); - } - - if (ruleCondition == null) { - throw new UnsupportedConditionException("Could not create a condition instance!"); - } - - return ruleCondition; - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConditionAndGroup.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConditionAndGroup.java deleted file mode 100644 index 1e60a2c72..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConditionAndGroup.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import java.util.List; -import java.util.Map; - -/** - * Concrete {@code RuleCondition} class for an AND group. If all conditions in this group evaluate - * to true, then the entire group evaluates to true. - */ -class RuleConditionAndGroup extends RuleConditionGroup { - - /** - * Constructs a new {@code RuleConditionAndGroup} - * @param conditions the {@link RuleCondition}s in this group - */ - RuleConditionAndGroup(final List conditions) { - this.conditions = conditions; - } - - /** - * Evaluate the data in {@code Event} object against the conditions in this rule condition group. - * - * @param ruleTokenParser {@link RuleTokenParser} instance to process keys in rule condition - * @param event triggering {@link Event} instance - * - * @return true if all conditions in this group evaluate to true. - */ - @Override - protected boolean evaluate(final RuleTokenParser ruleTokenParser, final Event event) { - if (conditions == null || conditions.isEmpty()) { - return false; - } - - for (RuleCondition ruleCondition : conditions) { - if (!ruleCondition.evaluate(ruleTokenParser, event)) { - return false; - } - } - - return true; - } - - @Override - public String toString() { - if (conditions == null || conditions.isEmpty()) { - return ""; - } - - StringBuilder andGroupString = new StringBuilder(); - andGroupString.append("("); - StringBuilder orGroupConditions = new StringBuilder(); - - for (RuleCondition ruleCondition : conditions) { - if (orGroupConditions.length() > 0) { - orGroupConditions.append(" AND "); - } - - orGroupConditions.append(ruleCondition.toString()); - } - - andGroupString.append(orGroupConditions); - andGroupString.append(")"); - return andGroupString.toString(); - } - -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConditionGroup.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConditionGroup.java deleted file mode 100644 index a6f099729..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConditionGroup.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import java.util.ArrayList; -import java.util.List; - -/** - * Base class for a group of {@code RuleConditions}. - */ -abstract class RuleConditionGroup extends RuleCondition { - - private static final String RULE_CONDITIONS_JSON_KEY = "conditions"; - private static final String RULE_CONDITION_JSON_DEFINITION_LOGIC = "logic"; - private static final String RULE_CONDITION_JSON_DEFINITION_LOGIC_AND = "and"; - private static final String RULE_CONDITION_JSON_DEFINITION_LOGIC_OR = "or"; - - List conditions; - - /** - * Creates a new {@code RuleConditionGroup} from the given {@code ruleConditionDefinitionJson} object. - * - *

    - * The required keys are
    - *

      - *
    • - * {@code logic} The logic operator. Currently supported are "or" and "and" - *
    • - *
    • - * {@code conditions} The list of conditions part of the group - *
    • - *
    - * - * @param ruleConditionDefinitionJson {@link JsonUtilityService.JSONObject} which defines the structure of a - * {@code RuleConditionGroup} - * @return new instance of a {@code RuleConditionGroup}, or null if the JSON definition is not valid - - * @throws JsonException if the json format is not supported. - * @throws UnsupportedConditionException If {@code ruleConditionDefinitionJson} does not contain a valid logic operator, or the json contains unsupported keys, or - * the conditions in the group are themselves invalid - **/ - protected static RuleConditionGroup ruleConditionGroupFromJson( - final JsonUtilityService.JSONObject ruleConditionDefinitionJson) throws JsonException, UnsupportedConditionException { - - if (ruleConditionDefinitionJson == null) { - return null; - } - - String logicalOperator = ruleConditionDefinitionJson.getString(RULE_CONDITION_JSON_DEFINITION_LOGIC); - - if (logicalOperator == null) { - return null; - } - - List conditions = new ArrayList(); - JsonUtilityService.JSONArray conditionsJson = ruleConditionDefinitionJson.getJSONArray(RULE_CONDITIONS_JSON_KEY); - - if (conditionsJson == null) { - return null; - } - - for (int conditionIndex = 0; conditionIndex < conditionsJson.length(); conditionIndex++) { - JsonUtilityService.JSONObject conditionJson = conditionsJson.getJSONObject(conditionIndex); - - if (conditionJson == null) { - continue; - } - - RuleCondition ruleCondition = ruleConditionFromJson(conditionJson); - conditions.add(ruleCondition); - } - - RuleConditionGroup ruleConditionGroup = null; - - if (logicalOperator.equals(RULE_CONDITION_JSON_DEFINITION_LOGIC_AND)) { - ruleConditionGroup = new RuleConditionAndGroup(conditions); - } else if (logicalOperator.equals(RULE_CONDITION_JSON_DEFINITION_LOGIC_OR)) { - ruleConditionGroup = new RuleConditionOrGroup(conditions); - } - - if (ruleConditionGroup == null) { - throw new UnsupportedConditionException("Could not create an instance of a condition group!"); - } - - return ruleConditionGroup; - } - -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConditionMatcher.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConditionMatcher.java deleted file mode 100644 index 5a83a93f7..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConditionMatcher.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -/** - * Rule condition matcher class which evaluates a specific rule condition. - */ -class RuleConditionMatcher extends RuleCondition { - Matcher matcher; - - /** - * Constructs a RuleConditionMatcher instance. - * @param matcher the condition matcher - */ - RuleConditionMatcher(final Matcher matcher) { - this.matcher = matcher; - } - - /** - * Evaluate the data in current {@code Event} object against this condition matcher. - * - * @param ruleTokenParser {@link RuleTokenParser} instance to process keys in rule condition - * @param event triggering {@link Event} instance - * - * @return true if {@code Event} data validates against the matcher - */ - @Override - protected boolean evaluate(final RuleTokenParser ruleTokenParser, final Event event) { - if (ruleTokenParser == null || event == null || matcher == null) { - return false; - } - - String value = ruleTokenParser.expandKey(matcher.key, event); - return matcher.matches(value); - } - - /** - * Create a new RuleConditionMatcher instance from a json file. - * - *

    - * The required fields are
    - *

      - *
    • - * {@code matcher} One of the supported matchers (example "ex", "eq") - *
    • - *
    • - * {@code key} The key to be matched - *
    • - *
    - * - *

    - * The {@code value} json key is not required if the matcher type is "ex" or "nx". Otherwise it is required. - *

    - * @param ruleConditionMatcherJson the {@link JsonUtilityService.JSONObject} containing the definition of a - * {@code RuleConditionMatcher} - * @return {@link RuleConditionMatcher} based on {@code ruleConditionMatcherJson} - * - * @throws UnsupportedConditionException If the JSON was could not be parsed - */ - protected static RuleConditionMatcher ruleConditionMatcherFromJson( - final JsonUtilityService.JSONObject ruleConditionMatcherJson) throws UnsupportedConditionException { - - if (ruleConditionMatcherJson == null || ruleConditionMatcherJson.length() == 0) { - return null; - } - - Matcher matcher = Matcher.matcherWithJsonObject(ruleConditionMatcherJson); - - if (matcher == null) { - throw new UnsupportedConditionException("Could not create instance of a matcher!"); - } - - return new RuleConditionMatcher(matcher); - } - - @Override - public String toString() { - if (matcher == null) { - return ""; - } - - return matcher.toString(); - } - -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConditionOrGroup.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConditionOrGroup.java deleted file mode 100644 index bf5df4c32..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConditionOrGroup.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import java.util.List; -import java.util.Map; - -/** - * Concrete {@code RuleCondition} class for an OR group. If any condition in this group evaluates - * to true, then the entire group evaluates to true. - */ -class RuleConditionOrGroup extends RuleConditionGroup { - - /** - * Constructs a new {@code RuleConditionOrGroup} - * @param conditions the {@link RuleCondition}s in this group - */ - RuleConditionOrGroup(final List conditions) { - this.conditions = conditions; - } - - /** - * Evaluate the data in {@code Event} object against the conditions in this rule condition group. - * - * @param ruleTokenParser {@link RuleTokenParser} instance to process keys in rule condition - * @param event triggering {@link Event} instance - * - * @return true if any condition in this group evaluates to true. - */ - @Override - protected boolean evaluate(final RuleTokenParser ruleTokenParser, final Event event) { - if (conditions == null || conditions.isEmpty()) { - return false; - } - - for (RuleCondition ruleCondition : conditions) { - if (ruleCondition.evaluate(ruleTokenParser, event)) { - return true; - } - } - - return false; - } - - @Override - public String toString() { - if (conditions == null || conditions.isEmpty()) { - return ""; - } - - StringBuilder orGroupString = new StringBuilder(); - orGroupString.append("("); - StringBuilder orGroupConditions = new StringBuilder(); - - for (RuleCondition ruleCondition : conditions) { - if (orGroupConditions.length() > 0) { - orGroupConditions.append(" OR "); - } - - orGroupConditions.append(ruleCondition.toString()); - } - - orGroupString.append(orGroupConditions); - orGroupString.append(")"); - return orGroupString.toString(); - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConsequence.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConsequence.java deleted file mode 100644 index bd33b2d3c..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RuleConsequence.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import com.adobe.marketing.mobile.JsonUtilityService.JSONObject; - -import java.util.HashMap; -import java.util.Map; - -class RuleConsequence { - private static final String LOG_TAG = RuleConsequence.class.getSimpleName(); - - private String id; - private String consequenceType; - private Map detail; - - /** - * Parse the consequence {@code jsonobject} into a {@code RuleConsequence} object - * - * @param jsonObject The {@link JSONObject} rule consequence - * @param jsonUtilityService {@link JsonUtilityService} instance - * @return a {@link RuleConsequence} instance. null if required fields are missing from the {@code jsonObject} - */ - static RuleConsequence consequenceFromJson(final JSONObject jsonObject, final JsonUtilityService jsonUtilityService) { - - if (jsonObject == null || jsonObject.length() == 0) { - return null; - } - - final RuleConsequence consequence = new RuleConsequence(); - - consequence.id = jsonObject.optString(ConfigurationConstants.EventDataKeys.RuleEngine.CONSEQUENCE_JSON_ID, null); - - if (StringUtils.isNullOrEmpty(consequence.id)) { - Log.warning(LOG_TAG, "Unable to find field \"id\" in rules consequence. This a required field."); - return null; - } - - consequence.consequenceType = jsonObject.optString( - ConfigurationConstants.EventDataKeys.RuleEngine.CONSEQUENCE_JSON_TYPE, null); - - if (StringUtils.isNullOrEmpty(consequence.consequenceType)) { - Log.warning(LOG_TAG, "Unable to find field \"type\" in rules consequence. This a required field."); - return null; - } - - final JSONObject detailJsonObject = jsonObject.optJSONObject( - ConfigurationConstants.EventDataKeys.RuleEngine.CONSEQUENCE_JSON_DETAIL); - - if (detailJsonObject == null || detailJsonObject.length() == 0) { - Log.warning(LOG_TAG, "Unable to find field \"detail\" in rules consequence. This a required field."); - return null; - } - - try { - consequence.detail = Variant.fromTypedObject(detailJsonObject, - new JsonObjectVariantSerializer(jsonUtilityService)).getVariantMap(); - } catch (VariantException ex) { - // shouldn't ever happen, but just in case - Log.warning(LOG_TAG, "Unable to convert detail json to a variant."); - return null; - } - - - return consequence; - } - - EventData generateEventData() { - final EventData eventData = new EventData(); - - HashMap consequenceMap = new HashMap(); - consequenceMap.put(ConfigurationConstants.EventDataKeys.RuleEngine.CONSEQUENCE_JSON_ID, Variant.fromString(id)); - consequenceMap.put(ConfigurationConstants.EventDataKeys.RuleEngine.CONSEQUENCE_JSON_TYPE, - Variant.fromString(consequenceType)); - consequenceMap.put(ConfigurationConstants.EventDataKeys.RuleEngine.CONSEQUENCE_JSON_DETAIL, - Variant.fromVariantMap(detail)); - - eventData.putVariantMap(ConfigurationConstants.EventDataKeys.RuleEngine.CONSEQUENCE_TRIGGERED, consequenceMap); - - return eventData; - } - -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RulesEngine.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RulesEngine.java deleted file mode 100644 index 7688cfea5..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RulesEngine.java +++ /dev/null @@ -1,577 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; - -/** - * - * {@code RulesEngine} is responsible for storing the relationship between modules and rules they have - * registered. It also provides the core mechanism for interpreting a set of rule conditions and - * generating a list of token-expanded consequence events. - */ -class RulesEngine { // not marked as final, but should not be extended except for testing purposes - protected final RuleTokenParser ruleTokenParser; - protected final ConcurrentHashMap> moduleRuleAssociation; - - /** - * Keeps a map of last dispatched event in a chained dispatch consequence flow and the number of times dispatch consequence was triggered. - * The max number of times an event can be evaluated for dispatch rules is controlled by {@link RulesEngineConstants#MAX_CHAINED_CONSEQUENCE_COUNT} - * and it is meant to prevent unintended event loops when using the dispatch consequence. - * Map: key is the event unique id and value is the current count in the consequence chain - */ - private final ConcurrentHashMap dispatchChainedEvents; - - private static final Object rulesOperationMutex = new Object(); - private static final String LOG_PREFIX = "Rules Engine"; - - /** - * Constructor for {@code RulesEngine} class - *

    - * Constructs a new instance of the {@code RulesEngine} class and associates it with - * it's owning {@link EventHub}. - * - * @param hub the {@link EventHub} parent instance. - */ - public RulesEngine(final EventHub hub) { - ruleTokenParser = new RuleTokenParser(hub); - moduleRuleAssociation = new ConcurrentHashMap>(); - dispatchChainedEvents = new ConcurrentHashMap(); - } - - /** - * Adds a rule to the set of rules associated with the given module. - * - * @param owningModule {@link Module} instance that's adding the {@link Rule} - * @param rule {@link Rule} object to be added - */ - protected void addRule(final Module owningModule, final Rule rule) { - synchronized (rulesOperationMutex) { - if (rule == null) { - return; - } - - if (rule.getConsequenceEvents() == null || rule.getConsequenceEvents().isEmpty()) { - return; - } - - moduleRuleAssociation.putIfAbsent(owningModule, new ConcurrentLinkedQueue()); - moduleRuleAssociation.get(owningModule).add(rule); - } - } - - /** - * Replaces all rules associated with the given module. - * - * @param owningModule {@link Module} instance that's adding the {@link Rule} - * @param rules a {@code List} of {@link Rule} object - */ - protected void replaceRules(final Module owningModule, final List rules) { - synchronized (rulesOperationMutex) { - if (rules == null) { - moduleRuleAssociation.remove(owningModule); - } else { - moduleRuleAssociation.put(owningModule, new ConcurrentLinkedQueue(rules)); - } - } - } - - ConcurrentHashMap> getModuleRuleAssociation() { - return this.moduleRuleAssociation; - } - - - /** - * Unregisters all rules for the given module. - * - * @param owningModule {@link Module} instance to clear all rules for. - */ - protected void unregisterAllRules(final Module owningModule) { - synchronized (rulesOperationMutex) { - moduleRuleAssociation.remove(owningModule); - } - } - - /** - * Evaluates all rules (across all registered modules) against the given triggerEvent - * - * @param triggerEvent {@link Event} object that the rules are being evaluated against - * - * @return a {@code List} of {@link Event} objects to publish as a result of the rule evaluations. - */ - protected List evaluateRules(final Event triggerEvent) { - synchronized (rulesOperationMutex) { - final List expandedConsequenceEvents = new ArrayList(); - // retrieve the current chained dispatch count for the trigger event before running through all rules - int dispatchCount = removeCurrentChainedDispatchCount(triggerEvent.getUniqueIdentifier()); - - for (final ConcurrentLinkedQueue ruleList : moduleRuleAssociation.values()) { - for (final Rule rule : ruleList) { - List consequences = evaluateRuleForEvent(triggerEvent, rule, dispatchCount); - expandedConsequenceEvents.addAll(consequences); - } - } - - return expandedConsequenceEvents; - } - - } - - /** - * Evaluates provided rules against the given triggerEvent - * - * @param triggerEvent {@link Event} object that the rules are being evaluated against - * @param rules {@link Rule} object used to evaluate events - * @return a {@code List} of {@link Event} objects to publish as a result of the rule evaluations. - */ - protected List evaluateEventWithRules(final Event triggerEvent, final List rules) { - final List expandedConsequenceEvents = new ArrayList(); - - synchronized (rulesOperationMutex) { - // retrieve the current chained dispatch count for the trigger event before running through all rules - int dispatchCount = removeCurrentChainedDispatchCount(triggerEvent.getUniqueIdentifier()); - - for (final Rule rule : rules) { - List consequences = evaluateRuleForEvent(triggerEvent, rule, dispatchCount); - expandedConsequenceEvents.addAll(consequences); - } - } - - return expandedConsequenceEvents; - } - - /** - * Evaluates the given {@code rule} against the supplied {@code triggerEvent}. - * - * @param triggerEvent The {@link Event} against which to evaluate the rule - * @param rule The {@link Rule} to be evaluated - * @param triggerEventDispatchCount The current number of chained dispatch consequences for the trigger event - * - * @return The list of consequences that will be dispatched if the rule evaluates to true. These consequences are already token expanded. - */ - protected List evaluateRuleForEvent(final Event triggerEvent, final Rule rule, - final int triggerEventDispatchCount) { - List expandedConsequenceEvents = new ArrayList(); - Log.trace(LOG_PREFIX, "Evaluating rule: %s for event number: %s", rule.toString(), triggerEvent.getEventNumber()); - - if (!rule.evaluateCondition(ruleTokenParser, triggerEvent)) { - return expandedConsequenceEvents; - } - - // condition matched - for (final Event consequenceEvent : rule.getConsequenceEvents()) { - final EventData expandedEventData = getTokenExpandedEventData(consequenceEvent.getData(), triggerEvent); - - if (expandedEventData == null) { - Log.debug(LOG_PREFIX, "Unable to process a RuleConsequence Event, unable to expand event data."); - continue; - } - - // AMSDK-8481 - // process attach data consequences in the rules engine - final Map consequenceMap = expandedEventData.optVariantMap( - RulesEngineConstants.EventDataKeys.CONSEQUENCE_TRIGGERED, null); - - if (consequenceMap == null || consequenceMap.isEmpty()) { - Log.debug(LOG_PREFIX, "Unable to process a RuleConsequence Event, 'triggeredconsequence' not found in payload."); - continue; - } - - if (!consequenceMap.containsKey(RulesEngineConstants.EventDataKeys.CONSEQUENCE_JSON_TYPE)) { - Log.debug(LOG_PREFIX, "Unable to process a RuleConsequence Event, no 'type' has been specified."); - continue; - } - - final String consequenceType = consequenceMap.get( - RulesEngineConstants.EventDataKeys.CONSEQUENCE_JSON_TYPE).optString(null); - - if (StringUtils.isNullOrEmpty(consequenceType)) { - Log.debug(LOG_PREFIX, "Unable to process a RuleConsequence Event, no 'type' has been specified."); - continue; - } - - if (RulesEngineConstants.ConsequenceType.ATTACH.equals(consequenceType)) { - processAttachDataConsequence(consequenceMap, triggerEvent); - } else if (RulesEngineConstants.ConsequenceType.MODIFY.equals(consequenceType)) { - processModifyDataConsequence(consequenceMap, triggerEvent); - } else if (RulesEngineConstants.ConsequenceType.DISPATCH.equals(consequenceType)) { - Event resultingEvent = processDispatchConsequence(consequenceMap, triggerEvent, triggerEventDispatchCount); - - if (resultingEvent != null) { - expandedConsequenceEvents.add(resultingEvent); - } - } else { - final Event.Builder outputEventBuilder = new Event.Builder(consequenceEvent.getName(), consequenceEvent.getEventType(), - consequenceEvent.getEventSource()).setData(expandedEventData); - - expandedConsequenceEvents.add(outputEventBuilder.build()); - } - } - - return expandedConsequenceEvents; - } - - protected void processModifyDataConsequence(final Map consequenceMap, final Event triggeringEvent) { - if (triggeringEvent == null) { - return; - } - - final Map consequenceDetails = getConsequenceDetails(consequenceMap, - RulesEngineConstants.ConsequenceType.MODIFY); - - if (consequenceDetails == null) { - return; - } - - if (!consequenceDetails.containsKey(RulesEngineConstants.EventDataKeys.CONSEQUENCE_DETAIL_EVENT_DATA)) { - Log.debug(LOG_PREFIX, "Unable to process a ModifyDataConsequence Event, 'eventData' is missing from 'details'."); - return; - } - - final Map newEventDataMap = consequenceDetails.get( - RulesEngineConstants.EventDataKeys.CONSEQUENCE_DETAIL_EVENT_DATA).optVariantMap(null); - final EventData newEventData = new EventData(newEventDataMap); - - // log the change and merge the data - Log.debug(LOG_PREFIX, "Modifying EventData on Event #%d with type '%s' and source '%s'.", - triggeringEvent.getEventNumber(), - triggeringEvent.getEventType().getName(), triggeringEvent.getEventSource().getName()); - - Log.debug(LOG_PREFIX, "Original EventData for Event #%d: %s", triggeringEvent.getEventNumber(), - triggeringEvent.getData().toString()); - - triggeringEvent.getData().overwrite(newEventData); - - Log.debug(LOG_PREFIX, "New EventData for Event #%d: %s", triggeringEvent.getEventNumber(), - triggeringEvent.getData().toString()); - } - - protected void processAttachDataConsequence(final Map consequenceMap, final Event triggeringEvent) { - if (triggeringEvent == null) { - return; - } - - final Map consequenceDetails = getConsequenceDetails(consequenceMap, - RulesEngineConstants.ConsequenceType.ATTACH); - - if (consequenceDetails == null) { - return; - } - - if (!consequenceDetails.containsKey(RulesEngineConstants.EventDataKeys.CONSEQUENCE_DETAIL_EVENT_DATA)) { - Log.debug(LOG_PREFIX, "Unable to process an AttachDataConsequence Event, 'eventData' is missing from 'details'."); - return; - } - - final Map newEventDataMap = consequenceDetails.get( - RulesEngineConstants.EventDataKeys.CONSEQUENCE_DETAIL_EVENT_DATA).optVariantMap(null); - final EventData newEventData = new EventData(newEventDataMap); - - // log the change and merge the data - Log.debug(LOG_PREFIX, "Adding EventData to Event #%d with type '%s' and source '%s'.", triggeringEvent.getEventNumber(), - triggeringEvent.getEventType().getName(), triggeringEvent.getEventSource().getName()); - - Log.debug(LOG_PREFIX, "Original EventData for Event #%d: %s", triggeringEvent.getEventNumber(), - triggeringEvent.getData().toString()); - - triggeringEvent.getData().merge(newEventData); - - Log.debug(LOG_PREFIX, "New EventData for Event #%d: %s", triggeringEvent.getEventNumber(), - triggeringEvent.getData().toString()); - } - - /** - * Processes the {@link RulesEngineConstants.ConsequenceType#DISPATCH} consequence and creates a new event with the - * type and source specified in the consequence, containing the event data from the {@code triggeringEvent}. - * This method updates the {@code dispatchChainedEvents} Map with the correct dispatch chained events count - * for the newly dispatched event. - * - * @param consequence consequence payload - * @param triggeringEvent the event that triggered this consequence - * @param triggerEventDispatchCount The current number of chained dispatch consequences for the trigger event - * @return the resulting event to be dispatched or null if the processing failed or the chained dispatch consequences - * are over {@link RulesEngineConstants#MAX_CHAINED_CONSEQUENCE_COUNT} - * @see #removeCurrentChainedDispatchCount - */ - protected Event processDispatchConsequence(final Map consequence, final Event triggeringEvent, - final int triggerEventDispatchCount) { - if (triggeringEvent == null) { - return null; - } - - if (triggerEventDispatchCount >= RulesEngineConstants.MAX_CHAINED_CONSEQUENCE_COUNT) { - Log.trace(LOG_PREFIX, - "Unable to process %s consequence, max chained limit of (%d) met for this event uuid (%s).", - RulesEngineConstants.ConsequenceType.DISPATCH, RulesEngineConstants.MAX_CHAINED_CONSEQUENCE_COUNT, - triggeringEvent.getUniqueIdentifier()); - return null; - } - - final Map consequenceDetails = getConsequenceDetails(consequence, - RulesEngineConstants.ConsequenceType.DISPATCH); - - if (consequenceDetails == null) { - return null; - } - - // verify that the required keys for this consequence exist - final String newEventType = getValueFromConsequenceDetails(consequenceDetails, - RulesEngineConstants.EventDataKeys.CONSEQUENCE_DETAIL_TYPE, - RulesEngineConstants.ConsequenceType.DISPATCH); - final String newEventSource = getValueFromConsequenceDetails(consequenceDetails, - RulesEngineConstants.EventDataKeys.CONSEQUENCE_DETAIL_SOURCE, - RulesEngineConstants.ConsequenceType.DISPATCH); - final String eventDataAction = getValueFromConsequenceDetails(consequenceDetails, - RulesEngineConstants.EventDataKeys.CONSEQUENCE_DETAIL_EVENT_DATA_ACTION, - RulesEngineConstants.ConsequenceType.DISPATCH); - - if (StringUtils.isNullOrEmpty(newEventType) || StringUtils.isNullOrEmpty(newEventSource) - || StringUtils.isNullOrEmpty(eventDataAction)) { - return null; - } - - Event resultingEvent; - - if (RulesEngineConstants.EventDataKeys.CONSEQUENCE_DETAIL_ACTION_COPY.equals(eventDataAction)) { - resultingEvent = new Event.Builder(RulesEngineConstants.DISPATCH_CONSEQUENCE_EVENT_NAME, newEventType, - newEventSource).setData(triggeringEvent.getData()).build(); - } else if (RulesEngineConstants.EventDataKeys.CONSEQUENCE_DETAIL_ACTION_NEW.equals(eventDataAction)) { - Map newEventDataMap = null; - - if (consequenceDetails.containsKey(RulesEngineConstants.EventDataKeys.CONSEQUENCE_DETAIL_EVENT_DATA)) { - newEventDataMap = consequenceDetails.get( - RulesEngineConstants.EventDataKeys.CONSEQUENCE_DETAIL_EVENT_DATA).optVariantMap(null); - } - - if (newEventDataMap != null) { - final EventData newEventData = new EventData(newEventDataMap); - resultingEvent = new Event.Builder(RulesEngineConstants.DISPATCH_CONSEQUENCE_EVENT_NAME, newEventType, - newEventSource).setData(newEventData).build(); - } else { - resultingEvent = new Event.Builder(RulesEngineConstants.DISPATCH_CONSEQUENCE_EVENT_NAME, newEventType, - newEventSource).build(); - } - } else { - Log.debug(LOG_PREFIX, - "Unable to process the %s consequence, unsupported (%s) 'eventdataaction', expected values are copy/new.", - RulesEngineConstants.ConsequenceType.DISPATCH, eventDataAction); - return null; - } - - if (resultingEvent != null) { - dispatchChainedEvents.put(resultingEvent.getUniqueIdentifier(), triggerEventDispatchCount + 1); - } - - return resultingEvent; - } - - //TODO: too much code here, we should be able to clean this up into a simpler function with less repetitive code. - - /** - * Expands tokens in the given {@link EventData} object - * - * @param eventData pre-token-expansion {@link EventData} object to expand - * @param triggerEvent {@link Event} object that triggered the rule that owns this consequence event - * - * @return {@link EventData} Token-Expanded version of the input eventdata object - */ - @SuppressWarnings("unchecked") - protected EventData getTokenExpandedEventData(final EventData eventData, final Event triggerEvent) { - if (eventData == null) { - return null; - } - - final EventData expandedData = new EventData(); - - for (final String key : eventData.keys()) { - Object value = eventData.getObject(key); - - if (value instanceof Map) { - expandedData.putObject(key, getTokenExpandedMap((Map) value, triggerEvent)); - } else if (value instanceof List) { - expandedData.putObject(key, getTokenExpandedList((List) value, triggerEvent)); - } else if (value instanceof String) { - expandedData.putObject(key, ruleTokenParser.expandTokensForString((String)value, triggerEvent)); - } else { - expandedData.putObject(key, value); - } - } - - return expandedData; - } - - /** - * Returns the original {@code Map} with tokens (if any) expanded with the appropriate values. - * - *

    - * If the {@code Map} contains native {@link Collections} like a Map or a List then this function will recursively expand tokens within them. - * If the {@code Map} contains any other Object apart from primitive data type containers, or collections, then it will be returned as is. - * - * @param mapWithTokens The {@link Map} with more zero or more tokens - * @param event The {@link Event} that will be used to expand tokens - * @return A {@code Map} with all the tokens expanded - */ - @SuppressWarnings("unchecked") - protected Map getTokenExpandedMap(final Map mapWithTokens, final Event event) { - if (mapWithTokens == null || mapWithTokens.isEmpty()) { - return mapWithTokens; - } - - Map expandedMap = new HashMap(); - - Set> entries = mapWithTokens.entrySet(); - - for (Map.Entry entry : entries) { - Object value = entry.getValue(); - - if (value instanceof Map) { - expandedMap.put(entry.getKey(), getTokenExpandedMap((Map) value, event)); - } else if (value instanceof List) { - expandedMap.put(entry.getKey(), getTokenExpandedList((List) value, event)); - } else if (value instanceof String) { - expandedMap.put(entry.getKey(), ruleTokenParser.expandTokensForString((String)value, event)); - } else { - expandedMap.put(entry.getKey(), entry.getValue()); - } - - } - - return expandedMap; - - } - - /** - * Returns the original list with tokens (if any) expanded with the appropriate values. - * - *

    - * If the {@code List} contains native {@link Collections} like a Map or a List then this function will recursively expand tokens within them. - * If the {@code List} contains any other Object apart from primitive data type containers, or collections, then it will be returned as is. - * - * @param listWithTokens The {@link List} with more zero or more tokens - * @param event The {@link Event} that will be used to expand tokens - * @return A {@code List} with all the tokens expanded - */ - @SuppressWarnings("unchecked") - protected List getTokenExpandedList(final List listWithTokens, final Event event) { - if (listWithTokens == null || listWithTokens.isEmpty()) { - return listWithTokens; - } - - List expandedList = new ArrayList(); - - for (final Object value : listWithTokens) { - if (value instanceof Map) { - expandedList.add(getTokenExpandedMap((Map) value, event)); - } else if (value instanceof List) { - expandedList.add(getTokenExpandedList((List) value, event)); - } else if (value instanceof String) { - expandedList.add(ruleTokenParser.expandTokensForString((String)value, event)); - } else { - expandedList.add(value); - } - } - - return expandedList; - } - - /** - * Used for testing - * @return unmodifiable copy of current {@code dispatchChainedEvents} - * @throws {@link UnsupportedOperationException} if any mutations are performed on the returned Map - */ - protected Map getDispatchChainedEvents() { - return Collections.unmodifiableMap(dispatchChainedEvents); - } - - /** - * Helper to extract the consequence details payload. - * - * @param consequence consequence payload - * @param consequenceType the consequence type to be used for logging - * @return the details payload or null if an error occurred - */ - private Map getConsequenceDetails(final Map consequence, - final String consequenceType) { - if (consequence == null || consequence.isEmpty()) { - return null; - } - - if (!consequence.containsKey(RulesEngineConstants.EventDataKeys.CONSEQUENCE_DETAIL)) { - Log.debug(LOG_PREFIX, String.format("Unexpected (%s) consequence format, 'details' object is missing.", - consequenceType)); - return null; - } - - final Map consequenceDetails = consequence.get( - RulesEngineConstants.EventDataKeys.CONSEQUENCE_DETAIL).optVariantMap(null); - - if (consequenceDetails == null || consequenceDetails.isEmpty()) { - Log.debug(LOG_PREFIX, String.format("Unexpected (%s) consequence format, 'details' is null/empty.", - consequenceType)); - return null; - } - - return consequenceDetails; - } - - /** - * Helper to extract a required {@link String} value for the specified {@code key}. - * - * @param consequenceDetails consequence details content - * @param key the expected {@link String} key in the consequence details - * @param consequenceType the consequence type to be used for logging - * @return the value for that key or null if an error occurred - */ - private String getValueFromConsequenceDetails(final Map consequenceDetails, final String key, - final String consequenceType) { - if (consequenceDetails == null || StringUtils.isNullOrEmpty(key)) { - return null; - } - - if (!consequenceDetails.containsKey(key)) { - Log.debug(LOG_PREFIX, - "Unexpected (%s) consequence format, required key (%s) is missing from 'details'", - consequenceType, key); - return null; - } - - final String value = consequenceDetails.get(key).optString(null); - - if (StringUtils.isNullOrEmpty(value)) { - Log.debug(LOG_PREFIX, - "Unexpected (%s) consequence format, required key (%s) has null/empty value in 'details'.", - consequenceType, key); - return null; - } - - return value; - } - - /** - * Retrieves current count of chained dispatch events for the provided {@code eventUniqueId} and removes it - * from the {@code dispatchChainedEvents}. - * - * @param eventUniqueId event unique identifier - * @return current count if a mapping exists in {@code dispatchChainedEvents} or 0 otherwise - */ - private int removeCurrentChainedDispatchCount(final String eventUniqueId) { - Integer dispatchCount = dispatchChainedEvents.remove(eventUniqueId); - return dispatchCount != null ? dispatchCount : 0; - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RulesEngineConstants.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RulesEngineConstants.java deleted file mode 100644 index 66db134a5..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/RulesEngineConstants.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -class RulesEngineConstants { - - static final String DISPATCH_CONSEQUENCE_EVENT_NAME = "Dispatch Consequence Result"; - static final int MAX_CHAINED_CONSEQUENCE_COUNT = 1; - - static final class ConsequenceType { - static final String ATTACH = "add"; - static final String MODIFY = "mod"; - static final String DISPATCH = "dispatch"; - } - - static final class EventDataKeys { - static final String CONSEQUENCE_DETAIL = "detail"; - static final String CONSEQUENCE_DETAIL_TYPE = "type"; - static final String CONSEQUENCE_DETAIL_SOURCE = "source"; - static final String CONSEQUENCE_DETAIL_EVENT_DATA = "eventdata"; - static final String CONSEQUENCE_DETAIL_EVENT_DATA_ACTION = "eventdataaction"; - static final String CONSEQUENCE_JSON_TYPE = "type"; - static final String CONSEQUENCE_TRIGGERED = "triggeredconsequence"; - static final String CONSEQUENCE_DETAIL_ACTION_COPY = "copy"; - static final String CONSEQUENCE_DETAIL_ACTION_NEW = "new"; - - private EventDataKeys() {} - } - - static final class EventHistory { - static final String ANY = "any"; - - static final class RuleDefinition { - static final String SEARCH_TYPE = "searchType"; - static final String MATCHER = "matcher"; - static final String VALUE = "value"; - static final String FROM = "from"; - static final String TO = "to"; - static final String EVENTS = "events"; - - private RuleDefinition() {} - } - private EventHistory() {} - } - - private RulesEngineConstants() {} -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRule.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRule.kt index 3a077e248..287c1e936 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRule.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRule.kt @@ -12,6 +12,7 @@ package com.adobe.marketing.mobile.launch.rulesengine import com.adobe.marketing.mobile.rulesengine.Evaluable +import com.adobe.marketing.mobile.rulesengine.Rule /** * The data class representing the given [Evaluable] and a list of [RuleConsequence] objects. @@ -20,4 +21,8 @@ import com.adobe.marketing.mobile.rulesengine.Evaluable * @property consequenceList a list of [RuleConsequence] objects * @constructor Constructs a new [LaunchRule] */ -data class LaunchRule(val condition: Evaluable, val consequenceList: List) +data class LaunchRule(val condition: Evaluable, val consequenceList: List) : Rule { + override fun getEvaluable(): Evaluable { + return condition + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngine.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngine.java new file mode 100644 index 000000000..8937733e9 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngine.java @@ -0,0 +1,45 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.launch.rulesengine; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.rulesengine.ConditionEvaluator; +import com.adobe.marketing.mobile.rulesengine.RulesEngine; +import com.adobe.marketing.mobile.rulesengine.TokenFinder; + +import java.util.List; + +public class LaunchRulesEngine { + private final RulesEngine ruleRulesEngine; + + @SuppressWarnings("rawtypes") + public LaunchRulesEngine() { + ruleRulesEngine = new RulesEngine<>(new ConditionEvaluator(), LaunchRuleTransformer.INSTANCE.createTransforming()); + } + + /** + * Set a new set of rules, the new rules replace the current rules. + * @param rules a list of {@link LaunchRule}s + */ + public void replaceRules(final List rules) { + ruleRulesEngine.replaceRules(rules); + } + + /** + * Evaluates all the current rules against the supplied {@link Event}. + * @param event the {@link Event} against which to evaluate the rules + * @return the processed {@link Event} + */ + public Event process(final Event event) { + ruleRulesEngine.evaluate(new LaunchTokenFinder(event)); + return event; + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt new file mode 100644 index 000000000..76a208c2e --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt @@ -0,0 +1,98 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.launch.rulesengine + +import com.adobe.marketing.mobile.Event +import com.adobe.marketing.mobile.EventPreprocessor +import com.adobe.marketing.mobile.LoggingMode +import com.adobe.marketing.mobile.MobileCore + +internal class LaunchRulesEvaluator( + private val name: String, + private val launchRulesEngine: LaunchRulesEngine +) : EventPreprocessor { + + constructor(name: String) : this(name, LaunchRulesEngine()) + + private var cachedEvents: MutableList? = mutableListOf() + private val logTag = "LaunchRulesEvaluator_$name" + + companion object { + const val CACHED_EVENT_MAX = 99 + + // TODO: we should move the following event type/event source values to the public EventType/EventSource classes once we have those. + const val EVENT_SOURCE = "com.adobe.eventsource.requestreset" + const val EVENT_TYPE = "com.adobe.eventtype.rulesengine" + } + + override fun process(event: Event?): Event? { + if (event == null) return null + + if (event.type == EVENT_TYPE && event.source == EVENT_SOURCE) { + reprocessCachedEvents() + } else { + cacheEvent(event) + launchRulesEngine.process(event) + // TODO: handle rules consequence + } + return event + } + + private fun reprocessCachedEvents() { + cachedEvents?.forEach { event -> + launchRulesEngine.process(event) + // TODO: handle rules consequence + } + clearCachedEvents() + } + + private fun clearCachedEvents() { + cachedEvents?.clear() + cachedEvents = null + } + + private fun cacheEvent(event: Event) { + cachedEvents?.let { + if ((cachedEvents?.size ?: -1) > CACHED_EVENT_MAX) { + clearCachedEvents() + MobileCore.log( + LoggingMode.WARNING, + logTag, + "Will not to reprocess cached events as the cached events have reached the limit: $CACHED_EVENT_MAX" + ) + } + cachedEvents?.add(event) + } + } + + /** + * Reset the current [LaunchRule]s. A RulesEngine Reset [Event] will be dispatched to reprocess the cached events. + * + * @param rules a list of [LaunchRule]s + */ + fun replaceRules(rules: List?) { + if (rules == null) return + launchRulesEngine.replaceRules(rules) + MobileCore.dispatchEvent( + Event.Builder( + name, + EVENT_TYPE, + EVENT_SOURCE + ).build() + ) { extensionError -> + MobileCore.log( + LoggingMode.ERROR, + logTag, + "Failed to reprocess cached events, caused by the error: ${extensionError.errorName}" + ) + } + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherUnknown.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt similarity index 51% rename from code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherUnknown.java rename to code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt index 85c51485f..b687c047e 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/MatcherUnknown.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt @@ -8,30 +8,13 @@ OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ +package com.adobe.marketing.mobile.launch.rulesengine -package com.adobe.marketing.mobile; +import com.adobe.marketing.mobile.Event +import com.adobe.marketing.mobile.rulesengine.TokenFinder -import java.util.Map; - -/** - * A rule condition matcher which is unknown. This class is used as a marker to denote a {@code Matcher} instance - * which is not of a known type. It always evaluates to false. - */ -class MatcherUnknown extends Matcher { - - /** - * Evaluates to false for this unknown {@code Matcher} type. - * - * @param value the value to match - * - * @return false, always. - */ - @Override - public boolean matches(final Object value) { - return false; - } - - @Override public String toString() { - return "(UNKNOWN)"; - } +internal class LaunchTokenFinder(event: Event) : TokenFinder { + override fun get(key: String?): Any { + TODO("Not yet implemented") + } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/GroupCondition.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/GroupCondition.kt index b5de36249..acf1f95a5 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/GroupCondition.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/GroupCondition.kt @@ -20,7 +20,7 @@ import java.util.Locale /** * The class representing a group of [JSONCondition]s */ -internal class GroupCondition(val definition: JSONDefinition) : JSONCondition() { +internal class GroupCondition(private val definition: JSONDefinition) : JSONCondition() { companion object { private const val LOG_TAG = "GroupCondition" private val LOGICAL_OPERATORS = listOf("or", "and") diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt index cd89a8fb3..bd14f5f17 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt @@ -19,7 +19,7 @@ import com.adobe.marketing.mobile.rulesengine.Evaluable import com.adobe.marketing.mobile.rulesengine.OperandFunction import com.adobe.marketing.mobile.rulesengine.OperandLiteral -internal class HistoricalCondition(val definition: JSONDefinition) : JSONCondition() { +internal class HistoricalCondition(private val definition: JSONDefinition) : JSONCondition() { companion object { private const val LOG_TAG = "HistoricalCondition" diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/MatcherCondition.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/MatcherCondition.kt index 4ca251c01..e5faa8216 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/MatcherCondition.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/MatcherCondition.kt @@ -22,7 +22,7 @@ import com.adobe.marketing.mobile.rulesengine.OperandMustacheToken /** * The class representing a matcher condition */ -internal class MatcherCondition(val definition: JSONDefinition) : JSONCondition() { +internal class MatcherCondition(private val definition: JSONDefinition) : JSONCondition() { companion object { private const val LOG_TAG = "MatcherCondition" diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ConditionEvaluator.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ConditionEvaluator.java index 375f559cc..80d19b6ff 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ConditionEvaluator.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/ConditionEvaluator.java @@ -37,6 +37,11 @@ public ConditionEvaluator(final Option option) { this.option = option; } + public ConditionEvaluator() { + this.option = Option.DEFAULT; + } + + /** * Runs operation on the operands. * diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/RulesEngine.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/RulesEngine.java index ba48f7685..1614712da 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/RulesEngine.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/RulesEngine.java @@ -15,34 +15,35 @@ import java.util.List; public class RulesEngine { - Evaluating evaluator; - Transforming transformer; - List rules; - - public RulesEngine(final Evaluating evaluator, final Transforming transformer) { - this.evaluator = evaluator; - this.transformer = transformer; - this.rules = new ArrayList<>(); - } - - public List evaluate(final TokenFinder tokenFinder) { - final Context context = new Context(tokenFinder, evaluator, transformer); - List triggerRules = new ArrayList<>(); - - for (final T rule : rules) { - if (rule.getEvaluable().evaluate(context).isSuccess()) { - triggerRules.add(rule); - } - } - - return triggerRules; - } - - public void addRules(final List newRules) { - rules.addAll(newRules); - } - - public void clearRules() { - rules.clear(); - } + private final Object rulesEngineMutex = new Object(); + private final Evaluating evaluator; + private final Transforming transformer; + private List rules; + + public RulesEngine(final Evaluating evaluator, final Transforming transformer) { + this.evaluator = evaluator; + this.transformer = transformer; + this.rules = new ArrayList<>(); + } + + public List evaluate(final TokenFinder tokenFinder) { + synchronized (rulesEngineMutex) { + final Context context = new Context(tokenFinder, evaluator, transformer); + List triggerRules = new ArrayList<>(); + + for (final T rule : rules) { + if (rule.getEvaluable().evaluate(context).isSuccess()) { + triggerRules.add(rule); + } + } + return triggerRules; + } + } + + public void replaceRules(final List newRules) { + synchronized (rulesEngineMutex) { + rules.clear(); + rules = newRules; + } + } } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/EventHubTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/EventHubTest.java index 527c792c4..961990976 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/EventHubTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/EventHubTest.java @@ -818,73 +818,6 @@ public void hear(final Event e) { } } - private boolean onEventReprocessingCompleteGetCalled = false; - private boolean rulesFlag = false; - @Test(timeout = 200) - public void replaceRulesAndEvaluateEvents_happy() throws Exception { - EVENT_LIST.clear(); - List rules = new ArrayList(); - - List consequenceEvents = new ArrayList(); - EventData eventData = new EventData(); - - Map consequenceMap = new HashMap(); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_JSON_ID, Variant.fromString("analyticsId")); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_JSON_TYPE, - Variant.fromString(RulesEngineConstantsTests.ConsequenceTypes.SEND_DATA_TO_ANALYTICS)); - Map detailMap = new HashMap(); - detailMap.put("key", "value"); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL, Variant.fromStringMap(detailMap)); - eventData.putVariantMap(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_TRIGGERED, consequenceMap); - - consequenceEvents.add(new Event.Builder("Test1", EventType.ANALYTICS, - EventSource.REQUEST_CONTENT).setData(eventData).build()); - consequenceEvents.add(new Event.Builder("Test2", EventType.ANALYTICS, - EventSource.REQUEST_CONTENT).setData(eventData).build()); - consequenceEvents.add(new Event.Builder("Test3", EventType.ANALYTICS, - EventSource.REQUEST_CONTENT).setData(eventData).build()); - rules.add(new Rule(new RuleCondition() { - @Override - protected boolean evaluate(final RuleTokenParser tokenParser, final Event event) { - //Make it evaluate to true - return rulesFlag; - } - @Override - public String toString() { - return null; - } - }, consequenceEvents)); - EventHub eventHub = new EventHub("eventhub", services); - eventHub.registerModule(TestableModule.class); - final CountDownLatch waitForBootLatch = new CountDownLatch(1); - eventHub.finishModulesRegistration(new AdobeCallback() { - @Override - public void call(Void value) { - waitForBootLatch.countDown(); - } - }); - waitForBootLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - Module activeModule = eventHub.getActiveModules().iterator().next(); - - eventHub.replaceRulesAndEvaluateEvents(activeModule, rules, new ReprocessEventsHandler() { - @Override - public List getEvents() { - rulesFlag = true; - List list = new ArrayList(); - list.add(new Event.Builder("Test", EventType.HUB, EventSource.NONE).build()); - return list; - } - @Override - public void onEventReprocessingComplete() { - onEventReprocessingCompleteGetCalled = true; - rulesFlag = false; - } - }); - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - assertTrue(onEventReprocessingCompleteGetCalled); - assertEquals(5, EVENT_LIST.size()); - } - @Test public void createOrUpdateSharedState_WithPendingStatus() throws Exception { EVENT_LIST.clear(); diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MatcherTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MatcherTests.java deleted file mode 100644 index ee181306c..000000000 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MatcherTests.java +++ /dev/null @@ -1,2485 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import org.json.JSONObject; -import org.junit.Before; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static junit.framework.TestCase.*; -import static org.junit.Assert.assertTrue; - -public class MatcherTests { - private PlatformServices platformServices; - private JsonUtilityService jsonUtilityService; - private RuleTokenParser ruleTokenParser; - - @Before - public void setUp() { - platformServices = new FakePlatformServices(); - EventHub testEventHub = new EventHub("eventhub", platformServices); - ruleTokenParser = new RuleTokenParser(testEventHub); - jsonUtilityService = platformServices.getJsonUtilityService(); - } - - - // ================================================================================ - // constructor tests - // ================================================================================ - @Test - public void constructorEquals() { - //setup - Map testData = new HashMap(); - testData.put("key", "blah"); - testData.put("matcher", "eq"); - List valuesList = new ArrayList(); - valuesList.add("testValue1"); - testData.put("values", valuesList); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - //test - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //assert - assertNotNull(matcher); - assertEquals(MatcherEquals.class, matcher.getClass()); - } - - @Test - public void constructorEmptyMatcher() { - //setup - Map testData = new HashMap(); - testData.put("key", "blah"); - //Empty matcher string - testData.put("matcher", ""); - List valuesList = new ArrayList(); - valuesList.add("testValue1"); - testData.put("values", valuesList); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - //test - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //assert - assertNotNull(matcher); - assertEquals(MatcherUnknown.class, matcher.getClass()); - } - - @Test - public void constructorNotEquals() { - //setup - Map testData = new HashMap(); - testData.put("key", "blah"); - testData.put("matcher", "ne"); - List valuesList = new ArrayList(); - valuesList.add("testValue1"); - testData.put("values", valuesList); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - //setup - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //verify - assertNotNull(matcher); - assertEquals(MatcherNotEquals.class, matcher.getClass()); - } - - @Test - public void constructorContains() { - //setup - Map testData = new HashMap(); - testData.put("key", "blah"); - testData.put("matcher", "co"); - List valuesList = new ArrayList(); - valuesList.add("testValue1"); - testData.put("values", valuesList); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - //test - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //verify - assertNotNull(matcher); - assertEquals(MatcherContains.class, matcher.getClass()); - } - - @Test - public void constructorNotContains() { - //setup - Map testData = new HashMap(); - testData.put("key", "blah"); - testData.put("matcher", "nc"); - List valuesList = new ArrayList(); - valuesList.add("testValue1"); - testData.put("values", valuesList); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - //test - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //verify - assertNotNull(matcher); - assertEquals(MatcherNotContains.class, matcher.getClass()); - } - - @Test - public void constructorGreaterThan() { - //setup - Map testData = new HashMap(); - testData.put("key", "blah"); - testData.put("matcher", "gt"); - List valuesList = new ArrayList(); - valuesList.add("testValue1"); - testData.put("values", valuesList); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - //test - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //verify - assertNotNull(matcher); - assertEquals(MatcherGreaterThan.class, matcher.getClass()); - } - - @Test - public void constructorGreaterThanOrEquals() { - //setup - Map testData = new HashMap(); - testData.put("key", "blah"); - testData.put("matcher", "ge"); - List valuesList = new ArrayList(); - valuesList.add("testValue1"); - testData.put("values", valuesList); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - //test - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //verify - assertNotNull(matcher); - assertEquals(MatcherGreaterThanOrEqual.class, matcher.getClass()); - } - - @Test - public void constructorLessThan() { - //setup - Map testData = new HashMap(); - testData.put("key", "blah"); - testData.put("matcher", "lt"); - List valuesList = new ArrayList(); - valuesList.add("testValue1"); - testData.put("values", valuesList); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - //test - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //verify - assertNotNull(matcher); - assertEquals(MatcherLessThan.class, matcher.getClass()); - } - - @Test - public void constructorLessThanOrEquals() { - //setup - Map testData = new HashMap(); - testData.put("key", "blah"); - testData.put("matcher", "le"); - List valuesList = new ArrayList(); - valuesList.add("testValue1"); - testData.put("values", valuesList); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - //test - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //verify - assertNotNull(matcher); - assertEquals(MatcherLessThanOrEqual.class, matcher.getClass()); - } - - @Test - public void constructorStartsWith() { - //setup - Map testData = new HashMap(); - testData.put("key", "blah"); - testData.put("matcher", "sw"); - List valuesList = new ArrayList(); - valuesList.add("testValue1"); - testData.put("values", valuesList); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - //test - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //verify - assertNotNull(matcher); - assertEquals(MatcherStartsWith.class, matcher.getClass()); - } - - @Test - public void constructorEndsWith() { - //setup - Map testData = new HashMap(); - testData.put("key", "blah"); - testData.put("matcher", "ew"); - List valuesList = new ArrayList(); - valuesList.add("testValue1"); - testData.put("values", valuesList); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - //test - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //verify - assertNotNull(matcher); - assertEquals(MatcherEndsWith.class, matcher.getClass()); - } - - @Test - public void constructorExists() { - //setup - Map testData = new HashMap(); - testData.put("key", "blah"); - testData.put("matcher", "ex"); - List valuesList = new ArrayList(); - valuesList.add("testValue1"); - testData.put("values", valuesList); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - //test - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //verify - assertNotNull(matcher); - assertEquals(MatcherExists.class, matcher.getClass()); - } - - @Test - public void constructorNotExists() { - //setup - Map testData = new HashMap(); - testData.put("key", "blah"); - testData.put("matcher", "nx"); - List valuesList = new ArrayList(); - valuesList.add("testValue1"); - testData.put("values", valuesList); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - //test - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //verify - assertNotNull(matcher); - assertEquals(MatcherNotExists.class, matcher.getClass()); - } - - @Test - public void constructorUnknown() { - //setup - Map testData = new HashMap(); - testData.put("key", "blah"); - testData.put("matcher", "UNKNOWN"); - List valuesList = new ArrayList(); - valuesList.add("testValue1"); - testData.put("values", valuesList); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - //test - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //verify - assertNotNull(matcher); - assertEquals(MatcherUnknown.class, matcher.getClass()); - } - - @Test - public void constructorUnknownNoMatchesParameter() { - //setup - Map testData = new HashMap(); - testData.put("key", "blah"); - List valuesList = new ArrayList(); - valuesList.add("testValue1"); - testData.put("values", valuesList); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - //test - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //verify - assertNotNull(matcher); - assertEquals(MatcherUnknown.class, matcher.getClass()); - } - - @Test - public void constructorMapsKeyProperly() { - //setup - Map testData = new HashMap(); - testData.put("key", "blah"); - List valuesList = new ArrayList(); - valuesList.add("testValue1"); - testData.put("values", valuesList); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - //test - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //verify - assertNotNull(matcher); - assertNotNull(matcher.key); - assertEquals("blah", matcher.key); - } - - @Test - public void constructorMapsValuesProperly() { - //setup - Map testData = new HashMap(); - testData.put("key", "blah"); - testData.put("matcher", "UNKNOWN"); - List valuesList = new ArrayList(); - valuesList.add("testValue1"); - valuesList.add("testValue2"); - testData.put("values", valuesList); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - //test - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //verify - assertNotNull(matcher); - assertNotNull(matcher.values); - assertEquals("testValue1", matcher.values.get(0)); - assertEquals("testValue2", matcher.values.get(1)); - } - - @Test - public void constructorMapsNullKeyProperly() { - //setup - Map testData = new HashMap(); - testData.put("key", null); - testData.put("matcher", "UNKNOWN"); - List valuesList = new ArrayList(); - valuesList.add("testValue1"); - testData.put("values", valuesList); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - //test - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //verify - assertNotNull(matcher); - assertNull(matcher.key); - } - - @Test - public void constructorMapsNullValuesProperly() { - //setup - Map testData = new HashMap(); - testData.put("key", "blah"); - testData.put("matcher", "UNKNOWN"); - testData.put("values", null); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - //test - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //verify - assertNotNull(matcher); - assertNotNull(matcher.values); - assertEquals(0, matcher.values.size()); - } - - // ================================================================================ - // matcher functionality - // ================================================================================ - private boolean runTestMatcher(Matcher matcher, Object value, List values) { - matcher.key = "testKey"; - matcher.values = new ArrayList(); - matcher.values.addAll(values); - - EventData testEventData = new EventData(); - testEventData.putObject("testKey", value); - Event testEvent = new Event.Builder("test", EventType.ANALYTICS, - EventSource.RESPONSE_CONTENT).setData(testEventData).build(); - return matcher.matches(ruleTokenParser.expandKey(matcher.key, testEvent)); - } - - private String generateToStringExpected(Matcher matcher, String key, ArrayList values) { - String string = "%s %s %s"; - StringBuilder sb = new StringBuilder(); - - String matcherString = null; - - if (matcher instanceof MatcherNotExists) { - return String.format("(%s NOT EXISTS)", key); - } else if (matcher instanceof MatcherExists) { - return String.format("(%s EXISTS)", key); - } - - if (matcher instanceof MatcherStartsWith) { - matcherString = "STARTS WITH"; - } - - if (matcher instanceof MatcherEndsWith) { - matcherString = "ENDS WITH"; - } - - if (matcher instanceof MatcherContains) { - matcherString = "CONTAINS"; - } - - if (matcher instanceof MatcherNotContains) { - matcherString = "NOT CONTAINS"; - } - - if (matcher instanceof MatcherEquals) { - matcherString = "EQUALS"; - } - - if (matcher instanceof MatcherNotEquals) { - matcherString = "NOT EQUALS"; - } - - if (matcher instanceof MatcherLessThan) { - matcherString = "LESS THAN"; - } - - if (matcher instanceof MatcherGreaterThan) { - matcherString = "GREATER THAN"; - } - - if (matcher instanceof MatcherGreaterThanOrEqual) { - matcherString = "GREATER THAN OR EQUALS"; - } - - if (matcher instanceof MatcherLessThanOrEqual) { - matcherString = "LESS THAN OR EQUALS"; - } - - ArrayList v = (values == null ? new ArrayList() : values); - - if (v.isEmpty()) { - v = new ArrayList(); - v.add(""); - } - - for (Object s : v) { - if (sb.length() > 0) { - sb.append(" OR "); - } - - sb.append(String.format(string, key, matcherString, s)); - } - - sb.insert(0, "("); - sb.append(")"); - - return sb.toString(); - - } - - // ================================================================================ - // MatcherEquals - // ================================================================================ - // string - @Test - public void equalsStringShouldPerformSingleMatchPass() { - //setup - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add("test"); - //test - assertTrue(runTestMatcher(testMatcher, "test", values)); - } - - @Test - public void equalsStringShouldPerformSingleMatchFail() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add("test"); - assertFalse(runTestMatcher(testMatcher, "testingBadMatch", values)); - } - - @Test - public void equalsStringShouldPerformMultipleMatchPass() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add("nope"); - values.add("stillnope"); - values.add("nothing to see here"); - values.add("tests"); - values.add("test"); - assertTrue(runTestMatcher(testMatcher, "test", values)); - } - - @Test - public void equalsStringShouldPerformMultipleMatchFail() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add("nope"); - values.add("stillnope"); - values.add("nothing to see here"); - values.add("tests"); - values.add("testingBadMatch"); - assertFalse(runTestMatcher(testMatcher, "test", values)); - } - - @Test - public void equalsStringShouldPerformSingleMatchPassCaseInsensitive() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add("test"); - assertTrue(runTestMatcher(testMatcher, "teSt", values)); - } - - @Test - public void equalsStringShouldPerformMultipleMatchPassCaseInsensitive() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add("nope"); - values.add("stillnope"); - values.add("nothing to see here"); - values.add("tests"); - values.add("testingBadMatch"); - assertTrue(runTestMatcher(testMatcher, "tesTS", values)); - } - - @Test - public void equalsStringShouldHandleIncorrectTypes() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add("test"); - assertFalse(runTestMatcher(testMatcher, 552, values)); - } - - // numeric - @Test - public void equalsNumericShouldPerformSingleMatchPass() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(552); - assertTrue(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void equalsNumericShouldPerformSingleMatchFail() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(553); - assertFalse(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void equalsNumericShouldPerformSinglePrecisionMatchPass() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(552.323); - assertTrue(runTestMatcher(testMatcher, 552.323, values)); - } - - @Test - public void equalsNumericShouldPerformSinglePrecisionMatchFail() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(552.323); - assertFalse(runTestMatcher(testMatcher, 552.3, values)); - } - - @Test - public void equalsNumericShouldPerformMultipleMatchPass() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(552); - values.add(551); - values.add(5544); - values.add(557); - assertTrue(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void equalsNumericShouldPerformMultipleMatchFail() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(552); - values.add(551); - values.add(5544); - values.add(557); - assertFalse(runTestMatcher(testMatcher, 550, values)); - } - - @Test - public void equalsNumericShouldPerformMultiplePrecisionMatchPass() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(552.222); - values.add(551.654); - values.add(5544.343); - values.add(557.8755); - assertTrue(runTestMatcher(testMatcher, 552.222, values)); - } - - @Test - public void equalsNumericShouldPerformMultiplePrecisionMatchFail() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(552.222); - values.add(551.654); - values.add(5544.343); - values.add(557.8755); - assertFalse(runTestMatcher(testMatcher, 552.888, values)); - } - - @Test - public void equalsNumericShouldPerformSingleMatchWithString() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(552); - assertTrue(runTestMatcher(testMatcher, "552", values)); - } - - @Test - public void equalsNumericShouldNotCrashWithStringThatIsNotANumber() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(552); - assertFalse(runTestMatcher(testMatcher, "sdfsd", values)); - } - - @Test - public void equalsNumericShouldHandleIncorrectTypesMatched() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add("552"); - assertTrue(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void equalsNumericShouldHandleIncorrectTypesNotMatched() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add("554"); - assertFalse(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void equalsToString_When_ValidData_Then_ReturnStringValue() { - MatcherEquals testMatcher = new MatcherEquals(); - testMatcher.key = "key"; - testMatcher.values = new ArrayList(); - testMatcher.values.add("down"); - testMatcher.values.add("hammer"); - testMatcher.values.add("stahp"); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - @Test - public void equalsToString_When_NullKey_Then_ReturnStringWithDefaults() { - MatcherEquals testMatcher = new MatcherEquals(); - testMatcher.values = new ArrayList(); - testMatcher.values.add("down"); - testMatcher.values.add("hammer"); - testMatcher.values.add("stahp"); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - @Test - public void equalsBooleanTrueShouldEqualsValueBooleanTrue() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(true); - assertTrue(runTestMatcher(testMatcher, true, values)); - } - - @Test - public void equalsBooleanFalseShouldEqualsValueBooleanFalse() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(false); - assertTrue(runTestMatcher(testMatcher, false, values)); - } - - @Test - public void equalsBooleanTrueShouldNotEqualsValueBooleanFalse() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(true); - assertFalse(runTestMatcher(testMatcher, false, values)); - } - - @Test - public void equalsBooleanTrueShouldEqualsValueStringTrue() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(true); - assertTrue(runTestMatcher(testMatcher, "true", values)); - } - - @Test - public void equalsBooleanTrueShouldEqualsValueStringTrueUpperCase() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(true); - assertTrue(runTestMatcher(testMatcher, "TRUE", values)); - } - - @Test - public void equalsBooleanTrueShouldEqualsValueString1() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(true); - assertTrue(runTestMatcher(testMatcher, "1", values)); - } - - @Test - public void equalsBooleanFalseShouldEqualsValueString1() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(false); - assertTrue(runTestMatcher(testMatcher, "0", values)); - } - - @Test - public void equalsBooleanFalseShouldNotEqualsValueStringABC() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(false); - assertFalse(runTestMatcher(testMatcher, "abc", values)); - } - - @Test - public void equalsBooleanTrueShouldNotEqualsValueStringABC() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(true); - assertFalse(runTestMatcher(testMatcher, "abc", values)); - } - - @Test - public void equalsBooleanTrueShouldEqualsValueInt1() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(true); - assertTrue(runTestMatcher(testMatcher, 1, values)); - } - - @Test - public void equalsBooleanTrueShouldEqualsValueLong1() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(true); - assertTrue(runTestMatcher(testMatcher, 1l, values)); - } - - @Test - public void equalsBooleanTrueShouldNotEqualsValueFloat1() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(true); - assertFalse(runTestMatcher(testMatcher, 1f, values)); - } - - @Test - public void equalsBooleanFalseShouldEqualsValueInt0() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(false); - assertTrue(runTestMatcher(testMatcher, 0, values)); - } - - @Test - public void equalsBooleanFalseShouldEqualsValueLong0() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(false); - assertTrue(runTestMatcher(testMatcher, 0l, values)); - } - - @Test - public void equalsBooleanFalseShouldNotEqualsValueFloat0() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(false); - assertFalse(runTestMatcher(testMatcher, 0f, values)); - } - - @Test - public void equalsBooleanTrueShouldNotEqualsValueInt2() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(true); - assertFalse(runTestMatcher(testMatcher, 2, values)); - } - - @Test - public void equalsBooleanFalseShouldNotEqualsValueInt2() { - MatcherEquals testMatcher = new MatcherEquals(); - List values = new ArrayList(); - values.add(false); - assertFalse(runTestMatcher(testMatcher, 2f, values)); - } - - // ================================================================================ - // MatcherNotEquals - // ================================================================================ - // string - @Test - public void notEqualsStringShouldPerformSingleMatchPass() { - MatcherNotEquals testMatcher = new MatcherNotEquals(); - List values = new ArrayList(); - values.add("test"); - assertTrue(runTestMatcher(testMatcher, "test not match", values)); - } - - @Test - public void notEqualsStringShouldPerformSingleMatchFail() { - MatcherNotEquals testMatcher = new MatcherNotEquals(); - List values = new ArrayList(); - values.add("test"); - assertFalse(runTestMatcher(testMatcher, "test", values)); - } - - @Test - public void notEqualsStringShouldPerformMultipleMatchPass() { - MatcherNotEquals testMatcher = new MatcherNotEquals(); - List values = new ArrayList(); - values.add("test"); - values.add("testing more"); - values.add("still test"); - values.add("test the things"); - values.add("still not matching"); - assertTrue(runTestMatcher(testMatcher, "test not match", values)); - } - - @Test - public void notEqualsStringShouldPerformMultipleMatchFail() { - MatcherNotEquals testMatcher = new MatcherNotEquals(); - List values = new ArrayList(); - values.add("test"); - values.add("testing more"); - values.add("still test"); - values.add("test the things"); - values.add("still not matching"); - assertFalse(runTestMatcher(testMatcher, "still not matching", values)); - } - - @Test - public void notEqualsStringShouldPerformSingleMatchPassCaseInsensitive() { - MatcherNotEquals testMatcher = new MatcherNotEquals(); - List values = new ArrayList(); - values.add("test"); - assertFalse(runTestMatcher(testMatcher, "TeST", values)); - } - - @Test - public void notEqualsStringShouldPerformMultipleMatchPassCaseInsensitive() { - MatcherNotEquals testMatcher = new MatcherNotEquals(); - List values = new ArrayList(); - values.add("test"); - values.add("testing more"); - values.add("still test"); - values.add("test the things"); - values.add("still not matching"); - assertFalse(runTestMatcher(testMatcher, "tEsT", values)); - } - - // numeric - @Test - public void notEqualsNumericShouldPerformSingleMatchPass() { - MatcherNotEquals testMatcher = new MatcherNotEquals(); - List values = new ArrayList(); - values.add(552); - assertFalse(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void notEqualsNumericShouldPerformSingleMatchFail() { - MatcherNotEquals testMatcher = new MatcherNotEquals(); - List values = new ArrayList(); - values.add(553); - assertTrue(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void notEqualsNumericShouldPerformSinglePrecisionMatchPass() { - MatcherNotEquals testMatcher = new MatcherNotEquals(); - List values = new ArrayList(); - values.add(552.323); - assertFalse(runTestMatcher(testMatcher, 552.323, values)); - } - - @Test - public void notEqualsNumericShouldPerformSinglePrecisionMatchFail() { - MatcherNotEquals testMatcher = new MatcherNotEquals(); - List values = new ArrayList(); - values.add(552.323); - assertTrue(runTestMatcher(testMatcher, 552.322, values)); - } - - @Test - public void notEqualsNumericShouldPerformMultipleMatchPass() { - MatcherNotEquals testMatcher = new MatcherNotEquals(); - List values = new ArrayList(); - values.add(550); - values.add(551); - values.add(553); - values.add(554); - values.add(552); - assertFalse(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void notEqualsNumericShouldPerformMultipleMatchFail() { - MatcherNotEquals testMatcher = new MatcherNotEquals(); - List values = new ArrayList(); - values.add(550); - values.add(551); - values.add(553); - values.add(554); - values.add(555); - assertTrue(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void notEqualsNumericShouldPerformMultiplePrecisionMatchPass() { - MatcherNotEquals testMatcher = new MatcherNotEquals(); - List values = new ArrayList(); - values.add(552.111); - values.add(552.222); - values.add(552.333); - values.add(552.444); - values.add(552.555); - assertFalse(runTestMatcher(testMatcher, 552.555, values)); - } - - @Test - public void notEqualsNumericShouldPerformMultiplePrecisionMatchFail() { - MatcherNotEquals testMatcher = new MatcherNotEquals(); - List values = new ArrayList(); - values.add(552.111); - values.add(552.222); - values.add(552.333); - values.add(552.444); - values.add(552.55); - assertTrue(runTestMatcher(testMatcher, 552.555, values)); - } - - @Test - public void notEqualsNumericShouldPerformSingleMatchWithString() { - MatcherNotEquals testMatcher = new MatcherNotEquals(); - List values = new ArrayList(); - values.add(552); - assertFalse(runTestMatcher(testMatcher, "552", values)); - } - - @Test - public void notEqualsNumericShouldNotCrashWithStringThatIsNotANumber() { - MatcherNotEquals testMatcher = new MatcherNotEquals(); - List values = new ArrayList(); - values.add(552); - assertTrue(runTestMatcher(testMatcher, "not a number", values)); - } - - @Test - public void notEqualsNumericShouldHandleIncorrectTypesMatched() { - MatcherNotEquals testMatcher = new MatcherNotEquals(); - List values = new ArrayList(); - values.add("552"); - assertFalse(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void notEqualsNumericShouldHandleIncorrectTypesNotMatched() { - MatcherNotEquals testMatcher = new MatcherNotEquals(); - List values = new ArrayList(); - values.add("554"); - assertTrue(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void notEqualsToString_When_ValidData_Then_ReturnStringValue() { - MatcherNotEquals testMatcher = new MatcherNotEquals(); - testMatcher.key = "key"; - testMatcher.values = new ArrayList(); - testMatcher.values.add("down"); - testMatcher.values.add("hammer"); - testMatcher.values.add("stahp"); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - @Test - public void notEqualsToString_When_NullKey_Then_ReturnStringWithDefaults() { - MatcherNotEquals testMatcher = new MatcherNotEquals(); - testMatcher.values = new ArrayList(); - testMatcher.values.add("down"); - testMatcher.values.add("hammer"); - testMatcher.values.add("stahp"); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - - // ================================================================================ - // MatcherGreaterThan - // ================================================================================ - @Test - public void greaterThanShouldMatchCorrectly() { - MatcherGreaterThan testMatcher = new MatcherGreaterThan(); - List values = new ArrayList(); - values.add(552); - assertTrue(runTestMatcher(testMatcher, 553, values)); - } - - @Test - public void greaterThanShouldFailCorrectly() { - MatcherGreaterThan testMatcher = new MatcherGreaterThan(); - List values = new ArrayList(); - values.add(552); - assertFalse(runTestMatcher(testMatcher, 551, values)); - } - - @Test - public void greaterThanShouldNotMatchEquals() { - MatcherGreaterThan testMatcher = new MatcherGreaterThan(); - List values = new ArrayList(); - values.add(552); - assertFalse(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void greaterThanShouldMatchMultiplesCorrectly() { - MatcherGreaterThan testMatcher = new MatcherGreaterThan(); - List values = new ArrayList(); - values.add(552); - values.add(554); - values.add(555); - values.add(556); - values.add(553); - assertTrue(runTestMatcher(testMatcher, 553, values)); - } - - @Test - public void greaterThanShouldFailMultiplesCorrectly() { - MatcherGreaterThan testMatcher = new MatcherGreaterThan(); - List values = new ArrayList(); - values.add(554); - values.add(555); - values.add(556); - values.add(553); - assertFalse(runTestMatcher(testMatcher, 553, values)); - } - - @Test - public void greaterThanShouldNotMatchMultipleEquals() { - MatcherGreaterThan testMatcher = new MatcherGreaterThan(); - List values = new ArrayList(); - values.add(552); - values.add(552); - values.add(552); - values.add(552); - assertFalse(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void greaterThanShouldMatchPrecisionCorrectly() { - MatcherGreaterThan testMatcher = new MatcherGreaterThan(); - List values = new ArrayList(); - values.add(552.552); - assertTrue(runTestMatcher(testMatcher, 552.553, values)); - } - - @Test - public void greaterThanShouldFailPrecisionCorrectly() { - MatcherGreaterThan testMatcher = new MatcherGreaterThan(); - List values = new ArrayList(); - values.add(552.552); - assertFalse(runTestMatcher(testMatcher, 552.551, values)); - } - - @Test - public void greaterThanShouldNotMatchPrecisionEquals() { - MatcherGreaterThan testMatcher = new MatcherGreaterThan(); - List values = new ArrayList(); - values.add(552.552); - assertFalse(runTestMatcher(testMatcher, 552.552, values)); - } - - @Test - public void greaterThanShouldMatchPrecisionMultiplesCorrectly() { - MatcherGreaterThan testMatcher = new MatcherGreaterThan(); - List values = new ArrayList(); - values.add(552.553); - values.add(552.554); - values.add(552.555); - values.add(552.556); - values.add(552.551); - assertTrue(runTestMatcher(testMatcher, 552.552, values)); - } - - @Test - public void greaterThanShouldFailPrecisionMultiplesCorrectly() { - MatcherGreaterThan testMatcher = new MatcherGreaterThan(); - List values = new ArrayList(); - values.add(552.553); - values.add(552.554); - values.add(552.555); - values.add(552.556); - assertFalse(runTestMatcher(testMatcher, 552.552, values)); - } - - @Test - public void greaterThanShouldConvertStringsWhenPossibleFail() { - MatcherGreaterThan testMatcher = new MatcherGreaterThan(); - List values = new ArrayList(); - values.add(552); - assertFalse(runTestMatcher(testMatcher, "551", values)); - } - - @Test - public void greaterThanShouldConvertStringsWhenPossibleMatch() { - MatcherGreaterThan testMatcher = new MatcherGreaterThan(); - List values = new ArrayList(); - values.add(552); - assertTrue(runTestMatcher(testMatcher, "553", values)); - } - - @Test - public void greaterThanShouldFailGracefulllyWithStringArray() { - MatcherGreaterThan testMatcher = new MatcherGreaterThan(); - List values = new ArrayList(); - values.add("iamastring"); - assertFalse(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void greaterThanShouldFailGracefulllyWithStringInput() { - MatcherGreaterThan testMatcher = new MatcherGreaterThan(); - List values = new ArrayList(); - values.add(552); - assertFalse(runTestMatcher(testMatcher, "iamastring", values)); - } - - @Test - public void greaterThanToString_When_ValidData_Then_ReturnStringValue() { - MatcherGreaterThan testMatcher = new MatcherGreaterThan(); - testMatcher.key = "key"; - testMatcher.values = new ArrayList(); - testMatcher.values.add("down"); - testMatcher.values.add("hammer"); - testMatcher.values.add("stahp"); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - @Test - public void greaterThanToString_When_NullKey_Then_ReturnStringWithDefaults() { - MatcherGreaterThan testMatcher = new MatcherGreaterThan(); - testMatcher.values = new ArrayList(); - testMatcher.values.add("down"); - testMatcher.values.add("hammer"); - testMatcher.values.add("stahp"); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - - // ================================================================================ - // MatcherGreaterThanOrEqual - // ================================================================================ - @Test - public void greaterThanEqShouldMatchCorrectly() { - MatcherGreaterThanOrEqual testMatcher = new MatcherGreaterThanOrEqual(); - List values = new ArrayList(); - values.add(552); - assertTrue(runTestMatcher(testMatcher, 553, values)); - } - - @Test - public void greaterThanEqShouldFailCorrectly() { - MatcherGreaterThanOrEqual testMatcher = new MatcherGreaterThanOrEqual(); - List values = new ArrayList(); - values.add(552); - assertFalse(runTestMatcher(testMatcher, 551, values)); - } - - @Test - public void greaterThanEqShouldMatchEquals() { - MatcherGreaterThanOrEqual testMatcher = new MatcherGreaterThanOrEqual(); - List values = new ArrayList(); - values.add(552); - assertTrue(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void greaterThanEqShouldMatchMultiplesCorrectly() { - MatcherGreaterThanOrEqual testMatcher = new MatcherGreaterThanOrEqual(); - List values = new ArrayList(); - values.add(554); - values.add(555); - values.add(556); - values.add(557); - values.add(553); - assertTrue(runTestMatcher(testMatcher, 553, values)); - } - - @Test - public void greaterThanEqShouldFailMultiplesCorrectly() { - MatcherGreaterThanOrEqual testMatcher = new MatcherGreaterThanOrEqual(); - List values = new ArrayList(); - values.add(554); - values.add(555); - values.add(556); - values.add(557); - values.add(558); - assertFalse(runTestMatcher(testMatcher, 553, values)); - } - - @Test - public void greaterThanEqShouldMatchMultipleEquals() { - MatcherGreaterThanOrEqual testMatcher = new MatcherGreaterThanOrEqual(); - List values = new ArrayList(); - values.add(552); - values.add(552); - values.add(552); - values.add(552); - assertTrue(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void greaterThanEqShouldMatchPrecisionCorrectly() { - MatcherGreaterThanOrEqual testMatcher = new MatcherGreaterThanOrEqual(); - List values = new ArrayList(); - values.add(552.552); - assertTrue(runTestMatcher(testMatcher, 552.553, values)); - } - - @Test - public void greaterThanEqShouldFailPrecisionCorrectly() { - MatcherGreaterThanOrEqual testMatcher = new MatcherGreaterThanOrEqual(); - List values = new ArrayList(); - values.add(552.552); - assertFalse(runTestMatcher(testMatcher, 552.551, values)); - } - - @Test - public void greaterThanEqShouldMatchPrecisionEquals() { - MatcherGreaterThanOrEqual testMatcher = new MatcherGreaterThanOrEqual(); - List values = new ArrayList(); - values.add(552.552); - assertTrue(runTestMatcher(testMatcher, 552.552, values)); - } - - @Test - public void greaterThanEqShouldMatchPrecisionMultiplesCorrectly() { - MatcherGreaterThanOrEqual testMatcher = new MatcherGreaterThanOrEqual(); - List values = new ArrayList(); - values.add(552.553); - values.add(552.554); - values.add(552.555); - values.add(552.556); - values.add(552.552); - assertTrue(runTestMatcher(testMatcher, 552.552, values)); - } - - @Test - public void greaterThanEqShouldFailPrecisionMultiplesCorrectly() { - MatcherGreaterThanOrEqual testMatcher = new MatcherGreaterThanOrEqual(); - List values = new ArrayList(); - values.add(552.553); - values.add(552.554); - values.add(552.555); - values.add(552.556); - values.add(552.557); - assertFalse(runTestMatcher(testMatcher, 552.552, values)); - } - - @Test - public void greaterThanEqShouldConvertStringsWhenPossibleMatch() { - MatcherGreaterThanOrEqual testMatcher = new MatcherGreaterThanOrEqual(); - List values = new ArrayList(); - values.add(552); - assertTrue(runTestMatcher(testMatcher, "553", values)); - } - - @Test - public void greaterThanEqShouldConvertStringsWhenPossibleFail() { - MatcherGreaterThanOrEqual testMatcher = new MatcherGreaterThanOrEqual(); - List values = new ArrayList(); - values.add(552); - assertFalse(runTestMatcher(testMatcher, "551", values)); - } - - @Test - public void greaterThanEqShouldFailGracefulllyWithStringArray() { - MatcherGreaterThanOrEqual testMatcher = new MatcherGreaterThanOrEqual(); - List values = new ArrayList(); - values.add("iamastring"); - assertFalse(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void greaterThanEqShouldFailGracefulllyWithStringInput() { - MatcherGreaterThanOrEqual testMatcher = new MatcherGreaterThanOrEqual(); - List values = new ArrayList(); - values.add(552); - assertFalse(runTestMatcher(testMatcher, "iamastring", values)); - } - - @Test - public void greaterThanOrEqualToString_When_ValidData_Then_ReturnStringValue() { - MatcherGreaterThanOrEqual testMatcher = new MatcherGreaterThanOrEqual(); - testMatcher.key = "key"; - testMatcher.values = new ArrayList(); - testMatcher.values.add("down"); - testMatcher.values.add("hammer"); - testMatcher.values.add("stahp"); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - @Test - public void greaterThanOrEqualToString_When_NullKey_Then_ReturnStringWithDefaults() { - MatcherGreaterThanOrEqual testMatcher = new MatcherGreaterThanOrEqual(); - testMatcher.values = new ArrayList(); - testMatcher.values.add("down"); - testMatcher.values.add("hammer"); - testMatcher.values.add("stahp"); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - - - // ================================================================================ - // MatcherLessThan - // ================================================================================ - @Test - public void lessThanShouldMatchCorrectly() { - MatcherLessThan testMatcher = new MatcherLessThan(); - List values = new ArrayList(); - values.add(552); - assertTrue(runTestMatcher(testMatcher, 551, values)); - } - - @Test - public void lessThanShouldFailCorrectly() { - MatcherLessThan testMatcher = new MatcherLessThan(); - List values = new ArrayList(); - values.add(552); - assertFalse(runTestMatcher(testMatcher, 553, values)); - } - - @Test - public void lessThanShouldNotMatchEquals() { - MatcherLessThan testMatcher = new MatcherLessThan(); - List values = new ArrayList(); - values.add(552); - assertFalse(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void lessThanShouldMatchMultiplesCorrectly() { - MatcherLessThan testMatcher = new MatcherLessThan(); - List values = new ArrayList(); - values.add(549); - values.add(550); - values.add(551); - values.add(552); - values.add(554); - assertTrue(runTestMatcher(testMatcher, 553, values)); - } - - @Test - public void lessThanShouldFailMultiplesCorrectly() { - MatcherLessThan testMatcher = new MatcherLessThan(); - List values = new ArrayList(); - values.add(549); - values.add(550); - values.add(551); - values.add(552); - values.add(553); - assertFalse(runTestMatcher(testMatcher, 553, values)); - } - - @Test - public void lessThanShouldMatchPrecisionCorrectly() { - MatcherLessThan testMatcher = new MatcherLessThan(); - List values = new ArrayList(); - values.add(552.552); - assertTrue(runTestMatcher(testMatcher, 552.551, values)); - } - - @Test - public void lessThanShouldFailPrecisionCorrectly() { - MatcherLessThan testMatcher = new MatcherLessThan(); - List values = new ArrayList(); - values.add(552.552); - assertFalse(runTestMatcher(testMatcher, 552.553, values)); - } - - @Test - public void lessThanShouldNotMatchPrecisionEquals() { - MatcherLessThan testMatcher = new MatcherLessThan(); - List values = new ArrayList(); - values.add(552.552); - assertFalse(runTestMatcher(testMatcher, 552.552, values)); - } - - @Test - public void lessThanShouldMatchPrecisionMultiplesCorrectly() { - MatcherLessThan testMatcher = new MatcherLessThan(); - List values = new ArrayList(); - values.add(552.549); - values.add(552.550); - values.add(552.551); - values.add(552.552); - values.add(552.553); - assertTrue(runTestMatcher(testMatcher, 552.552, values)); - } - - @Test - public void lessThanShouldFailPrecisionMultiplesCorrectly() { - MatcherLessThan testMatcher = new MatcherLessThan(); - List values = new ArrayList(); - values.add(552.547); - values.add(552.548); - values.add(552.549); - values.add(552.550); - values.add(552.551); - assertFalse(runTestMatcher(testMatcher, 552.552, values)); - } - - @Test - public void lessThanShouldConvertStringsWhenPossibleMatch() { - MatcherLessThan testMatcher = new MatcherLessThan(); - List values = new ArrayList(); - values.add(552); - assertTrue(runTestMatcher(testMatcher, "551", values)); - } - - @Test - public void lessThanShouldConvertStringsWhenPossibleFail() { - MatcherLessThan testMatcher = new MatcherLessThan(); - List values = new ArrayList(); - values.add(552); - assertFalse(runTestMatcher(testMatcher, "553", values)); - } - - @Test - public void lessThanShouldFailGracefulllyWithStringArray() { - MatcherLessThan testMatcher = new MatcherLessThan(); - List values = new ArrayList(); - values.add("iamastring"); - assertFalse(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void lessThanShouldFailGracefulllyWithStringInput() { - MatcherLessThan testMatcher = new MatcherLessThan(); - List values = new ArrayList(); - values.add(552); - assertFalse(runTestMatcher(testMatcher, "iamastring", values)); - } - - @Test - public void lessThanToString_When_ValidData_Then_ReturnStringValue() { - MatcherLessThan testMatcher = new MatcherLessThan(); - testMatcher.key = "key"; - testMatcher.values = new ArrayList(); - testMatcher.values.add("down"); - testMatcher.values.add("hammer"); - testMatcher.values.add("stahp"); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - @Test - public void lessThanToString_When_NullKey_Then_ReturnStringWithDefaults() { - MatcherLessThan testMatcher = new MatcherLessThan(); - testMatcher.values = new ArrayList(); - testMatcher.values.add("down"); - testMatcher.values.add("hammer"); - testMatcher.values.add("stahp"); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - - // ================================================================================ - // MatcherLessThanOrEqual - // ================================================================================ - @Test - public void lessThanEqShouldMatchCorrectly() { - MatcherLessThanOrEqual testMatcher = new MatcherLessThanOrEqual(); - List values = new ArrayList(); - values.add(552); - assertTrue(runTestMatcher(testMatcher, 551, values)); - } - - @Test - public void lessThanEqShouldFailCorrectly() { - MatcherLessThanOrEqual testMatcher = new MatcherLessThanOrEqual(); - List values = new ArrayList(); - values.add(552); - assertFalse(runTestMatcher(testMatcher, 553, values)); - } - - @Test - public void lessThanEqShouldMatchEquals() { - MatcherLessThanOrEqual testMatcher = new MatcherLessThanOrEqual(); - List values = new ArrayList(); - values.add(552); - assertTrue(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void lessThanEqShouldMatchMultiplesCorrectly() { - MatcherLessThanOrEqual testMatcher = new MatcherLessThanOrEqual(); - List values = new ArrayList(); - values.add(549); - values.add(550); - values.add(551); - values.add(552); - values.add(554); - assertTrue(runTestMatcher(testMatcher, 553, values)); - } - - @Test - public void lessThanEqShouldFailMultiplesCorrectly() { - MatcherLessThanOrEqual testMatcher = new MatcherLessThanOrEqual(); - List values = new ArrayList(); - values.add(549); - values.add(550); - values.add(551); - values.add(552); - assertFalse(runTestMatcher(testMatcher, 553, values)); - } - - @Test - public void lessThanEqShouldMatchPrecisionCorrectly() { - MatcherLessThanOrEqual testMatcher = new MatcherLessThanOrEqual(); - List values = new ArrayList(); - values.add(552.552); - assertTrue(runTestMatcher(testMatcher, 552.551, values)); - } - - @Test - public void lessThanEqShouldFailPrecisionCorrectly() { - MatcherLessThanOrEqual testMatcher = new MatcherLessThanOrEqual(); - List values = new ArrayList(); - values.add(552.552); - assertFalse(runTestMatcher(testMatcher, 552.553, values)); - } - - @Test - public void lessThanEqShouldMatchPrecisionEquals() { - MatcherLessThanOrEqual testMatcher = new MatcherLessThanOrEqual(); - List values = new ArrayList(); - values.add(552.552); - assertTrue(runTestMatcher(testMatcher, 552.552, values)); - } - - @Test - public void lessThanEqShouldMatchPrecisionMultiplesCorrectly() { - MatcherLessThanOrEqual testMatcher = new MatcherLessThanOrEqual(); - List values = new ArrayList(); - values.add(552.553); - values.add(552.554); - values.add(552.555); - values.add(552.556); - values.add(552.551); - assertTrue(runTestMatcher(testMatcher, 552.552, values)); - } - - @Test - public void lessThanEqShouldFailPrecisionMultiplesCorrectly() { - MatcherLessThanOrEqual testMatcher = new MatcherLessThanOrEqual(); - List values = new ArrayList(); - values.add(552.547); - values.add(552.548); - values.add(552.549); - values.add(552.550); - values.add(552.551); - assertFalse(runTestMatcher(testMatcher, 552.552, values)); - } - - @Test - public void lessThanEqShouldConvertStringsWhenPossibleMatch() { - MatcherLessThanOrEqual testMatcher = new MatcherLessThanOrEqual(); - List values = new ArrayList(); - values.add(552); - assertTrue(runTestMatcher(testMatcher, "551", values)); - } - - @Test - public void lessThanEqShouldConvertStringsWhenPossibleFail() { - MatcherLessThanOrEqual testMatcher = new MatcherLessThanOrEqual(); - List values = new ArrayList(); - values.add(552); - assertFalse(runTestMatcher(testMatcher, "553", values)); - } - - @Test - public void lessThanEqShouldFailGracefulllyWithStringArray() { - MatcherLessThanOrEqual testMatcher = new MatcherLessThanOrEqual(); - List values = new ArrayList(); - values.add("iamastring"); - assertFalse(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void lessThanEqShouldFailGracefulllyWithStringInput() { - MatcherLessThanOrEqual testMatcher = new MatcherLessThanOrEqual(); - List values = new ArrayList(); - values.add(552); - assertFalse(runTestMatcher(testMatcher, "iamastring", values)); - } - - @Test - public void lessThanOrEqualToString_When_ValidData_Then_ReturnStringValue() { - MatcherLessThanOrEqual testMatcher = new MatcherLessThanOrEqual(); - testMatcher.key = "key"; - testMatcher.values = new ArrayList(); - testMatcher.values.add("down"); - testMatcher.values.add("hammer"); - testMatcher.values.add("stahp"); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - @Test - public void lessThanOrEqualToString_When_NullKey_Then_ReturnStringWithDefaults() { - MatcherLessThanOrEqual testMatcher = new MatcherLessThanOrEqual(); - testMatcher.values = new ArrayList(); - testMatcher.values.add("down"); - testMatcher.values.add("hammer"); - testMatcher.values.add("stahp"); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - - // ================================================================================ - // MatcherContains - // ================================================================================ - @Test - public void containsShouldMatchExact() { - MatcherContains testMatcher = new MatcherContains(); - List values = new ArrayList(); - values.add("matchme"); - assertTrue(runTestMatcher(testMatcher, "matchme", values)); - } - - @Test - public void containsShouldMatchPartial() { - MatcherContains testMatcher = new MatcherContains(); - List values = new ArrayList(); - values.add("match"); - assertTrue(runTestMatcher(testMatcher, "matchme", values)); - } - - @Test - public void containsShouldNotMatchCorrectly() { - MatcherContains testMatcher = new MatcherContains(); - List values = new ArrayList(); - values.add("matchme"); - assertFalse(runTestMatcher(testMatcher, "nope", values)); - } - - @Test - public void containsShouldMatchInPhrase() { - MatcherContains testMatcher = new MatcherContains(); - List values = new ArrayList(); - values.add("diamond"); - assertTrue(runTestMatcher(testMatcher, "sometimes when hunter dies, he loses his diamond gear and gets angry", values)); - } - - @Test - public void containsShouldNotMatchCorrectlyInPhrase() { - MatcherContains testMatcher = new MatcherContains(); - List values = new ArrayList(); - values.add("diamond"); - assertFalse(runTestMatcher(testMatcher, "sometimes when hunter dies, he loses his gear and gets angry", values)); - } - - @Test - public void containsShouldMatchMultiplesInPhrase() { - MatcherContains testMatcher = new MatcherContains(); - List values = new ArrayList(); - values.add("diamond"); - values.add("honey"); - values.add("sto"); - assertTrue(runTestMatcher(testMatcher, "stop, get down, hammer time", values)); - } - - @Test - public void containsShouldNotMatchCorrectlyMultiplesInPhrase() { - MatcherContains testMatcher = new MatcherContains(); - List values = new ArrayList(); - values.add("diamond"); - values.add("honey"); - values.add("stahp"); - assertFalse(runTestMatcher(testMatcher, "stop, get down, hammer time", values)); - } - - @Test - public void containsShouldMatchExactCaseInsensitive() { - MatcherContains testMatcher = new MatcherContains(); - List values = new ArrayList(); - values.add("matchme"); - assertTrue(runTestMatcher(testMatcher, "maTchMe", values)); - values.clear(); - values.add("maTchMe"); - assertTrue(runTestMatcher(testMatcher, "matchme", values)); - } - - @Test - public void containsShouldMatchPartialCaseInsensitive() { - MatcherContains testMatcher = new MatcherContains(); - List values = new ArrayList(); - values.add("match"); - assertTrue(runTestMatcher(testMatcher, "MaTChme", values)); - values.clear(); - values.add("mAtCh"); - assertTrue(runTestMatcher(testMatcher, "matchme", values)); - } - - @Test - public void containsShouldNotMatchCorrectlyCaseInsensitive() { - MatcherContains testMatcher = new MatcherContains(); - List values = new ArrayList(); - values.add("maTcH"); - assertFalse(runTestMatcher(testMatcher, "sTilL NopE", values)); - } - - @Test - public void containsShouldMatchUnicode() { - MatcherContains testMatcher = new MatcherContains(); - List values = new ArrayList(); - values.add("\\u0041"); - assertTrue(runTestMatcher(testMatcher, "there is a string called \\u0041 in this string", values)); - } - - @Test - public void containsShouldHandleNonStrings() { - MatcherContains testMatcher = new MatcherContains(); - List values = new ArrayList(); - values.add("allofthethings"); - assertFalse(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void containsShouldMatchStringsIfTheyAreNumeric() { - MatcherContains testMatcher = new MatcherContains(); - List values = new ArrayList(); - values.add("552"); - assertTrue(runTestMatcher(testMatcher, 38552834, values)); - } - - @Test - public void containsToString_When_ValidData_Then_ReturnStringValue() { - MatcherContains testMatcher = new MatcherContains(); - testMatcher.key = "key"; - testMatcher.values = new ArrayList(); - testMatcher.values.add("down"); - testMatcher.values.add("hammer"); - testMatcher.values.add("stahp"); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - @Test - public void containsToString_When_NullKey_Then_ReturnStringWithDefaults() { - MatcherContains testMatcher = new MatcherContains(); - testMatcher.values = new ArrayList(); - testMatcher.values.add("down"); - testMatcher.values.add("hammer"); - testMatcher.values.add("stahp"); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - - // ================================================================================ - // MatcherNotContains - // ================================================================================ - @Test - public void notContainsShouldMatchCorrectly() { - MatcherNotContains testMatcher = new MatcherNotContains(); - List values = new ArrayList(); - values.add("matchme"); - assertTrue(runTestMatcher(testMatcher, "nope", values)); - } - - @Test - public void notContainsShouldNotMatchExact() { - MatcherNotContains testMatcher = new MatcherNotContains(); - List values = new ArrayList(); - values.add("matchme"); - assertFalse(runTestMatcher(testMatcher, "matchme", values)); - } - - @Test - public void notContainsShouldNotMatchPartial() { - MatcherNotContains testMatcher = new MatcherNotContains(); - List values = new ArrayList(); - values.add("match"); - assertFalse(runTestMatcher(testMatcher, "matchme", values)); - } - - @Test - public void notContainsShouldNotMatchInPhrase() { - MatcherNotContains testMatcher = new MatcherNotContains(); - List values = new ArrayList(); - values.add("diamond"); - assertFalse(runTestMatcher(testMatcher, "sometimes when hunter dies, he loses his diamond gear and gets angry", - values)); - } - - @Test - public void notContainsShouldMatchCorrectlyInPhrase() { - MatcherNotContains testMatcher = new MatcherNotContains(); - List values = new ArrayList(); - values.add("diamond"); - assertTrue(runTestMatcher(testMatcher, "sometimes when hunter dies, he loses his gear and gets angry", values)); - } - - @Test - public void notContainsShouldNotMatchMultiplesInPhrase() { - MatcherNotContains testMatcher = new MatcherNotContains(); - List values = new ArrayList(); - values.add("diamond"); - values.add("honey"); - values.add("sto"); - assertFalse(runTestMatcher(testMatcher, "stop, get down, hammer time", values)); - } - - @Test - public void notContainsShouldMatchCorrectlyMultiplesInPhrase() { - MatcherNotContains testMatcher = new MatcherNotContains(); - List values = new ArrayList(); - values.add("diamond"); - values.add("honey"); - values.add("stahp"); - assertTrue(runTestMatcher(testMatcher, "stop, get down, hammer time", values)); - } - - @Test - public void notContainsShouldNotMatchExactCaseInsensitive() { - MatcherNotContains testMatcher = new MatcherNotContains(); - List values = new ArrayList(); - values.add("matchme"); - assertFalse(runTestMatcher(testMatcher, "maTchMe", values)); - values.clear(); - values.add("maTchMe"); - assertFalse(runTestMatcher(testMatcher, "matchme", values)); - } - - @Test - public void notContainsShouldNotMatchPartialCaseInsensitive() { - MatcherNotContains testMatcher = new MatcherNotContains(); - List values = new ArrayList(); - values.add("match"); - assertFalse(runTestMatcher(testMatcher, "MaTChme", values)); - values.clear(); - values.add("mAtCh"); - assertFalse(runTestMatcher(testMatcher, "matchme", values)); - } - - @Test - public void notContainsShouldMatchCorrectlyCaseInsensitive() { - MatcherNotContains testMatcher = new MatcherNotContains(); - List values = new ArrayList(); - values.add("maTcH"); - assertTrue(runTestMatcher(testMatcher, "sTilL NopE", values)); - } - - @Test - public void notContainsShouldNotMatchUnicode() { - MatcherNotContains testMatcher = new MatcherNotContains(); - List values = new ArrayList(); - values.add("\u2755"); - assertFalse(runTestMatcher(testMatcher, "there is a string called \u2755 in this string", values)); - } - - @Test - public void notContainsShouldHandleNonStrings() { - MatcherNotContains testMatcher = new MatcherNotContains(); - List values = new ArrayList(); - values.add("allofthethings"); - assertTrue(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void notContainsShouldNotMatchStringsIfTheyAreNumeric() { - MatcherNotContains testMatcher = new MatcherNotContains(); - List values = new ArrayList(); - values.add("552"); - assertFalse(runTestMatcher(testMatcher, 38552834, values)); - } - - @Test - public void notContainsShouldMatchCorrectlyStringsIfTheyAreNumeric() { - MatcherNotContains testMatcher = new MatcherNotContains(); - List values = new ArrayList(); - values.add("552"); - assertTrue(runTestMatcher(testMatcher, 3852834, values)); - } - - @Test - public void notContainsToString_When_ValidData_Then_ReturnStringValue() { - MatcherNotContains testMatcher = new MatcherNotContains(); - testMatcher.key = "key"; - testMatcher.values = new ArrayList(); - testMatcher.values.add("down"); - testMatcher.values.add("hammer"); - testMatcher.values.add("stahp"); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - @Test - public void notContainsToString_When_NullKey_Then_ReturnStringWithDefaults() { - MatcherNotContains testMatcher = new MatcherNotContains(); - testMatcher.values = new ArrayList(); - testMatcher.values.add("down"); - testMatcher.values.add("hammer"); - testMatcher.values.add("stahp"); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - - // ================================================================================ - // MatcherStartsWith - // ================================================================================ - @Test - public void startsWithShouldMatchExact() { - MatcherStartsWith testMatcher = new MatcherStartsWith(); - List values = new ArrayList(); - values.add("matchme"); - assertTrue(runTestMatcher(testMatcher, "matchme", values)); - } - - @Test - public void startsWithShouldMatchPartialBeginning() { - MatcherStartsWith testMatcher = new MatcherStartsWith(); - List values = new ArrayList(); - values.add("match"); - assertTrue(runTestMatcher(testMatcher, "matchme", values)); - } - - @Test - public void startsWithShouldNotMatchCorrectly() { - MatcherStartsWith testMatcher = new MatcherStartsWith(); - List values = new ArrayList(); - values.add("me"); - assertFalse(runTestMatcher(testMatcher, "matchme", values)); - } - - @Test - public void startsWithShouldMatchInPhrase() { - MatcherStartsWith testMatcher = new MatcherStartsWith(); - List values = new ArrayList(); - values.add("sometimes"); - assertTrue(runTestMatcher(testMatcher, "sometimes when hunter dies, he loses his diamond gear and gets angry", values)); - } - - @Test - public void startsWithShouldNotMatchCorrectlyInPhrase() { - MatcherStartsWith testMatcher = new MatcherStartsWith(); - List values = new ArrayList(); - values.add("diamond"); - assertFalse(runTestMatcher(testMatcher, "sometimes when hunter dies, he loses his diamond gear and gets angry", - values)); - } - - @Test - public void startsWithShouldMatchMultiplesInPhrase() { - MatcherStartsWith testMatcher = new MatcherStartsWith(); - List values = new ArrayList(); - values.add("diamond"); - values.add("honey"); - values.add("sto"); - assertTrue(runTestMatcher(testMatcher, "stop, get down, hammer time", values)); - } - - @Test - public void startsWithShouldNotMatchCorrectlyMultiplesInPhrase() { - MatcherStartsWith testMatcher = new MatcherStartsWith(); - List values = new ArrayList(); - values.add("down"); - values.add("hammer"); - values.add("stahp"); - assertFalse(runTestMatcher(testMatcher, "stop, get down, hammer time", values)); - } - - @Test - public void startsWithToString_When_ValidData_Then_ReturnStringValue() { - MatcherStartsWith testMatcher = new MatcherStartsWith(); - testMatcher.key = "key"; - testMatcher.values = new ArrayList(); - testMatcher.values.add("down"); - testMatcher.values.add("hammer"); - testMatcher.values.add("stahp"); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - @Test - public void startsWithToString_When_NullKey_Then_ReturnStringWithDefaults() { - MatcherStartsWith testMatcher = new MatcherStartsWith(); - testMatcher.values = new ArrayList(); - testMatcher.values.add("down"); - testMatcher.values.add("hammer"); - testMatcher.values.add("stahp"); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - - - @Test - public void startsWithShouldMatchExactCaseInsensitive() { - MatcherStartsWith testMatcher = new MatcherStartsWith(); - List values = new ArrayList(); - values.add("matchme"); - assertTrue(runTestMatcher(testMatcher, "maTchMe", values)); - values.clear(); - values.add("maTchMe"); - assertTrue(runTestMatcher(testMatcher, "matchme", values)); - } - - @Test - public void startsWithShouldMatchPartialCaseInsensitive() { - MatcherStartsWith testMatcher = new MatcherStartsWith(); - List values = new ArrayList(); - values.add("match"); - assertTrue(runTestMatcher(testMatcher, "MaTChme", values)); - values.clear(); - values.add("mAtCh"); - assertTrue(runTestMatcher(testMatcher, "matchme", values)); - } - - @Test - public void startsWithShouldNotMatchCorrectlyCaseInsensitive() { - MatcherStartsWith testMatcher = new MatcherStartsWith(); - List values = new ArrayList(); - values.add("aTCH"); - assertFalse(runTestMatcher(testMatcher, "MaTCH", values)); - } - - @Test - public void startsWithShouldMatchUnicode() { - MatcherStartsWith testMatcher = new MatcherStartsWith(); - List values = new ArrayList(); - values.add("\\u0041"); - assertTrue(runTestMatcher(testMatcher, "\\u0041 in this string", values)); - } - - @Test - public void startsWithShouldHandleNonStrings() { - MatcherStartsWith testMatcher = new MatcherStartsWith(); - List values = new ArrayList(); - values.add("allofthethings"); - assertFalse(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void startsWithShouldMatchStringsIfTheyAreNumeric() { - MatcherStartsWith testMatcher = new MatcherStartsWith(); - List values = new ArrayList(); - values.add("552"); - assertTrue(runTestMatcher(testMatcher, 552834, values)); - } - - @Test - public void startsWithShouldFailStringsCorrectlyIfTheyAreNumeric() { - MatcherStartsWith testMatcher = new MatcherStartsWith(); - List values = new ArrayList(); - values.add("552"); - assertFalse(runTestMatcher(testMatcher, 1552834, values)); - } - - // ================================================================================ - // MatcherEndsWith - // ================================================================================ - @Test - public void endsWithShouldMatchExact() { - MatcherEndsWith testMatcher = new MatcherEndsWith(); - List values = new ArrayList(); - values.add("matchme"); - assertTrue(runTestMatcher(testMatcher, "matchme", values)); - } - - @Test - public void endsWithShouldMatchPartialEnd() { - MatcherEndsWith testMatcher = new MatcherEndsWith(); - List values = new ArrayList(); - values.add("me"); - assertTrue(runTestMatcher(testMatcher, "matchme", values)); - } - - @Test - public void endsWithShouldNotMatchCorrectly() { - MatcherEndsWith testMatcher = new MatcherEndsWith(); - List values = new ArrayList(); - values.add("match"); - assertFalse(runTestMatcher(testMatcher, "matchme", values)); - } - - @Test - public void endsWithShouldMatchInPhrase() { - MatcherEndsWith testMatcher = new MatcherEndsWith(); - List values = new ArrayList(); - values.add("angry"); - assertTrue(runTestMatcher(testMatcher, "sometimes when hunter dies, he loses his diamond gear and gets angry", values)); - } - - @Test - public void endsWithShouldNotMatchCorrectlyInPhrase() { - MatcherEndsWith testMatcher = new MatcherEndsWith(); - List values = new ArrayList(); - values.add("diamond"); - assertFalse(runTestMatcher(testMatcher, "sometimes when hunter dies, he loses his diamond gear and gets angry", - values)); - } - - @Test - public void endsWithShouldMatchMultiplesInPhrase() { - MatcherEndsWith testMatcher = new MatcherEndsWith(); - List values = new ArrayList(); - values.add("diamond"); - values.add("honey"); - values.add("me"); - assertTrue(runTestMatcher(testMatcher, "stop, get down, hammer time", values)); - } - - @Test - public void endsWithShouldNotMatchCorrectlyMultiplesInPhrase() { - MatcherEndsWith testMatcher = new MatcherEndsWith(); - List values = new ArrayList(); - values.add("diamond"); - values.add("hammer"); - values.add("tim"); - assertFalse(runTestMatcher(testMatcher, "stop, get down, hammer time", values)); - } - - @Test - public void endsWithShouldMatchExactCaseInsensitive() { - MatcherEndsWith testMatcher = new MatcherEndsWith(); - List values = new ArrayList(); - values.add("matchme"); - assertTrue(runTestMatcher(testMatcher, "maTchMe", values)); - values.clear(); - values.add("maTchMe"); - assertTrue(runTestMatcher(testMatcher, "matchme", values)); - } - - @Test - public void endsWithShouldMatchPartialCaseInsensitive() { - MatcherEndsWith testMatcher = new MatcherEndsWith(); - List values = new ArrayList(); - values.add("me"); - assertTrue(runTestMatcher(testMatcher, "MaTChmE", values)); - values.clear(); - values.add("mE"); - assertTrue(runTestMatcher(testMatcher, "matchme", values)); - } - - @Test - public void endsWithShouldNotMatchCorrectlyCaseInsensitive() { - MatcherEndsWith testMatcher = new MatcherEndsWith(); - List values = new ArrayList(); - values.add("MaTc"); - assertFalse(runTestMatcher(testMatcher, "MaTCH", values)); - } - - @Test - public void endsWithShouldMatchUnicode() { - MatcherEndsWith testMatcher = new MatcherEndsWith(); - List values = new ArrayList(); - values.add("\u0041"); - assertTrue(runTestMatcher(testMatcher, "in this string \u0041", values)); - } - - @Test - public void endsWithShouldHandleNonStrings() { - MatcherEndsWith testMatcher = new MatcherEndsWith(); - List values = new ArrayList(); - values.add("allofthethings"); - assertFalse(runTestMatcher(testMatcher, 552, values)); - } - - @Test - public void endsWithShouldMatchStringsIfTheyAreNumeric() { - MatcherEndsWith testMatcher = new MatcherEndsWith(); - List values = new ArrayList(); - values.add("552"); - assertTrue(runTestMatcher(testMatcher, 834552, values)); - } - - @Test - public void endsWithShouldFailStringsCorrectlyIfTheyAreNumeric() { - MatcherEndsWith testMatcher = new MatcherEndsWith(); - List values = new ArrayList(); - values.add("552"); - assertFalse(runTestMatcher(testMatcher, 1552834, values)); - } - - @Test - public void endsWithToString_When_ValidData_Then_ReturnStringValue() { - MatcherEndsWith testMatcher = new MatcherEndsWith(); - testMatcher.key = "key"; - testMatcher.values = new ArrayList(); - testMatcher.values.add("down"); - testMatcher.values.add("hammer"); - testMatcher.values.add("stahp"); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - @Test - public void endsWithToString_When_NullKey_Then_ReturnStringWithDefaults() { - MatcherEndsWith testMatcher = new MatcherEndsWith(); - testMatcher.values = new ArrayList(); - testMatcher.values.add("down"); - testMatcher.values.add("hammer"); - testMatcher.values.add("stahp"); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - // ================================================================================ - // MatcherExists - // ================================================================================ - @Test - public void existsShouldMatchIfKeyIsPresent() { - MatcherExists matcher = new MatcherExists(); - matcher.key = "testKey"; - - EventData testEventData = new EventData(); - testEventData.putString("testKey", "smoked hampster ribs"); - Event testEvent = new Event.Builder("test", EventType.ANALYTICS, - EventSource.RESPONSE_CONTENT).setData(testEventData).build(); - - assertTrue(matcher.matches(ruleTokenParser.expandKey(matcher.key, testEvent))); - } - - @Test - public void existsShouldNotMatchIfKeyIsNotPresent() { - MatcherExists matcher = new MatcherExists(); - matcher.key = "testKey"; - - EventData testEventData = new EventData(); - testEventData.putString("nottestKey", "smoked hampster ribs"); - Event testEvent = new Event.Builder("test", EventType.ANALYTICS, - EventSource.RESPONSE_CONTENT).setData(testEventData).build(); - - assertFalse(matcher.matches(ruleTokenParser.expandKey(matcher.key, testEvent))); - } - - @Test - public void existsToString_When_ValidData_Then_ReturnStringValue() { - MatcherExists testMatcher = new MatcherExists(); - testMatcher.key = "key"; - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - @Test - public void existsToString_When_NullKey_Then_ReturnStringWithDefaults() { - MatcherExists testMatcher = new MatcherExists(); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - - // ================================================================================ - // MatcherNotExists - // ================================================================================ - @Test - public void notExistsShouldMatchIfKeyIsNotPresent() { - MatcherNotExists matcher = new MatcherNotExists(); - matcher.key = "testKey"; - - EventData testEventData = new EventData(); - testEventData.putString("nottestKey", "smoked hampster ribs"); - Event testEvent = new Event.Builder("test", EventType.ANALYTICS, - EventSource.RESPONSE_CONTENT).setData(testEventData).build(); - - assertTrue(matcher.matches(ruleTokenParser.expandKey(matcher.key, testEvent))); - } - - @Test - public void notExistsShouldNotMatchIfKeyIsPresent() { - MatcherNotExists matcher = new MatcherNotExists(); - matcher.key = "testKey"; - - EventData testEventData = new EventData(); - testEventData.putString("testKey", "smoked hampster ribs"); - Event testEvent = new Event.Builder("test", EventType.ANALYTICS, - EventSource.RESPONSE_CONTENT).setData(testEventData).build(); - - assertFalse(matcher.matches(ruleTokenParser.expandKey(matcher.key, testEvent))); - } - - @Test - public void notExistsToString_When_ValidData_Then_ReturnStringValue() { - MatcherNotExists testMatcher = new MatcherNotExists(); - testMatcher.key = "key"; - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - @Test - public void notExistsToString_When_NullKey_Then_ReturnStringWithDefaults() { - MatcherNotExists testMatcher = new MatcherNotExists(); - String expected = generateToStringExpected(testMatcher, testMatcher.key, testMatcher.values); - assertEquals(expected, testMatcher.toString()); - } - - // ================================================================================ - // MatcherUnknown - // ================================================================================ - @Test - public void unknownShouldAlwaysReturnFalse() { - MatcherUnknown testMatcher = new MatcherUnknown(); - List values = new ArrayList(); - values.add("we match, but still false"); - assertFalse(runTestMatcher(testMatcher, "we match, but still false", values)); - values.clear(); - values.add("we no longer match...still false"); - assertFalse(runTestMatcher(testMatcher, "what he said", values)); - } - - @Test - public void setMatcherValuesFromJson_When_MatcherIsNull_DoNothing() throws JsonException { - //Test - Matcher.setMatcherValuesFromJson(platformServices.getJsonUtilityService().createJSONObject("{}"), null); - //Verify - //Should not have a NPE / Exception - - } - - @Test - public void setMatcherValuesFromJson_When_ValuesArrayIsEmpty_Then_DoNotChangeExistingMatcherValues() throws Exception { - //Setup - Map testData = new HashMap(); - testData.put("key", "blah"); - testData.put("matcher", "eq"); - testData.put("values", new ArrayList()); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - - Matcher matcher = new Matcher() { - @Override - public String toString() { - return "Empty Matcher"; - } - }; - - matcher.values = new ArrayList(); - matcher.values.add("value"); - //Test - Matcher.setMatcherValuesFromJson(jsonObject, matcher); - //Verify - assertEquals("Values should not change!", 1, matcher.values.size()); - assertEquals("value", matcher.values.get(0)); - - } - - @Test - public void setMatcherValuesFromJson_When_ValuesArrayIsEmptyAndMatcherValuesIsEmpty_Then_MatcherValuesRemainsEmpty() - throws Exception { - //Setup - Map testData = new HashMap(); - testData.put("key", "blah"); - testData.put("matcher", "eq"); - testData.put("values", new ArrayList()); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - - Matcher matcher = new Matcher() { - @Override - public String toString() { - return "Empty Matcher"; - } - }; - - matcher.values = new ArrayList(); - //Test - Matcher.setMatcherValuesFromJson(jsonObject, matcher); - //Verify - assertEquals("Values should not change!", 0, matcher.values.size()); - } - - @Test - public void setMatcherKeyFromJson_When_MatcherIsNull_DoNothing() { - //Test - Matcher.setMatcherKeyFromJson(platformServices.getJsonUtilityService().createJSONObject("{}"), null); - //Verify - //Should not have a NPE / Exception - } - - @Test - public void setMatcherKeyFromJson_When_KeyIsEmpty_Then_DoNotChangeExistingMatcherValues() { - //Setup - Map testData = new HashMap(); - testData.put("key", ""); - testData.put("matcher", "eq"); - ArrayList values = new ArrayList(); - values.add("blah"); - testData.put("values", values); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - - Matcher matcher = new Matcher() { - @Override - public String toString() { - return "Empty Matcher"; - } - }; - - matcher.values = new ArrayList(); - matcher.values.add("value"); - matcher.key = "key"; - //Test - Matcher.setMatcherKeyFromJson(jsonObject, matcher); - //Verify - assertEquals("Values should not change!", 1, matcher.values.size()); - assertEquals("value", matcher.values.get(0)); - assertEquals("key", matcher.key); - - } - - - @Test - public void matcherInMaps_When_MapsIsNull_Then_ReturnsFalse() { - //setup - Map testData = new HashMap(); - testData.put("key", "blah"); - testData.put("matcher", "eq"); - List valuesList = new ArrayList(); - valuesList.add("testValue1"); - testData.put("values", valuesList); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //Test and verify - Event testEvent = new Event.Builder("test", EventType.ANALYTICS, - EventSource.RESPONSE_CONTENT).build(); - - assertFalse(matcher.matches(ruleTokenParser.expandKey(matcher.key, testEvent))); - } - - @Test - public void constructor_When_ErrorWhileInstantiatingMatcherClass_Then_ReturnNull() { - //setup - Map testData = new HashMap(); - testData.put("key", "blah"); - //Invalid matcher!! - testData.put("matcher", "zz"); - List valuesList = new ArrayList(); - valuesList.add("testValue1"); - testData.put("values", valuesList); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - //test - Matcher._matcherTypeDictionary.put("zz", InAccessibleMatcher.class); - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //assert - assertNull(matcher); - } - - @Test - public void constructor_When_ErrorWhileInstantiatingMatcherClass_Then_ReturnNull1() { - //setup - Map testData = new HashMap(); - testData.put("key", "blah"); - //Empty matcher string!! - testData.put("matcher", ""); - List valuesList = new ArrayList(); - valuesList.add("testValue1"); - testData.put("values", valuesList); - JsonUtilityService.JSONObject jsonObject = jsonUtilityService.createJSONObject(new JSONObject(testData).toString()); - //test - Matcher matcher = Matcher.matcherWithJsonObject(jsonObject); - //assert - assertTrue(matcher instanceof MatcherUnknown); - } -} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleConditionAndGroupTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleConditionAndGroupTests.java deleted file mode 100644 index f128e6a9d..000000000 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleConditionAndGroupTests.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import org.junit.Before; -import org.junit.Test; - -import java.util.ArrayList; - -import static junit.framework.TestCase.assertFalse; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class RuleConditionAndGroupTests { - - private PlatformServices platformServices; - private RuleTokenParser ruleTokenParser; - - @Before - public void beforeEachTest() { - platformServices = new FakePlatformServices(); - final EventHub testEventHub = new EventHub("eventhub", platformServices); - ruleTokenParser = new RuleTokenParser(testEventHub); - } - - @Test - public void evaluate_Success_When_ValidTestDataUsed() throws Exception { - //setup - JsonUtilityService.JSONObject testRuleConditionGroupJson = platformServices.getJsonUtilityService() - .createJSONObject("{\"logic\":\"and\",\"conditions\":[{\"type\":\"matcher\",\"definition\":{\"key\":\"key\",\"matcher\":\"gt\",\"values\":[0]}},{\"type\":\"matcher\",\"definition\":{\"key\":\"key1\",\"matcher\":\"eq\",\"values\":[\"value1\"]}}]}"); - RuleConditionGroup testRuleConditionAndGroup = RuleConditionGroup.ruleConditionGroupFromJson( - testRuleConditionGroupJson); - - EventData testEventData = new EventData(); - testEventData.putString("key", "1"); - testEventData.putString("key1", "value1"); - Event testEvent = new Event.Builder("test", EventType.ANALYTICS, - EventSource.RESPONSE_CONTENT).setData(testEventData).build(); - - //test - boolean result = testRuleConditionAndGroup.evaluate(ruleTokenParser, testEvent); - //verify - assertTrue(result); - } - - @Test - public void evaluate_Fail_When_EmptyTestDataUsed() throws Exception { - //setup - JsonUtilityService.JSONObject testRuleConditionGroupJson = platformServices.getJsonUtilityService() - .createJSONObject("{\"logic\":\"and\",\"conditions\":[{\"type\":\"matcher\",\"definition\":{\"key\":\"latitude\",\"matcher\":\"gt\",\"values\":[0]}},{\"type\":\"matcher\",\"definition\":{\"key\":\"longitude\",\"matcher\":\"lt\",\"values\":[0]}}]}"); - RuleConditionGroup testRuleConditionAndGroup = RuleConditionGroup.ruleConditionGroupFromJson( - testRuleConditionGroupJson); - - EventData testEventData = new EventData(); - Event testEvent = new Event.Builder("test", EventType.ANALYTICS, - EventSource.RESPONSE_CONTENT).setData(testEventData).build(); - //test - boolean result = testRuleConditionAndGroup.evaluate(ruleTokenParser, testEvent); - //verify - assertFalse(result); - } - - @Test - public void evaluate_Fail_When_NullTestDataUsed() throws Exception { - //setup - JsonUtilityService.JSONObject testRuleConditionGroupJson = platformServices.getJsonUtilityService() - .createJSONObject("{\"logic\":\"and\",\"conditions\":[{\"type\":\"matcher\",\"definition\":{\"key\":\"latitude\",\"matcher\":\"gt\",\"values\":[0]}},{\"type\":\"matcher\",\"definition\":{\"key\":\"longitude\",\"matcher\":\"lt\",\"values\":[0]}}]}"); - RuleConditionGroup testRuleConditionAndGroup = RuleConditionGroup.ruleConditionGroupFromJson( - testRuleConditionGroupJson); - - Event testEvent = new Event.Builder("test", EventType.ANALYTICS, - EventSource.RESPONSE_CONTENT).setData(null).build(); - - //test - boolean result = testRuleConditionAndGroup.evaluate(ruleTokenParser, testEvent); - //verify - assertFalse(result); - } - - @Test - public void evaluate_Fail_When_ConditionsAreNull() { - //setup - RuleConditionAndGroup testRuleConditionAndGroup = new RuleConditionAndGroup(null); - - EventData testEventData = new EventData(); - testEventData.putString("key", "1"); - testEventData.putString("key1", "value1"); - Event testEvent = new Event.Builder("test", EventType.ANALYTICS, - EventSource.RESPONSE_CONTENT).setData(testEventData).build(); - - //test - boolean result = testRuleConditionAndGroup.evaluate(ruleTokenParser, testEvent); - //verify - assertFalse(result); - } - - @Test - public void evaluate_Fail_When_ConditionsAreEmpty() { - //setup - RuleConditionAndGroup testRuleConditionAndGroup = new RuleConditionAndGroup(new ArrayList()); - - EventData testEventData = new EventData(); - testEventData.putString("key", "1"); - testEventData.putString("key1", "value1"); - Event testEvent = new Event.Builder("test", EventType.ANALYTICS, - EventSource.RESPONSE_CONTENT).setData(testEventData).build(); - - //test - boolean result = testRuleConditionAndGroup.evaluate(ruleTokenParser, testEvent); - //verify - assertFalse(result); - } - - @Test - public void toString_ShouldReturn_EmptyString_When_ConditionsAreEmpty() { - //setup - RuleConditionAndGroup testRuleConditionAndGroup = new RuleConditionAndGroup(new ArrayList()); - //test - - //verify - assertEquals("", testRuleConditionAndGroup.toString()); - } - - @Test - public void toString_ShouldReturn_EmptyString_When_ConditionsAreNull() { - //setup - RuleConditionAndGroup testRuleConditionAndGroup = new RuleConditionAndGroup(null); - //test - - //verify - assertEquals("", testRuleConditionAndGroup.toString()); - } - - @Test - public void toString_ShouldReturn_ValidString() { - //setup - ArrayList conditions = new ArrayList(); - MatcherGreaterThan gtMatcher = new MatcherGreaterThan(); - gtMatcher.key = "key"; - gtMatcher.values = new ArrayList(); - gtMatcher.values.add("value"); - - RuleConditionMatcher ruleConditionMatcherLHS = new RuleConditionMatcher(gtMatcher); - conditions.add(ruleConditionMatcherLHS); - RuleConditionMatcher ruleConditionMatcherRHS = new RuleConditionMatcher(gtMatcher); - conditions.add(ruleConditionMatcherRHS); - - RuleConditionAndGroup testRuleConditionAndGroup = new RuleConditionAndGroup(conditions); - - //Test and verify - assertEquals("(" + ruleConditionMatcherLHS.toString() + " AND " + ruleConditionMatcherRHS + ")" - , testRuleConditionAndGroup.toString()); - } -} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleConditionGroupTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleConditionGroupTests.java deleted file mode 100644 index f9d9e5a58..000000000 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleConditionGroupTests.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import org.junit.Before; -import org.junit.Test; - -import static junit.framework.TestCase.assertNotNull; -import static junit.framework.TestCase.assertNull; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class RuleConditionGroupTests { - - private PlatformServices platformServices; - - @Before - public void beforeEachTest() { - platformServices = new FakePlatformServices(); - } - - @Test - public void ruleConditionGroupFromJson_ReturnsValidRuleConditionAndGroupInstance_When_ValidJsonStringUsed() - throws Exception { - //setup - JsonUtilityService.JSONObject testRuleConditionGroupJson = platformServices.getJsonUtilityService() - .createJSONObject("{\"logic\":\"and\",\"conditions\":[{\"type\":\"matcher\",\"definition\":{\"key\":\"latitude\",\"matcher\":\"gt\",\"values\":[0]}},{\"type\":\"matcher\",\"definition\":{\"key\":\"longitude\",\"matcher\":\"lt\",\"values\":[0]}}]}"); - //test - RuleConditionGroup testRuleConditionGroup = RuleConditionGroup.ruleConditionGroupFromJson(testRuleConditionGroupJson); - //verify - assertTrue(testRuleConditionGroup instanceof RuleConditionAndGroup); - assertNotNull(testRuleConditionGroup.conditions); - assertEquals(2, testRuleConditionGroup.conditions.size()); - } - - @Test - public void ruleConditionGroupFromJson_Returns_ValidRuleConditionOrGroupInstance_When_ValidJsonStringUsed() - throws Exception { - //setup - JsonUtilityService.JSONObject testRuleConditionGroupJson = platformServices.getJsonUtilityService() - .createJSONObject("{\"logic\":\"or\",\"conditions\":[{\"type\":\"matcher\",\"definition\":{\"key\":\"latitude\",\"matcher\":\"gt\",\"values\":[0]}},{\"type\":\"matcher\",\"definition\":{\"key\":\"longitude\",\"matcher\":\"lt\",\"values\":[0]}}]}"); - //test - RuleConditionGroup testRuleConditionGroup = RuleConditionGroup.ruleConditionGroupFromJson(testRuleConditionGroupJson); - //verify - assertTrue(testRuleConditionGroup instanceof RuleConditionOrGroup); - assertNotNull(testRuleConditionGroup.conditions); - assertEquals(2, testRuleConditionGroup.conditions.size()); - } - - @Test - @SuppressWarnings("SameParameterValue") - public void ruleConditionGroupFromJson_ReturnsNull_When_NullJsonStringUsed() throws Exception { - assertNull(RuleConditionGroup.ruleConditionGroupFromJson(null)); - } - - @Test - public void ruleConditionGroupFromJson_ReturnsNull_When_EmptyJsonStringUsed() throws Exception { - //setup - JsonUtilityService.JSONObject testRuleConditionGroupJson = - platformServices.getJsonUtilityService().createJSONObject("{}"); - //test - RuleConditionGroup.ruleConditionGroupFromJson(testRuleConditionGroupJson); - } - - @Test - public void ruleConditionGroupFromJson_ReturnsNull_When_InvalidJsonStringUsed() throws Exception { - //setup - JsonUtilityService.JSONObject testRuleConditionGroupJson = platformServices.getJsonUtilityService() - .createJSONObject("{\"invalid_type\":\"group\",\"invalid_definition\":{\"logic\":\"and\",\"invalid_condition\":[{\"type\":\"matcher\",\"definition\":{\"key\":\"latitude\",\"matcher\":\"gt\",\"values\":[0]}},{\"type\":\"matcher\",\"definition\":{\"key\":\"longitude\",\"matcher\":\"lt\",\"values\":[0]}}]}}"); - //test - RuleConditionGroup.ruleConditionGroupFromJson(testRuleConditionGroupJson); - } -} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleConditionOrGroupTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleConditionOrGroupTests.java deleted file mode 100644 index aad5079c1..000000000 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleConditionOrGroupTests.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import org.junit.Before; -import org.junit.Test; - -import java.util.ArrayList; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -public class RuleConditionOrGroupTests { - - private PlatformServices platformServices; - private RuleTokenParser ruleTokenParser; - - @Before - public void beforeEachTest() { - platformServices = new FakePlatformServices(); - final EventHub testEventHub = new EventHub("eventhub", platformServices); - ruleTokenParser = new RuleTokenParser(testEventHub); - } - - - @Test - public void evaluate_Success_When_OneOfTheConditionsMatch() throws Exception { - //setup - JsonUtilityService.JSONObject testRuleConditionGroupJson = platformServices.getJsonUtilityService() - .createJSONObject("{\"logic\":\"or\",\"conditions\":[{\"type\":\"matcher\",\"definition\":{\"key\":\"key\",\"matcher\":\"gt\",\"values\":[0]}},{\"type\":\"matcher\",\"definition\":{\"key\":\"key1\",\"matcher\":\"eq\",\"values\":[\"something\"]}}]}"); - RuleConditionGroup testRuleConditionOrGroup = RuleConditionGroup.ruleConditionGroupFromJson(testRuleConditionGroupJson); - - EventData testEventData = new EventData(); - testEventData.putString("key", "1"); - testEventData.putString("key1", "value1"); - Event testEvent = new Event.Builder("test", EventType.ANALYTICS, - EventSource.RESPONSE_CONTENT).setData(testEventData).build(); - - //test - boolean result = testRuleConditionOrGroup.evaluate(ruleTokenParser, testEvent); - //verify - assertTrue(result); - } - - @Test - public void evaluate_Failure_When_DataEmpty() throws Exception { - //setup - JsonUtilityService.JSONObject testRuleConditionGroupJson = - platformServices.getJsonUtilityService().createJSONObject("{\"logic\":\"or\",\"conditions\":[{\"type\":\"matcher\",\"definition\":{\"key\":\"latitude\",\"matcher\":\"gt\",\"values\":[0]}},{\"type\":\"matcher\",\"definition\":{\"key\":\"longitude\",\"matcher\":\"lt\",\"values\":[0]}}]}"); - RuleConditionGroup testRuleConditionOrGroup = RuleConditionGroup.ruleConditionGroupFromJson(testRuleConditionGroupJson); - - EventData testEventData = new EventData(); - Event testEvent = new Event.Builder("test", EventType.ANALYTICS, - EventSource.RESPONSE_CONTENT).setData(testEventData).build(); - //test - boolean result = testRuleConditionOrGroup.evaluate(ruleTokenParser, testEvent); - //verify - assertFalse(result); - } - - @Test - public void evaluate_Failure_When_DataNull() throws Exception { - //setup - JsonUtilityService.JSONObject testRuleConditionGroupJson = - platformServices.getJsonUtilityService().createJSONObject("{\"logic\":\"or\",\"conditions\":[{\"type\":\"matcher\",\"definition\":{\"key\":\"latitude\",\"matcher\":\"gt\",\"values\":[0]}},{\"type\":\"matcher\",\"definition\":{\"key\":\"longitude\",\"matcher\":\"lt\",\"values\":[0]}}]}"); - RuleConditionGroup testRuleConditionOrGroup = RuleConditionGroup.ruleConditionGroupFromJson(testRuleConditionGroupJson); - - Event testEvent = new Event.Builder("test", EventType.ANALYTICS, - EventSource.RESPONSE_CONTENT).setData(null).build(); - - //test - boolean result = testRuleConditionOrGroup.evaluate(ruleTokenParser, testEvent); - //verify - assertFalse(result); - } - - @Test - public void evaluate_Failure_When_ConditionsNull() { - //setup - RuleConditionOrGroup testRuleConditionOrGroup = new RuleConditionOrGroup(null); - - EventData testEventData = new EventData(); - testEventData.putString("key", "1"); - testEventData.putString("key1", "value1"); - Event testEvent = new Event.Builder("test", EventType.ANALYTICS, - EventSource.RESPONSE_CONTENT).setData(testEventData).build(); - - //test - boolean result = testRuleConditionOrGroup.evaluate(ruleTokenParser, testEvent); - //verify - assertFalse(result); - } - - @Test - public void evaluate_Failure_When_ConditionEmpty() { - //setup - RuleConditionOrGroup testRuleConditionOrGroup = new RuleConditionOrGroup(new ArrayList()); - - EventData testEventData = new EventData(); - testEventData.putString("key", "1"); - testEventData.putString("key1", "value1"); - Event testEvent = new Event.Builder("test", EventType.ANALYTICS, - EventSource.RESPONSE_CONTENT).setData(testEventData).build(); - - //test - boolean result = testRuleConditionOrGroup.evaluate(ruleTokenParser, testEvent); - //verify - assertFalse(result); - } - - @Test - public void toString_ShouldReturn_EmptyString_When_ConditionsAreEmpty() { - //setup - RuleConditionOrGroup testRuleConditionAndGroup = new RuleConditionOrGroup(new ArrayList()); - //test and Verify - assertEquals("", testRuleConditionAndGroup.toString()); - } - - @Test - public void toString_ShouldReturn_EmptyString_When_ConditionsAreNull() { - //setup - RuleConditionOrGroup testRuleConditionAndGroup = new RuleConditionOrGroup(null); - //test and Verify - assertEquals("", testRuleConditionAndGroup.toString()); - } - - @Test - public void toString_ShouldReturn_ValidString() { - //setup - ArrayList conditions = new ArrayList(); - MatcherGreaterThan gtMatcher = new MatcherGreaterThan(); - gtMatcher.key = "key"; - gtMatcher.values = new ArrayList(); - gtMatcher.values.add("value"); - - RuleConditionMatcher ruleConditionMatcherLHS = new RuleConditionMatcher(gtMatcher); - conditions.add(ruleConditionMatcherLHS); - RuleConditionMatcher ruleConditionMatcherRHS = new RuleConditionMatcher(gtMatcher); - conditions.add(ruleConditionMatcherRHS); - - RuleConditionOrGroup testRuleConditionAndGroup = new RuleConditionOrGroup(conditions); - - //Test and verify - assertEquals("(" + ruleConditionMatcherLHS.toString() + " OR " + ruleConditionMatcherRHS + ")" - , testRuleConditionAndGroup.toString()); - } -} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleConditionTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleConditionTests.java deleted file mode 100644 index 4ac083eec..000000000 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleConditionTests.java +++ /dev/null @@ -1,515 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import org.junit.Before; -import org.junit.Test; - -import java.util.ArrayList; - -import static org.junit.Assert.*; - -public class RuleConditionTests { - - private PlatformServices platformServices; - private EventHub testEventHub; - - @Before - public void beforeEachTest() { - platformServices = new FakePlatformServices(); - testEventHub = new EventHub("eventhub", platformServices); - } - - - @Test - public void ruleConditionFromJson_ReturnsNull_When_NullJsonObjectProvided() throws Exception { - assertNull(RuleCondition.ruleConditionFromJson(null)); - } - - @Test - public void ruleConditionFromJson_ReturnsNull_When_EmptyJsonObjectProvided() throws Exception { - //test - JsonUtilityService.JSONObject testRuleConditionJson = platformServices.getJsonUtilityService().createJSONObject("{}"); - RuleCondition testRuleCondition = RuleCondition.ruleConditionFromJson(testRuleConditionJson); - //verify - assertNull(testRuleCondition); - } - - @Test(expected = UnsupportedConditionException.class) - public void ruleConditionFromJson_ThrowsException_When_ConditionJsonWithInvalidTypeProvided() throws Exception { - //test - JsonUtilityService.JSONObject testRuleConditionJson = platformServices.getJsonUtilityService() - .createJSONObject("{\"type\":\"invalid_type\",\"definition\":{\"logic\":\"and\",\"conditions\":[{\"type\":\"matcher\",\"definition\":{\"key\":\"latitude\",\"matcher\":\"lt\",\"values\":[0]}},{\"type\":\"matcher\",\"definition\":{\"key\":\"longitude\",\"matcher\":\"gt\",\"values\":[0]}}]}}"); - RuleCondition testRuleCondition = RuleCondition.ruleConditionFromJson(testRuleConditionJson); - //verify - assertNull(testRuleCondition); - } - - @Test(expected = UnsupportedConditionException.class) - public void ruleConditionFromJson_ThrowsException_When_ConditionJsonWithInvalidTypeProvidedInANestedCondition() throws - Exception { - //test - JsonUtilityService.JSONObject testRuleConditionJson = platformServices.getJsonUtilityService() - .createJSONObject("{\"type\":\"group\",\"definition\":{\"logic\":\"and\",\"conditions\":[{\"type\":\"invalid_type\",\"definition\":{\"key\":\"latitude\",\"matcher\":\"lt\",\"values\":[0]}},{\"type\":\"matcher\",\"definition\":{\"key\":\"longitude\",\"matcher\":\"gt\",\"values\":[0]}}]}}"); - RuleCondition testRuleCondition = RuleCondition.ruleConditionFromJson(testRuleConditionJson); - //verify - assertNull(testRuleCondition); - } - - @Test - public void ruleConditionFromJson_ReturnsRuleConditionMatcherInstance_When_ValidJsonObjectWithTypeMatIsProvided() - throws Exception { - //test - JsonUtilityService.JSONObject testRuleConditionJson = - platformServices.getJsonUtilityService().createJSONObject("{\"type\":\"matcher\",\"definition\":{\"key\":\"key\",\"matcher\":\"eq\",\"values\":[\"value\"]}}"); - RuleCondition testRuleCondition = RuleCondition.ruleConditionFromJson(testRuleConditionJson); - //verify - assertTrue(testRuleCondition instanceof RuleConditionMatcher); - } - - @Test - public void ruleConditionFromJson_ReturnsRuleConditionMatcherInstance_When_MatcherUnknown() throws Exception { - //test - JsonUtilityService.JSONObject testRuleConditionJson = - platformServices.getJsonUtilityService().createJSONObject("{\"type\":\"matcher\",\"definition\":{\"key\":\"key\",\"matcher\":\"xx\",\"values\":[\"value\"]}}"); - RuleCondition testRuleCondition = RuleCondition.ruleConditionFromJson(testRuleConditionJson); - //verify - assertEquals("(UNKNOWN)", testRuleCondition.toString()); - } - - @Test - public void ruleConditionMatcher_toString_ShouldReturnValidString_When_ValidMatcherInstance() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "value"); - //test - MatcherEquals matcherEquals = new MatcherEquals(); - matcherEquals.key = "key"; - matcherEquals.values = new ArrayList(); - matcherEquals.values.add("value"); - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherEquals); - //verify - assertEquals(matcherEquals.toString(), testRuleCondition.toString()); - assertTrue(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_toString_ShouldReturnEmptyString_When_NullMatcherInstance() { - //test - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(null); - //verify - assertEquals("", testRuleCondition.toString()); - } - - @Test - public void ruleConditionFromJson_ReturnsRuleConditionMatcherInstance_ShouldReturnNull_When_NullJsonObjectProvided() - throws Exception { - //test and verify - assertNull(RuleConditionMatcher.ruleConditionMatcherFromJson(null)); - } - - @Test - public void ruleConditionFromJson_ReturnsRuleConditionMatcherInstance_ShouldReturnNull_When_EmptyJsonObjectProvided() - throws Exception { - //test and verify - assertNull(RuleConditionMatcher.ruleConditionMatcherFromJson( - platformServices.getJsonUtilityService().createJSONObject("{}"))); - } - - @Test - public void ruleConditionFromJson_ReturnsRuleConditionGroup_When_ValidJsonObjectWithTypeGrpIsProvided() - throws JsonException, UnsupportedConditionException { - //test - JsonUtilityService.JSONObject testRuleConditionJson = - platformServices.getJsonUtilityService().createJSONObject("{\"type\":\"group\",\"definition\":{\"logic\":\"and\",\"conditions\":[{\"type\":\"matcher\",\"definition\":{\"key\":\"latitude\",\"matcher\":\"lt\",\"values\":[0]}},{\"type\":\"matcher\",\"definition\":{\"key\":\"longitude\",\"matcher\":\"gt\",\"values\":[0]}}]}}"); - RuleCondition testRuleCondition = RuleCondition.ruleConditionFromJson(testRuleConditionJson); - //verify - assertTrue(testRuleCondition instanceof RuleConditionGroup); - } - - @Test - public void ruleConditionMatcher_Equals_Evaluate_ShouldReturnTrue_ForValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "value"); - //test - MatcherEquals matcherEquals = new MatcherEquals(); - matcherEquals.key = "key"; - matcherEquals.values = new ArrayList(); - matcherEquals.values.add("value"); - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherEquals); - //verify - assertTrue(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_Equals_Evaluate_ShouldReturnFalse_ForInValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "not-a-value"); - //test - MatcherEquals matcherEquals = new MatcherEquals(); - matcherEquals.key = "key"; - matcherEquals.values = new ArrayList(); - matcherEquals.values.add("value"); - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherEquals); - //verify - assertFalse(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_NotEquals_Evaluate_ShouldReturnTrue_ForValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "not-a-value"); - //test - MatcherEquals matcherNotEquals = new MatcherNotEquals(); - matcherNotEquals.key = "key"; - matcherNotEquals.values = new ArrayList(); - matcherNotEquals.values.add("value"); - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherNotEquals); - //verify - assertTrue(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_NotEquals_Evaluate_ShouldReturnFalse_ForInValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "value"); - //test - MatcherEquals matcherNotEquals = new MatcherNotEquals(); - matcherNotEquals.key = "key"; - matcherNotEquals.values = new ArrayList(); - matcherNotEquals.values.add("value"); - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherNotEquals); - //verify - assertFalse(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_NotExists_Evaluate_ShouldReturnTrue_ForValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, null); - //test - MatcherNotExists matcherNotExists = new MatcherNotExists(); - matcherNotExists.key = "key"; - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherNotExists); - //verify - assertTrue(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_NotExists_Evaluate_ShouldReturnFalse_ForInValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "value"); - //test - MatcherNotExists matcherNotExists = new MatcherNotExists(); - matcherNotExists.key = "key"; - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherNotExists); - //verify - assertFalse(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_Exists_Evaluate_ShouldReturnTrue_ForValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "value"); - //test - MatcherExists matcherExists = new MatcherExists(); - matcherExists.key = "key"; - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherExists); - //verify - assertTrue(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_Exists_Evaluate_ShouldReturnFalse_ForInValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, null); - //test - MatcherExists matcherExists = new MatcherExists(); - matcherExists.key = "key"; - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherExists); - //verify - assertFalse(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - - @Test - public void ruleConditionMatcher_Contains_Evaluate_ShouldReturnTrue_ForValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "valueWithA Bigger value"); - //test - MatcherContains matcherContains = new MatcherContains(); - matcherContains.key = "key"; - matcherContains.values = new ArrayList(); - matcherContains.values.add("value"); - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherContains); - //verify - assertTrue(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_Contains_Evaluate_ShouldReturnFalse_ForInValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "valueWithA Bigger value"); - //test - MatcherContains matcherContains = new MatcherContains(); - matcherContains.key = "key"; - matcherContains.values = new ArrayList(); - matcherContains.values.add("no-value"); - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherContains); - assertFalse(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_DoesNotContains_Evaluate_ShouldReturnTrue_ForValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "valueWithA Bigger value"); - //test - MatcherContains matcherNotContains = new MatcherNotContains(); - matcherNotContains.key = "key"; - matcherNotContains.values = new ArrayList(); - matcherNotContains.values.add("no-value"); - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherNotContains); - //verify - assertTrue(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_DoesNotContains_Evaluate_ShouldReturnFalse_ForInValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "valueWithA Bigger value"); - //test - MatcherContains matcherNotContains = new MatcherNotContains(); - matcherNotContains.key = "key"; - matcherNotContains.values = new ArrayList(); - matcherNotContains.values.add("value"); - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherNotContains); - //verify - assertFalse(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_GreaterThan_Evaluate_ShouldReturnTrue_ForValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "6"); - //test - MatcherGreaterThan matcherGreaterThan = new MatcherGreaterThan(); - matcherGreaterThan.key = "key"; - matcherGreaterThan.values = new ArrayList(); - matcherGreaterThan.values.add(5); - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherGreaterThan); - //verify - assertTrue(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_GreaterThan_Evaluate_ShouldReturnFalse_ForInValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "4"); - //test - MatcherGreaterThan matcherGreaterThan = new MatcherGreaterThan(); - matcherGreaterThan.key = "key"; - matcherGreaterThan.values = new ArrayList(); - matcherGreaterThan.values.add(5); - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherGreaterThan); - //verify - assertFalse(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_LessThan_Evaluate_ShouldReturnTrue_ForValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "4"); - //test - MatcherLessThan matcherContains = new MatcherLessThan(); - matcherContains.key = "key"; - matcherContains.values = new ArrayList(); - matcherContains.values.add(5); - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherContains); - //verify - assertTrue(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_LessThan_Evaluate_ShouldReturnFalse_ForInValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "6"); - //test - MatcherLessThan matcherContains = new MatcherLessThan(); - matcherContains.key = "key"; - matcherContains.values = new ArrayList(); - matcherContains.values.add(5); - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherContains); - //verify - assertFalse(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_LessThanOrEqualTo_Evaluate_ShouldReturnTrue_ForValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "5"); - //test - MatcherLessThanOrEqual matcherLessThanOrEqual = new MatcherLessThanOrEqual(); - matcherLessThanOrEqual.key = "key"; - matcherLessThanOrEqual.values = new ArrayList(); - matcherLessThanOrEqual.values.add(5); - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherLessThanOrEqual); - //verify - assertTrue(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_LessThanOrEqualTo_Evaluate_ShouldReturnFalse_ForInValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "6"); - //test - MatcherLessThanOrEqual matcherLessThanOrEqual = new MatcherLessThanOrEqual(); - matcherLessThanOrEqual.key = "key"; - matcherLessThanOrEqual.values = new ArrayList(); - matcherLessThanOrEqual.values.add(5); - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherLessThanOrEqual); - //verify - assertFalse(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_GreaterThanOrEqualTo_Evaluate_ShouldReturnTrue_ForValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "5"); - //test - MatcherGreaterThanOrEqual matcherGreaterThanOrEqual = new MatcherGreaterThanOrEqual(); - matcherGreaterThanOrEqual.key = "key"; - matcherGreaterThanOrEqual.values = new ArrayList(); - matcherGreaterThanOrEqual.values.add(5); - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherGreaterThanOrEqual); - //verify - assertTrue(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_GreaterThanOrEqualTo_Evaluate_ShouldReturnFalse_ForInValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "4"); - //test - MatcherGreaterThanOrEqual matcherGreaterThanOrEqual = new MatcherGreaterThanOrEqual(); - matcherGreaterThanOrEqual.key = "key"; - matcherGreaterThanOrEqual.values = new ArrayList(); - matcherGreaterThanOrEqual.values.add(5); - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherGreaterThanOrEqual); - //verify - assertFalse(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_EndsWith_Evaluate_ShouldReturnTrue_ForValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "valueending"); - //test - MatcherEndsWith matcherEndsWith = new MatcherEndsWith(); - matcherEndsWith.key = "key"; - matcherEndsWith.values = new ArrayList(); - matcherEndsWith.values.add("ending"); - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherEndsWith); - //verify - assertTrue(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_EndsWith_Evaluate_ShouldReturnFalse_ForInValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "valueending_doesnot"); - //test - MatcherEndsWith matcherEndsWith = new MatcherEndsWith(); - matcherEndsWith.key = "key"; - matcherEndsWith.values = new ArrayList(); - matcherEndsWith.values.add("ending"); - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherEndsWith); - //verify - assertFalse(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_StartsWith_Evaluate_ShouldReturnTrue_ForValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "value_starting"); - //test - MatcherStartsWith matcherEndsWith = new MatcherStartsWith(); - matcherEndsWith.key = "key"; - matcherEndsWith.values = new ArrayList(); - matcherEndsWith.values.add("value"); - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherEndsWith); - //verify - assertTrue(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } - - @Test - public void ruleConditionMatcher_StartsWith_Evaluate_ShouldReturnFalse_ForInValidMatch() { - //Setup - MockRuleTokenParser mockRuleTokenParser = new MockRuleTokenParser(testEventHub, "does_not_start_with_value"); - //test - MatcherStartsWith matcherEndsWith = new MatcherStartsWith(); - matcherEndsWith.key = "key"; - matcherEndsWith.values = new ArrayList(); - matcherEndsWith.values.add("value"); - - RuleConditionMatcher testRuleCondition = new RuleConditionMatcher(matcherEndsWith); - //verify - assertFalse(testRuleCondition.evaluate(mockRuleTokenParser, new Event.Builder("", EventType.CONFIGURATION, - EventSource.RESPONSE_CONTENT).build())); - } -} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleConsequenceTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleConsequenceTests.java deleted file mode 100644 index e9dbdca7a..000000000 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleConsequenceTests.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import org.junit.Before; -import org.junit.Test; - -import java.util.Map; -import java.util.HashMap; - -import static org.junit.Assert.*; -import static org.junit.Assert.assertEquals; - -public class RuleConsequenceTests { - - private static final String CONSEQUENCE_JSON_ID = "id"; - private static final String CONSEQUENCE_JSON_TYPE = "type"; - private static final String CONSEQUENCE_JSON_DETAIL = "detail"; - private static final String CONSEQUENCE_TRIGGERED = "triggeredconsequence"; - - private JsonUtilityService jsonUtilityService; - - private JsonUtilityService.JSONObject validConsequenceJSON() { - return jsonUtilityService.createJSONObject(" {\n" + - " \"id\": \"71335c64ae133c890603a73fd01f2df70119bb42\",\n" + - " \"type\": \"iam\",\n" + - " \"detail\": {\n" + - " \"template\": \"local\",\n" + - " \"content\": \"Hey Scooby, you have items in your cart!\",\n" + - " \"delay\": 5\n" + - " }\n" + - " }"); - } - - private JsonUtilityService.JSONObject consequenceJSON_NoID() { - return jsonUtilityService.createJSONObject(" {\n" + - " \"type\": \"iam\",\n" + - " \"detail\": {\n" + - " \"template\": \"local\",\n" + - " \"content\": \"Hey Scooby, you have items in your cart!\",\n" + - " \"delay\": 5\n" + - " }\n" + - " }"); - } - - private JsonUtilityService.JSONObject consequenceJSON_NoType() { - return jsonUtilityService.createJSONObject(" {\n" + - " \"id\": \"71335c64ae133c890603a73fd01f2df70119bb42\",\n" + - " \"detail\": {\n" + - " \"template\": \"local\",\n" + - " \"content\": \"Hey Scooby, you have items in your cart!\",\n" + - " \"delay\": 5\n" + - " }\n" + - " }"); - } - - private JsonUtilityService.JSONObject consequenceJSON_NoDetail() { - return jsonUtilityService.createJSONObject(" {\n" + - " \"id\": \"71335c64ae133c890603a73fd01f2df70119bb42\",\n" + - " \"type\": \"iam\"\n" + - " }"); - } - - private JsonUtilityService.JSONObject emptyJSON() { - return jsonUtilityService.createJSONObject("{}"); - } - - @Before - public void beforeEachTest() { - final PlatformServices platformServices = new FakePlatformServices(); - jsonUtilityService = platformServices.getJsonUtilityService(); - } - - @Test - @SuppressWarnings("unchecked") - public void consequenceFromJson_Happy() { - // test - RuleConsequence ruleConsequence = RuleConsequence.consequenceFromJson(validConsequenceJSON(), jsonUtilityService); - // verify - assertNotNull(ruleConsequence); - final EventData ruleConsequenceEventData = ruleConsequence.generateEventData(); - final Map consequenceTriggeredMap = (HashMap) ruleConsequenceEventData.getObject( - CONSEQUENCE_TRIGGERED); - final Map detailMap = (HashMap) consequenceTriggeredMap.get( - CONSEQUENCE_JSON_DETAIL); - - // verify consequence details - assertEquals("71335c64ae133c890603a73fd01f2df70119bb42", - consequenceTriggeredMap.get(CONSEQUENCE_JSON_ID)); - assertEquals("iam", consequenceTriggeredMap.get(CONSEQUENCE_JSON_TYPE)); - assertNotNull(detailMap); - assertEquals(5, detailMap.get("delay")); - assertEquals("local", detailMap.get("template")); - assertEquals("Hey Scooby, you have items in your cart!", detailMap.get("content")); - } - - @Test - public void consequenceFromJson_NullJSONObject() { - assertNull(RuleConsequence.consequenceFromJson(null, jsonUtilityService)); - } - - @Test - public void consequenceFromJson_EmptyJSON() { - assertNull(RuleConsequence.consequenceFromJson(emptyJSON(), jsonUtilityService)); - } - - @Test - public void consequenceFromJson_NoID() { - assertNull(RuleConsequence.consequenceFromJson(consequenceJSON_NoID(), jsonUtilityService)); - } - - @Test - public void consequenceFromJson_NoType() { - assertNull(RuleConsequence.consequenceFromJson(consequenceJSON_NoType(), jsonUtilityService)); - } - - @Test - public void consequenceFromJson_NoDetails() { - assertNull(RuleConsequence.consequenceFromJson(consequenceJSON_NoDetail(), jsonUtilityService)); - } - - @Test - @SuppressWarnings("unchecked") - public void generateEventData_Happy() { - // setup - final Map expandedDetails = new HashMap(); - expandedDetails.put("template", "local"); - expandedDetails.put("content", "Hey Scooby, you have items in your cart!"); - expandedDetails.put("delay", 5); - - - // test - final RuleConsequence ruleConsequence = RuleConsequence.consequenceFromJson(validConsequenceJSON(), jsonUtilityService); - final EventData eventData = ruleConsequence.generateEventData(); - - // verify - final Map consequenceMap = (HashMap) eventData.getObject( - CONSEQUENCE_TRIGGERED); - assertNotNull(consequenceMap); - assertEquals("71335c64ae133c890603a73fd01f2df70119bb42", - consequenceMap.get(CONSEQUENCE_JSON_ID)); - assertEquals("iam", consequenceMap.get(CONSEQUENCE_JSON_TYPE)); - assertEquals(expandedDetails, consequenceMap.get( - CONSEQUENCE_JSON_DETAIL)); - } -} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleTests.java deleted file mode 100644 index 4eb7cfcbc..000000000 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleTests.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import org.junit.*; - -import java.util.ArrayList; -import java.util.List; - -import static org.junit.Assert.*; - -public class RuleTests extends BaseTest { - - private RuleTokenParser ruleTokenParser; - - @BeforeClass - public static void setupTests() { - PlatformServices mockPlatformServices = new FakePlatformServices(); - Log.setLoggingService(mockPlatformServices.getLoggingService()); - Log.setLogLevel(LoggingMode.VERBOSE); - } - - @AfterClass - public static void tearDownTests() { - //Reset logger - Log.setLogLevel(LoggingMode.ERROR); - Log.setLoggingService(null); - } - - @Before - public void setup() { - super.beforeEach(); - final EventHub eventHub = new EventHub("testEventHub", new FakePlatformServices()); - ruleTokenParser = new RuleTokenParser(eventHub); - } - - @Test - public void evaluateCondition_ReturnsTrue_When_DataIsValid() { - //setup - EventData testEventData = new EventData(); - testEventData.putString("key0", "value0"); - testEventData.putString("key1", "value1"); - - Event testEvent = new Event.Builder("test", EventType.ANALYTICS, - EventSource.REQUEST_CONTENT).setData(testEventData).build(); - - RuleCondition condition = new RuleCondition() { - @Override - protected boolean evaluate(final RuleTokenParser tokenParser, final Event event) { - //Make it evaluate to true - return true; - } - @Override - public String toString() { - return null; - } - }; - Rule testRule = new Rule(condition, new ArrayList()); - //test - boolean result = testRule.evaluateCondition(ruleTokenParser, testEvent); - //verify - assertTrue("The condition evalutation should return true with valid data", result); - } - - @Test - public void toString_ReturnsValidString() { - //setup - RuleCondition condition = new RuleConditionMatcher(null); - List consequenceEvents = new ArrayList(); - - consequenceEvents.add(new Event.Builder("test", EventType.RULES_ENGINE, EventSource.NONE).build()); - //test - final Rule testRule = new Rule(condition, consequenceEvents); - - //verify - assertTrue(testRule.toString().contains("Condition: ")); - assertTrue(testRule.toString().contains("Consequences: ")); - } -} \ No newline at end of file diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleTokenParserTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleTokenParserTests.java deleted file mode 100644 index b8abd7c7a..000000000 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RuleTokenParserTests.java +++ /dev/null @@ -1,352 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.Assert.*; - -public class RuleTokenParserTests extends BaseTest { - private RuleTokenParser ruleTokenParser; - - @BeforeClass - public static void setupTests() { - PlatformServices mockPlatformServices = new FakePlatformServices(); - Log.setLoggingService(mockPlatformServices.getLoggingService()); - Log.setLogLevel(LoggingMode.VERBOSE); - } - - @AfterClass - public static void tearDownTests() { - //Reset logger - Log.setLogLevel(LoggingMode.ERROR); - Log.setLoggingService(null); - } - - @Before - public void setup() { - super.beforeEach(); - ruleTokenParser = new RuleTokenParser(eventHub); - } - - @Test - public void expandKey_ReturnsNull_When_KeyIsNull() { - //setup - Event testEvent = getDefaultEvent(); - //test - String result = ruleTokenParser.expandKey(null, testEvent); - //verify - assertNull("expandKey should return null on null input string", result); - } - - @Test - public void expandKey_ReturnsNull_When_KeyIsEmpty() { - //setup - Event testEvent = getDefaultEvent(); - //test - String result = ruleTokenParser.expandKey("", testEvent); - //verify - assertNull("expandKey should return null on empty input string", result); - } - - - @Test - public void expandKey_ReturnsNull_When_EntryIsNotExist() { - //setup - Event testEvent = getDefaultEvent(); - //test - String result = ruleTokenParser.expandKey("abc", testEvent); - //verify - assertNull("expandKey should return null on none-exist entry", result); - } - - @Test - public void expandKey_ReturnsNull_When_ValueIsNull() { - //setup - EventData testEventData = new EventData(); - testEventData.putNull("key1"); - Event testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData); - //test - String result = ruleTokenParser.expandKey("key1", testEvent); - //verify - assertNull("expandKey should return null on null variant value", result); - } - - @Test - public void expandKey_ReturnsEmptyString_When_ValueIsList() { - //setup - EventData testEventData = new EventData(); - testEventData.putVariantList("key1", new ArrayList()); - Event testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData); - //test - String result = ruleTokenParser.expandKey("key1", testEvent); - //verify - assertEquals("expandKey should return empty string on list variant", "", result); - } - - @Test - public void expandKey_ReturnsEmptyString_When_ValueIsMap() { - //setup - EventData testEventData = new EventData(); - testEventData.putVariantMap("key1", new HashMap()); - Event testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData); - //test - String result = ruleTokenParser.expandKey("key1", testEvent); - //verify - assertEquals("expandKey should return empty string on map variant", "", result); - } - - @Test - public void expandKey_ReturnsEventType_When_KeyPrefixIsType() { - //setup - Event testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT); - //test - String result = ruleTokenParser.expandKey("~type", testEvent); - //verify - assertEquals("expandKey should return Event Type on valid Event", result, "com.adobe.eventtype.analytics"); - } - - @Test - public void expandKey_ReturnsEventSource_When_KeyPrefixIsSource() { - //setup - Event testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT); - //test - String result = ruleTokenParser.expandKey("~source", testEvent); - //verify - assertEquals("expandKey should return Event Source on valid event", result, "com.adobe.eventsource.requestcontent"); - } - - @Test - public void expandKey_ReturnsCurrentUnixTimestamp_When_KeyPrefixIsTimestampu() { - //setup - Event testEvent = getDefaultEvent(); - //test - String result = ruleTokenParser.expandKey("~timestampu", testEvent); - //verify - assertNotNull("expandKey should return current unit timestamp on valid event", result); - } - - @Test - public void expandKey_ReturnsCurrentISO8601Timestamp_When_KeyPrefixIsTimestampz() { - //setup - Event testEvent = getDefaultEvent(); - //test - String result = ruleTokenParser.expandKey("~timestampz", testEvent); - //verify - assertNotNull("expandKey should return current ISO8601 timestamp on valid event", result); - } - - @Test - public void expandKey_ReturnsCurrentSdkVersion_When_KeyPrefixIsSdkVersion() { - //setup - Event testEvent = getDefaultEvent(); - //test - String result = ruleTokenParser.expandKey("~sdkver", testEvent); - //verify - assertEquals("expandKey should return current sdk version on valid event", result, "mockSdkVersion"); - } - - @Test - public void expandKey_ReturnsRandomNumber_When_KeyPrefixIsCachebust() { - //setup - Event testEvent = getDefaultEvent(); - //test - String result = ruleTokenParser.expandKey("~cachebust", testEvent); - //verify - assertNotNull("expandKey should return random cachebust on valid event", result); - } - - @Test - public void expandKey_ReturnsUrlEncoded_When_KeyPrefixIsAllUrl() { - //setup - EventData testEventData = new EventData(); - testEventData.putString("key1", "value 1"); - testEventData.putString("key2", "value 2"); - Event testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData); - //test - String result = ruleTokenParser.expandKey("~all_url", testEvent); - //verify - assertEquals("expandKey should return all variables on valid event", result, "&key1=value%201&key2=value%202"); - } - - @Test - public void expandKey_ReturnsJson_When_KeyPrefixIsAllJson() { - //setup - EventData testEventData = new EventData(); - testEventData.putString("key1", "value1"); - testEventData.putString("key2", "value2"); - Event testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData); - - //test - String result = ruleTokenParser.expandKey("~all_json", testEvent); - //verify - assertEquals("{\"key1\":\"value1\",\"key2\":\"value2\"}", result); - } - - @Test - public void expandKey_ReturnsSharedStateKey_When_KeyPrefixIsState() { - //setup - EventData testEventData = new EventData(); - testEventData.putString("key1", "value1"); - testEventData.putString("key2", "value2"); - Event testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData); - - EventData lcdata = new EventData(); - Map lifecycleSharedState = new HashMap(); - lifecycleSharedState.put("akey", "avalue"); - lcdata.putMap("analytics.contextData", lifecycleSharedState); - eventHub.setSharedState("com.adobe.marketing.mobile.Analytics", lcdata); - - //test - String result = ruleTokenParser.expandKey("~state.com.adobe.marketing.mobile.Analytics/analytics.contextData.akey", - testEvent); - //verify - assertEquals("expandKey should return current sdk version on valid event", result, "avalue"); - } - - - @Test - public void expandKey_ReturnsNull_When_KeyPrefixIsStateAndKeyNotExist() { - //setup - EventData testEventData = new EventData(); - Event testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData); - - EventData lcdata = new EventData(); - Map lifecycleSharedState = new HashMap(); - eventHub.setSharedState("com.adobe.marketing.mobile.Analytics", lcdata); - - //test - String result = ruleTokenParser.expandKey("~state.com.adobe.marketing.mobile.Analytics/analytics.contextData.akey", - testEvent); - //verify - assertEquals(null, result); - } - - @Test - public void expandTokensForString_ReturnsEmptyString_When_InputStringIsEmpty() { - //setup - Event testEvent = getDefaultEvent(); - - //test - String result = ruleTokenParser.expandTokensForString("", testEvent); - //verify - assertEquals("expandTokensForString should return current sdk version on valid event", result, ""); - } - - @Test - public void expandTokensForString_ReturnsNull_When_InputStringIsNull() { - //setup - Event testEvent = getDefaultEvent(); - - //test - String result = ruleTokenParser.expandTokensForString(null, testEvent); - //verify - assertNull("expandTokensForString should return current sdk version on valid event", result); - } - - @Test - public void expandTokensForString_ReplacesTokens_When_InputStringHasValidTokens() { - //setup - EventData testEventData = new EventData(); - testEventData.putString("key1", "value1"); - testEventData.putString("key2", "value2"); - Event testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData); - - //test - String result = - ruleTokenParser.expandTokensForString("This is {%key1%} and this is {%key2%} for my event of {%~type%} and {%~source%}", - testEvent); - //verify - assertEquals(result, - "This is value1 and this is value2 for my event of com.adobe.eventtype.analytics and com.adobe.eventsource.requestcontent"); - } - - @Test - public void expandTokensForString_ReplacesUnknownTokens_When_InputStringHasUnknownTokens() { - //setup - Event testEvent = getDefaultEvent(); - - //test - String result = ruleTokenParser.expandTokensForString("This key is unkn{%key3%}own to sdk version {%~sdkver%}", - testEvent); - //verify - assertEquals(result, "This key is unknown to sdk version mockSdkVersion"); - } - - @Test - public void expandTokensForString_IgnoresTokens_When_InputStringHasMalformedTokens() { - //setup - Event testEvent = getDefaultEvent(); - - //test - String result = ruleTokenParser.expandTokensForString("{%key1%} is a replacement for {key1}", testEvent); - //verify - assertEquals(result, "value1 is a replacement for {key1}"); - } - - private Event getEvent(final EventType type, final EventSource source, final EventData eventData) { - return new Event.Builder("TEST", type, source) - .setData(eventData).build(); - } - - private Event getEvent(final EventType type, final EventSource source) { - EventData testEventData = new EventData(); - testEventData.putString("key1", "value1"); - return getEvent(type, source, testEventData); - } - - private Event getDefaultEvent() { - EventData testEventData = new EventData(); - testEventData.putString("key1", "value1"); - return getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData); - } - - @Test - public void expandTokensForString_ReplacesTokens_When_InputStringHasUrlEncodingInValidTokens() { - //setup - EventData testEventData = new EventData(); - testEventData.putString("token", "value 1"); - Event testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData); - - //test - String result = - ruleTokenParser.expandTokensForString("Testing {%urlenc(token%}, {%urlenctoken%}, {%~urlenc%}, {%urlenc%}, {%urlencoding(token)%}, {%urlenctest_token%} and {%~test_tokenurlenc%} for my event. Only get {%urlenc(token)%} when formed correctly", - testEvent); - //verify - assertEquals(result, - "Testing , , , , {%urlencoding(token)%}, and for my event. Only get value%201 when formed correctly"); - } - - @Test - public void expandTokensForString_ReplacesTokens_When_InputStringHasUrlEncodingValidTokens() { - //setup - EventData testEventData = new EventData(); - testEventData.putString("key1", "value 1"); - testEventData.putString("key2", "value 2"); - Event testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData); - - //test - String result = - ruleTokenParser.expandTokensForString("{%urlenc(key1)%} is a replacement for {%urlenc(key2)%}", - testEvent); - //verify - assertEquals(result, - "value%201 is a replacement for value%202"); - } -} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RulesEngineTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RulesEngineTests.java deleted file mode 100644 index 82e8852fd..000000000 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RulesEngineTests.java +++ /dev/null @@ -1,1506 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -public class RulesEngineTests extends BaseTest { - class MockModule extends Module { - MockModule(String name, EventHub hub) { - super(name, hub); - } - } - - class MockRule extends Rule { - MockRule(RuleCondition condition, ArrayList consequences) { - super(condition, consequences); - } - - void setConsequenceEvents(final ArrayList newConsequenceEvents) { - consequenceEvents = newConsequenceEvents; - } - - void setCondition(final RuleCondition newRuleCondition) { - condition = newRuleCondition; - } - } - - class TestableRulesEngine extends RulesEngine { - TestableRulesEngine(EventHub hub) { - super(hub); - } - - ConcurrentHashMap> getModuleRules() { - return moduleRuleAssociation; - } - - void setModuleRules(ConcurrentHashMap> rules) { - moduleRuleAssociation.clear(); - - if (rules != null && rules.size() > 0) { - moduleRuleAssociation.putAll(rules); - } - } - } - - class TestableRuleCondition extends RuleCondition { - TestableRuleCondition() { - super(); - } - - public boolean evaluateReturnValue = true; - - @Override - protected boolean evaluate(RuleTokenParser parser, Event event) { - return evaluateReturnValue; - } - - @Override - public String toString() { - return ""; - } - } - - private static TestableRulesEngine _rulesEngine; - private static EventHub _eventHub; - private static PlatformServices _platformServices; - - private static MockModule _mockModule; - private static MockModule _mockAnalyticsModule; - private static ConcurrentLinkedQueue _mockRules; - private static MockRule _mockRuleOne; - private static ArrayList _mockRuleOneConsequences; - private static MockRule _mockRuleTwo; - private static ArrayList _mockRuleTwoConsequences; - private static MockRule _mockRuleThree; - private static ArrayList _mockRuleThreeConsequences; - private static MockRule _mockRuleFour; - private static ArrayList _mockRuleFourConsequences; - private static int _mockRulesCount; - - private static final String EVENT_DATA_RULES_URL_KEY = "rules.url"; - - @Before - public void setupTests() { - super.beforeEach(); - Log.setLoggingService(platformServices.getLoggingService()); - Log.setLogLevel(LoggingMode.VERBOSE); - - _platformServices = new FakePlatformServices(); - _eventHub = new EventHub("rulesEngineTestsEventHub", _platformServices); - _rulesEngine = new TestableRulesEngine(_eventHub); - _mockModule = new MockModule("a.mock.module", _eventHub); - _mockAnalyticsModule = new MockModule("com.adobe.module.analytics", _eventHub); - - try { - // setup mock rule #1 - analytics track - TestableRuleCondition conditionOne = new TestableRuleCondition(); - _mockRuleOneConsequences = new ArrayList(); - _mockRuleOneConsequences.add(GetConsequenceEventAnalytics()); - _mockRuleOne = new MockRule(conditionOne, _mockRuleOneConsequences); - - // setup mock rule #2 - attach data - TestableRuleCondition conditionTwo = new TestableRuleCondition(); - _mockRuleTwoConsequences = new ArrayList(); - _mockRuleTwoConsequences.add(GetConsequenceEventAttachData()); - _mockRuleTwo = new MockRule(conditionTwo, _mockRuleTwoConsequences); - - // setup mock rule #3 - modify data - TestableRuleCondition conditionThree = new TestableRuleCondition(); - _mockRuleThreeConsequences = new ArrayList(); - _mockRuleThreeConsequences.add(GetConsequenceEventModifyData()); - _mockRuleThree = new MockRule(conditionThree, _mockRuleThreeConsequences); - - // setup mock rule #4 - dispatch new event with copied data - TestableRuleCondition conditionFour = new TestableRuleCondition(); - _mockRuleFourConsequences = new ArrayList(); - _mockRuleFourConsequences.add(GetConsequenceEventDispatch("test.dispatch.type", "test.dispatch.source", - RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL_ACTION_COPY, null)); - _mockRuleFour = new MockRule(conditionFour, _mockRuleFourConsequences); - - _mockRules = new ConcurrentLinkedQueue(); - _mockRules.add(_mockRuleOne); - _mockRules.add(_mockRuleTwo); - _mockRules.add(_mockRuleThree); - _mockRules.add(_mockRuleFour); - _mockRulesCount = _mockRules.size(); - - ConcurrentHashMap> moduleRulesQueue = new - ConcurrentHashMap>(); - moduleRulesQueue.put(_mockModule, _mockRules); - _rulesEngine.setModuleRules(moduleRulesQueue); - - } catch (Exception ex) {} - } - - @After - public void tearDownTests() { - //Reset logger - Log.setLogLevel(LoggingMode.ERROR); - Log.setLoggingService(null); - } - - static EventData GetTriggeredEventData() { - EventData eventData = new EventData(); - eventData.putString("&&key1", "value1"); - eventData.putString("key2", "value2"); - eventData.putInteger("anInt", 552); - Map variantMap = new HashMap(); - variantMap.put("&&embeddedString", Variant.fromString("embeddedStringValue")); - eventData.putVariantMap("aMap", variantMap); - ArrayList variantList = new ArrayList(); - variantList.add(Variant.fromString("stringInList")); - eventData.putVariantList("aList", variantList); - return eventData; - } - - public static Event GetEventWithListOfObjects() { - EventData eventData = GetTriggeredEventData(); - final List listOfObjects = new ArrayList(); - final Map obj1 = new HashMap(); - obj1.put("name", Variant.fromString("request1")); - final Map obj1Details = new HashMap(); - obj1Details.put("size", Variant.fromString("large")); - obj1Details.put("color", Variant.fromString("red")); - obj1.put("details", Variant.fromVariantMap(obj1Details)); - final Map obj2 = new HashMap(); - obj2.put("name", Variant.fromString("request2")); - obj2.put("location", Variant.fromString("central")); - listOfObjects.add(Variant.fromVariantMap(obj1)); - listOfObjects.add(Variant.fromVariantMap(obj2)); - eventData.putVariantList("listOfObjects", listOfObjects); - return new Event.Builder("Test", EventType.ANALYTICS, EventSource.REQUEST_CONTENT).setData(eventData).build(); - } - - public static Event GetTriggeringEvent() { - return new Event.Builder("Test", EventType.ANALYTICS, - EventSource.REQUEST_CONTENT).setData(GetTriggeredEventData()).build(); - } - - public static Event GetConsequenceEventAnalytics() { - EventData eventData = new EventData(); - - Map consequenceMap = new HashMap(); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_JSON_ID, Variant.fromString("analyticsId")); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_JSON_TYPE, - Variant.fromString(RulesEngineConstantsTests.ConsequenceTypes.SEND_DATA_TO_ANALYTICS)); - Map detailMap = new HashMap(); - detailMap.put("key3", "{%&&key1%}"); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL, Variant.fromStringMap(detailMap)); - eventData.putVariantMap(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_TRIGGERED, consequenceMap); - - return new Event.Builder("Test", EventType.ANALYTICS, EventSource.REQUEST_CONTENT).setData(eventData).build(); - } - - public static Event GetConsequenceEventAttachData() { - EventData eventData = new EventData(); - - Map consequenceMap = new HashMap(); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_JSON_ID, Variant.fromString("attachId")); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_JSON_TYPE, - Variant.fromString(RulesEngineConstantsTests.ConsequenceTypes.ATTACH_DATA)); - - Map detailMap = new HashMap(); - Map eventDataMap = new HashMap(); - Map aMap = new HashMap(); - Map newMap = new HashMap(); - List aList = new ArrayList(); - List newList = new ArrayList(); - - eventDataMap.put("attachedKey", Variant.fromString("attachedValue")); - eventDataMap.put("&&key1", Variant.fromString("updatedValue1")); - eventDataMap.put("newInt", Variant.fromInteger(123)); - aMap.put("&&embeddedString", Variant.fromString("changedEmbeddedStringValue")); - aMap.put("newEmbeddedString", Variant.fromString("newEmbeddedStringValue")); - eventDataMap.put("aMap", Variant.fromVariantMap(aMap)); - newMap.put("newMapKey", Variant.fromString("newMapValue")); - eventDataMap.put("newMap", Variant.fromVariantMap(newMap)); - aList.add(Variant.fromString("stringInList")); - aList.add(Variant.fromString("newStringInList")); - eventDataMap.put("aList", Variant.fromVariantList(aList)); - newList.add(Variant.fromString("newListString")); - eventDataMap.put("newList", Variant.fromVariantList(newList)); - - detailMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL_EVENT_DATA, - Variant.fromVariantMap(eventDataMap)); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL, Variant.fromVariantMap(detailMap)); - eventData.putVariantMap(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_TRIGGERED, consequenceMap); - - return new Event.Builder("Test", EventType.ANALYTICS, EventSource.REQUEST_CONTENT).setData(eventData).build(); - } - - /** - * Contents of the EventData for the below Consequence Event: (all maps are represented as Map - * { - * "triggeredconsequence":{ - * "id":"modifyId", - * "type":"mod", - * "detail":{ - * "eventdata":{ - * "&&key1":null, - * "aList":null, - * "aMap":null, - * "listOfObjects[*]":{ - * "details":{ - * "temp":58.8, - * "color":"orange" - * } - * } - * } - * } - * } - * } - */ - public static Event GetConsequenceEventModifyData() { - EventData eventData = new EventData(); - - Map consequenceMap = new HashMap(); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_JSON_ID, Variant.fromString("modifyId")); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_JSON_TYPE, - Variant.fromString(RulesEngineConstantsTests.ConsequenceTypes.MODIFY_DATA)); - - Map detailMap = new HashMap(); - Map eventDataMap = new HashMap(); - - eventDataMap.put("&&key1", Variant.fromNull()); - eventDataMap.put("aMap", Variant.fromNull()); - eventDataMap.put("aList", Variant.fromNull()); - - final Map listOfObjectsAsMap = new HashMap(); - final Map details = new HashMap(); - details.put("color", Variant.fromString("orange")); - details.put("temp", Variant.fromDouble(58.8)); - listOfObjectsAsMap.put("details", Variant.fromVariantMap(details)); - eventDataMap.put("listOfObjects[*]", Variant.fromVariantMap(listOfObjectsAsMap)); - - detailMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL_EVENT_DATA, - Variant.fromVariantMap(eventDataMap)); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL, Variant.fromVariantMap(detailMap)); - eventData.putVariantMap(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_TRIGGERED, consequenceMap); - - return new Event.Builder("Test", EventType.ANALYTICS, EventSource.REQUEST_CONTENT).setData(eventData).build(); - } - - /** - * { - * "triggeredconsequence":{ - * "id": "dispatchId", - * "type": "dispatch" - * "detail": { - * "source":, - * "type":", - * "eventdataaction": - * } - * } - * } - * - * @param type event type of the new event to be dispatched - * @param source event source of the new event to be dispatched - * @param eventDataType event data type of the new event. Possible values are copy (copies data from the triggering event) - * or new (event data is provided under detail.eventdata) - * @param eventData (optional) event data, to be used with the {@code eventDataType} new option - */ - public static Event GetConsequenceEventDispatch(final String type, final String source, final String eventDataType, - final Map eventData) { - EventData consequenceEventData = new EventData(); - - Map consequenceMap = new HashMap(); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_JSON_ID, Variant.fromString("dispatchId")); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_JSON_TYPE, - Variant.fromString(RulesEngineConstantsTests.ConsequenceTypes.DISPATCH)); - - Map detailMap = new HashMap(); - - if (type != null) { - detailMap.put(RulesEngineConstantsTests.EventDataKeys.DISPATCH_CONSEQUENCE_TYPE, Variant.fromString(type)); - } - - if (source != null) { - detailMap.put(RulesEngineConstantsTests.EventDataKeys.DISPATCH_CONSEQUENCE_SOURCE, Variant.fromString(source)); - } - - if (eventDataType != null) { - detailMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL_EVENT_DATA_ACTION, - Variant.fromString(eventDataType)); - } - - if (eventData != null) { - detailMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL_EVENT_DATA, Variant.fromVariantMap(eventData)); - } - - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL, Variant.fromVariantMap(detailMap)); - consequenceEventData.putVariantMap(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_TRIGGERED, consequenceMap); - - return new Event.Builder("Test Dispatch", EventType.RULES_ENGINE, - EventSource.REQUEST_CONTENT).setData(consequenceEventData).build(); - } - - static Map GetConsequenceMapFromConsequenceEvent(final Event consequenceEvent) { - return consequenceEvent.getData().optVariantMap(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_TRIGGERED, null); - } - - public static Event GetEvent(final EventType eventType, final EventSource eventSource) { - EventData eventData = new EventData(); - eventData.putString("&&key1", "value1"); - return new Event.Builder("Test", eventType, eventSource).setData(eventData).build(); - } - - public static String GetStringFromDetailsOfTriggeredConsequenceEventData(final String key, final EventData eventData) { - try { - Map triggeredConsequenceMap = eventData.getVariantMap( - RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_TRIGGERED); - Map detailMap = triggeredConsequenceMap.get( - RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL).getVariantMap(); - - return detailMap.containsKey(key) ? detailMap.get(key).convertToString() : ""; - } catch (Exception ex) { - return ""; - } - } - - public static void SetAnalyticsSharedState(final EventData eventData) { - try { - _eventHub.createSharedState(_mockAnalyticsModule, 0, eventData); - } catch (Exception ex) {} - } - - - // ================================================================================================================= - // protected void addRule(final Module owningModule, final Rule rule) - // ================================================================================================================= - @Test - public void addRule_When_Happy_Then_RuleIsAdded() { - // setup - _rulesEngine.setModuleRules(null); - final ConcurrentHashMap> rulesBefore = _rulesEngine.getModuleRuleAssociation(); - assertEquals(0, rulesBefore.size()); - - // test - _rulesEngine.addRule(_mockModule, _mockRuleOne); - - // verify - final ConcurrentHashMap> rulesAfter = _rulesEngine.getModuleRules(); - assertEquals(1, rulesAfter.size()); - } - - @Test - public void addRule_When_NullInput_Then_NoRuleIsAdded() { - // setup - _rulesEngine.setModuleRules(null); - final ConcurrentHashMap> rulesBefore = _rulesEngine.getModuleRuleAssociation(); - assertEquals(0, rulesBefore.size()); - - // test - _rulesEngine.addRule(_mockModule, null); - - // verify - final ConcurrentHashMap> rulesAfter = _rulesEngine.getModuleRules(); - assertEquals(0, rulesAfter.size()); - } - - @Test - public void addRule_When_NoConsequenceEvents_Then_NoRuleIsAdded() { - // setup - _rulesEngine.setModuleRules(null); - _mockRuleOne.setConsequenceEvents(new ArrayList()); - final ConcurrentHashMap> rulesBefore = _rulesEngine.getModuleRuleAssociation(); - assertEquals(0, rulesBefore.size()); - - // test - _rulesEngine.addRule(_mockModule, _mockRuleOne); - - // verify - final ConcurrentHashMap> rulesAfter = _rulesEngine.getModuleRules(); - assertEquals(0, rulesAfter.size()); - } - - @Test - public void addRule_When_ModuleAlreadyInModuleMap_Then_RuleIsAddedToExistingModule() { - // setup - final ConcurrentHashMap> rulesBefore = _rulesEngine.getModuleRuleAssociation(); - assertEquals(1, rulesBefore.size()); - assertEquals(_mockRulesCount, rulesBefore.get(_mockModule).size()); - - // test - _rulesEngine.addRule(_mockModule, _mockRuleOne); - - // verify - final ConcurrentHashMap> rulesAfter = _rulesEngine.getModuleRules(); - assertEquals(1, rulesAfter.size()); - assertEquals(_mockRulesCount + 1, rulesAfter.get(_mockModule).size()); - } - - // ================================================================================================================= - // protected void replaceRules(final Module owningModule, final List rules) - // ================================================================================================================= - - // ================================================================================================================= - // ConcurrentHashMap> getModuleRuleAssociation() - // ================================================================================================================= - - // ================================================================================================================= - // protected void unregisterAllRules(final Module owningModule) - // ================================================================================================================= - - // ================================================================================================================= - // protected List evaluateRules(final Event triggerEvent) - // ================================================================================================================= - @Test - public void evaluateRules_When_Happy_Then_ShouldProperlyHandleVariousRuleConsequenceTypes() throws Exception { - // setup - final Event triggeringEvent = GetTriggeringEvent(); - assertEquals(5, triggeringEvent.getData().size()); - assertFalse(triggeringEvent.getData().containsKey("attachedKey")); - - // test - final List result = _rulesEngine.evaluateRules(triggeringEvent); - - // verify - assertEquals("Should have two consequence events: analytics + dispatch consequence", 2, result.size()); - final EventData event1Data = result.get(0).getData(); - final String valueForKey3 = GetStringFromDetailsOfTriggeredConsequenceEventData("key3", event1Data); - assertEquals("Token value should be replaced in consequence event", "value1", valueForKey3); - - final EventData newData = triggeringEvent.getData(); - assertNotNull(newData); - assertEquals(6, newData.size()); - assertTrue(newData.containsKey("attachedKey")); - assertEquals("attachedValue", newData.optString("attachedKey", "")); - assertFalse(newData.containsKey("&&key1")); - assertEquals("value2", newData.optString("key2", "")); - assertEquals(552, newData.optInteger("anInt", 0)); - assertEquals(123, newData.optInteger("newInt", 0)); - assertFalse(newData.containsKey("aMap")); - assertEquals("newMapValue", newData.getVariantMap("newMap").get("newMapKey").convertToString()); - assertEquals("newListString", newData.getVariantList("newList").get(0).convertToString()); - assertFalse(newData.containsKey("aList")); - - final Map event2Data = result.get(1).getEventData(); - assertEquals(triggeringEvent.getEventData(), event2Data); - } - - @Test - public void evaluateRules_When_NoModulesHaveRules_Then_ShouldReturnEmpty() { - // setup - _rulesEngine.setModuleRules(new ConcurrentHashMap>()); - - // test - final List result = _rulesEngine.evaluateRules(GetTriggeringEvent()); - - // verify - assertEquals(0, result.size()); - } - - @Test - public void evaluateRules_When_ModulesHaveNoRules_Then_ShouldReturnEmpty() { - // setup - final ConcurrentHashMap> rules = new - ConcurrentHashMap>(); - rules.put(_mockModule, new ConcurrentLinkedQueue()); - _rulesEngine.setModuleRules(rules); - - // test - final List result = _rulesEngine.evaluateRules(GetTriggeringEvent()); - - // verify - assertEquals(0, result.size()); - } - - @Test - public void evaluateRules_When_NoRulesEvaluateToTrue_Then_ShouldReturnEmpty() { - // setup - RuleCondition falseCondition = new RuleCondition() { - @Override - protected boolean evaluate(RuleTokenParser ruleTokenParser, Event event) { - return false; - } - - @Override - public String toString() { - return null; - } - }; - _mockRuleOne.setCondition(falseCondition); - _mockRuleTwo.setCondition(falseCondition); - _mockRuleThree.setCondition(falseCondition); - _mockRuleFour.setCondition(falseCondition); - - // test - final List result = _rulesEngine.evaluateRules(GetTriggeringEvent()); - - // verify - assertEquals(0, result.size()); - } - - @Test - public void evaluateRules_When_RulesHaveNoConsequenceEvents_Then_ShouldReturnEmpty() { - // setup - _mockRuleOne.setConsequenceEvents(new ArrayList()); - _mockRuleTwo.setConsequenceEvents(new ArrayList()); - _mockRuleThree.setConsequenceEvents(new ArrayList()); - _mockRuleFour.setConsequenceEvents(new ArrayList()); - - // test - final List result = _rulesEngine.evaluateRules(GetTriggeringEvent()); - - // verify - assertEquals(0, result.size()); - } - - @Test - public void evaluateRules_When_ConsequenceEventsHaveNoEventData_Then_ShouldReturnEmpty() { - // setup - final Event eventWithoutData = new Event.Builder("Test", EventType.ANALYTICS, EventSource.REQUEST_CONTENT).build(); - final ArrayList ruleOneEvents = new ArrayList(); - ruleOneEvents.add(eventWithoutData); - final ArrayList ruleTwoEvents = new ArrayList(); - ruleTwoEvents.add(eventWithoutData); - final ArrayList ruleThreeEvents = new ArrayList(); - ruleThreeEvents.add(eventWithoutData); - final ArrayList ruleFourEvents = new ArrayList(); - ruleFourEvents.add(eventWithoutData); - _mockRuleOne.setConsequenceEvents(ruleOneEvents); - _mockRuleTwo.setConsequenceEvents(ruleTwoEvents); - _mockRuleThree.setConsequenceEvents(ruleThreeEvents); - _mockRuleFour.setConsequenceEvents(ruleFourEvents); - - // test - final List result = _rulesEngine.evaluateRules(GetTriggeringEvent()); - - // verify - assertEquals(0, result.size()); - } - - @Test - public void evaluateRules_When_ConsequenceEventsHaveNoConsequenceType_Then_ShouldReturnEmpty() { - // setup - setNoTypeConsequenceFor(_mockRuleOne); - setNoTypeConsequenceFor(_mockRuleTwo); - setNoTypeConsequenceFor(_mockRuleThree); - setNoTypeConsequenceFor(_mockRuleFour); - - // test - final List result = _rulesEngine.evaluateRules(GetTriggeringEvent()); - - // verify - assertEquals(0, result.size()); - } - - @Test - public void evaluateRules_When_ConsequenceEventsHaveEmptyConsequenceType_Then_ShouldReturnEmpty() { - // setup - setEmptyConsequenceFor(_mockRuleOne); - setEmptyConsequenceFor(_mockRuleTwo); - setEmptyConsequenceFor(_mockRuleThree); - setEmptyConsequenceFor(_mockRuleFour); - - // test - final List result = _rulesEngine.evaluateRules(GetTriggeringEvent()); - - // verify - assertEquals(0, result.size()); - } - - // ================================================================================================================= - // protected List evaluateEventWithRules(final Event triggerEvent, final List rules) - // ================================================================================================================= - - @Test - public void evaluateEventWithRules_When_LoopDispatchConsequences_Then_ShouldTriggerDispatchOnceForEachRule() { - // setup - final int expectedConsequencesCount = 3; - final String eventType = "test.type"; - final String eventSource = "test.source"; - Event triggerEvent = new Event.Builder("Test loop, chained consequence", eventType, - eventSource).setEventData(new HashMap() { - { - put("hello", "world"); - } - }).build(); - - // add 3 dispatch consequences - List consequences = new ArrayList(); - consequences.add(GetConsequenceEventDispatch(eventType, eventSource, - RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL_ACTION_COPY, null)); - consequences.add(GetConsequenceEventDispatch(eventType, eventSource, - RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL_ACTION_COPY, null)); - consequences.add(GetConsequenceEventDispatch(eventType, eventSource, - RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL_ACTION_NEW, null)); - List rules = new ArrayList() { - { - add(new Rule(new TestableRuleCondition(), consequences)); - } - }; - - // send triggering event, dispatch chain count = 0 - // verify 3 events dispatched based on consequences - List triggeredConsequences1 = new ArrayList(); - triggeredConsequences1.addAll(_rulesEngine.evaluateEventWithRules(triggerEvent, rules)); - - assertEquals(expectedConsequencesCount, triggeredConsequences1.size()); - - // re-execute the triggered dispatch consequences to simulate rules run on EventHub events queue, dispatch chain count = 1 - // expect 0 new triggered consequences as max allowed chained events is 1, chain interrupted :) - List triggeredConsequences2 = new ArrayList(); - - for (Event event : triggeredConsequences1) { - triggeredConsequences2.addAll(_rulesEngine.evaluateEventWithRules(event, rules)); - } - - assertEquals(0, triggeredConsequences2.size()); - assertEquals(0, _rulesEngine.getDispatchChainedEvents().size()); - } - - @Test - public void - evaluateEventWithRules_When_LoopDispatchConsequence_And_SameEventSeenMultipleTimes_ShouldTriggerDispatchEachTimeOnce() { - // setup - final int repeatTest = 5; - final int expectedConsequencesCount = 15; // 3 consequences x 5 repeats - final String eventType = "test.type"; - final String eventSource = "test.source"; - Event triggerEvent = new Event.Builder("Test loop, same event", eventType, - eventSource).setEventData(new HashMap() { - { - put("hello", "world"); - } - }).build(); - - // add 3 dispatch consequences - List consequences = new ArrayList(); - consequences.add(GetConsequenceEventDispatch(eventType, eventSource, - RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL_ACTION_COPY, null)); - consequences.add(GetConsequenceEventDispatch(eventType, eventSource, - RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL_ACTION_COPY, null)); - consequences.add(GetConsequenceEventDispatch(eventType, eventSource, - RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL_ACTION_NEW, null)); - List rules = new ArrayList() { - { - add(new Rule(new TestableRuleCondition(), consequences)); - } - }; - - // test - List triggeredConsequences = new ArrayList(); - - for (int i = 0; i < repeatTest; i++) { - triggeredConsequences.addAll(_rulesEngine.evaluateEventWithRules(triggerEvent, rules)); - } - - // verify - assertEquals(expectedConsequencesCount, triggeredConsequences.size()); - Map dispatchChainedEvents = _rulesEngine.getDispatchChainedEvents(); - assertEquals(expectedConsequencesCount, dispatchChainedEvents.size()); - - for (int i = 0; i < dispatchChainedEvents.size(); i++) { - assertEquals(1, (int) dispatchChainedEvents.get(triggeredConsequences.get(i).getUniqueIdentifier())); - } - } - - @Test - public void evaluateEventWithRules_When_NewDispatchConsequenceAddedAtRuntime_ShouldTriggerDispatchAgainAsCountResets() { - // setup - final String eventType = "test.type"; - final String eventSource = "test.source"; - Event triggerEvent = new Event.Builder("Test discontinued loop", eventType, - eventSource).setEventData(new HashMap() { - { - put("hello", "world"); - } - }).build(); - - // add 1 dispatch consequences - List consequencesDispatch = new ArrayList(); - consequencesDispatch.add(GetConsequenceEventDispatch(eventType, eventSource, - RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL_ACTION_COPY, null)); - List rulesDispatch = new ArrayList() { - { - add(new Rule(new TestableRuleCondition(), consequencesDispatch)); - } - }; - - // add 1 attachData consequence - List consequencesAttachData = new ArrayList(); - consequencesAttachData.add(GetConsequenceEventAttachData()); - List rulesAttachData = new ArrayList() { - { - add(new Rule(new TestableRuleCondition(), consequencesAttachData)); - } - }; - - // test & verify - // step1: dispatch consequence triggered, dispatch events chain = 1 after this step - List triggeredConsequences = _rulesEngine.evaluateEventWithRules(triggerEvent, rulesDispatch); - assertEquals(1, triggeredConsequences.size()); - Map dispatchChainedEvents = _rulesEngine.getDispatchChainedEvents(); - assertEquals(1, dispatchChainedEvents.size()); - assertEquals(1, (int) dispatchChainedEvents.get(triggeredConsequences.get(0).getUniqueIdentifier())); - Event cachedTriggeredConsequence = triggeredConsequences.get(0); - - // step2: rules updated at runtime, no dispatch consequence, cachedTriggeredConsequence uuid is now removed from dispatchChainedEvents - // expect dispatch events chain = 0 - triggeredConsequences = _rulesEngine.evaluateEventWithRules(cachedTriggeredConsequence, rulesAttachData); - assertEquals(0, triggeredConsequences.size()); - assertEquals(0, _rulesEngine.getDispatchChainedEvents().size()); - - // step3: new dispatch rules added at runtime, cached triggered consequence event is replayed (edge-case) - // expect dispatch events chain = 1, the event is treated as the original event - triggeredConsequences = _rulesEngine.evaluateEventWithRules(cachedTriggeredConsequence, rulesDispatch); - assertEquals(1, triggeredConsequences.size()); - dispatchChainedEvents = _rulesEngine.getDispatchChainedEvents(); - assertEquals(1, dispatchChainedEvents.size()); - assertEquals(1, (int) dispatchChainedEvents.get(triggeredConsequences.get(0).getUniqueIdentifier())); - } - - @Test - public void evaluateEventWithRules_When_MultipleConsequencesIncludingDispatch_ShouldTriggerDispatchOnce() { - // setup - final String eventType = "test.type"; - final String eventSource = "test.source"; - Event triggerEvent = new Event.Builder("Test multiple rules attach first, then dispatch", eventType, - eventSource).setEventData(new HashMap() { - { - put("hello", "world"); - } - }).build(); - - // add 1 attach data and 1 dispatch consequence - List consequencesAttachData = new ArrayList(); - consequencesAttachData.add(GetConsequenceEventAttachData()); - List consequencesDispatch = new ArrayList(); - consequencesDispatch.add(GetConsequenceEventDispatch(eventType, eventSource, - RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL_ACTION_COPY, null)); - - List rules = new ArrayList() { - { - add(new Rule(new TestableRuleCondition(), consequencesAttachData)); - add(new Rule(new TestableRuleCondition(), consequencesDispatch)); - } - }; - - // test & verify - List triggeredConsequences = _rulesEngine.evaluateEventWithRules(triggerEvent, rules); - assertEquals(1, triggeredConsequences.size()); - Map dispatchChainedEvents = _rulesEngine.getDispatchChainedEvents(); - assertEquals(1, dispatchChainedEvents.size()); - assertEquals(1, (int) dispatchChainedEvents.get(triggeredConsequences.get(0).getUniqueIdentifier())); - - - // re-execute the triggered event to simulate EventHub queue, dispatch events chain = 1 - // max chain = 1, do not dispatch again - triggeredConsequences.addAll(_rulesEngine.evaluateEventWithRules(triggeredConsequences.get(0), rules)); - assertEquals(1, triggeredConsequences.size()); - } - - // ================================================================================================================= - // protected List evaluateRuleForEvent(final Event triggerEvent, final Rule rule) - // ================================================================================================================= - - // ================================================================================================================= - // protected void processAttachDataConsequence(final Map consequenceMap, final Event triggeringEvent) - // ================================================================================================================= - @Test - public void processAttachDataConsequence_When_Happy_Then_ShouldAttachDataButNotOverwriteExistingData() throws - Exception { - // setup - Map consequenceMap = GetConsequenceMapFromConsequenceEvent(GetConsequenceEventAttachData()); - Event triggeringEvent = GetTriggeringEvent(); - assertEquals(5, triggeringEvent.getData().size()); - assertFalse(triggeringEvent.getData().containsKey("attachedKey")); - assertEquals("value1", triggeringEvent.getData().optString("&&key1", "")); - assertEquals("value2", triggeringEvent.getData().optString("key2", "")); - assertEquals(552, triggeringEvent.getData().optInteger("anInt", 0)); - assertEquals("embeddedStringValue", - triggeringEvent.getData().getVariantMap("aMap").get("&&embeddedString").convertToString()); - assertEquals("stringInList", triggeringEvent.getData().getVariantList("aList").get(0).convertToString()); - - // test - _rulesEngine.processAttachDataConsequence(consequenceMap, triggeringEvent); - - // verify - final EventData newData = triggeringEvent.getData(); - assertNotNull(newData); - assertEquals(9, newData.size()); - assertTrue(newData.containsKey("attachedKey")); - assertEquals("value1", newData.optString("&&key1", "")); - assertEquals("value2", newData.optString("key2", "")); - assertEquals(552, newData.optInteger("anInt", 0)); - assertEquals(123, newData.optInteger("newInt", 0)); - assertEquals("embeddedStringValue", newData.getVariantMap("aMap").get("&&embeddedString").convertToString()); - assertEquals("newEmbeddedStringValue", newData.getVariantMap("aMap").get("newEmbeddedString").convertToString()); - assertEquals("newMapValue", newData.getVariantMap("newMap").get("newMapKey").convertToString()); - assertEquals("stringInList", newData.getVariantList("aList").get(0).convertToString()); - assertEquals("newStringInList", newData.getVariantList("aList").get(1).convertToString()); - assertEquals("newListString", newData.getVariantList("newList").get(0).convertToString()); - } - - @Test - public void processAttachDataConsequence_When_NullConsequenceEvent_Then_ShouldReturnTriggeringEventData() throws - Exception { - // setup - Event triggeringEvent = GetTriggeringEvent(); - assertEquals(5, triggeringEvent.getData().size()); - assertFalse(triggeringEvent.getData().containsKey("attachedKey")); - assertEquals("value1", triggeringEvent.getData().optString("&&key1", "")); - assertEquals("value2", triggeringEvent.getData().optString("key2", "")); - assertEquals(552, triggeringEvent.getData().optInteger("anInt", 0)); - assertEquals("embeddedStringValue", - triggeringEvent.getData().getVariantMap("aMap").get("&&embeddedString").convertToString()); - assertEquals("stringInList", triggeringEvent.getData().getVariantList("aList").get(0).convertToString()); - - // test - _rulesEngine.processAttachDataConsequence(null, triggeringEvent); - - // verify - assertEquals(5, triggeringEvent.getData().size()); - assertFalse(triggeringEvent.getData().containsKey("attachedKey")); - assertEquals("value1", triggeringEvent.getData().optString("&&key1", "")); - assertEquals("value2", triggeringEvent.getData().optString("key2", "")); - assertEquals(552, triggeringEvent.getData().optInteger("anInt", 0)); - assertEquals("embeddedStringValue", - triggeringEvent.getData().getVariantMap("aMap").get("&&embeddedString").convertToString()); - assertEquals("stringInList", triggeringEvent.getData().getVariantList("aList").get(0).convertToString()); - } - - @Test - public void processAttachDataConsequence_When_EmptyConsequenceEvent_Then_ShouldReturnTriggeringEventData() throws - Exception { - // setup - Event triggeringEvent = GetTriggeringEvent(); - assertEquals(5, triggeringEvent.getData().size()); - assertFalse(triggeringEvent.getData().containsKey("attachedKey")); - assertEquals("value1", triggeringEvent.getData().optString("&&key1", "")); - assertEquals("value2", triggeringEvent.getData().optString("key2", "")); - assertEquals(552, triggeringEvent.getData().optInteger("anInt", 0)); - assertEquals("embeddedStringValue", - triggeringEvent.getData().getVariantMap("aMap").get("&&embeddedString").convertToString()); - assertEquals("stringInList", triggeringEvent.getData().getVariantList("aList").get(0).convertToString()); - - // test - _rulesEngine.processAttachDataConsequence(new HashMap(), triggeringEvent); - - // verify - assertEquals(5, triggeringEvent.getData().size()); - assertFalse(triggeringEvent.getData().containsKey("attachedKey")); - assertEquals("value1", triggeringEvent.getData().optString("&&key1", "")); - assertEquals("value2", triggeringEvent.getData().optString("key2", "")); - assertEquals(552, triggeringEvent.getData().optInteger("anInt", 0)); - assertEquals("embeddedStringValue", - triggeringEvent.getData().getVariantMap("aMap").get("&&embeddedString").convertToString()); - assertEquals("stringInList", triggeringEvent.getData().getVariantList("aList").get(0).convertToString()); - } - - @Test - public void processAttachDataConsequence_When_NullTriggeringEvent_Then_ShouldNoOp() { - // setup - Map consequenceMap = GetConsequenceMapFromConsequenceEvent(GetConsequenceEventAttachData()); - - // test - _rulesEngine.processAttachDataConsequence(consequenceMap, null); - - // verify - // no-op - } - - @Test - public void processAttachDataConsequence_When_ConsequenceMapHasNoDetail_Then_ShouldReturnTriggeringEventData() throws - Exception { - // setup - Map consequenceMap = GetConsequenceMapFromConsequenceEvent(GetConsequenceEventAttachData()); - consequenceMap.remove(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL); - Event triggeringEvent = GetTriggeringEvent(); - assertEquals(5, triggeringEvent.getData().size()); - assertFalse(triggeringEvent.getData().containsKey("attachedKey")); - assertEquals("value1", triggeringEvent.getData().optString("&&key1", "")); - assertEquals("value2", triggeringEvent.getData().optString("key2", "")); - assertEquals(552, triggeringEvent.getData().optInteger("anInt", 0)); - assertEquals("embeddedStringValue", - triggeringEvent.getData().getVariantMap("aMap").get("&&embeddedString").convertToString()); - assertEquals("stringInList", triggeringEvent.getData().getVariantList("aList").get(0).convertToString()); - - // test - _rulesEngine.processAttachDataConsequence(consequenceMap, triggeringEvent); - - // verify - assertEquals(5, triggeringEvent.getData().size()); - assertFalse(triggeringEvent.getData().containsKey("attachedKey")); - assertEquals("value1", triggeringEvent.getData().optString("&&key1", "")); - assertEquals("value2", triggeringEvent.getData().optString("key2", "")); - assertEquals(552, triggeringEvent.getData().optInteger("anInt", 0)); - assertEquals("embeddedStringValue", - triggeringEvent.getData().getVariantMap("aMap").get("&&embeddedString").convertToString()); - assertEquals("stringInList", triggeringEvent.getData().getVariantList("aList").get(0).convertToString()); - } - - @Test - public void - processAttachDataConsequence_When_ConsequenceMapHasNoEventDataInDetails_Then_ShouldReturnTriggeringEventData() throws - Exception { - // setup - Map consequenceMap = GetConsequenceMapFromConsequenceEvent(GetConsequenceEventAttachData()); - Map newDetailMap = consequenceMap.get( - RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL).getVariantMap(); - newDetailMap.remove(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL_EVENT_DATA); - consequenceMap.remove(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL, Variant.fromVariantMap(newDetailMap)); - Event triggeringEvent = GetTriggeringEvent(); - assertEquals(5, triggeringEvent.getData().size()); - assertFalse(triggeringEvent.getData().containsKey("attachedKey")); - assertEquals("value1", triggeringEvent.getData().optString("&&key1", "")); - assertEquals("value2", triggeringEvent.getData().optString("key2", "")); - assertEquals(552, triggeringEvent.getData().optInteger("anInt", 0)); - assertEquals("embeddedStringValue", - triggeringEvent.getData().getVariantMap("aMap").get("&&embeddedString").convertToString()); - assertEquals("stringInList", triggeringEvent.getData().getVariantList("aList").get(0).convertToString()); - - // test - _rulesEngine.processAttachDataConsequence(consequenceMap, triggeringEvent); - - // verify - assertEquals(5, triggeringEvent.getData().size()); - assertFalse(triggeringEvent.getData().containsKey("attachedKey")); - assertEquals("value1", triggeringEvent.getData().optString("&&key1", "")); - assertEquals("value2", triggeringEvent.getData().optString("key2", "")); - assertEquals(552, triggeringEvent.getData().optInteger("anInt", 0)); - assertEquals("embeddedStringValue", - triggeringEvent.getData().getVariantMap("aMap").get("&&embeddedString").convertToString()); - assertEquals("stringInList", triggeringEvent.getData().getVariantList("aList").get(0).convertToString()); - } - - // ================================================================================================================= - // protected void processModifyDataConsequence(final Map consequenceMap, final Event triggeringEvent) - // ================================================================================================================= - @Test - public void processModifyDataConsequence_When_Happy_Then_ShouldAttachDataButNotOverwriteExistingData() throws - Exception { - // setup - Map consequenceMap = GetConsequenceMapFromConsequenceEvent(GetConsequenceEventModifyData()); - Event triggeringEvent = GetTriggeringEvent(); - - assertEquals(5, triggeringEvent.getData().size()); - assertFalse(triggeringEvent.getData().containsKey("attachedKey")); - assertEquals("value1", triggeringEvent.getData().optString("&&key1", "")); - assertEquals("value2", triggeringEvent.getData().optString("key2", "")); - assertEquals(552, triggeringEvent.getData().optInteger("anInt", 0)); - assertEquals("embeddedStringValue", - triggeringEvent.getData().getVariantMap("aMap").get("&&embeddedString").convertToString()); - assertEquals("stringInList", triggeringEvent.getData().getVariantList("aList").get(0).convertToString()); - - // test - _rulesEngine.processModifyDataConsequence(consequenceMap, triggeringEvent); - - // verify - final EventData newData = triggeringEvent.getData(); - assertNotNull(newData); - assertEquals(2, newData.size()); - assertFalse(newData.containsKey("attachedKey")); - assertFalse(triggeringEvent.getData().containsKey("value1")); - assertEquals("value2", newData.optString("key2", "")); - assertEquals(552, newData.optInteger("anInt", 0)); - assertFalse(newData.containsKey("aMap")); - assertFalse(newData.containsKey("aList")); - } - - @Test - public void processModifyDataConsequence_With_ListOfObjects_ShouldAttachDataButNotOverwriteExistingData() throws - Exception { - // setup - Map consequenceMap = GetConsequenceMapFromConsequenceEvent(GetConsequenceEventModifyData()); - Event triggeringEvent = GetEventWithListOfObjects(); - - assertEquals(6, triggeringEvent.getData().size()); - assertFalse(triggeringEvent.getData().containsKey("attachedKey")); - assertEquals("value1", triggeringEvent.getData().optString("&&key1", "")); - assertEquals("value2", triggeringEvent.getData().optString("key2", "")); - assertEquals(552, triggeringEvent.getData().optInteger("anInt", 0)); - assertEquals("embeddedStringValue", - triggeringEvent.getData().getVariantMap("aMap").get("&&embeddedString").convertToString()); - assertEquals("stringInList", triggeringEvent.getData().getVariantList("aList").get(0).convertToString()); - - // test - _rulesEngine.processModifyDataConsequence(consequenceMap, triggeringEvent); - - // verify - final EventData newData = triggeringEvent.getData(); - assertNotNull(newData); - assertEquals(3, newData.size()); - assertFalse(newData.containsKey("attachedKey")); - assertFalse(triggeringEvent.getData().containsKey("value1")); - assertEquals("value2", newData.optString("key2", "")); - assertEquals(552, newData.optInteger("anInt", 0)); - assertFalse(newData.containsKey("aMap")); - assertFalse(newData.containsKey("aList")); - assertEquals("orange", newData.optVariantList("listOfObjects", - null).get(0).optVariantMap(null).get("details").optVariantMap(null).get("color").optString(null)); - assertTrue(newData.optVariantList("listOfObjects", null).get(1).optVariantMap(null).containsKey("details")); - } - - @Test - public void processModifyDataConsequence_When_NullConsequenceEvent() throws Exception { - Event triggeringEvent = GetTriggeringEvent(); - assertEquals(5, triggeringEvent.getData().size()); - _rulesEngine.processModifyDataConsequence(null, triggeringEvent); - assertEquals(5, triggeringEvent.getData().size()); - } - - @Test - public void processModifyDataConsequence_When_NullConsequenceEvent_Then_ShouldReturnTriggeringEventData() throws - Exception { - // setup - Event triggeringEvent = GetTriggeringEvent(); - assertEquals(5, triggeringEvent.getData().size()); - assertFalse(triggeringEvent.getData().containsKey("attachedKey")); - assertEquals("value1", triggeringEvent.getData().optString("&&key1", "")); - assertEquals("value2", triggeringEvent.getData().optString("key2", "")); - assertEquals(552, triggeringEvent.getData().optInteger("anInt", 0)); - assertEquals("embeddedStringValue", - triggeringEvent.getData().getVariantMap("aMap").get("&&embeddedString").convertToString()); - assertEquals("stringInList", triggeringEvent.getData().getVariantList("aList").get(0).convertToString()); - - // test - _rulesEngine.processModifyDataConsequence(null, triggeringEvent); - - // verify - assertEquals(5, triggeringEvent.getData().size()); - assertFalse(triggeringEvent.getData().containsKey("attachedKey")); - assertEquals("value1", triggeringEvent.getData().optString("&&key1", "")); - assertEquals("value2", triggeringEvent.getData().optString("key2", "")); - assertEquals(552, triggeringEvent.getData().optInteger("anInt", 0)); - assertEquals("embeddedStringValue", - triggeringEvent.getData().getVariantMap("aMap").get("&&embeddedString").convertToString()); - assertEquals("stringInList", triggeringEvent.getData().getVariantList("aList").get(0).convertToString()); - } - - @Test - public void processModifyDataConsequence_When_EmptyConsequenceEvent_Then_ShouldReturnTriggeringEventData() throws - Exception { - // setup - Event triggeringEvent = GetTriggeringEvent(); - assertEquals(5, triggeringEvent.getData().size()); - assertFalse(triggeringEvent.getData().containsKey("attachedKey")); - assertEquals("value1", triggeringEvent.getData().optString("&&key1", "")); - assertEquals("value2", triggeringEvent.getData().optString("key2", "")); - assertEquals(552, triggeringEvent.getData().optInteger("anInt", 0)); - assertEquals("embeddedStringValue", - triggeringEvent.getData().getVariantMap("aMap").get("&&embeddedString").convertToString()); - assertEquals("stringInList", triggeringEvent.getData().getVariantList("aList").get(0).convertToString()); - - // test - _rulesEngine.processModifyDataConsequence(new HashMap(), triggeringEvent); - - // verify - assertEquals(5, triggeringEvent.getData().size()); - assertFalse(triggeringEvent.getData().containsKey("attachedKey")); - assertEquals("value1", triggeringEvent.getData().optString("&&key1", "")); - assertEquals("value2", triggeringEvent.getData().optString("key2", "")); - assertEquals(552, triggeringEvent.getData().optInteger("anInt", 0)); - assertEquals("embeddedStringValue", - triggeringEvent.getData().getVariantMap("aMap").get("&&embeddedString").convertToString()); - assertEquals("stringInList", triggeringEvent.getData().getVariantList("aList").get(0).convertToString()); - } - - @Test - public void processModifyDataConsequence_When_NullTriggeringEvent_Then_ShouldNoOp() { - // setup - Map consequenceMap = GetConsequenceMapFromConsequenceEvent(GetConsequenceEventModifyData()); - - // test - _rulesEngine.processAttachDataConsequence(consequenceMap, null); - - // verify - // no-op - } - - @Test - public void processModifyDataConsequence_When_ConsequenceMapHasNoDetail_Then_ShouldReturnTriggeringEventData() throws - Exception { - // setup - Map consequenceMap = GetConsequenceMapFromConsequenceEvent(GetConsequenceEventModifyData()); - consequenceMap.remove(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL); - Event triggeringEvent = GetTriggeringEvent(); - assertEquals(5, triggeringEvent.getData().size()); - assertFalse(triggeringEvent.getData().containsKey("attachedKey")); - assertEquals("value1", triggeringEvent.getData().optString("&&key1", "")); - assertEquals("value2", triggeringEvent.getData().optString("key2", "")); - assertEquals(552, triggeringEvent.getData().optInteger("anInt", 0)); - assertEquals("embeddedStringValue", - triggeringEvent.getData().getVariantMap("aMap").get("&&embeddedString").convertToString()); - assertEquals("stringInList", triggeringEvent.getData().getVariantList("aList").get(0).convertToString()); - - // test - _rulesEngine.processModifyDataConsequence(consequenceMap, triggeringEvent); - - // verify - assertEquals(5, triggeringEvent.getData().size()); - assertFalse(triggeringEvent.getData().containsKey("attachedKey")); - assertEquals("value1", triggeringEvent.getData().optString("&&key1", "")); - assertEquals("value2", triggeringEvent.getData().optString("key2", "")); - assertEquals(552, triggeringEvent.getData().optInteger("anInt", 0)); - assertEquals("embeddedStringValue", - triggeringEvent.getData().getVariantMap("aMap").get("&&embeddedString").convertToString()); - assertEquals("stringInList", triggeringEvent.getData().getVariantList("aList").get(0).convertToString()); - } - - @Test - public void - processModifyDataConsequence_When_ConsequenceMapHasNoEventDataInDetails_Then_ShouldReturnTriggeringEventData() throws - Exception { - // setup - Map consequenceMap = GetConsequenceMapFromConsequenceEvent(GetConsequenceEventModifyData()); - Map newDetailMap = consequenceMap.get( - RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL).getVariantMap(); - newDetailMap.remove(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL_EVENT_DATA); - consequenceMap.remove(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL, Variant.fromVariantMap(newDetailMap)); - Event triggeringEvent = GetTriggeringEvent(); - assertEquals(5, triggeringEvent.getData().size()); - assertFalse(triggeringEvent.getData().containsKey("attachedKey")); - assertEquals("value1", triggeringEvent.getData().optString("&&key1", "")); - assertEquals("value2", triggeringEvent.getData().optString("key2", "")); - assertEquals(552, triggeringEvent.getData().optInteger("anInt", 0)); - assertEquals("embeddedStringValue", - triggeringEvent.getData().getVariantMap("aMap").get("&&embeddedString").convertToString()); - assertEquals("stringInList", triggeringEvent.getData().getVariantList("aList").get(0).convertToString()); - - // test - _rulesEngine.processModifyDataConsequence(consequenceMap, triggeringEvent); - - // verify - assertEquals(5, triggeringEvent.getData().size()); - assertFalse(triggeringEvent.getData().containsKey("attachedKey")); - assertEquals("value1", triggeringEvent.getData().optString("&&key1", "")); - assertEquals("value2", triggeringEvent.getData().optString("key2", "")); - assertEquals(552, triggeringEvent.getData().optInteger("anInt", 0)); - assertEquals("embeddedStringValue", - triggeringEvent.getData().getVariantMap("aMap").get("&&embeddedString").convertToString()); - assertEquals("stringInList", triggeringEvent.getData().getVariantList("aList").get(0).convertToString()); - } - - // ================================================================================================================= - // protected Event processDispatchConsequence(final Map consequence, final Event triggeringEvent) - // ================================================================================================================= - @Test - public void processDispatchConsequence_When_Copy_Happy_Then_ShouldReturnNewEvent() throws - Exception { - // setup - String testEventType = "new.type.dispatched"; - String testEventSource = "new.source.dispatched"; - Map consequenceMap = GetConsequenceMapFromConsequenceEvent(GetConsequenceEventDispatch(testEventType, - testEventSource, RulesEngineConstants.EventDataKeys.CONSEQUENCE_DETAIL_ACTION_COPY, null)); - Event triggeringEvent = GetTriggeringEvent(); - // mock other Event properties - triggeringEvent.setPairId("abcPairId"); - triggeringEvent.setEventNumber(10); - Thread.sleep(5); // sleep 5ms to avoid having same timestamp for the result event - - // test - Event result = _rulesEngine.processDispatchConsequence(consequenceMap, triggeringEvent, 0); - - // verify a new event was created with same data, new type and source - assertEquals(testEventType, result.getType()); - assertEquals(testEventSource, result.getSource()); - assertEquals(triggeringEvent.getEventData(), result.getEventData()); - assertEquals(0, result.getEventNumber()); // event not dispatched yet, default 0 - assertNull(result.getPairID()); // pair ID not copied from the original event - assertNotEquals(triggeringEvent.getResponsePairID(), result.getResponsePairID()); // new response pair ID is generated - assertNotEquals(triggeringEvent.getTimestamp(), result.getTimestamp()); - } - - @Test - public void processDispatchConsequence_When_NewEmptyData_Happy_Then_ShouldReturnNewEvent() throws - Exception { - // setup - String testEventType = "new.type.dispatched"; - String testEventSource = "new.source.dispatched"; - Map consequenceMap = GetConsequenceMapFromConsequenceEvent(GetConsequenceEventDispatch(testEventType, - testEventSource, RulesEngineConstants.EventDataKeys.CONSEQUENCE_DETAIL_ACTION_NEW, null)); - Event triggeringEvent = GetTriggeringEvent(); - // mock other Event properties - triggeringEvent.setPairId("abcPairId"); - triggeringEvent.setEventNumber(10); - Thread.sleep(5); // sleep 5ms to avoid having same timestamp for the result event - - // test - Event result = _rulesEngine.processDispatchConsequence(consequenceMap, triggeringEvent, 0); - - // verify a new event was created with empty data, new type and source - assertEquals(testEventType, result.getType()); - assertEquals(testEventSource, result.getSource()); - assertEquals(0, result.getEventData().size()); - assertEquals(0, result.getEventNumber()); // event not dispatched yet, default 0 - assertNull(result.getPairID()); // pair ID not copied from the original event - assertNotEquals(triggeringEvent.getResponsePairID(), result.getResponsePairID()); // new response pair ID is generated - assertNotEquals(triggeringEvent.getTimestamp(), result.getTimestamp()); - } - - @Test - public void processDispatchConsequence_When_NewWithData_Happy_Then_ShouldReturnNewEvent() throws - Exception { - // setup - String testEventType = "new.type.dispatched"; - String testEventSource = "new.source.dispatched"; - Map data = new HashMap(); - data.put("key1", Variant.fromString("value1")); - data.put("key2", Variant.fromInteger(2)); - List stringList = new ArrayList() { - { - add("a"); - add("b"); - add(null); - } - }; - data.put("key3", Variant.fromStringList(stringList)); - Map objectMap = new HashMap() { - { - put("level2key1", "value1"); - put("level2key2", "value2"); - put("level2key3", 3); - } - }; - data.put("key4", Variant.fromTypedMap(objectMap, PermissiveVariantSerializer.DEFAULT_INSTANCE)); - Map consequenceMap = GetConsequenceMapFromConsequenceEvent(GetConsequenceEventDispatch(testEventType, - testEventSource, RulesEngineConstants.EventDataKeys.CONSEQUENCE_DETAIL_ACTION_NEW, data)); - Event triggeringEvent = GetTriggeringEvent(); - // mock other Event properties - triggeringEvent.setPairId("abcPairId"); - triggeringEvent.setEventNumber(10); - Thread.sleep(5); // sleep 5ms to avoid having same timestamp for the result event - - // test - Event result = _rulesEngine.processDispatchConsequence(consequenceMap, triggeringEvent, 0); - - // verify a new event was created with new data, new type and source - assertEquals(testEventType, result.getType()); - assertEquals(testEventSource, result.getSource()); - assertEquals(4, result.getEventData().size()); - assertEquals("value1", result.getData().optString("key1", "")); - assertEquals(2, result.getData().optInteger("key2", 0)); - assertEquals(stringList, result.getData().optStringList("key3", null)); - assertEquals(Variant.toVariantMap(objectMap), result.getData().optVariantMap("key4", null)); - assertEquals(0, result.getEventNumber()); // event not dispatched yet, default 0 - assertNull(result.getPairID()); // pair ID not copied from the original event - assertNotEquals(triggeringEvent.getResponsePairID(), result.getResponsePairID()); // new response pair ID is generated - assertNotEquals(triggeringEvent.getTimestamp(), result.getTimestamp()); - - // verify event data is copied - data.remove("key2"); - assertEquals(4, result.getEventData().size()); - } - - @Test - public void processDispatchConsequence_When_NullConsequenceEvent_Then_ShouldReturnNull() throws - Exception { - // setup - Event triggeringEvent = GetTriggeringEvent(); - - // test - Event result = _rulesEngine.processDispatchConsequence(null, triggeringEvent, 0); - - // verify - assertNull(result); - } - - @Test - public void processDispatchConsequence_When_EmptyConsequenceEvent_Then_ShouldReturnNull() throws - Exception { - // setup - Event triggeringEvent = GetTriggeringEvent(); - - // test - Event result = _rulesEngine.processDispatchConsequence(new HashMap(), triggeringEvent, 0); - - // verify - assertNull(result); - } - - @Test - public void processDispatchConsequence_When_NullTriggeringEvent_Then_ShouldReturnNull() { - // setup - String testEventType = "new.type.dispatched"; - String testEventSource = "new.source.dispatched"; - Map consequenceMap = GetConsequenceMapFromConsequenceEvent(GetConsequenceEventDispatch(testEventType, - testEventSource, RulesEngineConstants.EventDataKeys.CONSEQUENCE_DETAIL_ACTION_COPY, null)); - - // test - Event result = _rulesEngine.processDispatchConsequence(consequenceMap, null, 0); - - // verify - assertNull(result); - } - - @Test - public void processDispatchConsequence_When_InvalidDispatchConsequenceTypeSource_Then_ShouldReturnNull() throws - Exception { - // setup - String testEventType = "new.type.dispatched"; - String testEventSource = "new.source.dispatched"; - Event triggeringEvent = GetTriggeringEvent(); - - // test & verify - detail missing - Map consequenceMap = GetConsequenceMapFromConsequenceEvent(GetConsequenceEventDispatch(testEventType, - testEventSource, RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL_ACTION_COPY, null)); - consequenceMap.remove("detail"); - Event result = _rulesEngine.processDispatchConsequence(consequenceMap, triggeringEvent, 0); - assertNull(result); - - // test & verify - event type missing - consequenceMap = GetConsequenceMapFromConsequenceEvent(GetConsequenceEventDispatch(null, testEventSource, - RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL_ACTION_COPY, null)); - result = _rulesEngine.processDispatchConsequence(consequenceMap, triggeringEvent, 0); - assertNull(result); - - // test & verify - event type empty - consequenceMap = GetConsequenceMapFromConsequenceEvent(GetConsequenceEventDispatch("", testEventSource, - RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL_ACTION_COPY, null)); - result = _rulesEngine.processDispatchConsequence(consequenceMap, triggeringEvent, 0); - assertNull(result); - - // test & verify - event source missing - consequenceMap = GetConsequenceMapFromConsequenceEvent(GetConsequenceEventDispatch(testEventType, null, - RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL_ACTION_COPY, null)); - result = _rulesEngine.processDispatchConsequence(consequenceMap, triggeringEvent, 0); - assertNull(result); - - // test & verify - event source empty - consequenceMap = GetConsequenceMapFromConsequenceEvent(GetConsequenceEventDispatch(testEventType, "", - RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL_ACTION_COPY, null)); - result = _rulesEngine.processDispatchConsequence(consequenceMap, triggeringEvent, 0); - assertNull(result); - } - - @Test - public void processDispatchConsequence_When_InvalidDispatchType_Then_ShouldReturnNull() throws - Exception { - // setup - String testEventType = "new.type.dispatched"; - String testEventSource = "new.source.dispatched"; - Event triggeringEvent = GetTriggeringEvent(); - - // test & verify - eventdataaction missing - Map consequenceMap = GetConsequenceMapFromConsequenceEvent(GetConsequenceEventDispatch(testEventType, - testEventSource, null, null)); - Event result = _rulesEngine.processDispatchConsequence(consequenceMap, triggeringEvent, 0); - assertNull(result); - - // test & verify - eventdataaction unknown - consequenceMap = GetConsequenceMapFromConsequenceEvent(GetConsequenceEventDispatch(testEventType, - testEventSource, "unknown", null)); - result = _rulesEngine.processDispatchConsequence(consequenceMap, triggeringEvent, 0); - assertNull(result); - } - - @Test - public void processDispatchConsequence_When_MetMaxChainLength_Then_ShouldReturnNull() throws - Exception { - // setup - String testEventType = "new.type.dispatched"; - String testEventSource = "new.source.dispatched"; - Map consequenceMap = GetConsequenceMapFromConsequenceEvent(GetConsequenceEventDispatch(testEventType, - testEventSource, RulesEngineConstants.EventDataKeys.CONSEQUENCE_DETAIL_ACTION_NEW, null)); - Event triggeringEvent = GetTriggeringEvent(); - - // test&verify - Event result = _rulesEngine.processDispatchConsequence(consequenceMap, triggeringEvent, - RulesEngineConstants.MAX_CHAINED_CONSEQUENCE_COUNT); - assertNull(result); - - result = _rulesEngine.processDispatchConsequence(consequenceMap, triggeringEvent, - RulesEngineConstants.MAX_CHAINED_CONSEQUENCE_COUNT + 1); - assertNull(result); - } - - // ================================================================================================================= - // protected EventData getTokenExpandedEventData(final EventData eventData, final Event triggerEvent) - // ================================================================================================================= - - // ================================================================================================================= - // protected Map getTokenExpandedMap(final Map mapWithTokens, final Event event) - // ================================================================================================================= - @Test - public void getTokenExpanedMapHappyNested() { - // setup - String keyInTriggeringEvent = "\"{%aMap.&&embeddedString%}\""; - String valueForKey1 = "embeddedStringValue"; - Map variantMap = new HashMap(); - variantMap.put("igotreplaced", keyInTriggeringEvent); - - Map consequenceDefinitionMap = new HashMap(); - consequenceDefinitionMap.put("maptype", variantMap); - - // test - final Map result = _rulesEngine.getTokenExpandedMap(consequenceDefinitionMap, GetTriggeringEvent()); - - // verify - assertEquals(1, result.size()); - Map returnedMap = (Map) result.get("maptype"); - assertEquals("\"" + valueForKey1 + "\"", (String) returnedMap.get("igotreplaced")); - } - - - // ================================================================================================================= - // protected List getTokenExpandedList(final List listWithTokens, final Event event) - // ================================================================================================================= - - - // ================================================================================================================= - // Private helpers for this test - // ================================================================================================================= - private void setEmptyConsequenceFor(final MockRule mockRule) { - EventData eventData = new EventData(); - Map consequenceMap = new HashMap(); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_JSON_ID, Variant.fromString("analyticsId")); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_JSON_TYPE, Variant.fromString("")); - Map detailMap = new HashMap(); - detailMap.put("key3", "{%&&key1%}"); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL, Variant.fromStringMap(detailMap)); - eventData.putVariantMap(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_TRIGGERED, consequenceMap); - - final Event eventWithNoConsequenceType = new Event.Builder("Test", EventType.ANALYTICS, - EventSource.REQUEST_CONTENT).setData(eventData).build(); - final ArrayList mockEvents = new ArrayList(); - mockEvents.add(eventWithNoConsequenceType); - mockRule.setConsequenceEvents(mockEvents); - } - - private void setNoTypeConsequenceFor(final MockRule mockRule) { - EventData eventData = new EventData(); - Map consequenceMap = new HashMap(); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_JSON_ID, Variant.fromString("analyticsId")); - Map detailMap = new HashMap(); - detailMap.put("key3", "{%&&key1%}"); - consequenceMap.put(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_DETAIL, Variant.fromStringMap(detailMap)); - eventData.putVariantMap(RulesEngineConstantsTests.EventDataKeys.CONSEQUENCE_TRIGGERED, consequenceMap); - - final Event eventWithNoConsequenceType = new Event.Builder("Test", EventType.ANALYTICS, - EventSource.REQUEST_CONTENT).setData(eventData).build(); - - final ArrayList mockEvents = new ArrayList(); - mockEvents.add(eventWithNoConsequenceType); - mockRule.setConsequenceEvents(mockEvents); - } -} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt new file mode 100644 index 000000000..51d0321cc --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt @@ -0,0 +1,145 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.launch.rulesengine + +import com.adobe.marketing.mobile.Event +import com.adobe.marketing.mobile.MobileCore +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.any +import org.mockito.BDDMockito +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.powermock.api.mockito.PowerMockito +import org.powermock.core.classloader.annotations.PrepareForTest +import org.powermock.modules.junit4.PowerMockRunner +import org.powermock.reflect.Whitebox + +@RunWith(PowerMockRunner::class) +@PrepareForTest(MobileCore::class) +class LaunchRulesEvaluatorTests { + + @Mock + lateinit var launchRulesEngine: LaunchRulesEngine + + @Before + fun setup() { + PowerMockito.mockStatic(MobileCore::class.java) + } + + @Test + fun `Process a null event`() { + val launchRulesEvaluator = LaunchRulesEvaluator("", launchRulesEngine) + val cachedEvents: MutableList = mutableListOf() + Whitebox.setInternalState(launchRulesEvaluator, "cachedEvents", cachedEvents) + assertNull(launchRulesEvaluator.process(null)) + verify(launchRulesEngine, never()).process(any()) + assertEquals(0, cachedEvents.size) + } + + @Test + fun `Cache incoming events if rules are not set`() { + val launchRulesEvaluator = LaunchRulesEvaluator("", launchRulesEngine) + val cachedEvents: MutableList = mutableListOf() + Whitebox.setInternalState(launchRulesEvaluator, "cachedEvents", cachedEvents) + repeat(100) { + launchRulesEvaluator.process( + Event.Builder("event-$it", "type", "source").build() + ) + } + assertEquals(100, cachedEvents.size) + } + + @Test + fun `Clear cached events if reached the limit`() { + val launchRulesEvaluator = LaunchRulesEvaluator("", launchRulesEngine) + val cachedEvents: MutableList = mutableListOf() + Whitebox.setInternalState(launchRulesEvaluator, "cachedEvents", cachedEvents) + repeat(101) { + launchRulesEvaluator.process( + Event.Builder("event-$it", "type", "source").build() + ) + } + assertEquals(0, cachedEvents.size) + } + + @Test + fun `Reprocess cached events when rules are ready`() { + val launchRulesEvaluator = LaunchRulesEvaluator("", launchRulesEngine) + val cachedEvents: MutableList = mutableListOf() + Whitebox.setInternalState(launchRulesEvaluator, "cachedEvents", cachedEvents) + repeat(10) { + launchRulesEvaluator.process( + Event.Builder("event-$it", "type", "source").build() + ) + } + assertEquals(10, cachedEvents.size) + val eventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) + BDDMockito.given(MobileCore.dispatchEvent(eventCaptor.capture(), any())).willReturn(true) + launchRulesEvaluator.replaceRules(listOf()) + assertNotNull(eventCaptor.value) + assertEquals("com.adobe.eventtype.rulesengine", eventCaptor.value.type) + assertEquals("com.adobe.eventsource.requestreset", eventCaptor.value.source) + launchRulesEvaluator.process(eventCaptor.value) + assertEquals(0, cachedEvents.size) + } + + @Test + fun `Reprocess cached events in the right order`() { + val launchRulesEvaluator = LaunchRulesEvaluator("", launchRulesEngine) + val cachedEvents: MutableList = mutableListOf() + Whitebox.setInternalState(launchRulesEvaluator, "cachedEvents", cachedEvents) + repeat(10) { + launchRulesEvaluator.process( + Event.Builder("event-$it", "type", "source").build() + ) + } + assertEquals(10, cachedEvents.size) + val eventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) + BDDMockito.given(MobileCore.dispatchEvent(eventCaptor.capture(), any())).willReturn(true) + launchRulesEvaluator.replaceRules(listOf()) + assertNotNull(eventCaptor.value) + assertEquals("com.adobe.eventtype.rulesengine", eventCaptor.value.type) + assertEquals("com.adobe.eventsource.requestreset", eventCaptor.value.source) + Mockito.reset(launchRulesEngine) + launchRulesEvaluator.process(eventCaptor.value) + val cachedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) + verify(launchRulesEngine, Mockito.times(10)).process(cachedEventCaptor.capture()) + assertEquals(10, cachedEventCaptor.allValues.size) + cachedEventCaptor.allValues.forEachIndexed { index, element -> + assertEquals("event-$index", element.name) + } + assertEquals(0, cachedEvents.size) + } + + @Test + fun `Do nothing if set null rule`() { + val launchRulesEvaluator = LaunchRulesEvaluator("", launchRulesEngine) + val cachedEvents: MutableList = mutableListOf() + Whitebox.setInternalState(launchRulesEvaluator, "cachedEvents", cachedEvents) + repeat(10) { + launchRulesEvaluator.process( + Event.Builder("event-$it", "type", "source").build() + ) + } + assertEquals(10, cachedEvents.size) + launchRulesEvaluator.replaceRules(null) + PowerMockito.verifyNoMoreInteractions(MobileCore::class.java) + assertEquals(10, cachedEvents.size) + } +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParserTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParserTests.kt index ef2d71507..dfc761eac 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParserTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParserTests.kt @@ -25,8 +25,9 @@ class JSONRulesParserTests { @Test fun testNormal() { - val fileTxt = this::class.java.classLoader.getResource("rules_parser/launch_rule_root.json") - .readText() + val fileTxt = this::class.java.classLoader?.getResource("rules_parser/launch_rule_root.json") + ?.readText() + assertNotNull(fileTxt) val result = JSONRulesParser.parse(fileTxt) assertNotNull(result) assertEquals(1, result.size) From ef8b7371b9d093f05057fc77ef58020d0be94c9e Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Tue, 17 May 2022 11:13:22 -0700 Subject: [PATCH 050/476] [#29] Mark TODO for immutable param in set*SharedState() and kdoc changes --- .../com/adobe/marketing/mobile/internal/eventhub/EventHub.kt | 1 + .../marketing/mobile/internal/eventhub/ExtensionContainer.kt | 2 ++ 2 files changed, 3 insertions(+) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index fe2238275..ad15bacb5 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -106,6 +106,7 @@ internal class EventHub { /** * Sets the shared state for the extension - [extensionName] with [data] + * TODO : Make the [data] parameter immutable when EventData#toImmutableMap() is implemented. * * @param sharedStateType the type of shared state that needs to be set. * @param extensionName the name of the extension for which the state is being set diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt index 941c5f379..200278d1a 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt @@ -83,6 +83,7 @@ internal class ExtensionRuntime() : ExtensionApi() { errorCallback: ExtensionErrorCallback? ): Boolean { try { + // TODO : Convert the [state] parameter to be immutable before propagating when EventData#toImmutableMap() is implemented. return EventHub.shared.setSharedState(SharedStateType.XDM, extensionName, state, event, errorCallback) } catch (exception: Exception) { MobileCore.log(LoggingMode.ERROR, getTag(), @@ -250,6 +251,7 @@ internal class ExtensionContainer constructor( /** * Clears the shares states of type [sharedStateType] for this extension. + * @param sharedStateType the type of shared state that needs to be cleared * * @return false if an exception occurs clearing the state or if the extension is unregistered, * true otherwise. From ec3a0b89d93566e728120c824901d204a5b71c3d Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Thu, 19 May 2022 20:16:29 -0700 Subject: [PATCH 051/476] PR review comments --- .../mobile/internal/utility/MapExtensions.kt | 13 ++------- .../launch/rulesengine/LaunchRulesEngine.java | 4 ++- .../launch/rulesengine/LaunchTokenFinder.kt | 29 ++++++++++--------- .../marketing/mobile/LaunchTokenFinderTest.kt | 10 +++---- .../internal/utility/MapExtensionsTests.kt | 26 ++++++++--------- 5 files changed, 39 insertions(+), 43 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt index 94f85d73b..f22e657b3 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt @@ -105,7 +105,7 @@ internal fun Map.serializeToQueryString(): String { val builder = StringBuilder() for ((key, value) in this.entries) { val encodedKey = urlEncode(key) ?: continue - var encodedValue: String? = null + var encodedValue: String? // TODO add serializing for custom objects if (value is List<*>) { @@ -120,7 +120,7 @@ internal fun Map.serializeToQueryString(): String { } } - return builder.toString() + return builder.substring(1).toString() } private fun Set<*>.isAllString(): Boolean { @@ -143,14 +143,7 @@ private fun serializeKeyValuePair(key: String?, value: String?): String? { if (key == null || value == null || key.isEmpty()) { return null } - - val builder = StringBuilder() - builder.append("&") - builder.append(key) - builder.append("=") - builder.append(value) - - return builder.toString() + return "&$key=$value" } /** diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngine.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngine.java index 8937733e9..f41bb9bef 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngine.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngine.java @@ -20,6 +20,7 @@ public class LaunchRulesEngine { private final RulesEngine ruleRulesEngine; + // TODO pass in extensionApi to the constructor @SuppressWarnings("rawtypes") public LaunchRulesEngine() { ruleRulesEngine = new RulesEngine<>(new ConditionEvaluator(), LaunchRuleTransformer.INSTANCE.createTransforming()); @@ -39,7 +40,8 @@ public void replaceRules(final List rules) { * @return the processed {@link Event} */ public Event process(final Event event) { - ruleRulesEngine.evaluate(new LaunchTokenFinder(event)); + // TODO pass in extensionApi in call to LaunchTokenFinder + // ruleRulesEngine.evaluate(new LaunchTokenFinder(event)); return event; } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt index a7864f915..a68bd2395 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt @@ -20,10 +20,11 @@ import com.adobe.marketing.mobile.internal.utility.TimeUtil import com.adobe.marketing.mobile.internal.utility.flattening import com.adobe.marketing.mobile.internal.utility.getFlattenedDataMap import com.adobe.marketing.mobile.internal.utility.serializeToQueryString +import com.adobe.marketing.mobile.rulesengine.TokenFinder import java.security.SecureRandom import org.json.JSONObject -internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionApi) { +internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionApi) : TokenFinder { companion object { private const val LOG_TAG = "LaunchTokenFinder" @@ -58,7 +59,7 @@ internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionAp * * @return [Any] containing value to be substituted for the [key], null if the key does not exist */ - fun get(key: String): Any? { + override fun get(key: String): Any? { if (StringUtils.isNullOrEmpty(key)) { return null } @@ -69,9 +70,7 @@ internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionAp KEY_TIMESTAMP_UNIX -> TimeUtil.getUnixTimeInSeconds().toString() KEY_TIMESTAMP_ISO8601 -> TimeUtil.getIso8601Date() KEY_TIMESTAMP_PLATFORM -> TimeUtil.getIso8601DateTimeZoneISO8601() - KEY_SDK_VERSION -> { - MobileCore.extensionVersion() - } + KEY_SDK_VERSION -> MobileCore.extensionVersion() KEY_CACHEBUST -> SecureRandom().nextInt(RANDOM_INT_BOUNDARY).toString() KEY_ALL_URL -> { if (event.eventData == null) { @@ -93,7 +92,15 @@ internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionAp ) return EMPTY_STRING } - JSONObject(event.eventData).toString() + try { + JSONObject(event.eventData).toString() + } catch (e: Exception) { + MobileCore.log(LoggingMode.DEBUG, + LOG_TAG, + "Failed to generate a json string ${e.message}" + ) + return EMPTY_STRING + } } else -> { if (key.startsWith(KEY_SHARED_STATE)) { @@ -123,12 +130,10 @@ internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionAp if (StringUtils.isNullOrEmpty(sharedStateKeyString)) { return null } - val index = sharedStateKeyString.indexOf(SHARED_STATE_KEY_DELIMITER) - if (index == -1) { + if (!sharedStateKeyString.contains(SHARED_STATE_KEY_DELIMITER)) { return null } - val sharedStateName = sharedStateKeyString.substring(0, index) - val dataKeyName = sharedStateKeyString.substring(index + 1) + val (sharedStateName, dataKeyName) = sharedStateKeyString.split(SHARED_STATE_KEY_DELIMITER) val sharedStateMap = extensionApi.getSharedEventState(sharedStateName, event) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, @@ -158,10 +163,6 @@ internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionAp return EMPTY_STRING } val eventDataMap = event.eventData.getFlattenedDataMap() - if (!eventDataMap.containsKey(key)) { - return null - } return eventDataMap[key] } } - diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/LaunchTokenFinderTest.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/LaunchTokenFinderTest.kt index d6cc9cc8d..4b488e4e9 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/LaunchTokenFinderTest.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/LaunchTokenFinderTest.kt @@ -130,7 +130,7 @@ class LaunchTokenFinderTest : BaseTest() { // test val result = launchTokenFinder.get("~all_url") // verify - assertEquals("&key1=value%201", result) + assertEquals("key1=value%201", result) } @Test @@ -144,7 +144,7 @@ class LaunchTokenFinderTest : BaseTest() { // test val result = launchTokenFinder.get("~all_url") // verify - assertTrue("&key3=123&key4=-456" == result || "&key4=-456&key3=123" == result) + assertTrue("key3=123&key4=-456" == result || "key4=-456&key3=123" == result) } @Test @@ -158,7 +158,7 @@ class LaunchTokenFinderTest : BaseTest() { // test val result = launchTokenFinder.get("~all_url") // verify - assertTrue("&key2=true&key5=-123.456" == result || "&key5=-123.456&key2=true" == result) + assertTrue("key2=true&key5=-123.456" == result || "key5=-123.456&key2=true" == result) } @Test @@ -174,7 +174,7 @@ class LaunchTokenFinderTest : BaseTest() { // test val result = launchTokenFinder.get("~all_url") // verify - assertEquals("&key6=String1%2CString2", result) + assertEquals("key6=String1%2CString2", result) } @Test @@ -190,7 +190,7 @@ class LaunchTokenFinderTest : BaseTest() { // test val result = launchTokenFinder.get("~all_url") // verify - assertTrue("&key7.innerKey1=inner%20val1&key7.innerKey2=innerVal2" == result || "&key7.innerKey2=innerVal2&key7.innerKey1=inner%20val1" == result) + assertTrue("key7.innerKey1=inner%20val1&key7.innerKey2=innerVal2" == result || "key7.innerKey2=innerVal2&key7.innerKey1=inner%20val1" == result) } @Test diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt index fa88e302e..4dc02591d 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt @@ -157,9 +157,9 @@ class MapExtensionsTests { dict["key2"] = "val2" dict["key3"] = "val3" val valueUnderTest = dict.serializeToQueryString() - assertTrue(valueUnderTest.contains("&key3=val3")) - assertTrue(valueUnderTest.contains("&key2=val2")) - assertTrue(valueUnderTest.contains("&key1=val1")) + assertTrue(valueUnderTest.contains("key3=val3")) + assertTrue(valueUnderTest.contains("key2=val2")) + assertTrue(valueUnderTest.contains("key1=val1")) } @Test @@ -175,8 +175,8 @@ class MapExtensionsTests { dict["key1"] = "val1" dict["key2"] = null val valueUnderTest = dict.serializeToQueryString() - assertTrue(valueUnderTest.contains("&key1=val1")) - assertFalse(valueUnderTest.contains("&key2=val2")) + assertTrue(valueUnderTest.contains("key1=val1")) + assertFalse(valueUnderTest.contains("key2=val2")) } @Test @@ -185,8 +185,8 @@ class MapExtensionsTests { dict["key1"] = "val1" dict[""] = "val2" val valueUnderTest = dict.serializeToQueryString() - assertTrue(valueUnderTest.contains("&key1=val1")) - assertFalse(valueUnderTest.contains("&key2=val2")) + assertTrue(valueUnderTest.contains("key1=val1")) + assertFalse(valueUnderTest.contains("key2=val2")) } @Test @@ -195,8 +195,8 @@ class MapExtensionsTests { dict["key1"] = "val1" dict["key2"] = "" val valueUnderTest = dict.serializeToQueryString() - assertTrue(valueUnderTest.contains("&key1=val1")) - assertTrue(valueUnderTest.contains("&key2=")) + assertTrue(valueUnderTest.contains("key1=val1")) + assertTrue(valueUnderTest.contains("key2=")) } @Test @@ -204,7 +204,7 @@ class MapExtensionsTests { val dict = HashMap() dict["key1"] = 5 val valueUnderTest = dict.serializeToQueryString() - assertEquals("&key1=5", valueUnderTest) + assertEquals("key1=5", valueUnderTest) } @Test @@ -218,7 +218,7 @@ class MapExtensionsTests { dict["key1"] = list val valueUnderTest = dict.serializeToQueryString() assertEquals( - "&key1=TestArrayList1%2CTestArrayList2%2CTestArrayList3%2CTestArrayList4", + "key1=TestArrayList1%2CTestArrayList2%2CTestArrayList3%2CTestArrayList4", valueUnderTest ) } @@ -234,7 +234,7 @@ class MapExtensionsTests { dict["key1"] = list val valueUnderTest = dict.serializeToQueryString() assertEquals( - "&key1=TestArrayList1%2CTestArrayList2%2Cnull%2CTestArrayList4", + "key1=TestArrayList1%2CTestArrayList2%2Cnull%2CTestArrayList4", valueUnderTest ) } @@ -250,7 +250,7 @@ class MapExtensionsTests { dict["key1"] = list val valueUnderTest = dict.serializeToQueryString() assertEquals( - "&key1=TestArrayList1%2CTestArrayList2%2C%2CTestArrayList4", + "key1=TestArrayList1%2CTestArrayList2%2C%2CTestArrayList4", valueUnderTest ) } From ffd21621e79319296c4df38298b32c547ff4c1c0 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Tue, 17 May 2022 11:01:54 -0700 Subject: [PATCH 052/476] [31] Implement a serial work dispatcher for event/job dispatch - Create SerialWorkDispatcher to allow executing work serially - Add a dispatcher for dispatching and processing events. - Implemet Event.dispatch() and defer event listener implementatio to #33 --- .../mobile/internal/eventhub/EventHub.kt | 44 ++- .../mobile/util/SerialWorkDispatcher.kt | 290 ++++++++++++++++++ .../mobile/util/SerialWorkDispatcherTest.kt | 223 ++++++++++++++ 3 files changed, 553 insertions(+), 4 deletions(-) create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/util/SerialWorkDispatcher.kt create mode 100644 code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/util/SerialWorkDispatcherTest.kt diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index ad15bacb5..9994e30a0 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -17,6 +17,7 @@ import com.adobe.marketing.mobile.ExtensionError import com.adobe.marketing.mobile.ExtensionErrorCallback import com.adobe.marketing.mobile.LoggingMode import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.util.SerialWorkDispatcher import java.util.concurrent.Callable import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ExecutorService @@ -39,6 +40,20 @@ internal class EventHub { private val lastEventNumber: AtomicInteger = AtomicInteger(0) private var hubStarted = false + /** + * Responsible for handling and processing each event. Dispatching of an [Event] "e" is regarded complete when + * [SerialWorkDispatcher.doWork] finishes for "e". + */ + private val eventDispatcher: SerialWorkDispatcher = object : SerialWorkDispatcher("EventHub") { + override fun doWork(item: Event) { + // TODO: Perform pre-processing + + // TODO: Notify response event listeners + + // TODO: Send Event to each Extension Container + } + } + /** * A cache that maps UUID of an Event to an internal sequence of its dispatch. */ @@ -54,12 +69,32 @@ internal class EventHub { fun start() { eventHubExecutor.submit { this.hubStarted = true - + this.eventDispatcher.start() this.shareEventHubSharedState() MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Event Hub successfully started") } } + /** + * Dispatches a new [Event] to all listeners who have registered for the event type and source. + * If the `event` has a `mask`, this method will attempt to record the `event` in `eventHistory`. + * See [eventDispatcher] for more details. + * + * @param event the [Event] to be dispatched to listeners + */ + fun dispatch(event: Event) { + eventHubExecutor.submit { + // Assign the next available event number to the event. + eventNumberMap[event.uniqueIdentifier] = lastEventNumber.incrementAndGet() + + // Offer event to the serial dispatcher to perform operations on the event. + eventDispatcher.offer(event) + MobileCore.log(LoggingMode.VERBOSE, LOG_TAG, "Dispatching Event #${eventNumberMap[event.uniqueIdentifier]} - ($event)") + + // TODO: Record event to event history database if required. + } + } + /** * Registers a new `Extension` to the `EventHub`. This `Extension` must extends `Extension` class * @@ -94,8 +129,9 @@ internal class EventHub { eventHubExecutor.submit { val extensionName = extensionClass?.extensionTypeName val container = registeredExtensions.remove(extensionName) + if (container != null) { - container?.shutdown() + container.shutdown() shareEventHubSharedState() completion(EventHubError.None) } else { @@ -263,10 +299,10 @@ internal class EventHub { * Stops processing events and shuts down all registered extensions. */ fun shutdown() { - // Todo : Stop event processing - // Shutdown and clear all the extensions. eventHubExecutor.submit { + eventDispatcher.shutdown() + // Unregister all extensions registeredExtensions.forEach { (_, extensionContainer) -> extensionContainer.shutdown() diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/util/SerialWorkDispatcher.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/util/SerialWorkDispatcher.kt new file mode 100644 index 000000000..202e14aad --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/util/SerialWorkDispatcher.kt @@ -0,0 +1,290 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.util + +import android.support.annotation.VisibleForTesting +import com.adobe.marketing.mobile.LoggingMode +import com.adobe.marketing.mobile.MobileCore +import java.lang.Exception +import java.lang.IllegalStateException +import java.util.Queue +import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import java.util.concurrent.Future + +/** + * Provides a template for processing a queue of work items serially. Allows adding new work items + * while the another item is being processed. Aims to separate the lifecycle of the worker thread + * that process work items with the queue that they are fetched from to allow sub-classes to be + * agnostic of worker thread management. + */ +abstract class SerialWorkDispatcher(private val name: String) { + + companion object { + const val LOG_TAG = "WorkDispatcher" + } + + /** + * Represents the state of the [SerialWorkDispatcher]. + */ + enum class State { + /** + * Indicates that the dispatcher is not yet started. + * New work will be accepted and executed when started. + */ + NOT_STARTED, + + /** + * Indicates that the dispatcher has been started and work will + * be accepted. + */ + ACTIVE, + + /** + * Indicates that the dispatcher has been shutdown and no more work + * will be accepted. + */ + SHUTDOWN + } + + /** + * The [Executor] to which work is submitted for sequencing. + */ + private val executorService: ExecutorService = Executors.newSingleThreadExecutor() + + /** + * Holds the work items that need to be processed by this dispatcher. + */ + private val workQueue: Queue = ConcurrentLinkedQueue() + + /** + * A runnable responsible for draining the work items from the [workQueue] + * and processing them via [doWork]. + */ + private val workProcessor: WorkProcessor by lazy { WorkProcessor() } + + /** + * A handle for identifying and manipulating the state of the thread that this dispatcher owns. + * Can be used to ensure that only one [workProcessor] is active at a time. + */ + private var workProcessorFuture: Future<*>? = null + + /** + * Denotes the current state of the [SerialWorkDispatcher]. It is synonymous with the ability of the + * [workQueue] to accept new items. Note that this is not the state of the worker-thread that this + * dispatcher maintains. + */ + @Volatile + private var state: State = State.NOT_STARTED + + /** + * Used for guarding the "activeness" logic. Should never be used to wrap [workQueue] + */ + private val activenessMutex: Any = Any() + + /** + * Enqueues an item to the end of the [workQueue]. Additionally, + * resumes the queue processing if the [SerialWorkDispatcher] is active + * (but processing was stopped earlier due to lack of work). + * + * @param item item that needs to be processed. + * @return true if [item] has been enqueued successfully, false otherwise + */ + fun offer(item: T): Boolean { + // Hold the activeness lock to ensure that an update to state is + // not being made while a state change operation start/resume/stop is being done. + synchronized(activenessMutex) { + if (state == State.SHUTDOWN) return false + } + + // Read the state again in-case a context switch happens immediately after the sync block above. + // [state] being volatile allows us to prevent locking on the queue while also being sure that + // the new item being added to the queue, after a context switch (if any), is processed correctly + // (lazily) in [resume]. + // This in turn allows achieving the ability to add new work items to the queue while another + // item is being processed via [doWork]. + if (state != State.SHUTDOWN) { + workQueue.offer(item) + + // resume the processing the work items in the queue if necessary + resume() + return true + } + + return false + } + + /** + * Invoked immediately before processing the items in the queue for the first time. + * Implementers are expected to perform any one-time setup operations (bound by the activeness of + * [SerialWorkDispatcher]) before processing starts. + * Invoked on the thread that calls [start] + */ + protected open fun prepare() { + // no-op + // Intentionally non-abstract as most implementers + // may not need this. + } + + /** + * Puts the [SerialWorkDispatcher] in active state and starts processing the {@link #workQueue} + * if not already active. + * + * @return true - if [SerialWorkDispatcher] was successfully started, + * false - if it is already active or was shutdown + * @throws IllegalStateException when attempting to start a dispatcher after being shutdown + */ + fun start(): Boolean { + synchronized(activenessMutex) { + if (state == State.ACTIVE) { + MobileCore.log(LoggingMode.VERBOSE, getTag(), "WorkDispatcher ($name) is already active.") + return false + } + + if (state == State.SHUTDOWN) { + throw IllegalStateException("Cannot start WorkDispatcher ($name). Already shutdown.") + } + + state = State.ACTIVE + + prepare() + resume() + return true + } + } + + /** + * Invoked before processing each work item. Results in the worker thread being completed + * if the implementer returns false. Returning false will result in "pausing" the processing (which + * can later be resumed via [resume] explicitly or, when a re-evaluation of [canWork] happens when + * new item is added to the [workQueue] via [offer]). + * Implementers are expected to enforce any conditions that need + * to be checked before performing work here. + * + * @return true if all conditions are met for performing work, false otherwise. + */ + protected open fun canWork(): Boolean { + // Return true by default + return true + } + + /** + * Checks if there are any work items available for processing. + * + * @return true if there are any available in [workQueue] work items for processing, + * false otherwise + */ + private fun hasWork(): Boolean { + return workQueue.peek() != null + } + + /** + * Removes the work item at the front (earliest queued) of the [workQueue] + * + * @return the work item at the front (earliest queued) of the [workQueue], null if [workQueue] is empty + */ + private fun getWorkItem(): T? { + return workQueue.poll() + } + + /** + * Performs processing on the [item]. + * This is invoked from the background worker thread that the [SerialWorkDispatcher] maintains. + * + * @param item foremost work item in the queue that currently being processed. + */ + protected abstract fun doWork(item: T) + + /** + * Resumes processing the work items in the [workQueue] if the [SerialWorkDispatcher] + * is active and if no worker thread is actively processing the [workQueue]. + * Implementers can optionally trigger work via [resume] after any changes to logic in [canWork] + * now being true, without having to wait for the next item to be added. + */ + protected fun resume() { + synchronized(activenessMutex) { + val activeWorkProcessor: Future<*>? = workProcessorFuture + if (state != State.ACTIVE || (activeWorkProcessor != null && !activeWorkProcessor.isDone) || !canWork()) { + // if the dispatcher is inactive or, if there is any active worker processing + // the queue - do not do anything as the existing processor will process the items + return + } + + // start the work processor + workProcessorFuture = executorService.submit(workProcessor) + } + } + + /** + * Invoked immediately after stopping processing as a result of [shutdown]. + * Implementers are expected to perform any cleanup operations as a result of + * the [SerialWorkDispatcher] being stopped. + * Invoked on the calling thread that invokes [shutdown] + */ + protected open fun cleanup() { + // no-op + // Intentionally non-abstract as most implementers + // may not need this. + } + + /** + * Puts the [SerialWorkDispatcher] into inactive state and clears the [workQueue]. + * Calling [resume] or [start] will have no effect on the state of the [SerialWorkDispatcher] after + * this method is invoked. + */ + fun shutdown() { + synchronized(activenessMutex) { + if (state == State.SHUTDOWN) return + + state = State.SHUTDOWN + + // Cancel active work processing (if any) + val activeTask: Future<*>? = workProcessorFuture + activeTask?.cancel(true) + workProcessorFuture = null + } + + workQueue.clear() + executorService.shutdownNow() + cleanup() + } + + fun getState(): State { + return state + } + + private fun getTag() = "$LOG_TAG-$name" + + /** + * A runnable responsible for looping through the work items maintained by [SerialWorkDispatcher] + * in its [workQueue] + */ + @VisibleForTesting + internal inner class WorkProcessor : Runnable { + override fun run() { + // Perform work only if the dispatcher is unblocked and there are + // items in the queue to perform work on. + while (!Thread.interrupted() && canWork() && hasWork()) { + try { + val workItem = getWorkItem() ?: return + doWork(workItem) + } catch (exception: Exception) { + Thread.currentThread().interrupt() + MobileCore.log(LoggingMode.ERROR, + getTag(), + "Exception encountered while processing item. $exception") + } + } + } + } +} diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/util/SerialWorkDispatcherTest.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/util/SerialWorkDispatcherTest.kt new file mode 100644 index 000000000..f6fb27358 --- /dev/null +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/util/SerialWorkDispatcherTest.kt @@ -0,0 +1,223 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.util + +import com.adobe.marketing.mobile.Event +import java.lang.IllegalStateException +import java.util.concurrent.Callable +import java.util.concurrent.ExecutorService +import java.util.concurrent.Future +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue +import kotlin.test.fail +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.`when` +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.stubbing.Answer +import org.powermock.modules.junit4.PowerMockRunner +import org.powermock.reflect.Whitebox + +@RunWith(PowerMockRunner::class) +class SerialWorkDispatcherTest { + + /** + * A test implementation of [SerialWorkDispatcher] that enables testing internal state logic. + */ + class TestSerialWorkDispatcher(name: String) : SerialWorkDispatcher(name) { + var processedEvents: ArrayList? = null + var blockWork: Boolean = false + + override fun prepare() { + processedEvents = ArrayList() + } + + override fun doWork(item: Event) { + processedEvents?.add(item) + } + + override fun cleanup() { + processedEvents = null + } + + override fun canWork(): Boolean { + return !blockWork + } + } + + @Mock + private lateinit var mockExecutorService: ExecutorService + + private val serialWorkDispatcher: TestSerialWorkDispatcher = TestSerialWorkDispatcher("TestImpl") + + @Before + fun setUp() { + // + Whitebox.setInternalState(serialWorkDispatcher, "executorService", mockExecutorService) + + Mockito.doAnswer(Answer { + val runnable = it.getArgument(0) + runnable.run() + return@Answer null + }).`when`(mockExecutorService).submit(any(Runnable::class.java)) + + Mockito.doAnswer(Answer { + val mockFuture: Future<*> = Mockito.mock(Future::class.java) + val callable = it.getArgument>(0) + `when`(mockFuture.get()).then { callable.call() } + return@Answer mockFuture + }).`when`(mockExecutorService).submit(any(Callable::class.java)) + } + + @Test + fun `Prepare is called when dispatcher is started`() { + assertNull(serialWorkDispatcher.processedEvents) + + serialWorkDispatcher.start() + + assertEquals(SerialWorkDispatcher.State.ACTIVE, serialWorkDispatcher.getState()) + assertNotNull(serialWorkDispatcher.processedEvents) + } + + @Test + fun `Dispatcher can only be started once`() { + assertTrue(serialWorkDispatcher.start()) + + assertEquals(SerialWorkDispatcher.State.ACTIVE, serialWorkDispatcher.getState()) + assertFalse(serialWorkDispatcher.start()) + } + + @Test + fun `Dispatcher does not restart after shutdown`() { + assertTrue(serialWorkDispatcher.start()) + serialWorkDispatcher.shutdown() + + try { + serialWorkDispatcher.start() + fail("Dispatcher should not start after shutdown") + } catch (exception: IllegalStateException) { + // pass. + } + } + + @Test + fun `Work queued when dispatcher not yet started is not processed`() { + val event1: Event = Event.Builder("Event1", "Type", "Source").build() + val event2: Event = Event.Builder("Event2", "Type", "Source").build() + val event3: Event = Event.Builder("Event3", "Type", "Source").build() + + serialWorkDispatcher.offer(event1) + serialWorkDispatcher.offer(event2) + serialWorkDispatcher.offer(event3) + assertNull(serialWorkDispatcher.processedEvents) + } + + @Test + fun `Work queued when dispatcher not yet started is processed on start`() { + val event1: Event = Event.Builder("Event1", "Type", "Source").build() + val event2: Event = Event.Builder("Event2", "Type", "Source").build() + val event3: Event = Event.Builder("Event3", "Type", "Source").build() + serialWorkDispatcher.offer(event1) + serialWorkDispatcher.offer(event2) + serialWorkDispatcher.offer(event3) + assertNull(serialWorkDispatcher.processedEvents) + + serialWorkDispatcher.start() + assertNotNull(serialWorkDispatcher.processedEvents) + assertEquals(3, serialWorkDispatcher.processedEvents?.size) + } + + @Test + fun `Work is queued when processing is work condition is not met`() { + // Setup + val event1: Event = Event.Builder("Event1", "Type", "Source").build() + val event2: Event = Event.Builder("Event2", "Type", "Source").build() + val event3: Event = Event.Builder("Event3", "Type", "Source").build() + // + serialWorkDispatcher.offer(event1) + serialWorkDispatcher.start() + + // Simulate work condition being blocked + serialWorkDispatcher.blockWork = true + + // Offer new events + serialWorkDispatcher.offer(event2) + serialWorkDispatcher.offer(event3) + + // Verify that the work is submitted only once. + verify(mockExecutorService, times(1)).submit(any(Runnable::class.java)) + // verify that only event 1 is processed + assertNotNull(serialWorkDispatcher.processedEvents) + assertEquals(1, serialWorkDispatcher.processedEvents?.size) + } + + @Test + fun `Queued work is resumed when new work is offered after work condition is met`() { + // Setup + val event1: Event = Event.Builder("Event1", "Type", "Source").build() + val event2: Event = Event.Builder("Event2", "Type", "Source").build() + val event3: Event = Event.Builder("Event3", "Type", "Source").build() + // Offer only one event at start + serialWorkDispatcher.offer(event1) + serialWorkDispatcher.start() + + // Simulate work condition being blocked + serialWorkDispatcher.blockWork = true + + // Offer new event + serialWorkDispatcher.offer(event2) + // Simulate condition being met + serialWorkDispatcher.blockWork = false + + // Verify new work item is not yet started. + assertEquals(1, serialWorkDispatcher.processedEvents?.size) + + // Offer new event + serialWorkDispatcher.offer(event3) + + // verify that all events are processed + assertNotNull(serialWorkDispatcher.processedEvents) + assertEquals(3, serialWorkDispatcher.processedEvents?.size) + } + + @Test + fun `Executor terminated & cleanup called when shutdown`() { + val event: Event = Event.Builder("Event1", "Type", "Source").build() + serialWorkDispatcher.start() + serialWorkDispatcher.offer(event) + + serialWorkDispatcher.shutdown() + + assertEquals(SerialWorkDispatcher.State.SHUTDOWN, serialWorkDispatcher.getState()) + verify(mockExecutorService, times(1)).shutdownNow() + assertNull(serialWorkDispatcher.processedEvents) + } + + @Test + fun `Work cannot be queued when shutdown`() { + val event: Event = Event.Builder("Event1", "Type", "Source").build() + serialWorkDispatcher.start() + + serialWorkDispatcher.shutdown() + + assertEquals(SerialWorkDispatcher.State.SHUTDOWN, serialWorkDispatcher.getState()) + assertFalse(serialWorkDispatcher.offer(event)) + } +} From ac1f41c27f3c7d59bcae4da1b3b808ecd43ac852 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Wed, 25 May 2022 13:11:30 -0700 Subject: [PATCH 053/476] uncomment map flattening logic and tests --- .../mobile/internal/utility/MapExtensions.kt | 4 +++- .../launch/rulesengine/LaunchTokenFinder.kt | 13 ++++++++----- .../marketing/mobile/LaunchTokenFinderTest.kt | 15 +++++++++------ .../mobile/internal/utility/MapExtensionsTests.kt | 5 +++-- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt index f22e657b3..5dc77c320 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt @@ -67,6 +67,8 @@ internal fun Map.flattening(prefix: String = ""): Map.getFlattenedDataMap(prefix: String = ""): Map Date: Fri, 27 May 2022 12:32:29 -0700 Subject: [PATCH 054/476] Launch token finder PR review comments part 2 --- .../marketing/mobile/internal/utility/MapExtensions.kt | 8 ++++---- .../mobile/launch/rulesengine/LaunchTokenFinder.kt | 7 ++----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt index 5dc77c320..9f3f96c13 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt @@ -110,10 +110,10 @@ internal fun Map.serializeToQueryString(): String { var encodedValue: String? // TODO add serializing for custom objects - if (value is List<*>) { - encodedValue = urlEncode(join(value, ",")) + encodedValue = if (value is List<*>) { + urlEncode(join(value, ",")) } else { - encodedValue = urlEncode(value?.toString()) + urlEncode(value?.toString()) } val serializedKVP = serializeKeyValuePair(encodedKey, encodedValue) @@ -142,7 +142,7 @@ private fun Set<*>.isAllString(): Boolean { * @return [String] containing key/value pair encoded in URL format */ private fun serializeKeyValuePair(key: String?, value: String?): String? { - if (key == null || value == null || key.isEmpty()) { + if (key.isNullOrBlank() || value == null) { return null } return "&$key=$value" diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt index 9b2da6dd4..7f7da1d1a 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt @@ -59,11 +59,8 @@ internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionAp * @return [Any] containing value to be substituted for the [key], null if the key does not exist */ override fun get(key: String): Any? { - if (StringUtils.isNullOrEmpty(key)) { - return null - } - - return when (key) { + return when (key.trim()) { + EMPTY_STRING -> null KEY_EVENT_TYPE -> event.type KEY_EVENT_SOURCE -> event.source KEY_TIMESTAMP_UNIX -> TimeUtil.getUnixTimeInSeconds().toString() From 34d44d0c0bf9f9fd1394a514ecc0a6945c924f58 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Mon, 30 May 2022 13:25:26 -0700 Subject: [PATCH 055/476] [31] Make WorkProcessor a SAM interface & add threading, compat tests - Make WorkProcessor a SAM interface to prevent forced inheritance - Upgraded ktlint to 10.1.0 which is the lowest version identifying functional interface. This resulted in touching few classes for checkstyle and also needing an upgrade of gradle plugin from 4.0 to 4.1 for ktlint compatibility - Added Androidtests to verify serial dispatch and compatibility tests --- code/android-core-library/build.gradle | 2 + .../SerialWorkDispatcherTests.kt | 74 ++++++++++++ .../mobile/internal/eventhub/EventHub.kt | 32 ++++-- .../internal/eventhub/ExtensionContainer.kt | 76 ++++++++----- .../internal/eventhub/SharedStateManager.kt | 21 +++- .../mobile/util/SerialWorkDispatcher.kt | 50 ++++----- .../mobile/internal/eventhub/EventHubTests.kt | 4 +- .../eventhub/ExtensionContainerTest.kt | 38 ++++--- .../eventhub/SharedStateManagerTest.kt | 16 ++- .../SerialWorkDispatcherJavaCompatTest.java | 106 ++++++++++++++++++ .../mobile/util/SerialWorkDispatcherTest.kt | 55 ++++----- code/build.gradle | 6 +- code/gradle/wrapper/gradle-wrapper.properties | 3 +- 13 files changed, 350 insertions(+), 133 deletions(-) create mode 100644 code/android-core-library/src/androidTest/kotlin/com.adobe.marketing.mobile.util/SerialWorkDispatcherTests.kt create mode 100644 code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/util/SerialWorkDispatcherJavaCompatTest.java diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index fb3b21d71..02d72176f 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -28,6 +28,7 @@ android { } androidTest{ java.srcDirs += "src/legacy/androidTest-common/java" + java.srcDirs += "src/androidTest/kotlin" } } @@ -114,6 +115,7 @@ dependencies { //noinspection GradleCompatible implementation 'com.android.support:appcompat-v7:27.1.1' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_version" // unit tests testImplementation "junit:junit:4.13.2" //noinspection GradleDependency diff --git a/code/android-core-library/src/androidTest/kotlin/com.adobe.marketing.mobile.util/SerialWorkDispatcherTests.kt b/code/android-core-library/src/androidTest/kotlin/com.adobe.marketing.mobile.util/SerialWorkDispatcherTests.kt new file mode 100644 index 000000000..c31886b09 --- /dev/null +++ b/code/android-core-library/src/androidTest/kotlin/com.adobe.marketing.mobile.util/SerialWorkDispatcherTests.kt @@ -0,0 +1,74 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.util + +import android.support.test.runner.AndroidJUnit4 +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith +import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.CountDownLatch +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit + +@RunWith(AndroidJUnit4::class) +class SerialWorkDispatcherTests { + private val processedItems = ConcurrentLinkedQueue() + private val dispatchedItems = ConcurrentLinkedQueue() + private val workCompletionLatch: CountDownLatch = CountDownLatch(20) + private val dispatchMutex: Any = Any() + + /** + * A Test work handler that simulates doing work by sleeping a random amount of time. + */ + private val workHandler: SerialWorkDispatcher.WorkHandler = SerialWorkDispatcher.WorkHandler { + val lag = (50L..100L).random() + Thread.sleep(lag) + processedItems.add(it) + workCompletionLatch.countDown() + } + + private val serialWorkDispatcher: SerialWorkDispatcher = + SerialWorkDispatcher("TestSerialDispatcherImpl", workHandler) + + @Test + fun testDispatcher_ProcessesJobsInTheOrderOfDispatch() { + val executorService: ExecutorService = Executors.newFixedThreadPool(5) + // Start the event worker + serialWorkDispatcher.start() + + // Dispatch 20 jobs + runBlocking(executorService.asCoroutineDispatcher()) { + for (i in 1..20) launch { + synchronized(dispatchMutex) { + // Offer the item to the serial dispatcher + serialWorkDispatcher.offer(i) + + // Also add them to verification queue of dispatched items + dispatchedItems.add(i) + } + } + } + + workCompletionLatch.await(3, TimeUnit.SECONDS) + + // Verify that all dispatched items have been processed + Assert.assertTrue(processedItems.size == dispatchedItems.size) + // Verify that the items are processed in the order of their dispatch + for (i in 0..dispatchedItems.size) { + Assert.assertTrue(processedItems.poll() == dispatchedItems.poll()) + } + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index 9994e30a0..87df59a93 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -41,19 +41,22 @@ internal class EventHub { private var hubStarted = false /** - * Responsible for handling and processing each event. Dispatching of an [Event] "e" is regarded complete when - * [SerialWorkDispatcher.doWork] finishes for "e". + * Implementation of [SerialWorkDispatcher.WorkHandler] that is responsible for dispatching + * an [Event] "e". Dispatch is regarded complete when [SerialWorkDispatcher.WorkHandler.doWork] finishes for "e". */ - private val eventDispatcher: SerialWorkDispatcher = object : SerialWorkDispatcher("EventHub") { - override fun doWork(item: Event) { - // TODO: Perform pre-processing + private val dispatchJob: SerialWorkDispatcher.WorkHandler = SerialWorkDispatcher.WorkHandler { + // TODO: Perform pre-processing - // TODO: Notify response event listeners + // TODO: Notify response event listeners - // TODO: Send Event to each Extension Container - } + // TODO: Send Event to each Extension Container } + /** + * Responsible for processing and dispatching each event. + */ + private val eventDispatcher: SerialWorkDispatcher = SerialWorkDispatcher("EventHub", dispatchJob) + /** * A cache that maps UUID of an Event to an internal sequence of its dispatch. */ @@ -238,8 +241,11 @@ internal class EventHub { val extensionContainer: ExtensionContainer? = registeredExtensions[extensionName] if (extensionContainer == null) { - MobileCore.log(LoggingMode.ERROR, LOG_TAG, "Error retrieving SharedState for extension: [$extensionName]." + - "Extension may not have been registered.") + MobileCore.log( + LoggingMode.ERROR, LOG_TAG, + "Error retrieving SharedState for extension: [$extensionName]." + + "Extension may not have been registered." + ) errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) return@Callable null } @@ -283,8 +289,10 @@ internal class EventHub { val extensionContainer: ExtensionContainer? = registeredExtensions[extensionName] if (extensionContainer == null) { - MobileCore.log(LoggingMode.ERROR, - LOG_TAG, "Error clearing SharedState for extension: [$extensionName]. Extension may not have been registered.") + MobileCore.log( + LoggingMode.ERROR, + LOG_TAG, "Error clearing SharedState for extension: [$extensionName]. Extension may not have been registered." + ) errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) return@Callable false } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt index 200278d1a..3edce3ab7 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt @@ -69,8 +69,10 @@ internal class ExtensionRuntime() : ExtensionApi() { try { return EventHub.shared.setSharedState(SharedStateType.STANDARD, extensionName, state, event, errorCallback) } catch (exception: Exception) { - MobileCore.log(LoggingMode.ERROR, getTag(), - "Failed to set shared state at EventID: ${event?.uniqueIdentifier}. $exception") + MobileCore.log( + LoggingMode.ERROR, getTag(), + "Failed to set shared state at EventID: ${event?.uniqueIdentifier}. $exception" + ) errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) } @@ -86,8 +88,10 @@ internal class ExtensionRuntime() : ExtensionApi() { // TODO : Convert the [state] parameter to be immutable before propagating when EventData#toImmutableMap() is implemented. return EventHub.shared.setSharedState(SharedStateType.XDM, extensionName, state, event, errorCallback) } catch (exception: Exception) { - MobileCore.log(LoggingMode.ERROR, getTag(), - "Failed to set XDM shared state at EventID: ${event?.uniqueIdentifier}. $exception") + MobileCore.log( + LoggingMode.ERROR, getTag(), + "Failed to set XDM shared state at EventID: ${event?.uniqueIdentifier}. $exception" + ) errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) } @@ -124,8 +128,10 @@ internal class ExtensionRuntime() : ExtensionApi() { try { return EventHub.shared.getSharedState(SharedStateType.STANDARD, stateName, event, errorCallback) } catch (exception: Exception) { - MobileCore.log(LoggingMode.ERROR, getTag(), - "Failed to get shared state at EventID: ${event?.uniqueIdentifier}. $exception") + MobileCore.log( + LoggingMode.ERROR, getTag(), + "Failed to get shared state at EventID: ${event?.uniqueIdentifier}. $exception" + ) errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) } return null @@ -139,8 +145,10 @@ internal class ExtensionRuntime() : ExtensionApi() { try { return EventHub.shared.getSharedState(SharedStateType.STANDARD, stateName, event, errorCallback) } catch (exception: Exception) { - MobileCore.log(LoggingMode.ERROR, getTag(), - "Failed to get XDM shared state at EventID: ${event?.uniqueIdentifier}. $exception") + MobileCore.log( + LoggingMode.ERROR, getTag(), + "Failed to get XDM shared state at EventID: ${event?.uniqueIdentifier}. $exception" + ) errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) } @@ -179,8 +187,8 @@ internal class ExtensionContainer constructor( get() = extensionRuntime.extensionVersion private val sharedStateManagers: Map = mapOf( - SharedStateType.XDM to SharedStateManager(sharedStateName ?: ""), - SharedStateType.STANDARD to SharedStateManager(sharedStateName ?: "") + SharedStateType.XDM to SharedStateManager(sharedStateName ?: ""), + SharedStateType.STANDARD to SharedStateManager(sharedStateName ?: "") ) init { @@ -228,25 +236,27 @@ internal class ExtensionContainer constructor( ): SharedState.Status { if (taskExecutor.isShutdown) return SharedState.Status.NOT_SET - return taskExecutor.submit(Callable { - val stateManager: SharedStateManager = sharedStateManagers[sharedStateType] + return taskExecutor.submit( + Callable { + val stateManager: SharedStateManager = sharedStateManagers[sharedStateType] ?: return@Callable SharedState.Status.NOT_SET - // Existing public API infers a pending state as one with no data - val isPending: Boolean = (data == null) + // Existing public API infers a pending state as one with no data + val isPending: Boolean = (data == null) - // Attempt to create the state first - val createResult: SharedState.Status = stateManager.createSharedState(data, version, isPending) + // Attempt to create the state first + val createResult: SharedState.Status = stateManager.createSharedState(data, version, isPending) - if (createResult != SharedState.Status.NOT_SET) { - // If the creation was successful i.e the result of the operation was either SET or - // PENDING, return the result. - return@Callable createResult - } else { - // else, attempt to update it and return the update result. - return@Callable stateManager.updateSharedState(data, version, isPending) + if (createResult != SharedState.Status.NOT_SET) { + // If the creation was successful i.e the result of the operation was either SET or + // PENDING, return the result. + return@Callable createResult + } else { + // else, attempt to update it and return the update result. + return@Callable stateManager.updateSharedState(data, version, isPending) + } } - }).get() + ).get() } /** @@ -259,10 +269,12 @@ internal class ExtensionContainer constructor( fun clearSharedState(sharedStateType: SharedStateType): Boolean { if (taskExecutor.isShutdown) return false - return taskExecutor.submit(Callable { - sharedStateManagers[sharedStateType]?.clearSharedState() - return@Callable true - }).get() + return taskExecutor.submit( + Callable { + sharedStateManagers[sharedStateType]?.clearSharedState() + return@Callable true + } + ).get() } /** @@ -280,8 +292,10 @@ internal class ExtensionContainer constructor( ): SharedState? { if (taskExecutor.isShutdown) return null - return taskExecutor.submit(Callable { - return@Callable sharedStateManagers[sharedStateType]?.getSharedState(version) - }).get() + return taskExecutor.submit( + Callable { + return@Callable sharedStateManagers[sharedStateType]?.getSharedState(version) + } + ).get() } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedStateManager.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedStateManager.kt index f22f40441..eeb7d0f1f 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedStateManager.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedStateManager.kt @@ -63,8 +63,11 @@ internal class SharedStateManager(private val name: String) { // Check if there exists a state at a version equal to, or higher than the one provided. if (states.ceilingEntry(version) != null) { - MobileCore.log(LoggingMode.VERBOSE, LOG_TAG, "Cannot create $name shared state at version $version. " + - "More recent state exists.") + MobileCore.log( + LoggingMode.VERBOSE, LOG_TAG, + "Cannot create $name shared state at version $version. " + + "More recent state exists." + ) // If such a state exists a new state cannot be created, it can be updated, if pending, // via SharedStateManager#updateSharedState(..) return SharedState.Status.NOT_SET @@ -102,8 +105,11 @@ internal class SharedStateManager(private val name: String) { // Check if new state is pending. A pending state cannot be overwritten by another pending state if (isPending) { - MobileCore.log(LoggingMode.VERBOSE, LOG_TAG, "Cannot update pending $name shared state at " + - "version $version with a pending state.") + MobileCore.log( + LoggingMode.VERBOSE, LOG_TAG, + "Cannot update pending $name shared state at " + + "version $version with a pending state." + ) return SharedState.Status.NOT_SET } @@ -112,8 +118,11 @@ internal class SharedStateManager(private val name: String) { // Check there is a valid pending state for updating. if (stateAtVersion.status != SharedState.Status.PENDING) { - MobileCore.log(LoggingMode.WARNING, LOG_TAG, "Cannot update a non pending $name shared state " + - "at version $version.") + MobileCore.log( + LoggingMode.WARNING, LOG_TAG, + "Cannot update a non pending $name shared state " + + "at version $version." + ) return SharedState.Status.NOT_SET } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/util/SerialWorkDispatcher.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/util/SerialWorkDispatcher.kt index 202e14aad..a0f4ed283 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/util/SerialWorkDispatcher.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/util/SerialWorkDispatcher.kt @@ -28,7 +28,7 @@ import java.util.concurrent.Future * that process work items with the queue that they are fetched from to allow sub-classes to be * agnostic of worker thread management. */ -abstract class SerialWorkDispatcher(private val name: String) { +open class SerialWorkDispatcher(private val name: String, private val workHandler: WorkHandler) { companion object { const val LOG_TAG = "WorkDispatcher" @@ -57,6 +57,19 @@ abstract class SerialWorkDispatcher(private val name: String) { SHUTDOWN } + /** + * Represents the functional interface that is responsible for doing the desired work on each item of the [workQueue]. + * [WorkHandler.doWork] is called from the background worker thread that the [SerialWorkDispatcher] maintains. + */ + fun interface WorkHandler { + /** + * Handles processing on [item] + * + * @param item the work item on which is dispatched for processing. + */ + fun doWork(item: W) + } + /** * The [Executor] to which work is submitted for sequencing. */ @@ -88,7 +101,7 @@ abstract class SerialWorkDispatcher(private val name: String) { private var state: State = State.NOT_STARTED /** - * Used for guarding the "activeness" logic. Should never be used to wrap [workQueue] + * Used for guarding the "activeness" logic. */ private val activenessMutex: Any = Any() @@ -105,17 +118,10 @@ abstract class SerialWorkDispatcher(private val name: String) { // not being made while a state change operation start/resume/stop is being done. synchronized(activenessMutex) { if (state == State.SHUTDOWN) return false + workQueue.offer(item) } - // Read the state again in-case a context switch happens immediately after the sync block above. - // [state] being volatile allows us to prevent locking on the queue while also being sure that - // the new item being added to the queue, after a context switch (if any), is processed correctly - // (lazily) in [resume]. - // This in turn allows achieving the ability to add new work items to the queue while another - // item is being processed via [doWork]. if (state != State.SHUTDOWN) { - workQueue.offer(item) - // resume the processing the work items in the queue if necessary resume() return true @@ -147,12 +153,12 @@ abstract class SerialWorkDispatcher(private val name: String) { fun start(): Boolean { synchronized(activenessMutex) { if (state == State.ACTIVE) { - MobileCore.log(LoggingMode.VERBOSE, getTag(), "WorkDispatcher ($name) is already active.") + MobileCore.log(LoggingMode.VERBOSE, getTag(), "SerialWorkDispatcher ($name) is already active.") return false } if (state == State.SHUTDOWN) { - throw IllegalStateException("Cannot start WorkDispatcher ($name). Already shutdown.") + throw IllegalStateException("Cannot start SerialWorkDispatcher ($name). Already shutdown.") } state = State.ACTIVE @@ -197,14 +203,6 @@ abstract class SerialWorkDispatcher(private val name: String) { return workQueue.poll() } - /** - * Performs processing on the [item]. - * This is invoked from the background worker thread that the [SerialWorkDispatcher] maintains. - * - * @param item foremost work item in the queue that currently being processed. - */ - protected abstract fun doWork(item: T) - /** * Resumes processing the work items in the [workQueue] if the [SerialWorkDispatcher] * is active and if no worker thread is actively processing the [workQueue]. @@ -252,9 +250,9 @@ abstract class SerialWorkDispatcher(private val name: String) { val activeTask: Future<*>? = workProcessorFuture activeTask?.cancel(true) workProcessorFuture = null + workQueue.clear() } - workQueue.clear() executorService.shutdownNow() cleanup() } @@ -277,12 +275,14 @@ abstract class SerialWorkDispatcher(private val name: String) { while (!Thread.interrupted() && canWork() && hasWork()) { try { val workItem = getWorkItem() ?: return - doWork(workItem) + workHandler.doWork(workItem) } catch (exception: Exception) { Thread.currentThread().interrupt() - MobileCore.log(LoggingMode.ERROR, - getTag(), - "Exception encountered while processing item. $exception") + MobileCore.log( + LoggingMode.ERROR, + getTag(), + "Exception encountered while processing item. $exception" + ) } } } diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt index d7f2804b2..c17fb2963 100644 --- a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt @@ -13,12 +13,12 @@ package com.adobe.marketing.mobile.internal.eventhub import com.adobe.marketing.mobile.Extension import com.adobe.marketing.mobile.ExtensionApi +import org.junit.Before +import org.junit.Test import java.lang.Exception import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import kotlin.test.assertEquals -import org.junit.Before -import org.junit.Test private object MockExtensions { class MockExtensionInvalidConstructor(api: ExtensionApi, name: String?) : Extension(api) { diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainerTest.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainerTest.kt index 4aeda4a84..0d48a5fff 100644 --- a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainerTest.kt +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainerTest.kt @@ -11,12 +11,6 @@ package com.adobe.marketing.mobile.internal.eventhub -import java.util.concurrent.Callable -import java.util.concurrent.ExecutorService -import java.util.concurrent.Future -import kotlin.test.assertEquals -import kotlin.test.assertNull -import kotlin.test.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -31,6 +25,12 @@ import org.mockito.MockitoAnnotations import org.mockito.stubbing.Answer import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.modules.junit4.PowerMockRunner +import java.util.concurrent.Callable +import java.util.concurrent.ExecutorService +import java.util.concurrent.Future +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue @RunWith(PowerMockRunner::class) @PrepareForTest(ExtensionRuntime::class) @@ -49,17 +49,21 @@ class ExtensionContainerTest { fun setUp() { MockitoAnnotations.initMocks(this) - doAnswer(Answer { - // Create a mock Future to return - val mockFuture: Future<*> = Mockito.mock(Future::class.java) - // Make it so that the Callable passed to the ExecutorService is run when future result is queried via get() - val callableArgument = it.getArgument>(0) - `when`(mockFuture.get()).thenReturn(callableArgument.call()) - return@Answer mockFuture - }).`when`(mockExecutorService).submit(any(Callable::class.java)) - - extensionContainer = ExtensionContainer(MockExtension::class.java, - mockExtensionRuntime, mockExecutorService, mockErrorCallback) + doAnswer( + Answer { + // Create a mock Future to return + val mockFuture: Future<*> = Mockito.mock(Future::class.java) + // Make it so that the Callable passed to the ExecutorService is run when future result is queried via get() + val callableArgument = it.getArgument>(0) + `when`(mockFuture.get()).thenReturn(callableArgument.call()) + return@Answer mockFuture + } + ).`when`(mockExecutorService).submit(any(Callable::class.java)) + + extensionContainer = ExtensionContainer( + MockExtension::class.java, + mockExtensionRuntime, mockExecutorService, mockErrorCallback + ) } @Test diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/SharedStateManagerTest.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/SharedStateManagerTest.kt index 6b45b7fa3..8a776442c 100644 --- a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/SharedStateManagerTest.kt +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/SharedStateManagerTest.kt @@ -11,11 +11,11 @@ package com.adobe.marketing.mobile.internal.eventhub +import org.junit.Before +import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull -import org.junit.Before -import org.junit.Test internal class SharedStateManagerTest { @@ -108,8 +108,10 @@ internal class SharedStateManagerTest { sharedStateManager.createSharedState(mapOf(), 7, false) // Verify that pending state cannot be updated when no pending state exists at the version - assertEquals(SharedState.Status.NOT_SET, - sharedStateManager.updateSharedState(mapOf("One" to 1, "Yes" to true), 7, false)) + assertEquals( + SharedState.Status.NOT_SET, + sharedStateManager.updateSharedState(mapOf("One" to 1, "Yes" to true), 7, false) + ) } @Test @@ -118,8 +120,10 @@ internal class SharedStateManagerTest { sharedStateManager.createSharedState(null, 7, true) // Verify that pending state can be updated when pending state exists at the version - assertEquals(SharedState.Status.SET, - sharedStateManager.updateSharedState(mapOf("One" to 1, "Yes" to true), 7, false)) + assertEquals( + SharedState.Status.SET, + sharedStateManager.updateSharedState(mapOf("One" to 1, "Yes" to true), 7, false) + ) } @Test diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/util/SerialWorkDispatcherJavaCompatTest.java b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/util/SerialWorkDispatcherJavaCompatTest.java new file mode 100644 index 000000000..c3bc2abda --- /dev/null +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/util/SerialWorkDispatcherJavaCompatTest.java @@ -0,0 +1,106 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; + +import org.jetbrains.annotations.NotNull; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.reflect.Whitebox; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; + +@RunWith(PowerMockRunner.class) +@PrepareForTest(SerialWorkDispatcher.WorkProcessor.class) +public class SerialWorkDispatcherJavaCompatTest { + + /** + * A test implementation of [SerialWorkDispatcher] that enables testing compatibility with Kotlin. + */ + static class TestJavaSerialWorkDispatcher extends SerialWorkDispatcher { + private ArrayList processedItems = null; + + TestJavaSerialWorkDispatcher(@NotNull final String name, + @NotNull final WorkHandler workHandler) { + super(name, workHandler); + } + + @Override + public void prepare() { + processedItems = new ArrayList<>(); + } + + @Override + public void cleanup() { + processedItems = null; + } + + List getProcessedItems() { + return processedItems; + } + } + + private final SerialWorkDispatcher.WorkHandler workHandler = new SerialWorkDispatcher.WorkHandler() { + @Override + public void doWork(Integer item) { + javaSerialWorkDispatcher.getProcessedItems().add(item); + } + }; + + private final TestJavaSerialWorkDispatcher javaSerialWorkDispatcher + = new TestJavaSerialWorkDispatcher("JavaSerialWorkDispatcher", workHandler); + + @Mock + private ExecutorService mockExecutorService; + + @Before + public void setup() { + Whitebox.setInternalState(javaSerialWorkDispatcher, "executorService", mockExecutorService); + + Mockito.doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + final Runnable runnable = invocation.getArgument(0); + runnable.run(); + return null; + } + }).when(mockExecutorService).submit(any(Runnable.class)); + } + + @Test + public void testJavaSerialWorkDispatcherCompatibility() { + javaSerialWorkDispatcher.offer(1); + javaSerialWorkDispatcher.offer(2); + javaSerialWorkDispatcher.offer(3); + + assertNull(javaSerialWorkDispatcher.getProcessedItems()); + + javaSerialWorkDispatcher.start(); + assertNotNull(javaSerialWorkDispatcher.getProcessedItems()); + assertEquals(3, javaSerialWorkDispatcher.getProcessedItems().size()); + + javaSerialWorkDispatcher.shutdown(); + assertNull(javaSerialWorkDispatcher.getProcessedItems()); + } +} diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/util/SerialWorkDispatcherTest.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/util/SerialWorkDispatcherTest.kt index f6fb27358..1f54c62e5 100644 --- a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/util/SerialWorkDispatcherTest.kt +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/util/SerialWorkDispatcherTest.kt @@ -12,28 +12,25 @@ package com.adobe.marketing.mobile.util import com.adobe.marketing.mobile.Event -import java.lang.IllegalStateException -import java.util.concurrent.Callable -import java.util.concurrent.ExecutorService -import java.util.concurrent.Future -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNotNull -import kotlin.test.assertNull -import kotlin.test.assertTrue -import kotlin.test.fail import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any import org.mockito.Mock import org.mockito.Mockito -import org.mockito.Mockito.`when` import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.stubbing.Answer import org.powermock.modules.junit4.PowerMockRunner import org.powermock.reflect.Whitebox +import java.lang.IllegalStateException +import java.util.concurrent.ExecutorService +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue +import kotlin.test.fail @RunWith(PowerMockRunner::class) class SerialWorkDispatcherTest { @@ -41,16 +38,14 @@ class SerialWorkDispatcherTest { /** * A test implementation of [SerialWorkDispatcher] that enables testing internal state logic. */ - class TestSerialWorkDispatcher(name: String) : SerialWorkDispatcher(name) { + class TestSerialWorkDispatcher(name: String, workHandler: WorkHandler) : SerialWorkDispatcher(name, workHandler) { var processedEvents: ArrayList? = null + private set + var blockWork: Boolean = false override fun prepare() { - processedEvents = ArrayList() - } - - override fun doWork(item: Event) { - processedEvents?.add(item) + processedEvents = ArrayList() } override fun cleanup() { @@ -62,28 +57,26 @@ class SerialWorkDispatcherTest { } } + private val workHandler: SerialWorkDispatcher.WorkHandler = SerialWorkDispatcher.WorkHandler { + serialWorkDispatcher.processedEvents?.add(it) + } + @Mock private lateinit var mockExecutorService: ExecutorService - private val serialWorkDispatcher: TestSerialWorkDispatcher = TestSerialWorkDispatcher("TestImpl") + private val serialWorkDispatcher: TestSerialWorkDispatcher = TestSerialWorkDispatcher("TestImpl", workHandler) @Before fun setUp() { - // Whitebox.setInternalState(serialWorkDispatcher, "executorService", mockExecutorService) - Mockito.doAnswer(Answer { - val runnable = it.getArgument(0) - runnable.run() - return@Answer null - }).`when`(mockExecutorService).submit(any(Runnable::class.java)) - - Mockito.doAnswer(Answer { - val mockFuture: Future<*> = Mockito.mock(Future::class.java) - val callable = it.getArgument>(0) - `when`(mockFuture.get()).then { callable.call() } - return@Answer mockFuture - }).`when`(mockExecutorService).submit(any(Callable::class.java)) + Mockito.doAnswer( + Answer { + val runnable = it.getArgument(0) + runnable.run() + return@Answer null + } + ).`when`(mockExecutorService).submit(any(Runnable::class.java)) } @Test diff --git a/code/build.gradle b/code/build.gradle index 6016ae009..56f31ce4f 100644 --- a/code/build.gradle +++ b/code/build.gradle @@ -8,11 +8,13 @@ buildscript { } dependencies { //noinspection AndroidGradlePluginVersion - classpath 'com.android.tools.build:gradle:4.0.1' + classpath 'com.android.tools.build:gradle:4.1.0' classpath "org.jacoco:org.jacoco.core:0.8.7" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}" - classpath "org.jlleitschuh.gradle:ktlint-gradle:9.2.1" + + // ktlint Functional Interface identification works only from 10.1.0 or later + classpath "org.jlleitschuh.gradle:ktlint-gradle:10.1.0" } } diff --git a/code/gradle/wrapper/gradle-wrapper.properties b/code/gradle/wrapper/gradle-wrapper.properties index 4e1cc9db6..626c372d6 100644 --- a/code/gradle/wrapper/gradle-wrapper.properties +++ b/code/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Wed May 25 15:46:41 PDT 2022 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 5f93c8054cc2caeb89edb7738b348569b2afb341 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Tue, 31 May 2022 08:51:46 -0700 Subject: [PATCH 056/476] [#31] Fix Log tag and log message --- .../mobile/internal/eventhub/EventHub.kt | 15 +++++++++++++-- .../marketing/mobile/util/SerialWorkDispatcher.kt | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index 87df59a93..928c7621a 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -91,8 +91,19 @@ internal class EventHub { eventNumberMap[event.uniqueIdentifier] = lastEventNumber.incrementAndGet() // Offer event to the serial dispatcher to perform operations on the event. - eventDispatcher.offer(event) - MobileCore.log(LoggingMode.VERBOSE, LOG_TAG, "Dispatching Event #${eventNumberMap[event.uniqueIdentifier]} - ($event)") + if (eventDispatcher.offer(event)) { + MobileCore.log( + LoggingMode.VERBOSE, + LOG_TAG, + "Dispatching Event #${eventNumberMap[event.uniqueIdentifier]} - ($event)" + ) + } else { + MobileCore.log( + LoggingMode.WARNING, + LOG_TAG, + "Failed to dispatch event #${eventNumberMap[event.uniqueIdentifier]} - ($event)" + ) + } // TODO: Record event to event history database if required. } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/util/SerialWorkDispatcher.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/util/SerialWorkDispatcher.kt index a0f4ed283..6c9070402 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/util/SerialWorkDispatcher.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/util/SerialWorkDispatcher.kt @@ -31,7 +31,7 @@ import java.util.concurrent.Future open class SerialWorkDispatcher(private val name: String, private val workHandler: WorkHandler) { companion object { - const val LOG_TAG = "WorkDispatcher" + const val LOG_TAG = "SerialWorkDispatcher" } /** From 23e59cba4dff0210c2be0c56aa8e1f4d59a08978 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Wed, 1 Jun 2022 10:22:07 -0700 Subject: [PATCH 057/476] rerun CI/CD --- .../marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt index 7f7da1d1a..edf8b3a8f 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt @@ -162,7 +162,8 @@ internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionAp } // TODO uncomment once map flattening logic is finalized /* val eventDataMap = event.eventData.getFlattenedDataMap() - return eventDataMap[key] */ + return eventDataMap[key] + */ return EMPTY_STRING } } From a99c4307cc2d69ed36da6695c23bdc9098d7e69c Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Wed, 1 Jun 2022 11:27:03 -0700 Subject: [PATCH 058/476] [#31] Restrict coroutine dependency to tests and annotate dispatch() --- code/android-core-library/build.gradle | 2 +- .../com/adobe/marketing/mobile/internal/eventhub/EventHub.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index 02d72176f..033909837 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -115,7 +115,6 @@ dependencies { //noinspection GradleCompatible implementation 'com.android.support:appcompat-v7:27.1.1' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_version" // unit tests testImplementation "junit:junit:4.13.2" //noinspection GradleDependency @@ -129,6 +128,7 @@ dependencies { testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" // instrumentation tests androidTestImplementation "com.android.support.test:rules:1.0.2" + androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_version" } repositories { diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index 928c7621a..29f230916 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -11,6 +11,7 @@ package com.adobe.marketing.mobile.internal.eventhub +import android.support.annotation.NonNull import com.adobe.marketing.mobile.Event import com.adobe.marketing.mobile.Extension import com.adobe.marketing.mobile.ExtensionError @@ -85,7 +86,7 @@ internal class EventHub { * * @param event the [Event] to be dispatched to listeners */ - fun dispatch(event: Event) { + fun dispatch(@NonNull event: Event) { eventHubExecutor.submit { // Assign the next available event number to the event. eventNumberMap[event.uniqueIdentifier] = lastEventNumber.incrementAndGet() From cb8bc6c1e9e54d04c75ad29c45e321519b0fb7c5 Mon Sep 17 00:00:00 2001 From: Yansong Yang Date: Wed, 1 Jun 2022 13:18:08 -0600 Subject: [PATCH 059/476] Fix SQL injection vulnerability (#77) * Run CI jobs in Parallel (#3) (#4) * Build changes to support kotlin (#7) * Add two public methods in UIService -> setURIHandler & getIntentWithURI (#15) * add 2 public APIs in UIService -> setURIHandler & getIntentWithURI * add tests * address review comments * fix tests * part I * fix TODOs * add more tests & refine code * rename tests * code reordering * remove none used optional return * address review comments and add more kotlin docs * add more docs * WIP: potential solution without tests * Move historical event logic to a sub package and decouple code from internal classes * cleanup & add fnv1a32 implementation in Kotlin * cleanup unused import * [#39] Migrate aepsdk-core-android to AndroidX [Summary] - Uses the AndroidX migration tool for preliminary migration. - Manually grep for reflection use inside the project for class mapping. Following instance needed a manual change : * android.support.v4.app.NotificationCompat -> androidx.core.app.NotificationCompat - Following instances of instrumentation tests needed change manually : * InstrumentationRegistry.getContext() -> InstrumentationRegistry.getInstrumentation().getContext() - androidx.test.ext:junit:1.1.3 was added for AndroidJUnit4 test support. [Testing] - Used the TestApp to trigger changes and ensured that change surface works. - Instrumentation and unit tests pass * commit local temporary change * add more tests * add android tests * fix tests * Add javadoc and lint support for Kotlin * address review comments * fix test failure * support reprocessing cached events * fix tests and add code comments * add tests for LaunchRulesEvaluator * format * quick fix for LaunchRule * fix review comments * remove internal HitQueue classes and internal Android Database service classes * remove tests * refactor SQLiteDatabaseHelper class * add tests * fix review comments Co-authored-by: Praveen Co-authored-by: Prashanth Rudrabhat Co-authored-by: prudrabhat <96199823+prudrabhat@users.noreply.github.com> --- .../build.gradle | 5 + code/android-core-library/build.gradle | 9 +- .../marketing/mobile/AndroidCursorTests.java | 790 ------- .../mobile/AndroidDatabaseServiceTests.java | 146 -- .../mobile/AndroidDatabaseTests.java | 1827 ----------------- .../mobile/QueryStringBuilderTests.java | 365 ---- .../utility/SQLiteDatabaseHelperTests.java | 442 ++-- .../mobile/services/SqliteDataQueueTests.java | 103 + .../adobe/marketing/mobile/DataBaseTest.java | 58 - .../adobe/marketing/mobile/FakeDatabase.java | 386 ---- .../marketing/mobile/FakeDatabaseService.java | 50 - .../mobile/FakePlatformServices.java | 7 - .../marketing/mobile/FakeQueryResult.java | 110 - .../adobe/marketing/mobile/MockHitQueue.java | 105 - .../mobile/MockPlatformServices.java | 5 - .../marketing/mobile/MockQueryResult.java | 71 - .../mobile/ModuleTestPlatformServices.java | 14 - .../marketing/mobile/TestableDatabase.java | 97 - .../marketing/mobile/AbstractHitSchema.java | 82 - .../mobile/AbstractHitsDatabase.java | 249 --- .../adobe/marketing/mobile/AndroidCursor.java | 183 -- .../marketing/mobile/AndroidDatabase.java | 500 ----- .../mobile/AndroidDatabaseService.java | 138 -- .../marketing/mobile/DatabaseService.java | 262 --- .../com/adobe/marketing/mobile/HitQueue.java | 339 --- .../marketing/mobile/PlatformServices.java | 9 - .../com/adobe/marketing/mobile/Query.java | 205 -- .../marketing/mobile/QueryStringBuilder.java | 170 -- .../utility/DatabaseProcessing.java} | 31 +- .../utility/SQLiteDatabaseHelper.java | 162 +- .../mobile/AndroidPlatformServices.java | 8 - .../mobile/services/SQLiteDataQueue.java | 470 +++-- .../mobile/DummyPlatformService.java | 5 - .../mobile/services/SqliteDataQueueTests.java | 99 +- code/testapp/build.gradle | 5 + 35 files changed, 615 insertions(+), 6892 deletions(-) delete mode 100644 code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java delete mode 100644 code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java delete mode 100644 code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseTests.java delete mode 100644 code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/QueryStringBuilderTests.java create mode 100644 code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java delete mode 100755 code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/DataBaseTest.java delete mode 100755 code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakeDatabase.java delete mode 100755 code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakeDatabaseService.java delete mode 100755 code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakeQueryResult.java delete mode 100755 code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockHitQueue.java delete mode 100755 code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockQueryResult.java delete mode 100755 code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/TestableDatabase.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/AbstractHitSchema.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/AbstractHitsDatabase.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidCursor.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidDatabase.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidDatabaseService.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/DatabaseService.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/HitQueue.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/Query.java delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/QueryStringBuilder.java rename code/android-core-library/src/main/java/com/adobe/marketing/mobile/{AbstractHit.java => internal/utility/DatabaseProcessing.java} (55%) diff --git a/code/android-core-library-maven-root/build.gradle b/code/android-core-library-maven-root/build.gradle index 3568d661a..69308c246 100644 --- a/code/android-core-library-maven-root/build.gradle +++ b/code/android-core-library-maven-root/build.gradle @@ -21,6 +21,11 @@ android { } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + } publishing { diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index f9518a2ae..6880d9362 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -32,12 +32,6 @@ android { } testOptions { - unitTests.all{ - jacoco{ - includeNoLocationClasses = true - excludes = ['jdk.internal.*'] - } - } unitTests.returnDefaultValues = true unitTests.includeAndroidResources = true } @@ -123,10 +117,11 @@ dependencies { //noinspection GradleDependency testImplementation 'org.json:json:20160810' testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" - testImplementation project(path: ':android-core-library') +// testImplementation project(path: ':android-core-library') // instrumentation tests androidTestImplementation 'androidx.test:rules:1.1.1' androidTestImplementation 'androidx.test.ext:junit:1.1.3' +// androidTestImplementation project(path: ':android-core-library') } repositories { diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java deleted file mode 100644 index f63263a1d..000000000 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidCursorTests.java +++ /dev/null @@ -1,790 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - - -import com.adobe.marketing.mobile.DatabaseService.Database.ColumnDataType; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; -import org.junit.runner.RunWith; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import java.util.HashMap; -import java.util.Random; -import java.util.UUID; - -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnDataType.INTEGER; -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnDataType.REAL; -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnDataType.TEXT; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - -@SuppressWarnings("all") -@RunWith(AndroidJUnit4.class) -public class AndroidCursorTests { - - @Rule - public TestName name = new TestName(); - - private DatabaseService androidDatabaseService; - private DatabaseService.Database androidDatabase; - private String cacheDir; - private DatabaseService.QueryResult queryResult; - private Query testQuery; - - @Before - public void beforeEach() { - androidDatabaseService = new AndroidDatabaseService(null); - androidDatabase = androidDatabaseService.openDatabase(TestUtils.getCacheDir( - InstrumentationRegistry.getInstrumentation().getTargetContext()) + "/" + - name.getMethodName()); - } - - @After - public void afterEach() { - if (queryResult != null) { - queryResult.close(); - } - - TestUtils.deleteAllFilesInCacheDir(InstrumentationRegistry.getInstrumentation().getTargetContext()); - } - - @Test - public void testGetCount_Happy() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - DatabaseService.Database.ColumnDataType[] {INTEGER, TEXT, REAL}, null); - final Random random = new Random(); - - for (int i = 0; i < 10; i++) { - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", random.nextInt()); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", random.nextDouble()); - } - }); - } - - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertEquals(10, queryResult.getCount()); - } - - @Test - public void testGetCount_EmptyTable() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertEquals(0, queryResult.getCount()); - } - - @Test - public void testGetInt_Happy() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertTrue(queryResult.moveToFirst()); - assertEquals(1234, queryResult.getInt(0)); - } - - @Test - public void testGetInt_IncorrectType() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertTrue(queryResult.moveToFirst()); - assertEquals(0, queryResult.getInt(1)); - } - - @Test(expected = Exception.class) - public void testGetInt_InvalidIndex() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - queryResult.moveToFirst(); - queryResult.getInt(-1); - } - - @Test(expected = Exception.class) - public void testGetInt_IndexDoesNotExist() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - queryResult.moveToFirst(); - queryResult.getInt(1000); - } - - @Test - public void testGetDouble_Happy() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertTrue(queryResult.moveToFirst()); - assertEquals(5555.1245d, queryResult.getDouble(2)); - } - - @Test - public void testGetDouble_IncorrectType() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertTrue(queryResult.moveToFirst()); - assertEquals(0d, queryResult.getDouble(1)); - } - - @Test(expected = Exception.class) - public void testGetDouble_InvalidIndex() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - queryResult.moveToFirst(); - queryResult.getDouble(-1); - } - - @Test(expected = Exception.class) - public void testGetDouble_IndexDoesNotExist() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - queryResult.moveToFirst(); - queryResult.getDouble(1000); - } - - @Test - public void testGetFloat_Happy() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertTrue(queryResult.moveToFirst()); - assertEquals(5555.1245f, queryResult.getFloat(2)); - } - - @Test - public void testGetFloat_IncorrectType() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertTrue(queryResult.moveToFirst()); - assertEquals(0f, queryResult.getFloat(1)); - } - - @Test(expected = Exception.class) - public void testGetFloat_InvalidIndex() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - queryResult.moveToFirst(); - queryResult.getFloat(-1); - } - - @Test(expected = Exception.class) - public void testGetFloat_IndexDoesNotExist() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - queryResult.moveToFirst(); - queryResult.getFloat(1000); - } - - @Test - public void testGetLong_Happy() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertTrue(queryResult.moveToFirst()); - assertEquals(1234L, queryResult.getLong(0)); - } - - @Test - public void testGetLong_IncorrectType() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertTrue(queryResult.moveToFirst()); - assertEquals(0L, queryResult.getLong(1)); - } - - @Test(expected = Exception.class) - public void testGetLong_InvalidIndex() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - queryResult.moveToFirst(); - queryResult.getLong(-1); - } - - @Test(expected = Exception.class) - public void testGetLong_IndexDoesNotExist() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - queryResult.moveToFirst(); - queryResult.getLong(1000); - } - - @Test - public void testGetString_Happy() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertTrue(queryResult.moveToFirst()); - assertEquals("string", queryResult.getString(1)); - } - - @Test - public void testGetString_IncorrectType() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertTrue(queryResult.moveToFirst()); - assertEquals("1234", queryResult.getString(0)); - } - - @Test(expected = Exception.class) - public void testGetString_InvalidIndex() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - queryResult.moveToFirst(); - queryResult.getString(-1); - } - - @Test(expected = Exception.class) - public void testGetString_IndexDoesNotExist() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - queryResult.moveToFirst(); - queryResult.getString(1000); - } - - @Test - public void testIsNull_Happy() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", null); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertTrue(queryResult.moveToFirst()); - assertTrue(queryResult.isNull(1)); - } - - @Test - public void testIsNull_Happy_NotNull() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertTrue(queryResult.moveToFirst()); - assertFalse(queryResult.isNull(1)); - } - - @Test - public void testIsNull_DoubleType() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", null); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertTrue(queryResult.moveToFirst()); - assertEquals(0d, queryResult.getDouble(2)); - assertTrue(queryResult.isNull(2)); - } - - @Test - public void testIsNull_InvalidIndex() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", null); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertTrue(queryResult.moveToFirst()); - assertTrue(queryResult.isNull(-1)); - } - - @Test - public void testIsNull_IndexDoesNotExist() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", null); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertTrue(queryResult.moveToFirst()); - assertTrue(queryResult.isNull(1000)); - } - - @Test - public void testMoveToFirst_Happy() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertTrue(queryResult.moveToFirst()); - assertEquals(1234, queryResult.getInt(0)); - } - - @Test - public void testMoveToFirst_EmptyTable() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertFalse(queryResult.moveToFirst()); - } - - @Test - public void testMoveToLast_Happy() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 111); - put("test_col1", "string0"); - put("test_col2", 100.1234); - } - }); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 222); - put("test_col1", "string1"); - put("test_col2", 200.9876); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertTrue(queryResult.moveToLast()); - assertEquals(222, queryResult.getInt(0)); - assertEquals("string1", queryResult.getString(1)); - assertEquals(200.9876, queryResult.getDouble(2)); - } - - @Test - public void testMoveToLast_EmptyTable() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertFalse(queryResult.moveToLast()); - } - - @Test - public void testMoveToNext_Happy() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 111); - put("test_col1", "string0"); - put("test_col2", 100.1234); - } - }); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 222); - put("test_col1", "string1"); - put("test_col2", 200.9876); - } - }); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 333); - put("test_col1", "string2"); - put("test_col2", 300.5678); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertTrue(queryResult.moveToNext()); - assertEquals(111, queryResult.getInt(0)); - assertEquals("string0", queryResult.getString(1)); - assertEquals(100.1234, queryResult.getDouble(2)); - assertTrue(queryResult.moveToNext()); - assertEquals(222, queryResult.getInt(0)); - assertEquals("string1", queryResult.getString(1)); - assertEquals(200.9876, queryResult.getDouble(2)); - } - - @Test - public void testMoveToNext_EmptyTable() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertFalse(queryResult.moveToNext()); - } - - @Test - public void testClose_Happy() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertTrue(queryResult.moveToFirst()); - assertEquals(1234, queryResult.getInt(0)); - queryResult.close(); - } - - @Test(expected = Exception.class) - public void testClose_GetDoubleAfterClose() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245d); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - queryResult.moveToFirst(); - queryResult.close(); - queryResult.getDouble(2); - } - - @Test(expected = Exception.class) - public void testClose_GetIntAfterClose() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - queryResult.moveToFirst(); - queryResult.close(); - queryResult.getInt(0); - } - - @Test(expected = Exception.class) - public void testClose_GetStringAfterClose() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - queryResult.moveToFirst(); - queryResult.close(); - queryResult.getString(1); - } - - @Test(expected = Exception.class) - public void testClose_GetFloatAfterClose() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245f); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - queryResult.moveToFirst(); - queryResult.close(); - queryResult.getFloat(2); - } - - @Test(expected = Exception.class) - public void testClose_GetLongAfterClose() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234L); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - queryResult.moveToFirst(); - queryResult.close(); - queryResult.getLong(0); - } - - @Test(expected = Exception.class) - public void testClose_MoveToFirstAfterClose() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - queryResult.close(); - queryResult.moveToFirst(); - } - - @Test(expected = Exception.class) - public void testClose_MoveToLastAfterClose() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - queryResult.close(); - queryResult.moveToLast(); - } - - @Test(expected = Exception.class) - public void testClose_MoveToNextAfterClose() throws Exception { - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, REAL}, null); - androidDatabase.insert("test_table", new HashMap() { - { - put("test_col0", 1234); - put("test_col1", "string"); - put("test_col2", 5555.1245); - } - }); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - queryResult.close(); - queryResult.moveToNext(); - } - -} diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java deleted file mode 100644 index d883584e5..000000000 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseServiceTests.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import android.content.Context; -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertNull; -import static junit.framework.Assert.assertTrue; - -@SuppressWarnings("all") -@RunWith(AndroidJUnit4.class) -public class AndroidDatabaseServiceTests { - - private AndroidDatabaseService androidDatabaseService; - private String cacheDir; - private Context appContext; - - @Before - public void beforeEach() { - appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - cacheDir = appContext.getCacheDir().getPath(); - androidDatabaseService = new AndroidDatabaseService(null); - App.setAppContext(appContext); - } - - @After - public void afterEach() { - appContext.getCacheDir().delete(); - } - - @Test - public void testOpenDatabase_Happy() throws Exception { - assertNotNull(androidDatabaseService.openDatabase(cacheDir + "/testOpenDatabase_Happy")); - } - - @Test - public void testOpenDatabase_Exists() throws Exception { - assertNotNull(androidDatabaseService.openDatabase(cacheDir + "/testOpenDatabase_Happy")); - assertNotNull(androidDatabaseService.openDatabase(cacheDir + "/testOpenDatabase_Happy")); - } - - @Test - public void testOpenDatabase_NullFilePath() throws Exception { - assertNull(androidDatabaseService.openDatabase(null)); - } - - @Test - public void testOpenDatabase_EmptyFilePath() throws Exception { - assertNull(androidDatabaseService.openDatabase("")); - } - - @Test - public void testDeleteDatabase_Happy() { - assertNotNull(androidDatabaseService.openDatabase(cacheDir + "/testDeleteDatabase_Happy")); - assertTrue(androidDatabaseService.deleteDatabase(cacheDir + "/testDeleteDatabase_Happy")); - } - - @Test - public void testDeleteDatabase_DoesNotExist() { - assertFalse(androidDatabaseService.deleteDatabase(cacheDir + "/testDeleteDatabase_DoesNotExist")); - } - - @Test - public void testDeleteDatabase_NullFilePath() { - assertFalse(androidDatabaseService.deleteDatabase(null)); - } - - @Test - public void testDeleteDatabase_EmptyFilePath() { - assertFalse(androidDatabaseService.deleteDatabase("")); - } - - @Test - public void testDeleteDatabase_RelativePathBackslashClearnedUp() { - assertNotNull(androidDatabaseService.openDatabase(cacheDir + "/mydatabase\\..\\..\\database1")); - assertTrue(androidDatabaseService.deleteDatabase(cacheDir + "/mydatabase\\..\\..\\database1")); - } - - @Test - public void testDeleteDatabase_RelativePathForwardslashClearnedUp() { - assertNotNull(androidDatabaseService.openDatabase(cacheDir + "/mydatabase/../../database1")); - assertTrue(androidDatabaseService.deleteDatabase(cacheDir + "/mydatabase/../../database1")); - } - - @Test - public void testDeleteDatabase_RelativePathBackslashDoesNotChangeDir() { - assertNotNull(androidDatabaseService.openDatabase(cacheDir + "/mydatabase\\..\\database1")); - assertFalse(androidDatabaseService.deleteDatabase(cacheDir + "/database1")); - } - - @Test - public void testDeleteDatabase_RelativePathForwardslashDoesNotChangeDir() { - assertNotNull(androidDatabaseService.openDatabase(cacheDir + "/mydatabase/../database1")); - assertFalse(androidDatabaseService.deleteDatabase(cacheDir + "/database1")); - } - - @Test - public void testDeleteDatabase_RelativePathMixedWorkTheSameWhenNotMatch() { - assertNotNull(androidDatabaseService.openDatabase(cacheDir + "/mydatabase\\..\\database1")); - assertTrue(androidDatabaseService.deleteDatabase(cacheDir + "/mydatabase/../../database1")); - } - - @Test - public void testDeleteDatabase_RelativePathMixedWorkTheSameWhenMatch() { - assertNotNull(androidDatabaseService.openDatabase(cacheDir + "/mydatabase\\..\\database1")); - assertTrue(androidDatabaseService.deleteDatabase(cacheDir + "/mydatabase/../database1")); - } - - @Test - public void testDeleteDatabase_RelativePathMixedWorkTheSameWhenMatch1() { - - AndroidSystemInfoService systemInfoService = new AndroidSystemInfoService(); - androidDatabaseService = new AndroidDatabaseService(systemInfoService); - assertNull(androidDatabaseService.openDatabase("/invalid/file/path")); - } - - // - // TEST_F(DatabaseServiceTest, OpenAndDeleteRelativePathComponentMixedWorkTheSameWhenNotMatch) { - // EXPECT_NE(nullptr, database_service_->OpenDatabase("mydatabase\\..\\database1")) << "Open at relative path"; - // EXPECT_FALSE(database_service_->DeleteDatabase("mydatabase/../../database1")) << "Delete at relative path"; - // } - // - // TEST_F(DatabaseServiceTest, OpenAndDeleteRelativePathComponentMixedWorkTheSameWhenMatch) { - // EXPECT_NE(nullptr, database_service_->OpenDatabase("mydatabase\\..\\..\\database1")) << "Open at relative path"; - // EXPECT_TRUE(database_service_->DeleteDatabase("mydatabase/../../database1")) << "Delete at relative path"; - // } - -} diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseTests.java deleted file mode 100644 index 723078d30..000000000 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidDatabaseTests.java +++ /dev/null @@ -1,1827 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; -import org.junit.runner.RunWith; - -import android.content.Context; -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import android.util.Log; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnConstraint; -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnConstraint.AUTOINCREMENT; -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnConstraint.PRIMARY_KEY; -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnDataType; -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnDataType.INTEGER; -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnDataType.TEXT; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertNull; -import static junit.framework.Assert.assertTrue; -import static org.junit.Assert.assertNotEquals; - -@SuppressWarnings("all") -@RunWith(AndroidJUnit4.class) -public class AndroidDatabaseTests { - - @Rule - public TestName name = new TestName(); - - private DatabaseService androidDatabaseService; - private DatabaseService.Database androidDatabase; - private DatabaseService.QueryResult queryResult; - private Query testQuery; - - @Before - public void beforeEach() { - Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); - androidDatabaseService = new AndroidDatabaseService(null); - androidDatabase = androidDatabaseService.openDatabase(TestUtils.getCacheDir( - InstrumentationRegistry.getInstrumentation().getTargetContext()) + "/" + - name.getMethodName()); - } - - @After - public void afterEach() { - TestUtils.deleteAllFilesInCacheDir(InstrumentationRegistry.getInstrumentation().getTargetContext()); - - if (queryResult != null) { - queryResult.close(); - } - } - - @Test - public void testCreateTable_Happy() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - new ArrayList>() { - { - add(new ArrayList() { - { - add(PRIMARY_KEY); - add(AUTOINCREMENT); - } - }); - add(null); - } - }); - assertTrue(success); - } - - @Test - public void testCreateTable_Migrate() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"z_test_col0", "y_test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - new ArrayList>() { - { - add(new ArrayList() { - { - add(PRIMARY_KEY); - add(AUTOINCREMENT); - } - }); - add(null); - } - }); - assertTrue(success); - - - for (int i = 0; i < 10; i++) { - Map values = new HashMap(); - values.put("z_test_col0", i); - values.put("y_test_col1", "string_" + i); - assertTrue(androidDatabase.insert("test_table", values)); - } - - testQuery = new Query.Builder("test_table", new String[] {"z_test_col0", "y_test_col1"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(10, numRows); - - boolean successNewTable = androidDatabase.createTable("test_table", new String[] {"z_test_col0", "y_test_col1", "x_test_col2"}, - new - ColumnDataType[] {INTEGER, TEXT, TEXT}, - new ArrayList>() { - { - add(new ArrayList() { - { - add(PRIMARY_KEY); - add(AUTOINCREMENT); - } - }); - add(null); - add(null); - } - }); - assertTrue(successNewTable); - - testQuery = new Query.Builder("test_table", new String[] {"z_test_col0", "y_test_col1", "x_test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRowsInNewTable = 0; - - while (queryResult.moveToNext()) { - int c0 = queryResult.getInt(0); - String c1 = queryResult.getString(1); - String c2 = queryResult.getString(2); - assertEquals(numRowsInNewTable, c0); - assertEquals("string_" + numRowsInNewTable, c1); - assertNull(c2); - numRowsInNewTable++; - } - - assertEquals(10, numRowsInNewTable); - - } - - - @Test - public void testCreateTable_Happy_NullConstraints() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - } - - @Test - public void testCreateTable_EmptyConstraints() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - new ArrayList>()); - assertFalse(success); - } - - @Test - public void testCreateTable_Happy_EmptyConstraintsForEachColumn() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - new ArrayList>() { - { - add(new ArrayList()); - add(new ArrayList()); - } - }); - assertTrue(success); - } - - @Test - public void testCreateTable_NullTableName() throws Exception { - boolean success = androidDatabase.createTable(null, new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - new ArrayList>() { - { - add(new ArrayList() { - { - add(PRIMARY_KEY); - add(AUTOINCREMENT); - } - }); - add(null); - } - }); - assertFalse(success); - } - - @Test - public void testCreateTable_EmptyTableName() throws Exception { - boolean success = androidDatabase.createTable("", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - new ArrayList>() { - { - add(new ArrayList() { - { - add(PRIMARY_KEY); - add(AUTOINCREMENT); - } - }); - add(null); - } - }); - assertFalse(success); - } - - @Test - public void testCreateTable_NullColumns() throws Exception { - boolean success = androidDatabase.createTable("test_table", null, new - ColumnDataType[] {INTEGER, TEXT}, - new ArrayList>() { - { - add(new ArrayList() { - { - add(PRIMARY_KEY); - add(AUTOINCREMENT); - } - }); - add(null); - } - }); - assertFalse(success); - } - - @Test - public void testCreateTable_EmptyColumns() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {}, new - ColumnDataType[] {INTEGER, TEXT}, - new ArrayList>() { - { - add(new ArrayList() { - { - add(PRIMARY_KEY); - add(AUTOINCREMENT); - } - }); - add(null); - } - }); - assertFalse(success); - } - - @Test - public void testCreateTable_NullDataTypes() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, null, - new ArrayList>() { - { - add(new ArrayList() { - { - add(PRIMARY_KEY); - add(AUTOINCREMENT); - } - }); - add(null); - } - }); - assertFalse(success); - } - - @Test - public void testCreateTable_EmptyDataTypes() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {}, - new ArrayList>() { - { - add(new ArrayList() { - { - add(PRIMARY_KEY); - add(AUTOINCREMENT); - } - }); - add(null); - } - }); - assertFalse(success); - } - - @Test - public void testCreateTable_IncorrectNumDataTypes() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT, ColumnDataType.REAL}, - new ArrayList>() { - { - add(new ArrayList() { - { - add(PRIMARY_KEY); - add(AUTOINCREMENT); - } - }); - add(null); - } - }); - assertFalse(success); - } - - @Test - public void testQuery_Happy() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - Map values = new HashMap(); - values.put("test_col0", i); - values.put("test_col1", "string_" + UUID.randomUUID()); - androidDatabase.insert("test_table", values); - } - - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).selection("test_col0 < ?", - new String[] {"5"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(5, numRows); - } - - @Test - public void testQuery_NullTableName() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - Map values = new HashMap(); - values.put("test_col0", i); - values.put("test_col1", "string_" + UUID.randomUUID()); - androidDatabase.insert("test_table", values); - } - - testQuery = new Query.Builder(null, new String[] {"test_col0", "test_col1"}).selection("test_col0 < ?", new String[] {"5"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNull(queryResult); - } - - @Test - public void testQuery_EmptyTableName() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - Map values = new HashMap(); - values.put("test_col0", i); - values.put("test_col1", "string_" + UUID.randomUUID()); - androidDatabase.insert("test_table", values); - } - - testQuery = new Query.Builder("", new String[] {"test_col0", "test_col1"}).selection("test_col0 < ?", new String[] {"5"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNull(queryResult); - } - - @Test - public void testQuery_NullColumns() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - Map values = new HashMap(); - values.put("test_col0", i); - values.put("test_col1", "string_" + UUID.randomUUID()); - androidDatabase.insert("test_table", values); - } - - testQuery = new Query.Builder("test_table", null).selection("test_col0 < ?", new String[] {"5"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(5, numRows); - } - - @Test - public void testQuery_EmptyColumns() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - Map values = new HashMap(); - values.put("test_col0", i); - values.put("test_col1", "string_" + UUID.randomUUID()); - androidDatabase.insert("test_table", values); - } - - testQuery = new Query.Builder("test_table", new String[] {}).selection("test_col0 < ?", new String[] {"5"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(5, numRows); - } - - @Test - public void testQuery_NullSelection() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - Map values = new HashMap(); - values.put("test_col0", i); - values.put("test_col1", "string_" + UUID.randomUUID()); - androidDatabase.insert("test_table", values); - } - - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).selection(null, new String[] {"5"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNull(queryResult); - } - - @Test - public void testQuery_EmptySelection() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - Map values = new HashMap(); - values.put("test_col0", i); - values.put("test_col1", "string_" + UUID.randomUUID()); - androidDatabase.insert("test_table", values); - } - - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).selection("", new String[] {"5"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNull(queryResult); - } - - @Test - public void testQuery_NullSelectionArgs() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - Map values = new HashMap(); - values.put("test_col0", i); - values.put("test_col1", "string_" + UUID.randomUUID()); - androidDatabase.insert("test_table", values); - } - - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).selection("test_col0 < ?", - null).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(0, numRows); - } - - @Test - public void testQuery_EmptySelectionArgs() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - Map values = new HashMap(); - values.put("test_col0", i); - values.put("test_col1", "string_" + UUID.randomUUID()); - androidDatabase.insert("test_table", values); - } - - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).selection("test_col0 < ?", - new String[] {}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(0, numRows); - } - - @Test - public void testQuery_EmptyTable() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).selection("test_col0 < ?", - new String[] {"5"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(0, numRows); - } - - @Test - public void testQuery_TableNotExists() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - Map values = new HashMap(); - values.put("test_col0", i); - values.put("test_col1", "string_" + UUID.randomUUID()); - androidDatabase.insert("test_table", values); - } - - testQuery = new Query.Builder("test_table_not_exists", new String[] {"test_col0", "test_col1"}).selection("test_col0 < ?", - new String[] {"5"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNull(queryResult); - } - - @Test - public void testQuery_Happy_GroupBy() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - Map values = new HashMap(); - values.put("test_col0", i); - values.put("test_col1", "string_" + UUID.randomUUID()); - values.put("test_col2", "group"); - androidDatabase.insert("test_table", values); - } - - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).selection("test_col0 < ?", - new String[] {"5"}).groupBy("test_col2").build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(1, numRows); - } - - @Test - public void testQuery_EmptyGroupBy() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - Map values = new HashMap(); - values.put("test_col0", i); - values.put("test_col1", "string_" + UUID.randomUUID()); - androidDatabase.insert("test_table", values); - } - - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).selection("test_col0 < ?", - new String[] {"5"}).groupBy("").build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(5, numRows); - } - - @Test - public void testQuery_Happy_EmptyHaving() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - Map values = new HashMap(); - values.put("test_col0", i); - values.put("test_col1", "string_" + UUID.randomUUID()); - androidDatabase.insert("test_table", values); - } - - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).selection("test_col0 < ?", - new String[] {"5"}).having("").build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(5, numRows); - } - - @Ignore - @Test - public void testQuery_Happy_Having() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, TEXT}, - null); - assertTrue(success); - List> values = new ArrayList>() { - { - add(new HashMap() { - { - put("test_col0", 1); - put("test_col1", "string0"); - put("test_col2", "group0"); - } - }); - add(new HashMap() { - { - put("test_col0", 1); - put("test_col1", "string0"); - put("test_col2", "group1"); - } - }); - add(new HashMap() { - { - put("test_col0", 1); - put("test_col1", "string1"); - put("test_col2", "group0"); - } - }); - add(new HashMap() { - { - put("test_col0", 1); - put("test_col1", "string1"); - put("test_col2", "group1"); - } - }); - } - }; - - for (Map value : values) { - androidDatabase.insert("test_table", value); - } - - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).groupBy("test_col2").having("test_col1 = 'string1'").build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(2, numRows); - } - - @Test - public void testQuery_Having_NoGroupBy() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, TEXT}, - null); - assertTrue(success); - List> values = new ArrayList>() { - { - add(new HashMap() { - { - put("test_col0", 1); - put("test_col1", "string0"); - put("test_col2", "group0"); - } - }); - add(new HashMap() { - { - put("test_col0", 1); - put("test_col1", "string0"); - put("test_col2", "group1"); - } - }); - add(new HashMap() { - { - put("test_col0", 1); - put("test_col1", "string1"); - put("test_col2", "group0"); - } - }); - add(new HashMap() { - { - put("test_col0", 1); - put("test_col1", "string1"); - put("test_col2", "group1"); - } - }); - } - }; - - for (Map value : values) { - androidDatabase.insert("test_table", value); - } - - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).having("test_col1 = 'string1'").build(); - queryResult = androidDatabase.query(testQuery); - assertNull(queryResult); - } - - @Test - public void testQuery_Happy_Limit() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - Map values = new HashMap(); - values.put("test_col0", i); - values.put("test_col1", "string_" + UUID.randomUUID()); - androidDatabase.insert("test_table", values); - } - - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).selection("test_col0 < ?", - new String[] {"5"}).limit("2").build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(2, numRows); - } - - @Test - public void testQuery_Happy_EmptyLimit() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - Map values = new HashMap(); - values.put("test_col0", i); - values.put("test_col1", "string_" + UUID.randomUUID()); - androidDatabase.insert("test_table", values); - } - - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).selection("test_col0 < ?", - new String[] {"5"}).limit("").build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(5, numRows); - } - - - // long insert(String table, - // Map values); - - @Test - public void testInsert_Happy() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - Map values = new HashMap(); - values.put("test_col0", i); - values.put("test_col1", "string_" + UUID.randomUUID()); - assertTrue(androidDatabase.insert("test_table", values)); - } - - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(10, numRows); - } - - @Test - public void testInsert_NullTableName() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - Map values = new HashMap(); - values.put("test_col0", i); - values.put("test_col1", "string_" + UUID.randomUUID()); - assertFalse(androidDatabase.insert(null, values)); - } - - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(0, numRows); - } - - @Test - public void testInsert_EmptyTableName() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - Map values = new HashMap(); - values.put("test_col0", i); - values.put("test_col1", "string_" + UUID.randomUUID()); - assertFalse(androidDatabase.insert("", values)); - } - - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(0, numRows); - } - - @Test - public void testInsert_TableNotExists() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - Map values = new HashMap(); - values.put("test_col0", i); - values.put("test_col1", "string_" + UUID.randomUUID()); - assertFalse(androidDatabase.insert("test_table_not_exists", values)); - } - - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(0, numRows); - } - - @Test - public void testInsert_NullValues() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - assertFalse(androidDatabase.insert("test_table", null)); - } - - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(0, numRows); - } - - @Test - public void testInsert_EmptyValues() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - assertFalse(androidDatabase.insert("test_table", new HashMap())); - } - - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(0, numRows); - } - - @Test - public void testInsert_IncorrectColumnNames() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - assertFalse(androidDatabase.insert("test_table", new HashMap() { - { - put("test_col_not_exists", "suh"); - put("test_col1", "dude"); - } - })); - } - - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(0, numRows); - } - - //todo fix expectation? android's sqlitedatabase api will insert rows with incorrect value types. in this case it uses the int value 0 for the column test_col0 when attempting to insert a string value - // @Test - public void testInsert_IncorrectValueTypes() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - - for (int i = 0; i < 10; i++) { - Map values = new HashMap(); - values.put("test_col0", "this_should_be_an_int_" + UUID.randomUUID()); - values.put("test_col1", "string_" + UUID.randomUUID()); - // assertFalse(androidDatabase.insert("test_table", values)); - androidDatabase.insert("test_table", values); - } - - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).build(); - queryResult = androidDatabase.query(testQuery); - - while (queryResult.moveToNext()) { - int testCol0Value = queryResult.getInt(0); - String testCol1Value = queryResult.getString(1); - Log.d("QUERY RESULT: ", "col0 = " + testCol0Value + " col1 = " + testCol1Value); - } - - assertNull(queryResult); - } - - @Test - public void testUpdate_Happy() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - Map values = new HashMap(); - values.put("test_col0", 0); - values.put("test_col1", "string_" + UUID.randomUUID()); - assertTrue(androidDatabase.insert("test_table", values)); - Map newValues = new HashMap<>(); - newValues.put("test_col0", 1); - newValues.put("test_col1", "newString"); - assertTrue(androidDatabase.update("test_table", newValues, null, null)); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(1, numRows); - queryResult.moveToFirst(); - assertEquals(1, queryResult.getInt(0)); - assertEquals("newString", queryResult.getString(1)); - } - - @Test - public void testUpdate_NullTableName() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - Map values = new HashMap(); - values.put("test_col0", 0); - values.put("test_col1", "string_" + UUID.randomUUID()); - assertTrue(androidDatabase.insert("test_table", values)); - Map newValues = new HashMap<>(); - newValues.put("test_col0", 1); - newValues.put("test_col1", "newString"); - assertFalse(androidDatabase.update(null, newValues, null, null)); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(1, numRows); - queryResult.moveToFirst(); - assertEquals(0, queryResult.getInt(0)); - } - - @Test - public void testUpdate_EmptyTableName() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - Map values = new HashMap(); - values.put("test_col0", 0); - values.put("test_col1", "string_" + UUID.randomUUID()); - assertTrue(androidDatabase.insert("test_table", values)); - Map newValues = new HashMap<>(); - newValues.put("test_col0", 1); - newValues.put("test_col1", "newString"); - assertFalse(androidDatabase.update("", newValues, null, null)); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(1, numRows); - queryResult.moveToFirst(); - assertEquals(0, queryResult.getInt(0)); - } - - @Test - public void testUpdate_TableNotExists() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - Map values = new HashMap(); - values.put("test_col0", 0); - values.put("test_col1", "string_" + UUID.randomUUID()); - assertTrue(androidDatabase.insert("test_table", values)); - Map newValues = new HashMap<>(); - newValues.put("test_col0", 1); - newValues.put("test_col1", "newString"); - assertFalse(androidDatabase.update("test_table_not_exists", newValues, null, null)); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(1, numRows); - queryResult.moveToFirst(); - assertEquals(0, queryResult.getInt(0)); - } - - @Test - public void testUpdate_NullValues() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - Map values = new HashMap(); - values.put("test_col0", 0); - values.put("test_col1", "string_" + UUID.randomUUID()); - assertTrue(androidDatabase.insert("test_table", values)); - Map newValues = new HashMap<>(); - newValues.put("test_col0", 1); - newValues.put("test_col1", "newString"); - assertFalse(androidDatabase.update("test_table", null, null, null)); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(1, numRows); - queryResult.moveToFirst(); - assertEquals(0, queryResult.getInt(0)); - } - - @Test - public void testUpdate_EmptyValues() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - Map values = new HashMap(); - values.put("test_col0", 0); - values.put("test_col1", "string_" + UUID.randomUUID()); - assertTrue(androidDatabase.insert("test_table", values)); - Map newValues = new HashMap<>(); - assertFalse(androidDatabase.update("test_table", newValues, null, null)); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(1, numRows); - queryResult.moveToFirst(); - assertEquals(0, queryResult.getInt(0)); - } - - @Test - public void testUpdate_IncorrectValues() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - Map values = new HashMap(); - values.put("test_col0", "this_should_be_an_int"); - values.put("test_col1", "string_" + UUID.randomUUID()); - assertTrue(androidDatabase.insert("test_table", values)); - Map newValues = new HashMap<>(); - newValues.put("test_col0", 1); - newValues.put("test_col1", "newString"); - assertTrue(androidDatabase.update("test_table", newValues, null, null)); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(1, numRows); - queryResult.moveToFirst(); - assertEquals(1, queryResult.getInt(0)); - assertEquals("newString", queryResult.getString(1)); - } - - @Test - public void testUpdate_EmptyWhereClause() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - null); - assertTrue(success); - Map values = new HashMap(); - values.put("test_col0", 0); - values.put("test_col1", "string_" + UUID.randomUUID()); - assertTrue(androidDatabase.insert("test_table", values)); - Map newValues = new HashMap<>(); - newValues.put("test_col0", 1); - newValues.put("test_col1", "newString"); - assertTrue(androidDatabase.update("test_table", newValues, "", null)); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(1, numRows); - queryResult.moveToFirst(); - assertEquals(1, queryResult.getInt(0)); - assertEquals("newString", queryResult.getString(1)); - } - - @Test - public void testUpdate_Happy_WhereClause() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, TEXT}, - null); - assertTrue(success); - List> values = new ArrayList>() { - { - add(new HashMap() { - { - put("test_col0", 0); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group0"); - } - }); - add(new HashMap() { - { - put("test_col0", 1); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group0"); - } - }); - add(new HashMap() { - { - put("test_col0", 2); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group1"); - } - }); - add(new HashMap() { - { - put("test_col0", 3); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group1"); - } - }); - } - }; - - for (Map value : values) { - assertTrue(androidDatabase.insert("test_table", value)); - } - - Map newValues = new HashMap<>(); - newValues.put("test_col1", "newString"); - assertTrue(androidDatabase.update("test_table", newValues, "test_col2 = ?", new String[] {"group0"})); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - - if (queryResult.getString(2).equals("group0")) { - assertEquals("newString", queryResult.getString(1)); - } - } - - assertEquals(4, numRows); - } - - @Test - public void testUpdate_NullWhereArgs() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, TEXT}, - null); - assertTrue(success); - List> values = new ArrayList>() { - { - add(new HashMap() { - { - put("test_col0", 0); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group0"); - } - }); - add(new HashMap() { - { - put("test_col0", 1); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group0"); - } - }); - add(new HashMap() { - { - put("test_col0", 2); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group1"); - } - }); - add(new HashMap() { - { - put("test_col0", 3); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group1"); - } - }); - } - }; - - for (Map value : values) { - assertTrue(androidDatabase.insert("test_table", value)); - } - - Map newValues = new HashMap<>(); - newValues.put("test_col1", "newString"); - assertTrue(androidDatabase.update("test_table", newValues, "test_col2 = 'group0'", null)); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - - if (queryResult.getString(2).equals("group0")) { - assertEquals("newString", queryResult.getString(1)); - } - } - - assertEquals(4, numRows); - } - - @Test - public void testUpdate_EmptyWhereArgs() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, TEXT}, - null); - assertTrue(success); - List> values = new ArrayList>() { - { - add(new HashMap() { - { - put("test_col0", 0); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group0"); - } - }); - add(new HashMap() { - { - put("test_col0", 1); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group0"); - } - }); - add(new HashMap() { - { - put("test_col0", 2); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group1"); - } - }); - add(new HashMap() { - { - put("test_col0", 3); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group1"); - } - }); - } - }; - - for (Map value : values) { - assertTrue(androidDatabase.insert("test_table", value)); - } - - Map newValues = new HashMap<>(); - newValues.put("test_col1", "newString"); - assertFalse(androidDatabase.update("test_table", newValues, "test_col2 = ?", new String[] {})); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - - if (queryResult.getString(2).equals("group0")) { - assertNotEquals("newString", queryResult.getString(1)); - } - } - - assertEquals(4, numRows); - } - - @Test - public void testDelete_Happy() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, TEXT}, - null); - assertTrue(success); - List> values = new ArrayList>() { - { - add(new HashMap() { - { - put("test_col0", 0); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group0"); - } - }); - add(new HashMap() { - { - put("test_col0", 1); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group0"); - } - }); - add(new HashMap() { - { - put("test_col0", 2); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group1"); - } - }); - add(new HashMap() { - { - put("test_col0", 3); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group1"); - } - }); - } - }; - - for (Map value : values) { - assertTrue(androidDatabase.insert("test_table", value)); - } - - assertTrue(androidDatabase.delete("test_table", "test_col2 = ?", new String[] {"group1"})); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - assertNotEquals("group1", queryResult.getString(2)); - } - - assertEquals(2, numRows); - } - - @Test - public void testDelete_NullTableName() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, TEXT}, - null); - assertTrue(success); - List> values = new ArrayList>() { - { - add(new HashMap() { - { - put("test_col0", 0); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group0"); - } - }); - add(new HashMap() { - { - put("test_col0", 1); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group0"); - } - }); - } - }; - - for (Map value : values) { - assertTrue(androidDatabase.insert("test_table", value)); - } - - assertFalse(androidDatabase.delete(null, "test_col2 = ?", new String[] {"group0"})); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - - if (queryResult.getString(2).equals("group0")) { - assertNotEquals("newString", queryResult.getString(1)); - } - } - - assertEquals(2, numRows); - } - - @Test - public void testDelete_EmptyTableName() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, TEXT}, - null); - assertTrue(success); - List> values = new ArrayList>() { - { - add(new HashMap() { - { - put("test_col0", 0); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group0"); - } - }); - add(new HashMap() { - { - put("test_col0", 1); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group0"); - } - }); - } - }; - - for (Map value : values) { - assertTrue(androidDatabase.insert("test_table", value)); - } - - assertFalse(androidDatabase.delete("", "test_col2 = ?", new String[] {"group0"})); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(2, numRows); - } - - @Test - public void testDelete_TableNotExists() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, TEXT}, - null); - assertTrue(success); - List> values = new ArrayList>() { - { - add(new HashMap() { - { - put("test_col0", 0); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group0"); - } - }); - add(new HashMap() { - { - put("test_col0", 1); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group0"); - } - }); - } - }; - - for (Map value : values) { - assertTrue(androidDatabase.insert("test_table", value)); - } - - assertFalse(androidDatabase.delete("test_table_not_exists", "test_col2 = ?", new String[] {"group0"})); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(2, numRows); - } - - @Test - public void testDelete_EmptyWhereClause() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, TEXT}, - null); - assertTrue(success); - List> values = new ArrayList>() { - { - add(new HashMap() { - { - put("test_col0", 0); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group0"); - } - }); - add(new HashMap() { - { - put("test_col0", 1); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group1"); - } - }); - } - }; - - for (Map value : values) { - assertTrue(androidDatabase.insert("test_table", value)); - } - - assertTrue(androidDatabase.delete("test_table", "", null)); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(0, numRows); - } - - @Test - public void testDelete_NullWhereArgs() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, TEXT}, - null); - assertTrue(success); - List> values = new ArrayList>() { - { - add(new HashMap() { - { - put("test_col0", 0); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group0"); - } - }); - add(new HashMap() { - { - put("test_col0", 1); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group0"); - } - }); - add(new HashMap() { - { - put("test_col0", 2); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group1"); - } - }); - add(new HashMap() { - { - put("test_col0", 3); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group1"); - } - }); - } - }; - - for (Map value : values) { - assertTrue(androidDatabase.insert("test_table", value)); - } - - assertTrue(androidDatabase.delete("test_table", "test_col2 = ?", null)); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - } - - assertEquals(4, numRows); - } - - @Test - public void testDelete_EmptyWhereArgs() throws Exception { - boolean success = androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1", "test_col2"}, new - ColumnDataType[] {INTEGER, TEXT, TEXT}, - null); - assertTrue(success); - List> values = new ArrayList>() { - { - add(new HashMap() { - { - put("test_col0", 0); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group0"); - } - }); - add(new HashMap() { - { - put("test_col0", 1); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group0"); - } - }); - add(new HashMap() { - { - put("test_col0", 2); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group1"); - } - }); - add(new HashMap() { - { - put("test_col0", 3); - put("test_col1", "string_" + UUID.randomUUID()); - put("test_col2", "group1"); - } - }); - } - }; - - for (Map value : values) { - assertTrue(androidDatabase.insert("test_table", value)); - } - - assertTrue(androidDatabase.delete("test_table", "test_col2 = ?", new String[] {})); - testQuery = new Query.Builder("test_table", new String[] {"test_col0", "test_col1", "test_col2"}).build(); - queryResult = androidDatabase.query(testQuery); - assertNotNull(queryResult); - int numRows = 0; - - while (queryResult.moveToNext()) { - numRows++; - - if (queryResult.getString(2).equals("group0")) { - assertNotEquals("newString", queryResult.getString(1)); - } - } - - assertEquals(4, numRows); - } - - - @Test - public void testClose_Happy() throws Exception { - androidDatabase.close(); - } - - @Test - public void testDelete_FromEmptyTable() throws Exception { - boolean tableCreationResult = androidDatabase.createTable("test_table", new String[] {"col0", "col1"}, - new ColumnDataType[] {TEXT, TEXT}, new ArrayList>() { - { - List col1Constraints = new ArrayList<>(); - List col2Constraints = new ArrayList<>(); - col1Constraints.add(ColumnConstraint.NOT_NULL); - col2Constraints.add(ColumnConstraint.NOT_NULL); - add(col1Constraints); - add(col2Constraints); - } - }); - Assert.assertTrue(tableCreationResult); - - //Fetch all rows in table. Count should be 0. - Query query = new Query.Builder("test_table", new String[] {"col0", "col1"}).selection(null, null).build(); - DatabaseService.QueryResult queryResult = androidDatabase.query(query); - Assert.assertTrue("Table is expected to be empty.", queryResult.getCount() == 0); - - boolean emptyTableDeletionResult = androidDatabase.delete("test_table", null, null); - Assert.assertTrue("Tried to delete empty table, result should be true", emptyTableDeletionResult); - } - - // AMSDK-7648 - // This test should no longer receive an IllegalStateException because we now handle it - // gracefully. - @Test - public void testQueryAfterClose() throws Exception { - androidDatabase.close(); - androidDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new - ColumnDataType[] {INTEGER, TEXT}, - new ArrayList>() { - { - add(new ArrayList() { - { - add(PRIMARY_KEY); - add(AUTOINCREMENT); - } - }); - add(null); - } - }); - } - -} diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/QueryStringBuilderTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/QueryStringBuilderTests.java deleted file mode 100644 index 30ee19266..000000000 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/QueryStringBuilderTests.java +++ /dev/null @@ -1,365 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import android.content.Context; -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import java.util.ArrayList; -import java.util.List; - -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnConstraint; -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnConstraint.AUTOINCREMENT; -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnConstraint.NOT_NULL; -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnConstraint.PRIMARY_KEY; -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnConstraint.UNIQUE; -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnDataType; -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnDataType.INTEGER; -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnDataType.REAL; -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnDataType.TEXT; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNull; - -@SuppressWarnings("all") -@RunWith(AndroidJUnit4.class) -public class QueryStringBuilderTests { - - @Before - public void beforeEach() { - Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); - } - - @After - public void afterEach() { - } - - @Test - public void testGetCreateTableQueryString_Happy() throws Exception { - String testTableName = "test_table"; - String[] columns = new String[] {"test_col0", "test_col1", "test_col2"}; - ColumnDataType[] columnDataTypes = new ColumnDataType[] {INTEGER, TEXT, REAL}; - List> columnConstraints = new ArrayList>() { - { - add(new ArrayList() { - { - add(PRIMARY_KEY); - add(AUTOINCREMENT); - } - }); - add(new ArrayList() { - { - add(UNIQUE); - } - }); - add(new ArrayList() { - { - add(NOT_NULL); - } - }); - } - }; - String createTableQuery = QueryStringBuilder.getCreateTableQueryString(testTableName, columns, columnDataTypes, - columnConstraints, false); - assertEquals("CREATE TABLE IF NOT EXISTS test_table(test_col0 INTEGER PRIMARY KEY AUTOINCREMENT, test_col1 TEXT UNIQUE, test_col2 REAL NOT NULL)", - createTableQuery); - } - - @Test - public void testGetCreateTableQueryString_Happy_PartialConstraints() throws Exception { - String testTableName = "test_table"; - String[] columns = new String[] {"test_col0", "test_col1", "test_col2"}; - ColumnDataType[] columnDataTypes = new ColumnDataType[] {INTEGER, TEXT, REAL}; - List> columnConstraints = new ArrayList>() { - { - add(new ArrayList() { - { - add(PRIMARY_KEY); - add(AUTOINCREMENT); - } - }); - add(new ArrayList()); - add(new ArrayList() { - { - add(NOT_NULL); - } - }); - } - }; - String createTableQuery = QueryStringBuilder.getCreateTableQueryString(testTableName, columns, columnDataTypes, - columnConstraints, false); - assertEquals("CREATE TABLE IF NOT EXISTS test_table(test_col0 INTEGER PRIMARY KEY AUTOINCREMENT, test_col1 TEXT, test_col2 REAL NOT NULL)", - createTableQuery); - } - - @Test - public void testGetCreateTableQueryString_Happy_NullConstraints() throws Exception { - String testTableName = "test_table"; - String[] columns = new String[] {"test_col0", "test_col1", "test_col2"}; - ColumnDataType[] columnDataTypes = new ColumnDataType[] {INTEGER, TEXT, REAL}; - List> columnConstraints = null; - String createTableQuery = QueryStringBuilder.getCreateTableQueryString(testTableName, columns, columnDataTypes, - columnConstraints, false); - assertEquals("CREATE TABLE IF NOT EXISTS test_table(test_col0 INTEGER, test_col1 TEXT, test_col2 REAL)", - createTableQuery); - } - - @Test - public void testGetCreateTableQueryString_EmptyConstraints() throws Exception { - String testTableName = "test_table"; - String[] columns = new String[] {"test_col0", "test_col1", "test_col2"}; - ColumnDataType[] columnDataTypes = new ColumnDataType[] {INTEGER, TEXT, REAL}; - List> columnConstraints = new ArrayList>(); - String createTableQuery = QueryStringBuilder.getCreateTableQueryString(testTableName, columns, columnDataTypes, - columnConstraints, false); - assertEquals("CREATE TABLE IF NOT EXISTS test_table(test_col0 INTEGER, test_col1 TEXT, test_col2 REAL)", - createTableQuery); - } - - @Test - public void testGetCreateTableQueryString_NullTableName() throws Exception { - String testTableName = null; - String[] columns = new String[] {"test_col0", "test_col1", "test_col2"}; - ColumnDataType[] columnDataTypes = new ColumnDataType[] {INTEGER, TEXT, REAL}; - List> columnConstraints = new ArrayList>() { - { - add(new ArrayList() { - { - add(PRIMARY_KEY); - add(AUTOINCREMENT); - } - }); - add(new ArrayList() { - { - add(UNIQUE); - } - }); - add(new ArrayList() { - { - add(NOT_NULL); - } - }); - } - }; - String createTableQuery = QueryStringBuilder.getCreateTableQueryString(testTableName, columns, columnDataTypes, - columnConstraints, false); - assertNull(createTableQuery); - } - - @Test - public void testGetCreateTableQueryString_EmptyTableName() throws Exception { - String testTableName = ""; - String[] columns = new String[] {"test_col0", "test_col1", "test_col2"}; - ColumnDataType[] columnDataTypes = new ColumnDataType[] {INTEGER, TEXT, REAL}; - List> columnConstraints = new ArrayList>() { - { - add(new ArrayList() { - { - add(PRIMARY_KEY); - add(AUTOINCREMENT); - } - }); - add(new ArrayList() { - { - add(UNIQUE); - } - }); - add(new ArrayList() { - { - add(NOT_NULL); - } - }); - } - }; - String createTableQuery = QueryStringBuilder.getCreateTableQueryString(testTableName, columns, columnDataTypes, - columnConstraints, false); - assertNull(createTableQuery); - } - - @Test - public void testGetCreateTableQueryString_NullColumns() throws Exception { - String testTableName = "test_table"; - String[] columns = null; - ColumnDataType[] columnDataTypes = new ColumnDataType[] {INTEGER, TEXT, REAL}; - List> columnConstraints = new ArrayList>() { - { - add(new ArrayList() { - { - add(PRIMARY_KEY); - add(AUTOINCREMENT); - } - }); - add(new ArrayList() { - { - add(UNIQUE); - } - }); - add(new ArrayList() { - { - add(NOT_NULL); - } - }); - } - }; - String createTableQuery = QueryStringBuilder.getCreateTableQueryString(testTableName, columns, columnDataTypes, - columnConstraints, false); - assertNull(createTableQuery); - } - - @Test - public void testGetCreateTableQueryString_EmptyColumns() throws Exception { - String testTableName = "test_table"; - String[] columns = new String[] {}; - ColumnDataType[] columnDataTypes = new ColumnDataType[] {INTEGER, TEXT, REAL}; - List> columnConstraints = new ArrayList>() { - { - add(new ArrayList() { - { - add(PRIMARY_KEY); - add(AUTOINCREMENT); - } - }); - add(new ArrayList() { - { - add(UNIQUE); - } - }); - add(new ArrayList() { - { - add(NOT_NULL); - } - }); - } - }; - String createTableQuery = QueryStringBuilder.getCreateTableQueryString(testTableName, columns, columnDataTypes, - columnConstraints, false); - assertNull(createTableQuery); - } - - @Test - public void testGetCreateTableQueryString_IncorrectNumDataTypes() throws Exception { - String testTableName = "test_table"; - String[] columns = new String[] {"test_col0", "test_col1", "test_col2"}; - ColumnDataType[] columnDataTypes = new ColumnDataType[] {INTEGER, TEXT, REAL, INTEGER}; - List> columnConstraints = new ArrayList>() { - { - add(new ArrayList() { - { - add(PRIMARY_KEY); - add(AUTOINCREMENT); - } - }); - add(new ArrayList() { - { - add(UNIQUE); - } - }); - add(new ArrayList() { - { - add(NOT_NULL); - } - }); - } - }; - String createTableQuery = QueryStringBuilder.getCreateTableQueryString(testTableName, columns, columnDataTypes, - columnConstraints, false); - assertNull(createTableQuery); - } - - @Test - public void testGetCreateTableQueryString_IncorrectNumConstraints() throws Exception { - String testTableName = "test_table"; - String[] columns = new String[] {"test_col0", "test_col1", "test_col2"}; - ColumnDataType[] columnDataTypes = new ColumnDataType[] {INTEGER, TEXT, REAL}; - List> columnConstraints = new ArrayList>() { - { - add(new ArrayList() { - { - add(PRIMARY_KEY); - add(AUTOINCREMENT); - } - }); - add(new ArrayList() { - { - add(UNIQUE); - } - }); - add(new ArrayList() { - { - add(UNIQUE); - add(NOT_NULL); - } - }); - add(new ArrayList() { - { - add(NOT_NULL); - } - }); - } - }; - String createTableQuery = QueryStringBuilder.getCreateTableQueryString(testTableName, columns, columnDataTypes, - columnConstraints, false); - assertNull(createTableQuery); - } - - @Test - public void testGetCreateTableQueryString_AllNull() throws Exception { - String testTableName = null; - String[] columns = null; - ColumnDataType[] columnDataTypes = null; - List> columnConstraints = null; - String createTableQuery = QueryStringBuilder.getCreateTableQueryString(testTableName, columns, columnDataTypes, - columnConstraints, false); - assertNull(createTableQuery); - } - - @Test - public void testGetCreateTableQueryString_DefaultValue() throws Exception { - String testTableName = "test_table"; - String[] columns = new String[] {"test_col0", "test_col1", "test_col2", "test_col3"}; - ColumnDataType[] columnDataTypes = new ColumnDataType[] {INTEGER, TEXT, REAL, INTEGER}; - List> columnConstraints = new ArrayList>() { - { - add(new ArrayList() { - { - add(PRIMARY_KEY); - add(AUTOINCREMENT); - } - }); - add(new ArrayList() { - { - add(UNIQUE); - } - }); - add(new ArrayList() { - { - add(NOT_NULL); - } - }); - add(new ArrayList() { - { - add(NOT_NULL); - } - }); - } - }; - String createTableQuery = QueryStringBuilder.getCreateTableQueryString(testTableName, columns, columnDataTypes, - columnConstraints, true); - assertEquals("CREATE TABLE IF NOT EXISTS test_table(test_col0 INTEGER PRIMARY KEY AUTOINCREMENT, test_col1 TEXT UNIQUE DEFAULT '' , test_col2 REAL NOT NULL DEFAULT 0.0 , test_col3 INTEGER NOT NULL DEFAULT 0 )", - createTableQuery); - } -} diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/internal/utility/SQLiteDatabaseHelperTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/internal/utility/SQLiteDatabaseHelperTests.java index 8b1e890de..ecc592b55 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/internal/utility/SQLiteDatabaseHelperTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/internal/utility/SQLiteDatabaseHelperTests.java @@ -12,258 +12,220 @@ package com.adobe.marketing.mobile.internal.utility; import android.content.ContentValues; -import androidx.test.platform.app.InstrumentationRegistry; +import android.database.sqlite.SQLiteDatabase; + import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + import com.adobe.marketing.mobile.services.DataEntity; + import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; + import java.io.File; import java.util.HashMap; -import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; @RunWith(AndroidJUnit4.class) public class SQLiteDatabaseHelperTests { - private static final String TABLE_NAME = "TB_AEP_DATA_ENTITY"; - private static final String TB_KEY_UNIQUE_IDENTIFIER = "uniqueIdentifier"; - private static final String TB_KEY_TIMESTAMP = "timestamp"; - private static final String TB_KEY_DATA = "data"; - private String dbPath; - - @Before - public void setUp() { - dbPath = new File(InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(), "test.sqlite").getPath(); - createTable(); - } - - @After - public void dispose() { - SQLiteDatabaseHelper.clearTable(dbPath, TABLE_NAME); - } - - private void createTable() { - final String tableCreationQuery = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + - " (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, " + - "uniqueIdentifier TEXT NOT NULL UNIQUE, " + - "timestamp INTEGER NOT NULL, " + - "data TEXT);"; - - SQLiteDatabaseHelper.createTableIfNotExist(dbPath, tableCreationQuery); - } - - @Test - public void testTableIsEmptyInitially() { - //Action - int size = SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME); - - //Assert - Assert.assertEquals(size, 0); - } - - @Test - public void testAddData_Success() { - //Prepare data - DataEntity dataEntity = new DataEntity("dataentity1"); - Map row = new HashMap<>(); - row.put(TB_KEY_DATA, dataEntity.getData()); - row.put(TB_KEY_UNIQUE_IDENTIFIER, dataEntity.getUniqueIdentifier()); - row.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); - - //Action - Assert.assertTrue(SQLiteDatabaseHelper.insertRow(dbPath, TABLE_NAME, row)); - - //Assert - Assert.assertEquals(SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME), 1); - } - - @Test - public void testAddData_Failure() { - //Prepare data - DataEntity dataEntity = new DataEntity("dataentity1"); - Map row = new HashMap<>(); - row.put(TB_KEY_DATA, dataEntity.getData()); - row.put(TB_KEY_UNIQUE_IDENTIFIER, dataEntity.getUniqueIdentifier()); - row.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); - - String incorrectDbPath = "incorrect_db_path"; - - //Action - Assert.assertFalse(SQLiteDatabaseHelper.insertRow(incorrectDbPath, TABLE_NAME, row)); - - //Assert - Assert.assertEquals(SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME), 0); - } - - @Test - public void testQueryDb_Success() { - //Prepare data - final String dataEntityName = "dataentity"; - DataEntity dataEntity = new DataEntity(dataEntityName); - Map row = new HashMap<>(); - row.put(TB_KEY_DATA, dataEntity.getData()); - row.put(TB_KEY_UNIQUE_IDENTIFIER, dataEntity.getUniqueIdentifier()); - row.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); - - //Action - Assert.assertTrue(SQLiteDatabaseHelper.insertRow(dbPath, TABLE_NAME, row)); - - List contentValues = SQLiteDatabaseHelper.query(dbPath, TABLE_NAME, new String[] {TB_KEY_UNIQUE_IDENTIFIER, TB_KEY_TIMESTAMP, TB_KEY_DATA}, - 1); - - //Assert - Assert.assertEquals(contentValues.size(), 1); - Assert.assertEquals(contentValues.get(0).get(TB_KEY_DATA), dataEntityName); - Assert.assertNotNull(contentValues.get(0).get(TB_KEY_TIMESTAMP)); - Assert.assertNotNull(contentValues.get(0).get(TB_KEY_UNIQUE_IDENTIFIER)); - - } - - @Test - public void testQueryDb_Failure() { - //Prepare data - final String dataEntityName = "dataentity"; - DataEntity dataEntity = new DataEntity(dataEntityName); - Map row = new HashMap<>(); - row.put(TB_KEY_DATA, dataEntity.getData()); - row.put(TB_KEY_UNIQUE_IDENTIFIER, dataEntity.getUniqueIdentifier()); - row.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); - - String incorrectDbPath = "incorrect_db_path"; - //Action - Assert.assertFalse(SQLiteDatabaseHelper.insertRow(incorrectDbPath, TABLE_NAME, row)); - - List contentValues = SQLiteDatabaseHelper.query(incorrectDbPath, TABLE_NAME, new String[] {TB_KEY_UNIQUE_IDENTIFIER, TB_KEY_TIMESTAMP, TB_KEY_DATA}, - 1); - - //Assert - Assert.assertTrue(contentValues.isEmpty()); - } - - @Test - public void testGetTableSize_Success() { - //Prepare data - final String dataEntityName = "dataentity"; - DataEntity dataEntity = new DataEntity(dataEntityName); - Map row = new HashMap<>(); - row.put(TB_KEY_DATA, dataEntity.getData()); - row.put(TB_KEY_UNIQUE_IDENTIFIER, dataEntity.getUniqueIdentifier()); - row.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); - - //Action - Assert.assertTrue(SQLiteDatabaseHelper.insertRow(dbPath, TABLE_NAME, row)); - - int tableSize = SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME); - - //Assert - Assert.assertEquals(tableSize, 1); - } - - @Test - public void testGetTableSize_Failure() { - //Prepare data - final String dataEntityName = "dataentity"; - DataEntity dataEntity = new DataEntity(dataEntityName); - Map row = new HashMap<>(); - row.put(TB_KEY_DATA, dataEntity.getData()); - row.put(TB_KEY_UNIQUE_IDENTIFIER, dataEntity.getUniqueIdentifier()); - row.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); - - - final String incorrectDbPath = "incorrect_database_path"; - - //Action - Assert.assertFalse(SQLiteDatabaseHelper.insertRow(incorrectDbPath, TABLE_NAME, row)); - - //Action - Assert.assertEquals(SQLiteDatabaseHelper.getTableSize(incorrectDbPath, TABLE_NAME), 0); - } - - @Test - public void testRemoveRows_Success() { - //Prepare data - final String dataEntityName = "dataentity"; - DataEntity dataEntity = new DataEntity(dataEntityName); - Map row = new HashMap<>(); - row.put(TB_KEY_DATA, dataEntity.getData()); - row.put(TB_KEY_UNIQUE_IDENTIFIER, dataEntity.getUniqueIdentifier()); - row.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); - - //Action - Assert.assertTrue(SQLiteDatabaseHelper.insertRow(dbPath, TABLE_NAME, row)); - Assert.assertEquals(1, SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); - - SQLiteDatabaseHelper.removeRows(dbPath, TABLE_NAME, "id", 1); - - int tableSize = SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME); - - //Assert - Assert.assertEquals(tableSize, 0); - } - - @Test - public void testRemoveRows_Failure() { - //Prepare data - final String dataEntityName = "dataentity"; - DataEntity dataEntity = new DataEntity(dataEntityName); - Map row = new HashMap<>(); - row.put(TB_KEY_DATA, dataEntity.getData()); - row.put(TB_KEY_UNIQUE_IDENTIFIER, dataEntity.getUniqueIdentifier()); - row.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); - - //Action - Assert.assertTrue(SQLiteDatabaseHelper.insertRow(dbPath, TABLE_NAME, row)); - Assert.assertEquals(1, SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); - - final String incorrectDbPath = "incorrect_db_path"; - - Assert.assertEquals(-1, SQLiteDatabaseHelper.removeRows(incorrectDbPath, TABLE_NAME, "id", 1)); - Assert.assertEquals(1, SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); - } - - @Test - public void testClearTable_Success() { - //Prepare data - final String dataEntityName = "dataentity"; - DataEntity dataEntity = new DataEntity(dataEntityName); - Map row = new HashMap<>(); - row.put(TB_KEY_DATA, dataEntity.getData()); - row.put(TB_KEY_UNIQUE_IDENTIFIER, dataEntity.getUniqueIdentifier()); - row.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); - - //Action - Assert.assertTrue(SQLiteDatabaseHelper.insertRow(dbPath, TABLE_NAME, row)); - Assert.assertEquals(1, SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); - - SQLiteDatabaseHelper.clearTable(dbPath, TABLE_NAME); - - //Assert - Assert.assertTrue(SQLiteDatabaseHelper.clearTable(dbPath, TABLE_NAME)); - Assert.assertEquals(0, SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); - } - - @Test - public void testClearTable_Failure() { - //Prepare data - final String dataEntityName = "dataentity"; - DataEntity dataEntity = new DataEntity(dataEntityName); - Map row = new HashMap<>(); - row.put(TB_KEY_DATA, dataEntity.getData()); - row.put(TB_KEY_UNIQUE_IDENTIFIER, dataEntity.getUniqueIdentifier()); - row.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); - - //Action - Assert.assertTrue(SQLiteDatabaseHelper.insertRow(dbPath, TABLE_NAME, row)); - Assert.assertEquals(1, SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); - - - String incorrectDBPath = "incorrect_database_path"; - - //Assert - Assert.assertFalse(SQLiteDatabaseHelper.clearTable(incorrectDBPath, TABLE_NAME)); - Assert.assertEquals(1, SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); - } + private static final String TABLE_NAME = "TB_AEP_DATA_ENTITY"; + private static final String TB_KEY_UNIQUE_IDENTIFIER = "uniqueIdentifier"; + private static final String TB_KEY_TIMESTAMP = "timestamp"; + private static final String TB_KEY_DATA = "data"; + private String dbPath; + + @Before + public void setUp() { + dbPath = new File(InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(), "test.sqlite").getPath(); + createTable(); + } + + @After + public void dispose() { + SQLiteDatabaseHelper.clearTable(dbPath, TABLE_NAME); + } + + private void createTable() { + final String tableCreationQuery = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + + " (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, " + + "uniqueIdentifier TEXT NOT NULL UNIQUE, " + + "timestamp INTEGER NOT NULL, " + + "data TEXT);"; + + SQLiteDatabaseHelper.createTableIfNotExist(dbPath, tableCreationQuery); + } + + @Test + public void testTableIsEmptyInitially() { + //Action + int size = SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME); + + //Assert + Assert.assertEquals(size, 0); + } + + @Test + public void testGetTableSize_Success() { + //Prepare data + final String dataEntityName = "dataentity"; + DataEntity dataEntity = new DataEntity(dataEntityName); + Map row = new HashMap<>(); + row.put(TB_KEY_DATA, dataEntity.getData()); + row.put(TB_KEY_UNIQUE_IDENTIFIER, dataEntity.getUniqueIdentifier()); + row.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); + + //Action + SQLiteDatabaseHelper.process(dbPath, SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE, (database) -> { + Assert.assertNotNull(database); + return database.insert(TABLE_NAME, null, getContentValueFromMap(row)) > -1; + }); + + int tableSize = SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME); + + //Assert + Assert.assertEquals(tableSize, 1); + } + + @Test + public void testGetTableSize_Failure() { + //Prepare data + final String dataEntityName = "dataentity"; + DataEntity dataEntity = new DataEntity(dataEntityName); + Map row = new HashMap<>(); + row.put(TB_KEY_DATA, dataEntity.getData()); + row.put(TB_KEY_UNIQUE_IDENTIFIER, dataEntity.getUniqueIdentifier()); + row.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); + + + final String incorrectDbPath = "incorrect_database_path"; + + //Action + SQLiteDatabaseHelper.process(dbPath, SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE, (database) -> { + Assert.assertNotNull(database); + return database.insert(TABLE_NAME, null, getContentValueFromMap(row)) > -1; + }); + + //Action + Assert.assertEquals(SQLiteDatabaseHelper.getTableSize(incorrectDbPath, TABLE_NAME), 0); + } + + @Test + public void testClearTable_Success() { + //Prepare data + final String dataEntityName = "dataentity"; + DataEntity dataEntity = new DataEntity(dataEntityName); + Map row = new HashMap<>(); + row.put(TB_KEY_DATA, dataEntity.getData()); + row.put(TB_KEY_UNIQUE_IDENTIFIER, dataEntity.getUniqueIdentifier()); + row.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); + + //Action + SQLiteDatabaseHelper.process(dbPath, SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE, (database) -> { + Assert.assertNotNull(database); + return database.insert(TABLE_NAME, null, getContentValueFromMap(row)) > -1; + }); + Assert.assertEquals(1, SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); + + SQLiteDatabaseHelper.clearTable(dbPath, TABLE_NAME); + + //Assert + Assert.assertTrue(SQLiteDatabaseHelper.clearTable(dbPath, TABLE_NAME)); + Assert.assertEquals(0, SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); + } + + @Test + public void testClearTable_Failure() { + //Prepare data + final String dataEntityName = "dataentity"; + DataEntity dataEntity = new DataEntity(dataEntityName); + Map row = new HashMap<>(); + row.put(TB_KEY_DATA, dataEntity.getData()); + row.put(TB_KEY_UNIQUE_IDENTIFIER, dataEntity.getUniqueIdentifier()); + row.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); + + //Action + SQLiteDatabaseHelper.process(dbPath, SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE, (database) -> { + Assert.assertNotNull(database); + return database.insert(TABLE_NAME, null, getContentValueFromMap(row)) > -1; + }); + Assert.assertEquals(1, SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); + + + String incorrectDBPath = "incorrect_database_path"; + + //Assert + Assert.assertFalse(SQLiteDatabaseHelper.clearTable(incorrectDBPath, TABLE_NAME)); + Assert.assertEquals(1, SQLiteDatabaseHelper.getTableSize(dbPath, TABLE_NAME)); + } + + @Test + public void testProcessShouldCloseDatabase() { + final AtomicReference processedDatabase = new AtomicReference<>(null); + SQLiteDatabaseHelper.process(dbPath, SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE, (database) -> { + Assert.assertNotNull(database); + Assert.assertTrue(database.isOpen()); + processedDatabase.set(database); + return true; + }); + Assert.assertNotNull(processedDatabase.get()); + Assert.assertFalse(processedDatabase.get().isOpen()); + } + + @Test + public void testProcessShouldCatchException_badDatabaseConnection() { + boolean result = SQLiteDatabaseHelper.process("xxx", SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE, (database) -> { + Assert.assertNull(database); + return false; + }); + Assert.assertFalse(result); + } + + @Test + public void testProcessShouldCatchException_badDatabaseOperations() { + boolean result = SQLiteDatabaseHelper.process(dbPath, SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE, (database) -> { + Assert.assertNotNull(database); + throw new RuntimeException("xxxx"); + }); + Assert.assertFalse(result); + } + + + private ContentValues getContentValueFromMap(final Map values) { + ContentValues contentValues = new ContentValues(); + + for (Map.Entry value : values.entrySet()) { + String columnName = value.getKey(); + Object columnValue = value.getValue(); + + if (columnValue == null) { + contentValues.putNull(columnName); + } else if (columnValue instanceof String) { + contentValues.put(columnName, (String) columnValue); + } else if (columnValue instanceof Long) { + contentValues.put(columnName, (Long) columnValue); + } else if (columnValue instanceof Integer) { + contentValues.put(columnName, (Integer) columnValue); + } else if (columnValue instanceof Short) { + contentValues.put(columnName, (Short) columnValue); + } else if (columnValue instanceof Byte) { + contentValues.put(columnName, (Byte) columnValue); + } else if (columnValue instanceof Double) { + contentValues.put(columnName, (Double) columnValue); + } else if (columnValue instanceof Float) { + contentValues.put(columnName, (Float) columnValue); + } else if (columnValue instanceof Boolean) { + contentValues.put(columnName, (Boolean) columnValue); + } else if (columnValue instanceof byte[]) { + contentValues.put(columnName, (byte[]) columnValue); + } + } + + return contentValues; + } + } diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java new file mode 100644 index 000000000..c262aa0c6 --- /dev/null +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java @@ -0,0 +1,103 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.services; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +public class SqliteDataQueueTests { + private File dbFile; + private DataQueue dataQueue; + private static final String QUEUE_NAME = "test.dataQueue"; + + + @Before + public void setUp() { + dbFile = new File(InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(), QUEUE_NAME); + dataQueue = new SQLiteDataQueue(InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(), QUEUE_NAME); + } + + @After + public void tearDown() { + if (dbFile != null && dbFile.exists()) { + dbFile.delete(); + } + } + + + @Test + public void testAddPeek() { + dataQueue.add(new DataEntity("test_data_1")); + dataQueue.add(new DataEntity("test_data_2")); + dataQueue.add(new DataEntity("test_data_3")); + Assert.assertEquals("test_data_1", dataQueue.peek().getData()); + } + + @Test + public void testAddPeekN() { + dataQueue.add(new DataEntity("test_data_1")); + dataQueue.add(new DataEntity("test_data_2")); + dataQueue.add(new DataEntity("test_data_3")); + List results = dataQueue.peek(3); + Assert.assertEquals(3, results.size()); + Assert.assertEquals("test_data_1", results.get(0).getData()); + Assert.assertEquals("test_data_2", results.get(1).getData()); + Assert.assertEquals("test_data_3", results.get(2).getData()); + } + + @Test + public void testAddRemove() { + dataQueue.add(new DataEntity("test_data_1")); + dataQueue.add(new DataEntity("test_data_2")); + dataQueue.add(new DataEntity("test_data_3")); + Assert.assertEquals(3, dataQueue.peek(4).size()); + dataQueue.remove(); + List results = dataQueue.peek(4); + Assert.assertEquals(2, results.size()); + Assert.assertEquals("test_data_2", results.get(0).getData()); + Assert.assertEquals("test_data_3", results.get(1).getData()); + } + + @Test + public void testAddRemoveN() { + dataQueue.add(new DataEntity("test_data_1")); + dataQueue.add(new DataEntity("test_data_2")); + dataQueue.add(new DataEntity("test_data_3")); + Assert.assertEquals(3, dataQueue.peek(4).size()); + dataQueue.remove(2); + List results = dataQueue.peek(4); + Assert.assertEquals(1, results.size()); + Assert.assertEquals("test_data_3", results.get(0).getData()); + } + + @Test + public void testClear() { + dataQueue.add(new DataEntity("test_data_1")); + dataQueue.add(new DataEntity("test_data_2")); + dataQueue.add(new DataEntity("test_data_3")); + Assert.assertEquals(3, dataQueue.peek(4).size()); + dataQueue.clear(); + List results = dataQueue.peek(4); + Assert.assertEquals(0, results.size()); + } + +} diff --git a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/DataBaseTest.java b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/DataBaseTest.java deleted file mode 100755 index f1427f8ff..000000000 --- a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/DataBaseTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import org.junit.Test; - -import java.util.HashMap; - -import static com.adobe.marketing.mobile.DatabaseService.Database.ColumnDataType.*; - -/** - * Created by jgeng on 2/28/17. - */ -public class DataBaseTest { -// @Test -// public void testdb() { -// FakeDatabase mockDatabase = new FakeDatabase(); -// -// try { -// mockDatabase.createTable("test_table", new String[] {"test_col0", "test_col1"}, new -// DatabaseService.Database.ColumnDataType[] {INTEGER, TEXT}, -// null); -// mockDatabase.insert("test_table", new HashMap() { -// { -// put("test_col0", 3); -// put("test_col1", "123"); -// } -// }); -// mockDatabase.insert("test_table", new HashMap() { -// { -// put("test_col0", 1); -// put("test_col1", "abc"); -// } -// }); -// mockDatabase.update("test_table", new HashMap() { -// { -// put("test_col0", 4); -// put("test_col1", "456"); -// } -// }, "WHERE test_col0=3", null); -// Query testQuery = new Query.Builder("test_table", new String[] {"test_col1"}).build(); -// DatabaseService.QueryResult result = mockDatabase.query(testQuery); -// mockDatabase.delete("test_table", "WHERE test_col0=4", null); -// result = mockDatabase.query(testQuery); -// } catch (Exception e) { -// e.printStackTrace(); -// } -// } -} diff --git a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakeDatabase.java b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakeDatabase.java deleted file mode 100755 index b56cf1153..000000000 --- a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakeDatabase.java +++ /dev/null @@ -1,386 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import android.database.sqlite.SQLiteDatabase; - -import java.sql.*; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class FakeDatabase implements DatabaseService.Database { - private Connection connection; - private final Object dbMutex = new Object(); - public FakeDatabase() { - synchronized (dbMutex) { - initializeConnection(); - } - } - - @Override - public boolean createTable(final String name, final String[] columnNames, final ColumnDataType[] dataTypes, - final List> columnConstraints) { - - PreparedStatement stmt = null; - - synchronized (dbMutex) { - initializeConnection(); - - try { - String createTableQuery = QueryStringBuilder.getCreateTableQueryString(name, columnNames, dataTypes, - columnConstraints); - stmt = connection.prepareStatement(createTableQuery); - stmt.executeUpdate(); - return true; - } catch (SQLException e) { - return false; - } finally { - if (stmt != null) { - try { - stmt.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - } - } - } - - @Override - public DatabaseService.QueryResult query(final Query query) { - PreparedStatement stmt; - - synchronized (dbMutex) { - try { - // this one is needed so we can count the number of rows and then scroll back to first row in the table - StringBuilder sb = new StringBuilder(); - sb.append("SELECT "); - String[] columns = query.getColumns(); - - if (columns != null && columns.length > 0) { - for (String column : columns) { - sb.append(column).append(", "); - } - - sb.delete(sb.length() - 2, sb.length()); - } else { - sb.append("*"); - } - - sb.append(" FROM "); - sb.append(query.getTable()); - sb.append(" "); - String selection = query.getSelection(); - - if (selection != null && selection.length() > 0) { - String newWhereClause = selection; - String[] selectionArgs = query.getSelectionArgs(); - - if (selectionArgs != null && selectionArgs.length > 0) { - for (String arg : selectionArgs) { - newWhereClause = newWhereClause.replaceFirst("\\?", arg); - } - } - - sb.append(" WHERE " + newWhereClause); - } - - String groupBy = query.getGroupBy(); - - if (groupBy != null && groupBy.length() > 0) { - sb.append(" GROUP BY ").append(groupBy).append(" "); - } - - String having = query.getHaving(); - - if (having != null && having.length() > 0) { - sb.append(" HAVING ").append(having).append(" "); - } - - String orderBy = query.getOrderBy(); - - if (orderBy != null && orderBy.length() > 0) { - sb.append(" ORDER BY ").append(orderBy).append(" "); - } - - String limit = query.getLimit(); - - if (limit != null && limit.length() > 0) { - sb.append(" LIMIT ").append(limit).append(" "); - } - - stmt = connection.prepareStatement(sb.toString(), ResultSet.TYPE_SCROLL_SENSITIVE, - ResultSet.CONCUR_READ_ONLY); - ResultSet resultSet = stmt.executeQuery(); - return new FakeQueryResult(resultSet); - } catch (SQLException e) { - return null; - } - } - } - - private void sqlBindStatement(final String sb, final Map values) throws SQLException { - PreparedStatement stmt = (PreparedStatement) connection.prepareStatement(sb); - - int index = 0; - - for (Map.Entry entry : values.entrySet()) { - Object ob = entry.getValue(); - index += 1; - - if (ob instanceof String) { - stmt.setString(index, ob.toString()); - } else if (ob instanceof Integer) { - stmt.setInt(index, ((Integer) ob).intValue()); - } else if (ob instanceof Long) { - stmt.setLong(index, ((Long) ob).longValue()); - } else if (ob instanceof Double) { - stmt.setDouble(index, ((Double) ob).doubleValue()); - } else if (ob instanceof Boolean) { - stmt.setBoolean(index, ((Boolean) ob).booleanValue()); - } else if (ob instanceof Float) { - stmt.setFloat(index, ((Float) ob).floatValue()); - } - } - - stmt.executeUpdate(); - } - - @Override - public boolean insert(final String table, final Map values) { - synchronized (dbMutex) { - - try { - StringBuilder sb = new StringBuilder(); - sb.append("INSERT INTO ").append(table); - sb.append("("); - - for (Map.Entry entry : values.entrySet()) { - sb.append(entry.getKey()).append(", "); - } - - if (values.size() > 0) { - sb.delete(sb.length() - 2, sb.length()); - } - - sb.append(") VALUES ("); - - for (Map.Entry entry : values.entrySet()) { - sb.append("?, "); - } - - if (values.size() > 0) { - sb.delete(sb.length() - 2, sb.length()); - } - - sb.append(")"); - sqlBindStatement(sb.toString(), values); - return true; - } catch (SQLException e) { - e.printStackTrace(); - return false; - } - } - } - - @Override - public boolean update(final String table, final Map values, final String whereClause, - final String[] whereArgs) { - String newWhereClause = whereClause; - - if (whereArgs != null && whereArgs.length > 0) { - for (String arg : whereArgs) { - newWhereClause = newWhereClause.replaceFirst("\\?", arg); - } - } - - PreparedStatement stmt; - - synchronized (dbMutex) { - try { - StringBuilder sb = new StringBuilder(); - sb.append("UPDATE ").append(table); - sb.append(" SET "); - - for (Map.Entry entry : values.entrySet()) { - sb.append(entry.getKey()).append("=?, "); - } - - if (values.size() > 0) { - sb.delete(sb.length() - 2, sb.length()); - } - - if (newWhereClause != null) { - sb.append(" WHERE " + newWhereClause); - } - - sqlBindStatement(sb.toString(), values); - return true; - } catch (SQLException e) { - return false; - } - } - } - - @Override - public boolean delete (final String table, final String whereClause, final String[] whereArgs) { - String newWhereClause = whereClause; - - if (whereArgs != null && whereArgs.length > 0) { - for (String arg : whereArgs) { - newWhereClause = newWhereClause.replaceFirst("\\?", arg); - } - } - - PreparedStatement stmt; - - synchronized (dbMutex) { - try { - String query = "DELETE FROM " + table; - - if (newWhereClause != null) { - query += " WHERE " + newWhereClause; - } - - stmt = connection.prepareStatement(query.toString()); - return stmt.executeUpdate() > 0; - } catch (SQLException e) { - return false; - } - } - } - - public boolean closeWasCalled = false; - @Override - public void close() { - synchronized (dbMutex) { - closeWasCalled = true; - - try { - this.connection.close(); - } catch (Exception e) { - } - } - } - - private void initializeConnection() { - try { - if (connection == null || connection.isClosed()) { - this.connection = DriverManager.getConnection("jdbc:h2:mem:"); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - // copied from Android code - private static class QueryStringBuilder { - - private static final Map COLUMN_CONSTRAINT_STRING_MAP = new - HashMap() { - { - put(ColumnConstraint.PRIMARY_KEY, "PRIMARY KEY"); - put(ColumnConstraint.AUTOINCREMENT, "AUTO_INCREMENT"); - put(ColumnConstraint.NOT_NULL, "NOT NULL"); - put(ColumnConstraint.UNIQUE, "UNIQUE"); - } - }; - - static String getCreateTableQueryString(final String name, - final String[] columnNames, - final ColumnDataType[] columnDataTypes, - final List> columnConstraints) throws SQLException { - - if (StringUtils.isNullOrEmpty(name) - || columnNames == null || columnNames.length == 0 - || columnDataTypes == null || columnDataTypes.length != columnNames.length - || (columnConstraints != null && !columnConstraints.isEmpty() && columnConstraints.size() != columnNames.length)) { - return null; - } - - List dataTypesList = getColumnDataTypes(columnDataTypes); - List columnConstraintsList = getColumnConstraints(columnConstraints); - - StringBuilder createTableQueryBuilder = new StringBuilder(); - createTableQueryBuilder.append("CREATE TABLE IF NOT EXISTS "); - createTableQueryBuilder.append(name); - createTableQueryBuilder.append("("); - - for (int columnIndex = 0; columnIndex < columnNames.length; columnIndex++) { - createTableQueryBuilder.append(columnNames[columnIndex]); - - createTableQueryBuilder.append(" "); - createTableQueryBuilder.append(dataTypesList.get(columnIndex)); - - if (columnConstraintsList != null) { - String constraints = columnConstraintsList.get(columnIndex); - - if (constraints != null) { - createTableQueryBuilder.append(" "); - createTableQueryBuilder.append(columnConstraintsList.get(columnIndex)); - } - } - - if (columnIndex < columnNames.length - 1) { - createTableQueryBuilder.append(", "); - } - } - - createTableQueryBuilder.append(")"); - return createTableQueryBuilder.toString(); - } - - private static List getColumnDataTypes(final ColumnDataType[] columnDataTypes) throws SQLException { - if (columnDataTypes == null || columnDataTypes.length == 0) { - return null; - } - - List dataTypesList = new ArrayList(); - - for (ColumnDataType dataType : columnDataTypes) { - dataTypesList.add(dataType.name()); - } - - return dataTypesList; - } - - private static List getColumnConstraints(List> - columnConstraints) throws SQLException { - if (columnConstraints == null || columnConstraints.isEmpty()) { - return null; - } - - List constraints = new ArrayList(); - - for (List constraintList : columnConstraints) { - if (constraintList == null || constraintList.isEmpty()) { - constraints.add(null); - continue; - } - - StringBuilder columnConstraintBuilder = new StringBuilder(); - - for (ColumnConstraint constraint : constraintList) { - columnConstraintBuilder.append(COLUMN_CONSTRAINT_STRING_MAP.get(constraint)); - columnConstraintBuilder.append(" "); - } - - constraints.add(columnConstraintBuilder.toString().trim()); - } - - return constraints; - } - - } -} diff --git a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakeDatabaseService.java b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakeDatabaseService.java deleted file mode 100755 index 0ed5b435a..000000000 --- a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakeDatabaseService.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import java.util.HashMap; -import java.util.Map; - -public class FakeDatabaseService implements DatabaseService { - private final Object dbServiceMutex = new Object(); - Map mapping; - - public FakeDatabaseService() { - synchronized (dbServiceMutex) { - mapping = new HashMap(); - } - } - - - @Override - public Database openDatabase(final String filePath) { - synchronized (dbServiceMutex) { - if (!mapping.containsKey(filePath)) { - mapping.put(filePath, new FakeDatabase()); - } - - return mapping.get(filePath); - } - } - - @Override - public boolean deleteDatabase(final String filePath) { - synchronized (dbServiceMutex) { - if (mapping.containsKey(filePath)) { - mapping.remove(filePath); - return true; - } - - return false; - } - } -} diff --git a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakePlatformServices.java b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakePlatformServices.java index f5717a897..074c722cd 100755 --- a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakePlatformServices.java +++ b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakePlatformServices.java @@ -17,7 +17,6 @@ public class FakePlatformServices implements PlatformServices { FakeJsonUtilityService fakeJsonUtilityService; FakeLocalStorageService fakeLocalStorageService; FakeLoggingService fakeLoggingService; - FakeDatabaseService fakeDatabaseService; MockSystemInfoService mockSystemInfoService; MockSystemNotificationService mockSystemNotificationService; MockUIService mockUIService; @@ -30,7 +29,6 @@ public FakePlatformServices() { fakeJsonUtilityService = new FakeJsonUtilityService(); fakeLocalStorageService = new FakeLocalStorageService(); fakeLoggingService = new FakeLoggingService(); - fakeDatabaseService = new FakeDatabaseService(); mockSystemInfoService = new MockSystemInfoService(); mockSystemNotificationService = new MockSystemNotificationService(); mockUIService = new MockUIService(); @@ -74,11 +72,6 @@ public LocalStorageService getLocalStorageService() { return fakeLocalStorageService; } - @Override - public DatabaseService getDatabaseService() { - return fakeDatabaseService; - } - @Override public SystemNotificationService getSystemNotificationService() { return mockSystemNotificationService; diff --git a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakeQueryResult.java b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakeQueryResult.java deleted file mode 100755 index 76e32d3b6..000000000 --- a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/FakeQueryResult.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.SQLException; - -public class FakeQueryResult implements DatabaseService.QueryResult { - ResultSet resultSet; - ResultSetMetaData resultMetaData; - - FakeQueryResult(ResultSet resultSet) throws SQLException { - this.resultSet = resultSet; - this.resultMetaData = resultSet.getMetaData(); - } - - @Override - public int getCount() throws Exception { - int totalRows; - - resultSet.last(); - totalRows = resultSet.getRow(); - resultSet.beforeFirst(); - - return totalRows ; - } - - @Override - public int getInt(final int columnIndex) throws Exception { - return resultSet.getInt(columnIndex + 1); - } - - @Override - public double getDouble(final int columnIndex) throws Exception { - return resultSet.getDouble(columnIndex + 1); - } - - @Override - public float getFloat(final int columnIndex) throws Exception { - return resultSet.getFloat(columnIndex + 1); - } - - @Override - public long getLong(final int columnIndex) throws Exception { - return resultSet.getLong(columnIndex + 1); - } - - @Override - public String getString(final int columnIndex) throws Exception { - return resultSet.getString(columnIndex + 1); - } - - @Override - public boolean isNull(final int columnIndex) throws Exception { - return resultSet.getObject(columnIndex + 1) == null; - } - - @Override - public boolean moveToFirst() throws Exception { - this.resultSet.beforeFirst(); - this.resultSet.next(); - return true; - } - - @Override - public boolean moveToLast() throws Exception { - this.resultSet.afterLast(); - return true; - } - - @Override - public boolean moveToNext() throws Exception { - return this.resultSet.next(); - } - - @Override - public void close() { - try { - this.resultSet.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public String getColumnName(int columnIndex) throws Exception { - return this.resultMetaData.getColumnName(columnIndex); - } - - public String getColumnTypeName(int columnIndex) throws Exception { - return this.resultMetaData.getColumnTypeName(columnIndex); - } - - public int getColumnCount() throws Exception { - return this.resultMetaData.getColumnCount(); - } - - public boolean isAutoincrement(int columnIndex) throws Exception { - return this.resultMetaData.isAutoIncrement(columnIndex); - } -} diff --git a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockHitQueue.java b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockHitQueue.java deleted file mode 100755 index 8597ecf1a..000000000 --- a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockHitQueue.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.adobe.marketing.mobile; - -import java.io.File; -import java.util.Map; - -class MockHitQueue> extends HitQueue { - MockHitQueue(PlatformServices services) { - this(services, null, null, null, null); - } - - MockHitQueue(PlatformServices services, File dbFile, String tableName, E hitSchema, - IHitProcessor hitProcessor) { - super(services, dbFile, tableName, hitSchema, hitProcessor); - } - - boolean selectOldestHitWasCalled; - T selectOldestHitReturnValue; - T selectOldestHit() { - selectOldestHitWasCalled = true; - return selectOldestHitReturnValue; - } - - boolean queryHitWasCalled; - Query queryHitParametersQuery; - T queryHitReturnValue; - @Override - T queryHit(Query query) { - queryHitWasCalled = true; - queryHitParametersQuery = query; - return queryHitReturnValue; - } - - - boolean updateHitWasCalled; - T updateHitParametersHit; - @Override - boolean updateHit(final T hit) { - updateHitWasCalled = true; - updateHitParametersHit = hit; - return true; - } - - boolean updateAllHitsWasCalled; - Map updateAllHitsParameters; - @Override - boolean updateAllHits(final Map parameters) { - updateAllHitsWasCalled = true; - updateAllHitsParameters = parameters; - return true; - } - - - boolean queueWasCalled; - T queueParametersHit; - boolean queueReturnValue; - @Override - protected boolean queue(final T hit) { - queueWasCalled = true; - queueParametersHit = hit; - return queueReturnValue; - } - - - boolean bringOnlineWasCalled; - @Override - void bringOnline() { - bringOnlineWasCalled = true; - } - - boolean suspendWasCalled; - @Override - void suspend() { - suspendWasCalled = true; - } - - boolean deleteHitWithIdentifierWasCalled; - String deleteHitWithIdentifierParametersIdentifier; - boolean deleteHitWithIdentifierReturnValue; - @Override - boolean deleteHitWithIdentifier(final String identifier) { - deleteHitWithIdentifierWasCalled = true; - deleteHitWithIdentifierParametersIdentifier = identifier; - return deleteHitWithIdentifierReturnValue; - } - - - long getSizeReturnValue; - @Override - protected long getSize() { - return getSizeReturnValue; - } - - - long getSizeWithQueryReturnValue; - @Override - protected long getSize(Query query) { - return getSizeWithQueryReturnValue; - } - - boolean deleteAllHitsWasCalled; - @Override - protected void deleteAllHits() { - deleteAllHitsWasCalled = true; - } -} diff --git a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockPlatformServices.java b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockPlatformServices.java index 0c5e6f628..f463d2711 100755 --- a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockPlatformServices.java +++ b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockPlatformServices.java @@ -27,11 +27,6 @@ public LocalStorageService getLocalStorageService() { return null; } - @Override - public DatabaseService getDatabaseService() { - return null; - } - @Override public SystemNotificationService getSystemNotificationService() { return null; diff --git a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockQueryResult.java b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockQueryResult.java deleted file mode 100755 index 3f83482a7..000000000 --- a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/MockQueryResult.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.adobe.marketing.mobile; - -import java.util.Map; - -public class MockQueryResult implements DatabaseService.QueryResult { - Object[][] results; - int index = 0; - - MockQueryResult(final Object[][] results) { - this.results = results; - } - - @Override - public int getCount() throws Exception { - return results.length; - } - - @Override - public int getInt(int columnIndex) throws Exception { - return (Integer)results[index][columnIndex]; - } - - @Override - public double getDouble(int columnIndex) throws Exception { - return (Double)results[index][columnIndex]; - } - - @Override - public float getFloat(int columnIndex) throws Exception { - return (Float)results[index][columnIndex]; - } - - @Override - public long getLong(int columnIndex) throws Exception { - return (Long)results[index][columnIndex]; - } - - @Override - public String getString(int columnIndex) throws Exception { - return (String)results[index][columnIndex]; - } - - @Override - public boolean isNull(int columnIndex) throws Exception { - return results[index][columnIndex] == null; - } - - @Override - public boolean moveToFirst() throws Exception { - index = 0; - return true; - } - - @Override - public boolean moveToLast() throws Exception { - - index = this.results.length - 1; - return true; - } - - @Override - public boolean moveToNext() throws Exception { - index++; - return true; - } - - @Override - public void close() { - - } -} diff --git a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/ModuleTestPlatformServices.java b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/ModuleTestPlatformServices.java index 7cf5beee2..a9af0412f 100755 --- a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/ModuleTestPlatformServices.java +++ b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/ModuleTestPlatformServices.java @@ -5,7 +5,6 @@ public class ModuleTestPlatformServices implements PlatformServices { private FakeJsonUtilityService fakeJsonUtilityService; private FakeLocalStorageService fakeLocalStorageService; private FakeLoggingService fakeLoggingService; - private FakeDatabaseService mockStructuredDataService; private MockSystemInfoService mockSystemInfoService; private MockSystemNotificationService mockSystemNotificationService; private MockUIService mockUIService; @@ -18,7 +17,6 @@ public ModuleTestPlatformServices() { fakeJsonUtilityService = new FakeJsonUtilityService(); fakeLocalStorageService = new FakeLocalStorageService(); fakeLoggingService = new FakeLoggingService(); - mockStructuredDataService = new FakeDatabaseService(); mockSystemInfoService = new MockSystemInfoService(); mockSystemNotificationService = new MockSystemNotificationService(); mockUIService = new MockUIService(); @@ -43,10 +41,6 @@ public MockUIService getMockUIService() { return mockUIService; } - public FakeDatabaseService getFakeDatabaseService() { - return mockStructuredDataService; - } - @Override public LoggingService getLoggingService() { return fakeLoggingService; @@ -62,10 +56,6 @@ public LocalStorageService getLocalStorageService() { return fakeLocalStorageService; } - @Override - public DatabaseService getDatabaseService() { - return mockStructuredDataService; - } @Override public SystemNotificationService getSystemNotificationService() { @@ -106,8 +96,4 @@ public void setFakeLocalStorageService(FakeLocalStorageService fakeLocalStorageS this.fakeLocalStorageService = fakeLocalStorageService; } - public void setMockStructuredDataService(FakeDatabaseService structuredDataService) { - this.mockStructuredDataService = structuredDataService; - } - } diff --git a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/TestableDatabase.java b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/TestableDatabase.java deleted file mode 100755 index ec0adbc8a..000000000 --- a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/TestableDatabase.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -class TestableDatabase { - private String databaseName; - private String tableName; - - private FakeDatabaseService testableDatabaseService; - private FakeDatabase testableDatabase; - MockSystemInfoService mockSystemInfoService; - - TestableDatabase(final MockEventHubModuleTest eventHub, final String databaseName, final String tableName, - final String[] columnNames, final DatabaseService.Database.ColumnDataType[] - columnDataTypes) { - this.databaseName = databaseName; - this.tableName = tableName; - - testableDatabaseService = (FakeDatabaseService) eventHub.getPlatformServices().getDatabaseService(); - mockSystemInfoService = (MockSystemInfoService) eventHub.getPlatformServices().getSystemInfoService(); - initializeDatabase(columnNames, columnDataTypes); - } - - private void openDatabase() { - File dbPath = new File(mockSystemInfoService.applicationCacheDir, databaseName); - testableDatabase = (FakeDatabase) testableDatabaseService.openDatabase(dbPath.getPath()); - } - - private void initializeDatabase(final String[] columnNames, - final DatabaseService.Database.ColumnDataType[] columnDataTypes) { - if (testableDatabase == null) { - openDatabase(); - } - - int columnsNo = columnDataTypes.length; - List> columnConstraints = - new ArrayList>(); - List idColumnConstraints = - new ArrayList(); - idColumnConstraints.add(DatabaseService.Database.ColumnConstraint.PRIMARY_KEY); - idColumnConstraints.add(DatabaseService.Database.ColumnConstraint.AUTOINCREMENT); - columnConstraints.add(idColumnConstraints); - - for (int i = 0; i < columnsNo - 1; i++) { - columnConstraints.add(new ArrayList()); - } - - testableDatabase.createTable(tableName, columnNames, columnDataTypes, columnConstraints); - } - - void closeDatabase() { - File dbPath = new File(mockSystemInfoService.applicationCacheDir, databaseName); - testableDatabase.close(); - testableDatabaseService.deleteDatabase(dbPath.getPath()); - } - - boolean insert(final Map hit) { - return testableDatabase.insert(tableName, hit); - } - - void insertNHits(final Map hit, final int hitsNumber) { - for (int i = 0; i < hitsNumber; i++) { - testableDatabase.insert(tableName, hit); - } - } - - boolean delete (final int hitIdentifier) { - return testableDatabase.delete(tableName, "ID = ?", new String[] {String.valueOf(hitIdentifier)}); - } - - boolean deleteAll() { - return testableDatabase.delete(tableName, null, null); - } - - int count() { - try { - return testableDatabase.query(new Query.Builder(tableName, new String[] {"ID", "URL", "TIMESTAMP"}).build()) - .getCount(); - } catch (Exception e) { - return 0; - } - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AbstractHitSchema.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AbstractHitSchema.java deleted file mode 100644 index a79a3fb5a..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AbstractHitSchema.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ -package com.adobe.marketing.mobile; - -import com.adobe.marketing.mobile.DatabaseService.Database.ColumnConstraint; -import com.adobe.marketing.mobile.DatabaseService.Database.ColumnDataType; -import com.adobe.marketing.mobile.DatabaseService.QueryResult; - -import java.util.List; -import java.util.Map; - -/** - * Base class for any {@code HitSchema} class. Defines the database table architecture and is needed - * for an implementation of {@link AbstractHitsDatabase}. - * - * @param {@link AbstractHit} subclass that represents this implementation's database schema - */ -abstract class AbstractHitSchema { - - List> columnConstraints; - ColumnDataType[] columnDataTypes; - String[] columnNames; - - // ======================================================================================== - // abstract methods - // ======================================================================================== - /** - * Receives a T (extends {@link AbstractHit}) object and returns a {@code Map} to be used - * as a parameter for a database query. - * - * @param hit T (extends {@link AbstractHit}) object to be written to the underlying database - * @return {@code Map} where the key is the column name and the value is the corresponding - * column value represented by {@code hit} - */ - abstract Map generateDataMap(final T hit); - - /** - * Receives a {@code QueryResult} object and returns its object representation. - * - * @param queryResult {@link QueryResult} representing the result of a database query - * @return T (extends {@link AbstractHit}) instance representing the provided {@code queryResult} - */ - abstract T generateHit(final QueryResult queryResult); - - // ======================================================================================== - // package-private methods - // ======================================================================================== - /** - * Gets the column constraints represented by this schema. - * - * @return an ordered {@link List} of {@code List} containing any constraints for the given field - */ - final List> getColumnConstraints() { - return columnConstraints; - } - - /** - * Gets the column data types represented by this schema. - * - * @return an ordered {@code ColumnDataType} array containing the data types for each field in the table - */ - final ColumnDataType[] getColumnDataTypes() { - return columnDataTypes; - } - - /** - * Gets the column names represented by this schema. - * - * @return an ordered {@code String} array containing the names of each field in the table - */ - final String[] getColumnNames() { - return columnNames; - } -} \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AbstractHitsDatabase.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AbstractHitsDatabase.java deleted file mode 100644 index 8f4a690ba..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AbstractHitsDatabase.java +++ /dev/null @@ -1,249 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import com.adobe.marketing.mobile.DatabaseService.Database; -import com.adobe.marketing.mobile.DatabaseService.QueryResult; - -import java.io.File; - -/** - * Base class that allows implementing subclasses to interface with an underlying {@link Database}. - */ -abstract class AbstractHitsDatabase { - // ======================================================================================== - // private fields - // ======================================================================================== - private static final String LOG_TAG = "HitsDatabase"; - private DatabaseService structuredDataService; - private File databaseFile; - - // ======================================================================================== - // package-private fields - // ======================================================================================== - String tableName; - final Object dbMutex = new Object(); - Database database; - DatabaseStatus databaseStatus; - - // ======================================================================================== - // DatabaseStatus enum - // ======================================================================================== - enum DatabaseStatus { - OK(0), - FATAL_ERROR(1); - - public final int id; - - DatabaseStatus(final int identifier) { - id = identifier; - } - } - - // ======================================================================================== - // Constructor - // ======================================================================================== - /** - * Constructor which sets internal {@link #structuredDataService}, {@link #databaseFile}, and {@link #tableName} properties. - * - * @param databaseService {@code DatabaseService} instance used for interfacing with a native database - * @param databaseFile {@code File} of the underlying database - * @param tableName {@code String} containing the name of the table in the database - */ - AbstractHitsDatabase(final DatabaseService databaseService, final File databaseFile, final String tableName) { - this.structuredDataService = databaseService; - this.databaseFile = databaseFile; - this.tableName = tableName; - } - - // ======================================================================================== - // abstract methods - // ======================================================================================== - /** - * Runs after opening or creating a database. - */ - abstract void initializeDatabase(); - - /** - * Optional abstract method. If overridden, this method is called upon completion of calling the {@link #reset()} method. - */ - void postReset() {} - - // ======================================================================================== - // package-private methods - // ======================================================================================== - /** - * Attempts to delete all records from the underlying {@link #database}. - */ - void deleteAllHits() { - synchronized (dbMutex) { - if (database == null) { - Log.warning(LOG_TAG, "%s (Database), couldn't delete hits, db file path: %s", Log.UNEXPECTED_NULL_VALUE, - databaseFile.getAbsolutePath()); - return; - } - - if (!database.delete(tableName, null, null)) { - Log.warning(LOG_TAG, "Unable to delete all hits from the database table"); - } - - } - } - - /** - * Attempts to remove the hit with given identifier from database. - *

    - * If the database operation fails, this method will reset the {@link #database} and return false. - * - * @param identifier {@code String} containing the identifier of the hit to be removed - * @return {@code boolean} representing the success of the delete operation - */ - boolean deleteHitWithIdentifier(final String identifier) { - if (StringUtils.isNullOrEmpty(identifier)) { - Log.warning(LOG_TAG, "Unable to delete hit with empty identifier"); - return false; - } - - synchronized (dbMutex) { - if (database == null) { - Log.warning(LOG_TAG, "Couldn't delete hit, %s (Database) - Path to db: %s", Log.UNEXPECTED_NULL_VALUE, - databaseFile.getAbsolutePath()); - return false; - } - - if (!database.delete(tableName, "ID = ?", new String[] {identifier})) { - Log.warning(LOG_TAG, "Unable to delete hit due to unexpected error"); - reset(); - return false; - } - return true; - } - } - - /** - * Opens the existing database at the location represented by {@link #databaseFile}, or creates a new one. - *

    - * Logs an error if create or open operation failed. - */ - void openOrCreateDatabase() { - synchronized (dbMutex) { - closeDatabase(); - - if (databaseFile == null) { - Log.debug(LOG_TAG, "Database creation failed, %s - database file", Log.UNEXPECTED_NULL_VALUE); - return; - } - - if (structuredDataService == null) { - Log.debug(LOG_TAG, "%s (Database service)", Log.UNEXPECTED_NULL_VALUE); - return; - } - - Log.trace(LOG_TAG, "Trying to open database file located at %s", databaseFile.getAbsolutePath()); - database = structuredDataService.openDatabase(databaseFile.getPath()); - - if (database == null) { - Log.debug(LOG_TAG, "Database creation failed for %s", databaseFile.getPath()); - } else { - initializeDatabase(); - } - } - } - - // ======================================================================================== - // protected methods - // ======================================================================================== - /** - * Returns the number of hits currently in the {@link #database}. - *

    - * If the database query fails, this method logs an error message and returns 0. - * - * @return {@code long} representing the number of rows in the table - */ - protected long getSize() { - return getSize(new Query.Builder(tableName, new String[] {"ID"}).build()); - } - - /** - * Returns the number of hits in the {@link #database} match the query. - *

    - * If the database query fails, this method logs an error message and returns 0. - * - * @param query the {@link Query} object used to query the database - * @return {@code long} representing the number of rows match the query in the table - */ - protected long getSize(final Query query) { - synchronized (dbMutex) { - if (database == null) { - Log.debug(LOG_TAG, "Couldn't get size, %s (database) - Filepath: %s", Log.UNEXPECTED_NULL_VALUE, - databaseFile.getAbsolutePath()); - return 0; - } - - QueryResult queryResult = null; - - try { - queryResult = database.query(query); - - if (queryResult == null) { - Log.debug(LOG_TAG, "%s (query result), unable to get tracking queue size", Log.UNEXPECTED_NULL_VALUE); - return 0; - } - - return queryResult.getCount(); - } catch (Exception e) { - Log.debug(LOG_TAG, "Unable to get the count from the cursor."); - return 0; - } finally { - if (queryResult != null) { - queryResult.close(); - } - } - } - } - - /** - * Resets the underlying {@link #database}, usually as a result of a {@link DatabaseStatus#FATAL_ERROR}. - *

    - * This method removes the existing database and creates a new one with the same {@link #databaseFile} and structure. - */ - protected final void reset() { - Log.error(LOG_TAG, "Database in unrecoverable state, resetting."); - - synchronized (dbMutex) { - if (databaseFile != null && databaseFile.exists() && !structuredDataService.deleteDatabase(databaseFile.getPath())) { - Log.debug(LOG_TAG, String.format("Failed to delete database file(%s).", databaseFile.getAbsolutePath())); - databaseStatus = DatabaseStatus.FATAL_ERROR; - return; - } - } - - // Create new database - openOrCreateDatabase(); - postReset(); - - } - - // ======================================================================================== - // private methods - // ======================================================================================== - /** - * Attempts to close the underlying {@link #database}. - */ - private void closeDatabase() { - synchronized (dbMutex) { - if (database != null) { - database.close(); - } - } - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidCursor.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidCursor.java deleted file mode 100644 index 1ebe045b3..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidCursor.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import com.adobe.marketing.mobile.DatabaseService.QueryResult; - -import android.database.Cursor; - -/** - * AndroidCursor class implements QueryResult Interface for reading query results from a Database. - */ -class AndroidCursor implements QueryResult { - - private final Cursor cursor; - - /** - * Constructor - * - * @param cursor {@link Cursor} for accessing the result set from a database query - */ - AndroidCursor(final Cursor cursor) { - this.cursor = cursor; - } - - /** - * Returns the total number of rows in this {@code QueryResult}. - * - * @return {@code int} indicating number of rows in the {@link QueryResult} - * - * @throws Exception if the underlying table does not exist, or if the {@link QueryResult} is already closed - */ - @Override - public int getCount() throws Exception { - return cursor.getCount(); - } - - /** - * Returns the value of the requested column as an {@code int}. - * - * @param columnIndex {@code int} zero-based index of the target column - * - * @return the value of the column as an {@code int} - * - * @throws Exception if the {@code columnIndex} is out of bounds, or if the {@link QueryResult} is already closed - */ - @Override - public int getInt(final int columnIndex) throws Exception { - return cursor.getInt(columnIndex); - } - - /** - * Returns the value of the requested column as a {code double}. - * - * @param columnIndex {@code int} zero-based index of the target column - * - * @return the value of the column as a {@code double} - * - * @throws Exception if the {@code columnIndex} is out of bounds, or if the {@link QueryResult} is already closed - */ - @Override - public double getDouble(final int columnIndex) throws Exception { - return cursor.getDouble(columnIndex); - } - - /** - * Returns the value of the requested column as a {@code float}. - * - * @param columnIndex {@code int} zero-based index of the target column - * - * @return the value of the column as a {@code float} - * - * @throws Exception if the {@code columnIndex} is out of bounds, or if the {@link QueryResult} is already closed - */ - @Override - public float getFloat(final int columnIndex) throws Exception { - return cursor.getFloat(columnIndex); - } - - /** - * Returns the value of the requested column as a {@code long}. - * - * @param columnIndex {@code int} zero-based index of the target column - * - * @return the value of the column as a {@code long} - * - * @throws Exception if the {@code columnIndex} is out of bounds, or if the {@link QueryResult} is already closed - */ - @Override - public long getLong(final int columnIndex) throws Exception { - return cursor.getLong(columnIndex); - } - - /** - * Returns the value of the requested column as a {@code String}. - * - * @param columnIndex {@code int} zero-based index of the target column - * - * @return the value of the column as a {@link String} - * - * @throws Exception if the {@code columnIndex} is out of bounds, or if the {@link QueryResult} is already closed - */ - @Override - public String getString(final int columnIndex) throws Exception { - return cursor.getString(columnIndex); - } - - /** - * Returns true if the value of the requested column is null, false otherwise. - * - * @param columnIndex {@code int} zero-based index of the target column - * - * @return {@code boolean} indicating whether the column value is null - * - * @throws Exception if the {@link QueryResult} is already closed - */ - @Override - public boolean isNull(final int columnIndex) throws Exception { - return cursor.isNull(columnIndex); - } - - /** - * Move to the first row in the {@code QueryResult} - *

    - * This method will return false if the {@link QueryResult} is empty. - * - * @return {@code boolean} indicating whether the move was successful - * - * @throws Exception if the {@code QueryResult} is already closed - */ - @Override - public boolean moveToFirst() throws Exception { - return cursor.moveToFirst(); - } - - /** - * Move to the last row in the {@code QueryResult} - *

    - * This method will return false if the {@link QueryResult} is empty. - * - * @return {@code boolean} indicating whether the move was successful - * - * @throws Exception if the {@code QueryResult} is already closed - */ - @Override - public boolean moveToLast() throws Exception { - return cursor.moveToLast(); - } - - /** - * Move to the next row in the {@code QueryResult} - *

    - * This method will return false if the {@link QueryResult} is empty. - * - * @return {@code boolean} indicating whether the move was successful - * - * @throws Exception if the {@code QueryResult} is already closed - */ - @Override - public boolean moveToNext() throws Exception { - return cursor.moveToNext(); - } - - /** - * Close this {@code QueryResult} - */ - @Override - public void close() { - try { - cursor.close(); - } catch (Exception e) { - // do nothing - } - } -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidDatabase.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidDatabase.java deleted file mode 100644 index ecf8e6cdb..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidDatabase.java +++ /dev/null @@ -1,500 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import android.database.sqlite.SQLiteStatement; -import com.adobe.marketing.mobile.DatabaseService.QueryResult; - -import android.annotation.SuppressLint; -import android.content.ContentValues; -import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.os.Parcel; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -/** - * AndroidDatabase class implements Database Interface to define relational database operations. - */ -class AndroidDatabase implements DatabaseService.Database { - private final Object dbMutex = new Object(); - private static final String LOG_TAG = AndroidDatabase.class.getSimpleName(); - private static final String MIGRATION_DATABASE_TAG = "_MIGRATION"; - private final SQLiteDatabase database; - - /** - * Constructor - * - * @param database {@link SQLiteDatabase} instance - */ - AndroidDatabase(final SQLiteDatabase database) { - synchronized (dbMutex) { - this.database = database; - } - } - - /** - * Create a table if it doesn't exist. - * - * @param name {@link String} containing table name - * @param columnNames {@code String[]} array containing column names - * @param columnDataTypes {@code ColumnDataType[]} array containing data types for each column - * @param columnConstraints {@code List>} a list of lists containing column constraints - * for each table column - * - * @return {@code boolean} indicating whether the create table operation was successful - * - * @see ColumnConstraint - * @see ColumnDataType - */ - @Override - public boolean createTable(final String name, final String[] columnNames, final ColumnDataType[] columnDataTypes, - final List> columnConstraints) { - return createTable(name, columnNames, columnDataTypes, columnConstraints, false); - - } - - public boolean createTable(final String tableName, final String[] columnNames, final ColumnDataType[] columnDataTypes, - final List> columnConstraints, final boolean setColumnsDefault) { - - if (StringUtils.isNullOrEmpty(tableName) - || columnNames == null || columnNames.length == 0 - || columnDataTypes == null || columnDataTypes.length == 0 - || columnDataTypes.length != columnNames.length || (columnConstraints != null - && columnConstraints.size() != columnNames.length)) { - Log.warning(LOG_TAG, "Failed to create table, one or more input parameters is invalid."); - return false; - } - - if (!databaseIsWritable()) { - return false; - } - - String cleanTableName = cleanString(tableName); - String[] cleanColumnNames = cleanColumnNames(columnNames); - - - synchronized (dbMutex) { - try { - final String createTableQuery = QueryStringBuilder.getCreateTableQueryString(cleanTableName, cleanColumnNames, - columnDataTypes, - columnConstraints, setColumnsDefault); - SQLiteStatement stmt = database.compileStatement(createTableQuery); - stmt.execute(); - stmt.close(); - - migrateDatabaseIfNeeded(cleanTableName, cleanColumnNames, columnDataTypes, columnConstraints); - return true; - } catch (Exception e) { - Log.debug(LOG_TAG, "Failed to create table (%s)", - (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getMessage())); - return false; - } - } - } - - - private boolean migrateDatabaseIfNeeded(final String name, - final String[] columnNames, - final ColumnDataType[] columnDataTypes, - final List> columnConstraints) { - if (Arrays.equals(getColumnNames(name), columnNames)) { - return true; - } - - return migrateDatabase(name, columnNames, columnDataTypes, columnConstraints); - - } - - private boolean migrateDatabase(final String name, - final String[] columnNames, - final ColumnDataType[] columnDataTypes, - final List> columnConstraints) { - synchronized (dbMutex) { - try { - - // 1. Create a temp migration table - // 2. Find which columns are present in both schemas and copy their data into the new migration table - // 3. Delete the original table and rename migration table to original table name - - database.beginTransaction(); - final String tempTableName = name + MIGRATION_DATABASE_TAG; - - if (!createTable(tempTableName, columnNames, columnDataTypes, columnConstraints, true)) { - return false; - } - - final String[] unionColumns = getUnionColumns(name, columnNames); - - if (!copyColumnsFromTableToTable(name, tempTableName, unionColumns) - || !deleteTable(name) - || !renameTable(tempTableName, name)) { - return false; - } - - - database.setTransactionSuccessful(); - } catch (Exception e) { - Log.warning(LOG_TAG, "Failed to execute query (%s)", - (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getMessage())); - return false; - } finally { - database.endTransaction(); - } - - return true; - } - } - - private boolean copyColumnsFromTableToTable(final String fromTableName, final String toTableName, - final String[] columnNames) { - - String[] cleanColumnNames = cleanColumnNames(columnNames); - StringBuilder queryStringBuilder = new StringBuilder(); - queryStringBuilder.append("INSERT INTO ").append(toTableName).append(" ("); - - StringBuilder columnsStringBuilder = new StringBuilder(); - - for (int i = 0; i < cleanColumnNames.length ; i++) { - columnsStringBuilder.append(cleanColumnNames[i]).append(" "); - - if (i != cleanColumnNames.length - 1) { - columnsStringBuilder.append(", "); - } - } - - queryStringBuilder.append(columnsStringBuilder); - queryStringBuilder.append(") SELECT "); - queryStringBuilder.append(columnsStringBuilder); - queryStringBuilder.append(" FROM "); - queryStringBuilder.append(fromTableName); - queryStringBuilder.append(";"); - - synchronized (dbMutex) { - try { - SQLiteStatement stmt = database.compileStatement(queryStringBuilder.toString()); - stmt.execute(); - stmt.close(); - return true; - } catch (Exception e) { - Log.debug(LOG_TAG, "Failed to create table (%s)", - (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getMessage())); - return false; - } - } - - } - - private String[] getUnionColumns(final String name, - final String[] columnNames) { - String[] oldColumnNames = getColumnNames(name); - Arrays.sort(oldColumnNames); - - List unionColumnNameList - = new ArrayList(); - - for (int i = 0; i < columnNames.length; i++) { - if (Arrays.binarySearch(oldColumnNames, columnNames[i]) >= 0) { - unionColumnNameList.add(columnNames[i]); - } - } - - return unionColumnNameList.toArray(new String[] {}); - - } - - private String[] getColumnNames(final String tableName) { - - synchronized (dbMutex) { - Cursor dbCursor = null; - - try { - dbCursor = database.query(tableName, null, null, null, null, null, null); - return cleanColumnNames(dbCursor.getColumnNames()); - } catch (Exception e) { - Log.warning(LOG_TAG, "Failed to execute query (%s)", - (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getMessage())); - return new String[] {}; - } finally { - try { - if (dbCursor != null) { - dbCursor.close(); - } - } catch (Exception e) { - // no-op - } - } - - } - } - - - /** - * Query a table in the database. - * - * @param query {@link Query} object indicating the query to execute - * - * @return {@link QueryResult} the result of this query, positioned at the first row - */ - @Override - public QueryResult query(final Query query) { - if (query == null) { - Log.debug(LOG_TAG, "%s (Query), could not provide query result.", Log.UNEXPECTED_NULL_VALUE); - return null; - } - - synchronized (dbMutex) { - try { - @SuppressLint("Recycle") - Cursor cursor = database.query(cleanString(query.getTable()), - cleanColumnNames(query.getColumns()), - query.getSelection(), - query.getSelectionArgs(), - query.getGroupBy(), - query.getHaving(), - query.getOrderBy(), - query.getLimit()); - return new AndroidCursor(cursor); - } catch (Exception e) { - Log.warning(LOG_TAG, "Failed to execute query (%s)", - (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getMessage())); - return null; - } - } - } - - /** - * Insert a row into a table in the database. - * - * @param table {@link String} containing the table name to insert the row into - * @param values {@code Map} map containing the column name value pairs for the row - * - * @return {@code boolean} indicating whether the insert operation was successful - */ - @Override - public boolean insert(final String table, final Map values) { - if (StringUtils.isNullOrEmpty(table) || values == null || values.isEmpty()) { - Log.debug(LOG_TAG, "Could not insert row, table name or column values were empty or null."); - return false; - } - - if (!databaseIsWritable()) { - return false; - } - - synchronized (dbMutex) { - try { - return database.insert(cleanString(table), null, getContentValueFromMap(values)) != -1; - } catch (Exception e) { - Log.warning(LOG_TAG, "Failed to insert rows into the table (%s)", - (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getMessage())); - return false; - } - } - } - - /** - * Update rows for a table in the database. - *

    - * A null {@code whereClause} will cause updation of all table rows. - * - * @param table {@link String} containing the table name to update rows in - * @param values {@code Map} containing column name value pairs - * @param whereClause {@code String} indicating the optional WHERE clause to apply when updating - * @param whereArgs {@code String[]} array containing values that shall replace ?s in the WHERE clause - * - * @return {@code boolean} indicating whether the update operation was successful - */ - @Override - public boolean update(final String table, final Map values, final String whereClause, - final String[] whereArgs) { - if (StringUtils.isNullOrEmpty(table) || values == null || values.isEmpty()) { - Log.debug(LOG_TAG, "Could not update rows, table name or column values were empty or null."); - return false; - } - - if (!databaseIsWritable()) { - return false; - } - - synchronized (dbMutex) { - try { - return database.update(cleanString(table), getContentValueFromMap(values), whereClause, whereArgs) != 0; - } catch (Exception e) { - Log.warning(LOG_TAG, "Failed to update table rows (%s)", - (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getMessage())); - return false; - } - } - } - - /** - * Delete rows for a table in the database. - *

    - * A null {@code whereClause} will result in deletion of all table rows. - * - * @param table {@link String} containing the table name to delete rows from - * @param whereClause {@code String} containing the optional WHERE clause to apply when deleting - * @param whereArgs {@code String[]} array containing values that shall replace ?s in the WHERE clause - * - * @return {@code boolean} indicating whether the delete operation was successful - */ - @Override - public boolean delete (final String table, final String whereClause, final String[] whereArgs) { - if (!databaseIsWritable()) { - return false; - } - - synchronized (dbMutex) { - try { - //To remove all rows and get a count pass "1" as the whereClause. - int affectedRowsCount = database.delete(cleanString(table), (whereClause != null ? whereClause : "1"), whereArgs); - Log.trace(LOG_TAG, "Count of rows deleted in table %s are %d", table, affectedRowsCount); - - return true; - } catch (Exception e) { - Log.debug(LOG_TAG, "Failed to delete table rows (%s)", - (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getMessage())); - return false; - } - } - } - - boolean deleteTable(final String tableName) { - synchronized (dbMutex) { - try { - database.execSQL("DROP TABLE IF EXISTS " + cleanString(tableName)); - return true; - } catch (Exception e) { - Log.debug(LOG_TAG, "Failed to delete table (%s)", - (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getMessage())); - return false; - } - } - } - - boolean renameTable(final String tableName, final String newTableName) { - synchronized (dbMutex) { - try { - database.execSQL(String.format("ALTER TABLE %s RENAME TO %s;", tableName, newTableName)); - return true; - } catch (Exception e) { - Log.debug(LOG_TAG, "Failed to rename table (%s)", - (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getMessage())); - return false; - } - } - } - - /** - * Close this database. - */ - @Override - public void close() { - synchronized (dbMutex) { - database.close(); - } - } - - private boolean databaseIsWritable() { - synchronized (dbMutex) { - if (database == null) { - Log.debug(LOG_TAG, "%s (Database), unable to write", Log.UNEXPECTED_NULL_VALUE); - return false; - } - - if (!database.isOpen()) { - Log.debug(LOG_TAG, "Unable to write to database, it is not open"); - return false; - } - - if (database.isReadOnly()) { - Log.debug(LOG_TAG, "Unable to write to database, it is read-only"); - return false; - } - - return true; - } - } - - - - /** - * Populates and returns the {@link ContentValues} with the provided table hit details from the {@link Map}. - *

    - * Always returns a non-null content value. - * Map elements containing types other than {@code String}, {@code long}, {@code Integer}, {@code Short}, {@code Byte} - * {@code Float}, {@code Boolean} ,{@code Double} will be ignored to be put in contentValue. - * - * @param values A {Map} containing columnNames and columnValues of a database hit - */ - private static ContentValues getContentValueFromMap(final Map values) { - ContentValues contentValues = new ContentValues(); - - for (Map.Entry value : values.entrySet()) { - String columnName = value.getKey(); - Object columnValue = value.getValue(); - - if (columnValue == null) { - contentValues.putNull(columnName); - } else if (columnValue instanceof String) { - contentValues.put(columnName, (String) columnValue); - } else if (columnValue instanceof Long) { - contentValues.put(columnName, (Long) columnValue); - } else if (columnValue instanceof Integer) { - contentValues.put(columnName, (Integer) columnValue); - } else if (columnValue instanceof Short) { - contentValues.put(columnName, (Short) columnValue); - } else if (columnValue instanceof Byte) { - contentValues.put(columnName, (Byte) columnValue); - } else if (columnValue instanceof Double) { - contentValues.put(columnName, (Double) columnValue); - } else if (columnValue instanceof Float) { - contentValues.put(columnName, (Float) columnValue); - } else if (columnValue instanceof Boolean) { - contentValues.put(columnName, (Boolean) columnValue); - } else if (columnValue instanceof byte[]) { - contentValues.put(columnName, (byte[]) columnValue); - } else { - Log.warning(LOG_TAG, "Unsupported data type received for database insertion: columnName " + columnName + " value: " + - columnValue); - } - - } - - return contentValues; - } - - - private static String cleanString(final String input) { - return input.replaceAll("[^a-zA-Z0-9_]", ""); - } - - private static String[] cleanColumnNames(final String[] columnNames) { - if (columnNames == null) { - return null; - } - - String[] cleanedColumnNames = new String[columnNames.length]; - - for (int i = 0; i < columnNames.length; i++) { - cleanedColumnNames[i] = cleanString(columnNames[i]); - } - - return cleanedColumnNames; - } - -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidDatabaseService.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidDatabaseService.java deleted file mode 100644 index ae3b5d52e..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AndroidDatabaseService.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; - -import java.io.File; -import java.util.HashMap; -import java.util.Map; - -/** - * AndroidDatabaseService class implements DatabaseService Interface to manage a SQLite database. - */ -class AndroidDatabaseService implements DatabaseService { - private final SystemInfoService systemInfoService; - private final Object dbServiceMutex = new Object(); - private static final String TAG = AndroidDatabaseService.class.getSimpleName(); - private Map map = new HashMap<>(); - - AndroidDatabaseService(final SystemInfoService systemInfoService) { - this.systemInfoService = systemInfoService; - - if (systemInfoService == null) { - Log.warning(TAG, "Unable to access system info service while creating the database service"); - } - } - - private String removeRelativePath(final String filePath) { - try { - // we don't want to leave any extra "/" or "\" but also can't touch existing slashes - // first use a regex to find all of the ".\" and "./" and turn them into "." - // (\\. is escape for ., \\\\ is escape for \\, which means \ in a String) - // for example: /data/user/0/com.adobe.marketing.mobile.test/cache/mydatabase/../../database1 - // will become /data/user/0/com.adobe.marketing.mobile.test/cache/mydatabase/....database1 - String result = filePath.replaceAll("\\.[/\\\\]", "\\."); - // now use a regex to find all of the "/.." and "\.." and turn into "_", any "." occurs more than 2 times will be counted - // for example : /data/user/0/com.adobe.marketing.mobile.test/cache/mydatabase/....database1 - // will become /data/user/0/com.adobe.marketing.mobile.test/cache/mydatabase_database1 - result = result.replaceAll("[/\\\\](\\.{2,})", "_"); - return result; - } catch (IllegalArgumentException e) { - return filePath; - } - } - - /** - * Opens a database if it exists, otherwise creates a new one at the specified path. - * - * @param filePath {@link String} containing the database file path - * - * @return {@link Database} instance, or null if error occurs - */ - @Override - public Database openDatabase(final String filePath) { - if (StringUtils.isNullOrEmpty(filePath)) { - Log.debug(TAG, "Failed to open database - filepath is null or empty"); - return null; - } - - final String cleanedPath = removeRelativePath(filePath); - - if (this.systemInfoService != null && this.systemInfoService.getApplicationCacheDir() != null) { - try { - final String cacheDirCanonicalPath = this.systemInfoService.getApplicationCacheDir().getCanonicalPath(); - final File file = new File(cleanedPath); - final String dbFileCanonicalPath = file.getCanonicalPath(); - - if (!dbFileCanonicalPath.startsWith(cacheDirCanonicalPath)) { - Log.warning(TAG, "Invalid database file path (%s)", cleanedPath); - return null; - } - } catch (Exception e) { - Log.warning(TAG, "Failed to read database file (%s)", e); - return null; - } - } - - synchronized (dbServiceMutex) { - try { - SQLiteDatabase database = SQLiteDatabase.openDatabase(cleanedPath, - null, - SQLiteDatabase.NO_LOCALIZED_COLLATORS | SQLiteDatabase.CREATE_IF_NECESSARY); - - AndroidDatabase androidDatabase = new AndroidDatabase(database); - map.put(cleanedPath, androidDatabase); - return androidDatabase; - } catch (Exception e) { - Log.error(TAG, "Failed to open database (%s)", e); - return null; - } - } - } - - /** - * Delete database at the specified path, if it exists. - * - * @param filePath {@link String} containing the database file path - * - * @return {@code boolean} indicating whether the database file delete operation was successful - */ - @Override - public boolean deleteDatabase(final String filePath) { - if (StringUtils.isNullOrEmpty(filePath)) { - Log.debug(TAG, "Failed to delete database - filepath is null or empty"); - return false; - } - - String cleanedPath = removeRelativePath(filePath); - - synchronized (dbServiceMutex) { - if (map.containsKey(cleanedPath)) { - try { - File databaseFile = new File(cleanedPath); - map.remove(cleanedPath); - return databaseFile.delete(); - } catch (SecurityException e) { - Log.error(TAG, "Failed to delete database (%s)", e); - return false; - } - } else { - return false; - } - } - - } - - -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/DatabaseService.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/DatabaseService.java deleted file mode 100644 index 11f6feb34..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/DatabaseService.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import java.util.List; -import java.util.Map; - -@SuppressWarnings("unused") -interface DatabaseService { - - /** - * Opens a database if it exists, otherwise creates a new one at the specified path. - * - * @param filePath {@link String} containing the database file path - * - * @return {@link Database} instance, or null if error occurs - * - * @throws IllegalArgumentException if the database cannot be opened - */ - Database openDatabase(final String filePath); - - /** - * Delete database at the specified path, if it exists. - * - * @param filePath {@link String} containing the database file path - * - * @return {@code boolean} indicating whether the database file delete operation was successful - */ - boolean deleteDatabase(final String filePath); - - /** - * Interface defining relational database operations. - */ - interface Database { - - /** - * Allowed data types for database columns - */ - enum ColumnDataType { - INTEGER, - REAL, - TEXT; - } - - /** - * Allowed constraints for database columns - */ - enum ColumnConstraint { - NOT_NULL, - UNIQUE, - PRIMARY_KEY, - AUTOINCREMENT; - } - - /** - * Create a table if it doesn't exist. - * - * @param name {@link String} containing table name - * @param columnNames {@code String[]} array containing column names - * @param columnDataTypes {@code ColumnDataType[]} array containing data types for each column - * @param columnConstraints {@code List>} a list of lists containing column constraints - * for each table column - * - * @return {@code boolean} indicating whether the create table operation was successful - * - * @see ColumnConstraint - * @see ColumnDataType - */ - boolean createTable(final String name, - final String[] columnNames, - final ColumnDataType[] columnDataTypes, - final List> columnConstraints); - - /** - * Query a table in the database. - * - * @param query {@link Query} object indicating the query to execute - * - * @return {@link QueryResult} the result of this query, positioned at the first row - */ - QueryResult query(final Query query); - - /** - * Insert a row into a table in the database. - * - * @param table {@link String} containing the table name to insert the row into - * @param values {@code Map} map containing the column name value pairs for the row - * - * @return {@code boolean} indicating whether the insert operation was successful - */ - boolean insert(final String table, - final Map values); - - /** - * Update rows for a table in the database. - *

    - * A null {@code whereClause} will cause updation of all table rows. - * - * @param table {@link String} containing the table name to update rows in - * @param values {@code Map} containing column name value pairs - * @param whereClause {@code String} indicating the optional WHERE clause to apply when updating - * @param whereArgs {@code String[]} array containing values that shall replace ?s in the WHERE clause - * - * @return {@code boolean} indicating whether the update operation was successful - */ - boolean update(final String table, - final Map values, - final String whereClause, - final String[] whereArgs); - - /** - * Delete rows for a table in the database. - *

    - * A null {@code whereClause} will result in deletion of all table rows. - * - * @param table {@link String} containing the table name to delete rows from - * @param whereClause {@code String} containing the optional WHERE clause to apply when deleting - * @param whereArgs {@code String[]} array containing values that shall replace ?s in the WHERE clause - * - * @return {@code boolean} indicating whether the delete operation was successful - */ - boolean delete (final String table, - final String whereClause, - final String[] whereArgs); - - /** - * Close this database. - */ - void close(); - - } - - /** - * Interface defining methods for reading query results from a Database. - */ - interface QueryResult { - - /** - * Returns the total number of rows in this {@code QueryResult}. - * - * @return {@code int} indicating number of rows in the {@link QueryResult} - * - * @throws Exception if the underlying table does not exist, or if the {@link QueryResult} is already closed - */ - int getCount() throws Exception; - - /** - * Returns the value of the requested column as an {@code int}. - * - * @param columnIndex {@code int} zero-based index of the target column - * - * @return the value of the column as an {@code int} - * - * @throws Exception if the {@code columnIndex} is out of bounds, or if the {@link QueryResult} is already closed - */ - int getInt(final int columnIndex) throws Exception; - - /** - * Returns the value of the requested column as a {code double}. - * - * @param columnIndex {@code int} zero-based index of the target column - * - * @return the value of the column as a {@code double} - * - * @throws Exception if the {@code columnIndex} is out of bounds, or if the {@link QueryResult} is already closed - */ - double getDouble(final int columnIndex) throws Exception; - - /** - * Returns the value of the requested column as a {@code float}. - * - * @param columnIndex {@code int} zero-based index of the target column - * - * @return the value of the column as a {@code float} - * - * @throws Exception if the {@code columnIndex} is out of bounds, or if the {@link QueryResult} is already closed - */ - float getFloat(final int columnIndex) throws Exception; - - /** - * Returns the value of the requested column as a {@code long}. - * - * @param columnIndex {@code int} zero-based index of the target column - * - * @return the value of the column as a {@code long} - * - * @throws Exception if the {@code columnIndex} is out of bounds, or if the {@link QueryResult} is already closed - */ - long getLong(final int columnIndex) throws Exception; - - /** - * Returns the value of the requested column as a {@code String}. - * - * @param columnIndex {@code int} zero-based index of the target column - * - * @return the value of the column as a {@link String} - * - * @throws Exception if the {@code columnIndex} is out of bounds, or if the {@link QueryResult} is already closed - */ - String getString(final int columnIndex) throws Exception; - - /** - * Returns true if the value of the requested column is null, false otherwise. - * - * @param columnIndex {@code int} zero-based index of the target column - * - * @return {@code boolean} indicating whether the column value is null - * - * @throws Exception if the {@link QueryResult} is already closed - */ - boolean isNull(final int columnIndex) throws Exception; - - /** - * Move to the first row in the {@code QueryResult} - *

    - * This method will return false if the {@link QueryResult} is empty. - * - * @return {@code boolean} indicating whether the move was successful - * - * @throws Exception if the {@code QueryResult} is already closed - */ - boolean moveToFirst() throws Exception; - - /** - * Move to the last row in the {@code QueryResult} - *

    - * This method will return false if the {@link QueryResult} is empty. - * - * @return {@code boolean} indicating whether the move was successful - * - * @throws Exception if the {@code QueryResult} is already closed - */ - boolean moveToLast() throws Exception; - - /** - * Move to the next row in the {@code QueryResult} - *

    - * This method will return false if the {@link QueryResult} is empty. - * - * @return {@code boolean} indicating whether the move was successful - * - * @throws Exception if the {@code QueryResult} is already closed - */ - boolean moveToNext() throws Exception; - - /** - * Close this {@code QueryResult} - */ - void close(); - - } - -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/HitQueue.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/HitQueue.java deleted file mode 100644 index ea6c38377..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/HitQueue.java +++ /dev/null @@ -1,339 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import com.adobe.marketing.mobile.DatabaseService.QueryResult; - -import java.io.File; -import java.util.Map; - -/** - * Provides basic interactions with a database table. - * - * @param class extending {@link AbstractHit} which represents a record in the database table - * @param class extending {@link AbstractHitSchema} which contains the definition of the database table - */ -class HitQueue> extends AbstractHitsDatabase { - private static final int DEFAULT_NETWORK_CONNECTION_FAIL_DELAY_TIME = 30; - private static final int ONE_SECOND = 1000; - private static final String LOG_TAG = HitQueue.class.getSimpleName(); - private final Object backgroundMutex = new Object(); - private final SystemInfoService systemInfoService; - private final IHitProcessor hitProcessor; - private final E hitSchema; - private volatile boolean bgThreadActive; - private volatile boolean isSuspended; - - /** - * Used to communicate from the {@link IHitProcessor} to the {@link HitQueue} whether the processed - * {@link AbstractHit} should be removed from the queue - */ - enum RetryType { - NO, - YES, - WAIT, - } - - /** - * Constructor calls {@link AbstractHitsDatabase#AbstractHitsDatabase(DatabaseService, File, String)} then calls - * {@link #openOrCreateDatabase()} after initializing the following fields: - *

      - *
    • {@link #bgThreadActive}
    • - *
    • {@link #isSuspended}
    • - *
    • {@link #systemInfoService}
    • - *
    • {@link #hitSchema}
    • - *
    • {@link #hitProcessor}
    • - *
    - * - * @param services the {@code PlatformServices} instance - * @param dbFile the {@code File} representing the underlying database - * @param tableName {@code String} containing the database table name - * @param hitSchema {@code AbstractHitSchema} containing the database table definition - * @param hitProcessor object implementing the {@code IHitProcessor} responsible for processing hits - */ - HitQueue(final PlatformServices services, final File dbFile, final String tableName, - final E hitSchema, final IHitProcessor hitProcessor) { - super(services.getDatabaseService(), dbFile, tableName); - bgThreadActive = false; - isSuspended = false; - this.systemInfoService = services.getSystemInfoService(); - this.hitSchema = hitSchema; - this.hitProcessor = hitProcessor; - openOrCreateDatabase(); - } - - /** - * Indicates to the {@link HitQueue} that it should resume processing of queued hits. - */ - void bringOnline() { - this.isSuspended = false; - - if (!bgThreadActive) { - // indicate that the background thread is now active - bgThreadActive = true; - - synchronized (backgroundMutex) { - new Thread(workerThread(), "ADBMobileBackgroundThread").start(); - } - } - } - - /** - * Creates the table specified by {@link #tableName} in the underlying {@link #database}. - */ - @Override - void initializeDatabase() { - synchronized (dbMutex) { - boolean isSuccess = database.createTable(tableName, hitSchema.getColumnNames(), hitSchema.getColumnDataTypes(), - hitSchema.getColumnConstraints()); - - if (!isSuccess) { - Log.warning(LOG_TAG, "Unable to initialize the database properly, table name (%s)", tableName); - } - } - } - - /** - * Queries the {@link #database} and returns the first matching record. - * - * @param query {@code Query} defining the database query - * @return {@link AbstractHit} that represents the first hit matched by the {@code query} - */ - T queryHit(final Query query) { - synchronized (dbMutex) { - - if (this.database == null || this.databaseStatus == DatabaseStatus.FATAL_ERROR) { - Log.warning(LOG_TAG, "Update hit operation failed due to database error"); - return null; - } - - T hit = null; - QueryResult queryResult = null; - - try { - queryResult = database.query(query); - - if (queryResult != null && queryResult.getCount() > 0 && queryResult.moveToFirst()) { - hit = hitSchema.generateHit(queryResult); - } - } catch (Exception e) { - Log.error(LOG_TAG, "Unable to read from database. Query failed with error %s", e); - } finally { - if (queryResult != null) { - queryResult.close(); - } - } - - return hit; - } - } - - /** - * Inserts the provided {@link AbstractHit} into the {@link #database}. - * - * @param hit {@code AbstractHit} to be queued - * @return {@code boolean} indicating whether the database insert was successful - */ - protected boolean queue(final T hit) { - if (hit == null) { - Log.debug(LOG_TAG, "Ignoring null hit"); - return false; - } - - synchronized (dbMutex) { - if (this.database == null || this.databaseStatus == DatabaseStatus.FATAL_ERROR) { - Log.warning(LOG_TAG, "Ignoring hit due to database error"); - return false; - } - - if (!database.insert(tableName, hitSchema.generateDataMap(hit))) { - Log.warning(LOG_TAG, "A database error occurred preventing a hit from being inserted"); - reset(); - return false; - } else { - Log.trace(LOG_TAG, "Hit queued (%s)", hit.getClass().toString()); - } - } - - return true; - } - - /** - * Select the oldest hit from the {@link #database}. - *

    - * Returns null if there are no hits in the database or if the query operation failed. If an error occurred, - * an error message will be logged. - * - * @return the {@link AbstractHit} object representing the oldest record in the {@code database} - */ - T selectOldestHit() { - final Query.Builder queryBuilder = new Query.Builder(tableName, hitSchema.getColumnNames()); - queryBuilder.orderBy("ID ASC"); - queryBuilder.limit("1"); - return queryHit(queryBuilder.build()); - } - - /** - * Suspends the queue. New hits can still be queued, but processing will not be resumed until - * {@link #bringOnline()} is called. - */ - void suspend() { - isSuspended = true; - } - - /** - * Returns the status of suspended queue processing. - * - * @return {@code boolean} indicated if the queue processing is suspended. - */ - boolean isSuspended() { - return isSuspended; - } - - /** - * Updates all hits in the {@link #database} table with new values provided. - * - * @param parameters {@code Map} of column names and values to be updated in the database table - * @return {@code boolean} indicating whether the update operation was successful - */ - boolean updateAllHits(final Map parameters) { - synchronized (dbMutex) { - if (this.database == null || this.databaseStatus == DatabaseStatus.FATAL_ERROR) { - Log.warning(LOG_TAG, "Update hits operation failed due to database error"); - return false; - } - - // make sure we have records in the database that need to be updated to prevent false positive database errors - if (getSize() <= 0) { - return true; - } - - if (!database.update(tableName, parameters, null, null)) { - Log.warning(LOG_TAG, "An error occurred updating database. Resetting database."); - reset(); - return false; - } - - return true; - } - } - - /** - * Updates the hit in the {@link #database}. - * - * @param hit {@link AbstractHit} containing the hit to be updated in the database - * @return {@code boolean} indicating whether the update operation was successful - */ - boolean updateHit(final T hit) { - if (StringUtils.isNullOrEmpty(hit.identifier)) { - Log.warning(LOG_TAG, "Unable to update hit with empty identifier"); - return false; - } - - synchronized (dbMutex) { - if (this.database == null || this.databaseStatus == DatabaseStatus.FATAL_ERROR) { - Log.warning(LOG_TAG, "Update hit operation failed due to database error"); - return false; - } - - if (!database.update(tableName, hitSchema.generateDataMap(hit), "ID = ?", new String[] {hit.identifier})) { - Log.warning(LOG_TAG, "Unable to update hit in database"); - return false; - } - - return true; - } - } - - /** - * Loops through the table in the {@link #database} and causes the {@link #hitProcessor} to process them, in FIFO order. - *

    - * The processing loop will terminate under the following conditions: - *

      - *
    • There are no hits left in the table
    • - *
    • The {@code hitProcessor} is null
    • - *
    • After processing, we attempt to delete the processed hit and fail
    • - *
    - *

    - * If the {@code hitProcessor} indicates that we should attempt to re-process a hit, the loop will sleep for - * 30 seconds prior to resuming. - * - * @return a {@link Runnable} object to be dispatched by the caller - */ - private Runnable workerThread() { - return new Runnable() { - @Override - public void run() { - // loop while privacy status is opt-in, we have network, and we are not waiting on referrerData - while (!isSuspended && systemInfoService != null - && SystemInfoService.ConnectionStatus.CONNECTED == systemInfoService.getNetworkConnectionStatus()) { - // pull the top hit from the database - final T hit = selectOldestHit(); - - // if we do not have a hit, break out of this loop - if (hit == null || hitProcessor == null) { - break; - } - - final RetryType result = hitProcessor.process(hit); - - if (result == RetryType.YES) { - delayNetworkAttempts(); - } else if (result == RetryType.NO) { - if (!deleteHitWithIdentifier(hit.identifier)) { - break; - } - } else { - break; - } - } - - // indicate that the background thread is no longer active - bgThreadActive = false; - } - }; - } - - /** - * Sleeps the current thread in one second intervals for {@link #DEFAULT_NETWORK_CONNECTION_FAIL_DELAY_TIME} iterations. - *

    - * This method is called from {@link #workerThread()} and is a soft delay used between failed network calls. - */ - private void delayNetworkAttempts() { - try { - // pause for delay interval as long as the network remains available. - for (int i = 0; i < DEFAULT_NETWORK_CONNECTION_FAIL_DELAY_TIME - && SystemInfoService.ConnectionStatus.CONNECTED == systemInfoService.getNetworkConnectionStatus(); i++) { - Thread.sleep(ONE_SECOND); - } - } catch (final Exception e) { - Log.debug(LOG_TAG, "Background Thread Interrupted (%s)", e); - } - } - - /** - * Defines the interface providing the {@link HitQueue} instance with a way to process hits from its {@link #database}. - * - * @param A class extending {@link AbstractHit} - */ - interface IHitProcessor { - /** - * Called when a hit retrieved from the {@link #database} needs to be processed. - *

    - * The return value of this method determines whether the processed hit will be removed from the database. - * - * @param hit {@link AbstractHit} retrieved from the database - * @return {@link RetryType} indicating whether the hit should be re-processed - */ - RetryType process(T hit); - } -} \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/PlatformServices.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/PlatformServices.java index 9282eddf1..6bbe8ab27 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/PlatformServices.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/PlatformServices.java @@ -40,15 +40,6 @@ interface PlatformServices { */ LocalStorageService getLocalStorageService(); - /** - * Returns the Structured Data Service implementation from the Platform. - * - * @return DatabaseService implementation, if the platform provides any. null otherwise. - * - * @see DatabaseService - */ - DatabaseService getDatabaseService(); - /** * Returns the System Notification service implementation from the Platform. * diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Query.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Query.java deleted file mode 100644 index 1e24295a3..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Query.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import java.util.Arrays; - -class Query { - private String table; - private String[] columns; - private String selection; - private String[] selectionArgs; - private String groupBy; - private String having; - private String orderBy; - private String limit; - - private Query() { - } - - public static class Builder { - private Query query; - - /** - * Create a {@code Query.Builder} object with required Query parameters. - *

    - * If null is passed for {@code columns}, all the columns for the rows shall be returned in query result. - * - * @param table {@link String} containing the table name to compile the query against - * @param columns {@code String[]} containing the list of columns to return - */ - public Builder(final String table, final String[] columns) { - query = new Query(); - query.table = table; - query.columns = columns; - } - - /** - * Query selection. - *

    - * If null value is passed in {@code selection}, all the table rows shall be returned in query result. - * - * @param selection {@link String} containing filter declaring which rows to return, formatted as an SQL WHERE clause (excluding the WHERE itself) - * @param selectionArgs {@code String[]} array containing values which will replace ?s in selection, in order that they appear in the selection - * - * @return this {@link Query.Builder} object - */ - Builder selection(final String selection, final String[] selectionArgs) { - query.selection = selection; - query.selectionArgs = selectionArgs; - return this; - } - - /** - * Group query result. - *

    - * If null value is passed in {@code groupBy}, the table rows shall not be grouped in query result. - * - * @param groupBy {@link String} containing filter declaring how to group rows, formatted as an SQL GROUP BY clause (excluding - * the GROUP BY itself) - * - * @return this {@link Query.Builder} object - */ - Builder groupBy(final String groupBy) { - query.groupBy = groupBy; - return this; - } - - /** - * Having clause for this query. - *

    - * If null value is passed in {@code having}, all row groups will be included in the query result. - * - * @param having {@link String} containing filter declaring which row groups to include in the query result, if row grouping is being - * used, formatted as an SQL HAVING clause (excluding the HAVING itself) - * - * @return this {@link Query.Builder} object - */ - Builder having(final String having) { - query.having = having; - return this; - } - - /** - * Order result rows for this query's result. - *

    - * If null value is passed in {@code orderBy}, default sort order will be used for the query result, which may be unordered. - * - * @param orderBy {@link String} describing how to order the rows, formatted as an SQL ORDER BY clause (excluding the ORDER BY - * itself) - * - * @return this {@link Query.Builder} object - */ - Builder orderBy(final String orderBy) { - query.orderBy = orderBy; - return this; - } - - /** - * Limit the number of rows in the query result. - *

    - * Passing null value in {@code limit}, denotes absence of LIMIT clause. - * - * @param limit {@link String} indicating the number of rows returned by the query, formatted as LIMIT clause (excluding - * the LIMIT itself) - * - * @return this {@link Query.Builder} object - */ - Builder limit(final String limit) { - query.limit = limit; - return this; - } - - /** - * Build the {@code Query} object - * - * @return the {@link Query} object - */ - public Query build() { - return query; - } - } - - /** - * Returns the table name in this {@code Query}. - * - * @return {@link String} containing the table name - */ - String getTable() { - return table; - } - - /** - * Returns the table column names in this {@code Query}. - * - * @return {@code String[]} array containing the column names - */ - String[] getColumns() { - return columns != null ? Arrays.copyOf(columns, columns.length) : null; - } - - - /** - * Returns the SELECT clause in this {@code Query}. - * - * @return {@link String} specifying the SELECT clause - */ - String getSelection() { - return selection; - } - - /** - * Returns the SELECT clause arguments in this {@code Query}. - * - * @return {@code String[]} array containing the SELECT clause arguments - */ - String[] getSelectionArgs() { - return selectionArgs != null ? Arrays.copyOf(selectionArgs, selectionArgs.length) : null; - } - - /** - * Returns the GROUP BY clause in this {@code Query}. - * - * @return {@link String} specifying the GROUP BY clause - */ - String getGroupBy() { - return groupBy; - } - - /** - * Returns the HAVING clause in this {@code Query}. - * - * @return {@link String} specifying the HAVING clause - */ - String getHaving() { - return having; - } - - /** - * Returns the ORDER BY clause in this {@code Query}. - * - * @return {@link String} specifying the ORDER BY clause - */ - String getOrderBy() { - return orderBy; - } - - /** - * Returns the LIMIT clause in this {@code Query}. - * - * @return {@link String} specifying the LIMIT clause - */ - String getLimit() { - return limit; - } - -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/QueryStringBuilder.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/QueryStringBuilder.java deleted file mode 100644 index 652bee1c4..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/QueryStringBuilder.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import com.adobe.marketing.mobile.DatabaseService.Database.ColumnConstraint; -import com.adobe.marketing.mobile.DatabaseService.Database.ColumnDataType; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * QueryStringBuilder class exposes method to build create table query string given column names, data types and optional constraints. - */ -final class QueryStringBuilder { - - private QueryStringBuilder() {} - - private static final Map COLUMN_CONSTRAINT_STRING_MAP = new - HashMap() { - { - put(ColumnConstraint.PRIMARY_KEY, "PRIMARY KEY"); - put(ColumnConstraint.AUTOINCREMENT, "AUTOINCREMENT"); - put(ColumnConstraint.NOT_NULL, "NOT NULL"); - put(ColumnConstraint.UNIQUE, "UNIQUE"); - } - }; - - private static final Map COLUMN_DATA_TYPE_DEFAULT_MAP = new - HashMap() { - { - put(ColumnDataType.INTEGER, "0"); - put(ColumnDataType.REAL, "0.0"); - put(ColumnDataType.TEXT, "''"); - } - }; - /** - * Returns a query string to create table with the given column names, data types and constraints. - * - * @param name {@link String} containing table name - * @param columnNames {@code String[]} array containing column names in the table - * @param columnDataTypes {@code ColumnDataType[]} array containing column data types for the table columns - * @param columnConstraints {@code List>} list of column constraints' lists for the table columns - * - * @return {@code String} specifying create table query - * @see ColumnDataType - * @see ColumnConstraint - */ - static String getCreateTableQueryString(final String name, - final String[] columnNames, - final ColumnDataType[] columnDataTypes, - final List> columnConstraints, - final boolean setColumnsDefault) { - - if (StringUtils.isNullOrEmpty(name) - || columnNames == null || columnNames.length == 0 - || columnDataTypes == null || columnDataTypes.length != columnNames.length - || (columnConstraints != null && !columnConstraints.isEmpty() && columnConstraints.size() != columnNames.length)) { - return null; - } - - List dataTypesList = getColumnDataTypes(columnDataTypes); - List columnConstraintsList = getColumnConstraints(columnConstraints); - - - StringBuilder createTableQueryBuilder = new StringBuilder(); - createTableQueryBuilder.append("CREATE TABLE IF NOT EXISTS "); - createTableQueryBuilder.append(name); - createTableQueryBuilder.append("("); - - for (int columnIndex = 0; columnIndex < columnNames.length; columnIndex++) { - createTableQueryBuilder.append(columnNames[columnIndex]); - - createTableQueryBuilder.append(" "); - createTableQueryBuilder.append(dataTypesList.get(columnIndex)); - - if (columnConstraintsList != null) { - String constraints = columnConstraintsList.get(columnIndex); - - if (constraints != null) { - createTableQueryBuilder.append(" "); - createTableQueryBuilder.append(columnConstraintsList.get(columnIndex)); - } - } - - if (setColumnsDefault && columnConstraintsList != null - && columnConstraints.get(columnIndex) != null - && !columnConstraints.get(columnIndex).contains(ColumnConstraint.AUTOINCREMENT) - && !columnConstraints.get(columnIndex).contains(ColumnConstraint.PRIMARY_KEY)) { - createTableQueryBuilder.append(" DEFAULT "); - createTableQueryBuilder.append(COLUMN_DATA_TYPE_DEFAULT_MAP.get(columnDataTypes[columnIndex])); - createTableQueryBuilder.append(" "); - } - - - if (columnIndex < columnNames.length - 1) { - createTableQueryBuilder.append(", "); - } - } - - createTableQueryBuilder.append(")"); - return createTableQueryBuilder.toString(); - } - - /** - * Returns the list of column data types. - * - * @param columnDataTypes {@code ColumnDataType[]} array containing column data types - * @return {@code List} containing column data types expressed as {@link String} - * @see ColumnDataType - */ - private static List getColumnDataTypes(final ColumnDataType[] columnDataTypes) { - if (columnDataTypes == null || columnDataTypes.length == 0) { - return null; - } - - List dataTypesList = new ArrayList(); - - for (ColumnDataType dataType : columnDataTypes) { - dataTypesList.add(dataType.name()); - } - - return dataTypesList; - } - - /** - * Returns the list of column constraints. - * - * @param columnConstraints {@code List>} list containing column constraints' lists - * @return {@code List} containing column constraints expressed as {@link String} - * @see ColumnConstraint - */ - private static List getColumnConstraints(List> - columnConstraints) { - if (columnConstraints == null || columnConstraints.isEmpty()) { - return null; - } - - List constraints = new ArrayList(); - - for (List constraintList : columnConstraints) { - if (constraintList == null || constraintList.isEmpty()) { - constraints.add(null); - continue; - } - - StringBuilder columnConstraintBuilder = new StringBuilder(); - - for (ColumnConstraint constraint : constraintList) { - columnConstraintBuilder.append(COLUMN_CONSTRAINT_STRING_MAP.get(constraint)); - columnConstraintBuilder.append(" "); - } - - constraints.add(columnConstraintBuilder.toString().trim()); - } - - return constraints; - } - -} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AbstractHit.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/DatabaseProcessing.java similarity index 55% rename from code/android-core-library/src/main/java/com/adobe/marketing/mobile/AbstractHit.java rename to code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/DatabaseProcessing.java index 0b8f54e83..7651f3db6 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/AbstractHit.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/DatabaseProcessing.java @@ -9,22 +9,19 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile; +package com.adobe.marketing.mobile.internal.utility; -import java.io.Serializable; +import android.database.sqlite.SQLiteDatabase; -/** - * Base for any class that will represent a database record. - *

    - * Extenders of this class will be used in conjunction with an implementation of {@link AbstractHitsDatabase}. - *

    - * The base class includes the following fields: - *

      - *
    • {@link String} identifier
    • - *
    • {@code long} timestamp
    • - *
    - */ -abstract class AbstractHit { - String identifier; - long timestamp; -} \ No newline at end of file +import androidx.annotation.Nullable; + +@FunctionalInterface +public interface DatabaseProcessing { + /** + * Performs the database operations with the {@link SQLiteDatabase} connection. + * + * @param database the (nullable) newly opened database + * @return the result of the database operations. + */ + boolean execute(@Nullable final SQLiteDatabase database); +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/SQLiteDatabaseHelper.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/SQLiteDatabaseHelper.java index fd17bdf4f..e6040a53b 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/SQLiteDatabaseHelper.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/SQLiteDatabaseHelper.java @@ -11,18 +11,12 @@ package com.adobe.marketing.mobile.internal.utility; -import android.content.ContentValues; import android.database.Cursor; -import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; -import android.database.sqlite.SQLiteStatement; + import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; /** * Helper class for performing atomic operation on SQLite Database. @@ -57,72 +51,6 @@ public static boolean createTableIfNotExist(final String dbPath, final String qu } } - /** - * Inserts a new entity in database. - * - * @param dbPath path to database. - * @param tableName table name in which new row has to be inserted. - * @param data a {@link Map} contains mapping of column and values for new row. - * @return true if row is successfully inserted else false. - */ - public static boolean insertRow(final String dbPath, final String tableName, final Map data) { - SQLiteDatabase database = null; - - try { - database = openDatabase(dbPath, DatabaseOpenMode.READ_WRITE); - long rowId = database.insert(tableName, null, getContentValueFromMap(data)); - return rowId != -1; - } catch (final SQLiteException e) { - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, - String.format("insertRow - Error in inserting row into table (%s). Error: (%s)", tableName, e.getMessage())); - return false; - } finally { - closeDatabase(database); - } - } - - /** - * Returns a read only instance of {@link SQLiteDatabase} for reading data from database at path @dbPath. - * - * @param dbPath path to database from where data has to be read. - * @param columns the names of columns to read. - * @param count the number of rows to be read - * @return {@link List} of {@link ContentValues} where each ContentValue represents a row read from database. - */ - public static List query(final String dbPath, final String tableName, final String[] columns, final int count) { - SQLiteDatabase database = null; - Cursor cursor = null; - - try { - database = openDatabase(dbPath, DatabaseOpenMode.READ_ONLY); - cursor = database.query(tableName, columns, - null, null, null, null, "id ASC", String.valueOf(count)); - List rows = new ArrayList<>(cursor.getCount()); - - if (cursor.moveToFirst()) { - do { - ContentValues contentValues = new ContentValues(); - DatabaseUtils.cursorRowToContentValues(cursor, contentValues); - rows.add(contentValues); - } while (cursor.moveToNext()); - } - - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, String.format("query - Successfully read %d rows from table(%s)", - rows.size(), tableName)); - return Collections.unmodifiableList(rows); - } catch (final SQLiteException e) { - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, - String.format("query - Error in querying database table (%s). Error: (%s)", tableName, e.getMessage())); - return Collections.EMPTY_LIST; - } finally { - if (cursor != null) { - cursor.close(); - } - - closeDatabase(database); - } - } - /** * Returns the count of rows in table @tableName * @@ -160,41 +88,6 @@ public static int getTableSize(final String dbPath, final String tableName) { } } - /** - * Deletes the @count rows from Database. - * - * @param dbPath path to database. - * @param tableName name of table - * @param orderBy the order in which rows need to be read. It should be in the format "{columnname} asc/des" - * @param count the number of rows need to be deleted. - * @return number of affected rows. - */ - public static int removeRows(final String dbPath, final String tableName, final String orderBy, final int count) { - SQLiteDatabase database = null; - SQLiteStatement statement = null; - - try { - database = openDatabase(dbPath, DatabaseOpenMode.READ_WRITE); - StringBuilder builder = new StringBuilder("DELETE FROM ").append( - tableName).append(" WHERE id in (").append("SELECT id from ").append(tableName).append(" order by ").append( - orderBy).append(" limit ").append(count).append(')'); - statement = database.compileStatement(builder.toString()); - int deletedRowsCount = statement.executeUpdateDelete(); - return deletedRowsCount; - } catch (final SQLiteException e) { - MobileCore.log(LoggingMode.WARNING, LOG_PREFIX, - String.format("removeRows - Error in deleting rows from table(%s). Returning 0. Error: (%s)", tableName, - e.getMessage())); - return -1; //-1 indicates error in deleting rows. - } finally { - if (statement != null) { - statement.close(); - } - - closeDatabase(database); - } - } - /** * Deletes all the rows in table. * @@ -257,46 +150,26 @@ public static void closeDatabase(final SQLiteDatabase database) { } /** - * Convert values in {@link Map} to @{@link ContentValues} + * Open the database and begin processing database operations in {@link DatabaseProcessing#execute(SQLiteDatabase)}, then disconnecting the database. * - * @param values and instance of {@link Map} - * @return instance of {@link ContentValues} + * @param filePath path to database + * @param dbOpenMode an instance of {@link DatabaseOpenMode} + * @param databaseProcessing the function interface to include database operations + * @return the result of the {@link DatabaseProcessing#execute(SQLiteDatabase)} */ - private static ContentValues getContentValueFromMap(final Map values) { - ContentValues contentValues = new ContentValues(); - - for (Map.Entry value : values.entrySet()) { - String columnName = value.getKey(); - Object columnValue = value.getValue(); - - if (columnValue == null) { - contentValues.putNull(columnName); - } else if (columnValue instanceof String) { - contentValues.put(columnName, (String) columnValue); - } else if (columnValue instanceof Long) { - contentValues.put(columnName, (Long) columnValue); - } else if (columnValue instanceof Integer) { - contentValues.put(columnName, (Integer) columnValue); - } else if (columnValue instanceof Short) { - contentValues.put(columnName, (Short) columnValue); - } else if (columnValue instanceof Byte) { - contentValues.put(columnName, (Byte) columnValue); - } else if (columnValue instanceof Double) { - contentValues.put(columnName, (Double) columnValue); - } else if (columnValue instanceof Float) { - contentValues.put(columnName, (Float) columnValue); - } else if (columnValue instanceof Boolean) { - contentValues.put(columnName, (Boolean) columnValue); - } else if (columnValue instanceof byte[]) { - contentValues.put(columnName, (byte[]) columnValue); - } else { - MobileCore.log(LoggingMode.WARNING, LOG_PREFIX, - String.format("Unsupported data type received for database insertion: columnName (%s) value (%s)", columnName, - columnValue)); + public static boolean process(final String filePath, final DatabaseOpenMode dbOpenMode, final DatabaseProcessing databaseProcessing) { + SQLiteDatabase database = null; + try { + database = openDatabase(filePath, dbOpenMode); + return databaseProcessing.execute(database); + } catch (Exception e) { + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "Failed to open database -" + e.getLocalizedMessage()); + return false; + } finally { + if (database != null) { + closeDatabase(database); } } - - return contentValues; } /** @@ -312,4 +185,5 @@ public enum DatabaseOpenMode { this.mode = mode; } } + } diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/AndroidPlatformServices.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/AndroidPlatformServices.java index 67c5bc41b..170f8e6f8 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/AndroidPlatformServices.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/AndroidPlatformServices.java @@ -25,8 +25,6 @@ class AndroidPlatformServices implements PlatformServices { private AndroidLoggingService loggingService; - private AndroidDatabaseService databaseService; - private AndroidUIService uiService; private DeepLinkService deepLinkService; @@ -40,7 +38,6 @@ class AndroidPlatformServices implements PlatformServices { systemInfoService = new AndroidSystemInfoService(); networkService = new AndroidNetworkService(ServiceProvider.getInstance().getNetworkService()); loggingService = new AndroidLoggingService(); - databaseService = new AndroidDatabaseService(systemInfoService); uiService = new AndroidUIService(); localStorageService = new AndroidLocalStorageService(); deepLinkService = new AndroidDeepLinkService(); @@ -78,11 +75,6 @@ public LocalStorageService getLocalStorageService() { return localStorageService; } - @Override - public DatabaseService getDatabaseService() { - return databaseService; - } - @Override public SystemNotificationService getSystemNotificationService() { return null; diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java index e12b4bd1c..78efa482d 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/SQLiteDataQueue.java @@ -12,6 +12,10 @@ package com.adobe.marketing.mobile.services; import android.content.ContentValues; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteStatement; import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; @@ -20,221 +24,267 @@ import java.io.File; import java.util.ArrayList; import java.util.Date; -import java.util.HashMap; import java.util.List; -import java.util.Map; /** * SQLite backed implementation of {@link DataQueue}. */ final class SQLiteDataQueue implements DataQueue { - private static final String TABLE_NAME = "TB_AEP_DATA_ENTITY"; - private static final String TB_KEY_UNIQUE_IDENTIFIER = "uniqueIdentifier"; - private static final String TB_KEY_TIMESTAMP = "timestamp"; - private static final String TB_KEY_DATA = "data"; - private static final String LOG_PREFIX = "SQLiteDataQueue"; - - private final String databasePath; - private boolean isClose = false; - private final Object dbMutex = new Object(); - - SQLiteDataQueue(final File cacheDir, final String databaseName) { - this.databasePath = new File(cacheDir, removeRelativePath(databaseName)).getPath(); - createTableIfNotExists(); - } - - @Override - public boolean add(final DataEntity dataEntity) { - if (isClose) { - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "add - Returning false, DataQueue is closed."); - return false; - } - - if (dataEntity == null) { - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "add - Returning false, DataEntity is null."); - return false; - } - - Map dataToInsert = new HashMap<>(); - dataToInsert.put(TB_KEY_UNIQUE_IDENTIFIER, dataEntity.getUniqueIdentifier()); - dataToInsert.put(TB_KEY_TIMESTAMP, dataEntity.getTimestamp().getTime()); - dataToInsert.put(TB_KEY_DATA, dataEntity.getData() != null ? dataEntity.getData() : ""); - - synchronized (dbMutex) { - boolean result = SQLiteDatabaseHelper.insertRow(databasePath, TABLE_NAME, dataToInsert); - MobileCore.log(LoggingMode.VERBOSE, LOG_PREFIX, String.format("add - Successfully added DataEntity (%s) to DataQueue", - dataEntity.toString())); - return result; - } - } - - @Override - public List peek(final int n) { - if (isClose) { - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "peek n - Returning null, DataQueue is closed."); - return null; - } - - if (n <= 0) { - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "peek n - Returning null, n <= 0."); - return null; - } - - List rows; - - synchronized (dbMutex) { - rows = SQLiteDatabaseHelper.query(databasePath, TABLE_NAME, new String[] {TB_KEY_TIMESTAMP, TB_KEY_UNIQUE_IDENTIFIER, TB_KEY_DATA}, - n); - } - - if (rows == null || rows.isEmpty()) { - return new ArrayList<>(); - } - - final List dataEntitiesList = new ArrayList<>(rows.size()); - - for (ContentValues row : rows) { - dataEntitiesList.add(new DataEntity( - row.getAsString(TB_KEY_UNIQUE_IDENTIFIER), - new Date(row.getAsLong(TB_KEY_TIMESTAMP)), - row.getAsString(TB_KEY_DATA) - )); - } - - MobileCore.log(LoggingMode.VERBOSE, LOG_PREFIX, String.format("peek n - Successfully returned %d DataEntities", - dataEntitiesList.size())); - return dataEntitiesList; - } - - @Override - public DataEntity peek() { - if (isClose) { - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "peek - Returning null, DataQueue is closed"); - return null; - } - - final List dataEntities = peek(1); - - if (dataEntities == null) { - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "peek - Unable to fetch DataEntity, returning null"); - return null; - } - - if (dataEntities.isEmpty()) { - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "peek - 0 DataEntities fetch, returning null"); - return null; - } - - MobileCore.log(LoggingMode.VERBOSE, LOG_PREFIX, String.format("peek - Successfully returned DataEntity (%s)", - dataEntities.get(0).toString())); - return dataEntities.get(0); - } - - @Override - public boolean remove(final int n) { - if (isClose) { - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "remove n - Returning false, DataQueue is closed"); - return false; - } - - if (n <= 0) { - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "remove n - Returning false, n <= 0"); - return false; - } - - synchronized (dbMutex) { - int count = SQLiteDatabaseHelper.removeRows(databasePath, TABLE_NAME, "id ASC", n); - MobileCore.log(LoggingMode.VERBOSE, LOG_PREFIX, String.format("remove n - Successfully removed %d DataEntities", - count)); - return count != -1; - } - } - - @Override - public boolean remove() { - if (isClose) { - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "remove - Returning false, DataQueue is closed"); - return false; - } - - return remove(1); - } - - @Override - public boolean clear() { - if (isClose) { - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "clear - Returning false, DataQueue is closed"); - return false; - } - - synchronized (dbMutex) { - boolean result = SQLiteDatabaseHelper.clearTable(databasePath, TABLE_NAME); - MobileCore.log(LoggingMode.VERBOSE, LOG_PREFIX, String.format("clear - %s in clearing Table %s", - (result ? "Successful" : "Failed"), TABLE_NAME)); - return result; - } - } - - @Override - public int count() { - if (isClose) { - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "count - Returning 0, DataQueue is closed"); - return 0; - } - - synchronized (dbMutex) { - return SQLiteDatabaseHelper.getTableSize(databasePath, TABLE_NAME); - } - } - - @Override - public void close() { - isClose = true; - } - - /** - * Creates a Table with name {@link #TABLE_NAME}, if not already exists in database at path {@link #databasePath}. - */ - private void createTableIfNotExists() { - final String tableCreationQuery = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + - " (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, " + - "uniqueIdentifier TEXT NOT NULL UNIQUE, " + - "timestamp INTEGER NOT NULL, " + - "data TEXT);"; - - synchronized (dbMutex) { - if (SQLiteDatabaseHelper.createTableIfNotExist(databasePath, tableCreationQuery)) { - MobileCore.log(LoggingMode.VERBOSE, LOG_PREFIX, - String.format("createTableIfNotExists - Successfully created/already existed table (%s) ", TABLE_NAME)); - return; - } - } - - MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, - String.format("createTableIfNotExists - Error creating/accessing table (%s) ", TABLE_NAME)); - } - - /** - * Removes the relative part of the file name(if exists). - *

    - * for ex: File name `/mydatabase/../../database1` will be converted to `mydatabase_database1` - *

    - * - * @param filePath the file name - * @return file name without relative path - */ - private String removeRelativePath(final String filePath) { - if (filePath == null || filePath.isEmpty()) { - return filePath; - } - - try { - String result = filePath.replaceAll("\\.[/\\\\]", "\\."); - result = result.replaceAll("[/\\\\](\\.{2,})", "_"); - return result; - } catch (IllegalArgumentException e) { - return filePath; - } - } + private static final String TABLE_NAME = "TB_AEP_DATA_ENTITY"; + private static final String TB_KEY_UNIQUE_IDENTIFIER = "uniqueIdentifier"; + private static final String TB_KEY_TIMESTAMP = "timestamp"; + private static final String TB_KEY_DATA = "data"; + private static final String LOG_PREFIX = "SQLiteDataQueue"; + private final String databasePath; + private boolean isClose = false; + private final Object dbMutex = new Object(); + + SQLiteDataQueue(final File cacheDir, final String databaseName) { + this.databasePath = new File(cacheDir, removeRelativePath(databaseName)).getPath(); + createTableIfNotExists(); + } + + @Override + public boolean add(final DataEntity dataEntity) { + if (isClose) { + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "add - Returning false, DataQueue is closed."); + return false; + } + + if (dataEntity == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "add - Returning false, DataEntity is null."); + return false; + } + + synchronized (dbMutex) { + return SQLiteDatabaseHelper.process(databasePath, SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE, + database -> { + if (database == null) { + return false; + } + try (SQLiteStatement insertStatement = database.compileStatement( + "INSERT INTO " + TABLE_NAME + " (uniqueIdentifier, timestamp, data) VALUES (?, ?, ?)")) { + insertStatement.bindString(1, dataEntity.getUniqueIdentifier()); + insertStatement.bindLong(2, dataEntity.getTimestamp().getTime()); + insertStatement.bindString(3, dataEntity.getData() != null ? dataEntity.getData() : ""); + long rowId = insertStatement.executeInsert(); + return rowId >= 0; + } catch (Exception e) { + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "add - Returning false: " + e.getLocalizedMessage()); + return false; + } + }); + } + } + + @Override + public List peek(final int n) { + if (isClose) { + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "peek n - Returning null, DataQueue is closed."); + return null; + } + + if (n <= 0) { + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "peek n - Returning null, n <= 0."); + return null; + } + + final List rows = new ArrayList<>(); + + synchronized (dbMutex) { + SQLiteDatabaseHelper.process(databasePath, SQLiteDatabaseHelper.DatabaseOpenMode.READ_ONLY, + database -> { + if (database == null) { + return false; + } + + try (Cursor cursor = database.query(TABLE_NAME, new String[]{TB_KEY_TIMESTAMP, TB_KEY_UNIQUE_IDENTIFIER, TB_KEY_DATA}, + null, null, null, null, "id ASC", String.valueOf(n))) { + + if (cursor.moveToFirst()) { + do { + ContentValues contentValues = new ContentValues(); + DatabaseUtils.cursorRowToContentValues(cursor, contentValues); + rows.add(contentValues); + } while (cursor.moveToNext()); + } + + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, String.format("query - Successfully read %d rows from table(%s)", + rows.size(), TABLE_NAME)); + return true; + } catch (final SQLiteException e) { + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, + String.format("query - Error in querying database table (%s). Error: (%s)", TABLE_NAME, e.getLocalizedMessage())); + return false; + } + }); + } + + if (rows.isEmpty()) { + return new ArrayList<>(); + } + + final List dataEntitiesList = new ArrayList<>(rows.size()); + + for (ContentValues row : rows) { + dataEntitiesList.add(new DataEntity( + row.getAsString(TB_KEY_UNIQUE_IDENTIFIER), + new Date(row.getAsLong(TB_KEY_TIMESTAMP)), + row.getAsString(TB_KEY_DATA) + )); + } + + MobileCore.log(LoggingMode.VERBOSE, LOG_PREFIX, String.format("peek n - Successfully returned %d DataEntities", + dataEntitiesList.size())); + return dataEntitiesList; + } + + @Override + public DataEntity peek() { + if (isClose) { + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "peek - Returning null, DataQueue is closed"); + return null; + } + + final List dataEntities = peek(1); + + if (dataEntities == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "peek - Unable to fetch DataEntity, returning null"); + return null; + } + + if (dataEntities.isEmpty()) { + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "peek - 0 DataEntities fetch, returning null"); + return null; + } + + MobileCore.log(LoggingMode.VERBOSE, LOG_PREFIX, String.format("peek - Successfully returned DataEntity (%s)", + dataEntities.get(0).toString())); + return dataEntities.get(0); + } + + @Override + public boolean remove(final int n) { + if (isClose) { + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "remove n - Returning false, DataQueue is closed"); + return false; + } + + if (n <= 0) { + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "remove n - Returning false, n <= 0"); + return false; + } + + synchronized (dbMutex) { + return SQLiteDatabaseHelper.process(databasePath, SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE, + database -> { + int deletedRowsCount = -1; + if (database == null) { + return false; + } + String builder = "DELETE FROM " + + TABLE_NAME + " WHERE id in (" + "SELECT id from " + TABLE_NAME + " order by id ASC" + " limit " + n + ')'; + try (SQLiteStatement statement = database.compileStatement(builder)) { + deletedRowsCount = statement.executeUpdateDelete(); + MobileCore.log(LoggingMode.VERBOSE, LOG_PREFIX, String.format("remove n - Removed %d DataEntities", + deletedRowsCount)); + return deletedRowsCount > -1; + } catch (final SQLiteException e) { + MobileCore.log(LoggingMode.WARNING, LOG_PREFIX, + String.format("removeRows - Error in deleting rows from table(%s). Returning 0. Error: (%s)", TABLE_NAME, + e.getMessage())); + return false; + } + }); + } + } + + @Override + public boolean remove() { + if (isClose) { + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "remove - Returning false, DataQueue is closed"); + return false; + } + + return remove(1); + } + + @Override + public boolean clear() { + if (isClose) { + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "clear - Returning false, DataQueue is closed"); + return false; + } + + synchronized (dbMutex) { + boolean result = SQLiteDatabaseHelper.clearTable(databasePath, TABLE_NAME); + MobileCore.log(LoggingMode.VERBOSE, LOG_PREFIX, String.format("clear - %s in clearing Table %s", + (result ? "Successful" : "Failed"), TABLE_NAME)); + return result; + } + } + + @Override + public int count() { + if (isClose) { + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, "count - Returning 0, DataQueue is closed"); + return 0; + } + + synchronized (dbMutex) { + return SQLiteDatabaseHelper.getTableSize(databasePath, TABLE_NAME); + } + } + + @Override + public void close() { + isClose = true; + } + + /** + * Creates a Table with name {@link #TABLE_NAME}, if not already exists in database at path {@link #databasePath}. + */ + private void createTableIfNotExists() { + final String tableCreationQuery = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + + " (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, " + + "uniqueIdentifier TEXT NOT NULL UNIQUE, " + + "timestamp INTEGER NOT NULL, " + + "data TEXT);"; + + synchronized (dbMutex) { + if (SQLiteDatabaseHelper.createTableIfNotExist(databasePath, tableCreationQuery)) { + MobileCore.log(LoggingMode.VERBOSE, LOG_PREFIX, + String.format("createTableIfNotExists - Successfully created/already existed table (%s) ", TABLE_NAME)); + return; + } + } + + MobileCore.log(LoggingMode.DEBUG, LOG_PREFIX, + String.format("createTableIfNotExists - Error creating/accessing table (%s) ", TABLE_NAME)); + } + + /** + * Removes the relative part of the file name(if exists). + *

    + * for ex: File name `/mydatabase/../../database1` will be converted to `mydatabase_database1` + *

    + * + * @param filePath the file name + * @return file name without relative path + */ + //TODO: This method is moved to an internal utility class in another branch, need to remove it once the utility class is merged to the dev branch https://github.com/adobe/aepsdk-core-android/blob/0ea895adf692b9b96855472d7fd027f79b0c524c/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/utility/FileUtil.kt#L26 + private String removeRelativePath(final String filePath) { + if (filePath == null || filePath.isEmpty()) { + return filePath; + } + + try { + String result = filePath.replaceAll("\\.[/\\\\]", "\\."); + result = result.replaceAll("[/\\\\](\\.{2,})", "_"); + return result; + } catch (IllegalArgumentException e) { + return filePath; + } + } } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/DummyPlatformService.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/DummyPlatformService.java index 1a9cd0ce2..467290c36 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/DummyPlatformService.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/DummyPlatformService.java @@ -30,11 +30,6 @@ public LocalStorageService getLocalStorageService() { return null; } - @Override - public DatabaseService getDatabaseService() { - return null; - } - @Override public SystemNotificationService getSystemNotificationService() { return null; diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java index 7124ce864..8c29a5438 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/SqliteDataQueueTests.java @@ -15,10 +15,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyString; -import android.content.ContentValues; import android.database.sqlite.SQLiteException; import com.adobe.marketing.mobile.internal.utility.SQLiteDatabaseHelper; @@ -27,25 +25,17 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentMatchers; -import org.mockito.Mock; import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; -import java.util.Arrays; -import java.util.List; - @RunWith(PowerMockRunner.class) @PrepareForTest({SQLiteDatabaseHelper.class}) public class SqliteDataQueueTests { private DataQueue dataQueue; - @Mock - ContentValues contentValues; - private static final String DATABASE_NAME = "test.sqlite"; private static final String TABLE_NAME = "TB_AEP_DATA_ENTITY"; private static final String EMPTY_JSON_STRING = "{}"; @@ -61,21 +51,17 @@ public void setUp() { PowerMockito.mockStatic(SQLiteDatabaseHelper.class); PowerMockito.when(SQLiteDatabaseHelper.createTableIfNotExist(Mockito.anyString(), Mockito.anyString())).thenReturn(true); dataQueue = new SQLiteDataQueue(null, DATABASE_NAME); - } @Test public void addDataEntitySuccess() { - //Setup DataEntity dataEntity = new DataEntity(EMPTY_JSON_STRING); - PowerMockito.when(SQLiteDatabaseHelper.insertRow(Mockito.anyString(), Mockito.anyString(), - Mockito.anyMap())).thenReturn(true); + PowerMockito.when(SQLiteDatabaseHelper.process(Mockito.anyString(), Mockito.any(), + Mockito.any())).thenReturn(true); - //Actions boolean result = dataQueue.add(dataEntity); - //Assertions assertTrue(result); } @@ -85,8 +71,8 @@ public void addDataEntityFailure() { //Setup DataEntity dataEntity = new DataEntity(EMPTY_JSON_STRING); - Mockito.when(SQLiteDatabaseHelper.insertRow(Mockito.anyString(), Mockito.anyString(), - Mockito.anyMap())).thenReturn(false); + PowerMockito.when(SQLiteDatabaseHelper.process(Mockito.anyString(), Mockito.any(), + Mockito.any())).thenReturn(false); //Action boolean result = dataQueue.add(dataEntity); @@ -95,66 +81,6 @@ public void addDataEntityFailure() { assertFalse(result); } - @Test - public void testPeekSuccess() { - //Setup - - Mockito.when(contentValues.getAsString(TB_KEY_UNIQUE_IDENTIFIER)).thenReturn(TB_KEY_UNIQUE_IDENTIFIER); - Mockito.when(contentValues.getAsLong(TB_KEY_TIMESTAMP)).thenReturn(System.currentTimeMillis()); - Mockito.when(contentValues.getAsString(TB_KEY_DATA)).thenReturn(EMPTY_JSON_STRING); - Mockito.when(SQLiteDatabaseHelper.query(anyString(), anyString(), (String[]) Mockito.any(), - anyInt())).thenReturn(Arrays.asList(contentValues)); - - //Actions - DataEntity dataEntity = dataQueue.peek(); - - //Assertions - assertTrue(dataEntity != null); - - } - - - @Test - public void testPeekNSuccess() { - //Setup - Mockito.when(contentValues.getAsString(TB_KEY_UNIQUE_IDENTIFIER)).thenReturn(TB_KEY_UNIQUE_IDENTIFIER); - Mockito.when(contentValues.getAsLong(TB_KEY_TIMESTAMP)).thenReturn(System.currentTimeMillis()); - Mockito.when(contentValues.getAsString(TB_KEY_DATA)).thenReturn(EMPTY_JSON_STRING); - Mockito.when(SQLiteDatabaseHelper.query(anyString(), anyString(), (String[]) Mockito.any(), - anyInt())).thenReturn(Arrays.asList(contentValues, contentValues)); - - //Actions - List dataEntityList = dataQueue.peek(2); - - //Assertions - assertEquals(2, dataEntityList.size()); - - } - - @Test - public void testRemoveRows() { - //setup - Mockito.when(SQLiteDatabaseHelper.removeRows(anyString(), anyString(), anyString(), anyInt())).thenReturn(1); - - //Action - boolean result = dataQueue.remove(); - - //Assertions - assertTrue(result); - } - - @Test - public void testRemoveNRows() { - //Setup - Mockito.when(SQLiteDatabaseHelper.removeRows(anyString(), anyString(), anyString(), anyInt())).thenReturn(1); - - //Actions - boolean result = dataQueue.remove(1); - - //Assertions - assertTrue(result); - } - @Test public void testClearTable() { //etup @@ -200,8 +126,8 @@ public void testClose() { @Test public void addDataEntityWithDatabaseOpenError() { - Mockito.when(SQLiteDatabaseHelper.insertRow(anyString(), anyString(), - ArgumentMatchers.anyMap())).thenCallRealMethod(); + PowerMockito.when(SQLiteDatabaseHelper.process(Mockito.anyString(), Mockito.any(), + Mockito.any())).thenCallRealMethod(); Mockito.when(SQLiteDatabaseHelper.openDatabase(DATABASE_NAME, SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE)).thenThrow(SQLiteException.class); @@ -211,19 +137,6 @@ public void addDataEntityWithDatabaseOpenError() { Assert.assertFalse(result); } - @Test - public void peekNWithDatabaseOpenError() { - - Mockito.when(SQLiteDatabaseHelper.removeRows(anyString(), anyString(), anyString(), anyInt())).thenCallRealMethod(); - Mockito.when(SQLiteDatabaseHelper.openDatabase(DATABASE_NAME, - SQLiteDatabaseHelper.DatabaseOpenMode.READ_WRITE)).thenThrow(SQLiteException.class); - - boolean result = dataQueue.remove(2); - - //Assertions - Assert.assertFalse(result); - } - @Test public void clearTableWithDatabaseOpenError() { diff --git a/code/testapp/build.gradle b/code/testapp/build.gradle index dce376679..0073a55ad 100644 --- a/code/testapp/build.gradle +++ b/code/testapp/build.gradle @@ -25,6 +25,11 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + testOptions { animationsDisabled true From 63f65ceaad60808878c969c24fc71a26c3e6a3f2 Mon Sep 17 00:00:00 2001 From: Yansong Date: Wed, 1 Jun 2022 16:36:13 -0600 Subject: [PATCH 060/476] Combine MobileCore and Core classes. --- .../java/com/adobe/marketing/mobile/App.java | 400 ++-- .../java/com/adobe/marketing/mobile/Core.java | 597 ------ .../adobe/marketing/mobile/MobileCore.java | 1809 +++++++++-------- 3 files changed, 1220 insertions(+), 1586 deletions(-) delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/Core.java diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/App.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/App.java index b2bfcf286..8bea6c17b 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/App.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/App.java @@ -26,199 +26,211 @@ */ class App { - private static final String DATASTORE_NAME = "ADOBE_MOBILE_APP_STATE"; - private static final String SMALL_ICON_RESOURCE_ID_KEY = "SMALL_ICON_RESOURCE_ID"; - private static final String LARGE_ICON_RESOURCE_ID_KEY = "LARGE_ICON_RESOURCE_ID"; - - private static volatile Context appContext; - private static volatile WeakReference currentActivity; - private static volatile WeakReference application; - private static volatile int smallIconResourceID = -1; - private static volatile int largeIconResourceID = -1; - - private App() {} - - /** - * Registers {@code Application.ActivityLifecycleCallbacks} to the {@code Application} instance, - * and the context variable. - * - * @param app the current {@code Application} - */ - static void setApplication(Application app) { - if (application != null && application.get() != null) { - return; - } - - application = new WeakReference(app); - AppLifecycleListener.getInstance().registerActivityLifecycleCallbacks(app); - setAppContext(app); - ServiceProvider.getInstance().setContext(app); - } - - static Application getApplication() { - return application != null ? application.get() : null; - } - - /** - * Sets the {@code context} variable. - * @param context the current {@code Context} - */ - static void setAppContext(Context context) { - appContext = context != null ? context.getApplicationContext() : null; - } - - /** - * Returns the {@code Context} which was set either by {@code setApplication} or {@code setAppContext}. - * - * @return the current {@code Context} - */ - static Context getAppContext() { - return appContext; - } - - /** - * Sets the {@code activity} variable and also update the {@code context} variable - * @param activity the current {@code Activity} - */ - static void setCurrentActivity(Activity activity) { - if (activity == null) { - return; - } - - currentActivity = new WeakReference(activity); - setAppContext(activity); - ServiceProvider.getInstance().setCurrentActivity(currentActivity.get()); - } - - /** - * Returns the {@code Activity} which was set by {@code setCurrentActivity}. - * - * @return the current {@code Activity} - */ - static Activity getCurrentActivity() { - if (currentActivity == null) { - return null; - } - - return currentActivity.get(); - } - - /** - * Returns the current orientation of the device. - * - * @return a {@code int} value indicates the orientation. 0 for unknown, 1 for portrait and 2 for landscape - */ - static int getCurrentOrientation() { - if (currentActivity == null || currentActivity.get() == null) { - return 0; //neither landscape nor portrait - } - - return currentActivity.get().getResources().getConfiguration().orientation; - } - - /** - * Returns the resource Id for small icon if it was set by {@code setSmallIconResourceID}. - * - * @return a {@code int} value if it has been set, otherwise -1 - */ - static int getSmallIconResourceID() { - if (smallIconResourceID == -1) { - AndroidLocalStorageService localStorageService = new AndroidLocalStorageService(); - LocalStorageService.DataStore dataStore = localStorageService.getDataStore(DATASTORE_NAME); - - if (dataStore != null) { - smallIconResourceID = dataStore.getInt(SMALL_ICON_RESOURCE_ID_KEY, -1); - } - } - - return smallIconResourceID; - } - - /** - * Sets the resource Id for small icon. - * - * @param resourceID the resource Id of the icon - */ - static void setSmallIconResourceID(int resourceID) { - smallIconResourceID = resourceID; - AndroidLocalStorageService localStorageService = new AndroidLocalStorageService(); - LocalStorageService.DataStore dataStore = localStorageService.getDataStore(DATASTORE_NAME); - - if (dataStore != null) { - dataStore.setInt(SMALL_ICON_RESOURCE_ID_KEY, smallIconResourceID); - } - } - - /** - * Returns the resource Id for large icon if it was set by {@code setLargeIconResourceID}. - * - * @return a {@code int} value if it has been set, otherwise -1 - */ - static int getLargeIconResourceID() { - if (largeIconResourceID == -1) { - AndroidLocalStorageService localStorageService = new AndroidLocalStorageService(); - LocalStorageService.DataStore dataStore = localStorageService.getDataStore(DATASTORE_NAME); - - if (dataStore != null) { - largeIconResourceID = dataStore.getInt(LARGE_ICON_RESOURCE_ID_KEY, -1); - } - } - - return largeIconResourceID; - } - - /** - * Sets the resource Id for large icon. - * - * @param resourceID the resource Id of the icon - */ - static void setLargeIconResourceID(int resourceID) { - largeIconResourceID = resourceID; - AndroidLocalStorageService localStorageService = new AndroidLocalStorageService(); - LocalStorageService.DataStore dataStore = localStorageService.getDataStore(DATASTORE_NAME); - - if (dataStore != null) { - dataStore.setInt(LARGE_ICON_RESOURCE_ID_KEY, largeIconResourceID); - } - } - - /** - * For testing. - * Clear this App of all held variables and object references. - * Method clears the {@link Application}, {@link Context}, {@link Activity}, - * notification icon resources and clears the icons in local persistent storage. - */ - static void clearAppResources() { - if (appContext != null) { - appContext = null; - } - - if (application != null) { - Application app = application.get(); - - if (app != null) { - app.unregisterActivityLifecycleCallbacks(AppLifecycleListener.getInstance()); - } - - application.clear(); - application = null; - } - - if (currentActivity != null) { - currentActivity.clear(); - currentActivity = null; - } - - AndroidLocalStorageService localStorageService = new AndroidLocalStorageService(); - LocalStorageService.DataStore dataStore = localStorageService.getDataStore(DATASTORE_NAME); - - if (dataStore != null) { - dataStore.remove(SMALL_ICON_RESOURCE_ID_KEY); - dataStore.remove(LARGE_ICON_RESOURCE_ID_KEY); - } - - smallIconResourceID = -1; - largeIconResourceID = -1; - } + private static final String DATASTORE_NAME = "ADOBE_MOBILE_APP_STATE"; + private static final String SMALL_ICON_RESOURCE_ID_KEY = "SMALL_ICON_RESOURCE_ID"; + private static final String LARGE_ICON_RESOURCE_ID_KEY = "LARGE_ICON_RESOURCE_ID"; + private static volatile Context appContext; + private static volatile PlatformServices platformServices; + private static volatile WeakReference currentActivity; + private static volatile WeakReference application; + private static volatile int smallIconResourceID = -1; + private static volatile int largeIconResourceID = -1; + + private App() { + } + + /** + * Registers {@code Application.ActivityLifecycleCallbacks} to the {@code Application} instance, + * and the context variable. + * + * @param app the current {@code Application} + */ + static void setApplication(Application app) { + if (application != null && application.get() != null) { + return; + } + + application = new WeakReference(app); + AppLifecycleListener.getInstance().registerActivityLifecycleCallbacks(app); + setAppContext(app); + ServiceProvider.getInstance().setContext(app); + } + + static Application getApplication() { + return application != null ? application.get() : null; + } + + + static PlatformServices getPlatformServices() { + return platformServices; + } + + static void setPlatformServices(PlatformServices platformServices) { + App.platformServices = platformServices; + } + + /** + * Sets the {@code context} variable. + * + * @param context the current {@code Context} + */ + static void setAppContext(Context context) { + appContext = context != null ? context.getApplicationContext() : null; + } + + /** + * Returns the {@code Context} which was set either by {@code setApplication} or {@code setAppContext}. + * + * @return the current {@code Context} + */ + static Context getAppContext() { + return appContext; + } + + /** + * Sets the {@code activity} variable and also update the {@code context} variable + * + * @param activity the current {@code Activity} + */ + static void setCurrentActivity(Activity activity) { + if (activity == null) { + return; + } + + currentActivity = new WeakReference(activity); + setAppContext(activity); + ServiceProvider.getInstance().setCurrentActivity(currentActivity.get()); + } + + /** + * Returns the {@code Activity} which was set by {@code setCurrentActivity}. + * + * @return the current {@code Activity} + */ + static Activity getCurrentActivity() { + if (currentActivity == null) { + return null; + } + + return currentActivity.get(); + } + + /** + * Returns the current orientation of the device. + * + * @return a {@code int} value indicates the orientation. 0 for unknown, 1 for portrait and 2 for landscape + */ + static int getCurrentOrientation() { + if (currentActivity == null || currentActivity.get() == null) { + return 0; //neither landscape nor portrait + } + + return currentActivity.get().getResources().getConfiguration().orientation; + } + + /** + * Returns the resource Id for small icon if it was set by {@code setSmallIconResourceID}. + * + * @return a {@code int} value if it has been set, otherwise -1 + */ + static int getSmallIconResourceID() { + if (smallIconResourceID == -1) { + AndroidLocalStorageService localStorageService = new AndroidLocalStorageService(); + LocalStorageService.DataStore dataStore = localStorageService.getDataStore(DATASTORE_NAME); + + if (dataStore != null) { + smallIconResourceID = dataStore.getInt(SMALL_ICON_RESOURCE_ID_KEY, -1); + } + } + + return smallIconResourceID; + } + + /** + * Sets the resource Id for small icon. + * + * @param resourceID the resource Id of the icon + */ + static void setSmallIconResourceID(int resourceID) { + smallIconResourceID = resourceID; + AndroidLocalStorageService localStorageService = new AndroidLocalStorageService(); + LocalStorageService.DataStore dataStore = localStorageService.getDataStore(DATASTORE_NAME); + + if (dataStore != null) { + dataStore.setInt(SMALL_ICON_RESOURCE_ID_KEY, smallIconResourceID); + } + } + + /** + * Returns the resource Id for large icon if it was set by {@code setLargeIconResourceID}. + * + * @return a {@code int} value if it has been set, otherwise -1 + */ + static int getLargeIconResourceID() { + if (largeIconResourceID == -1) { + AndroidLocalStorageService localStorageService = new AndroidLocalStorageService(); + LocalStorageService.DataStore dataStore = localStorageService.getDataStore(DATASTORE_NAME); + + if (dataStore != null) { + largeIconResourceID = dataStore.getInt(LARGE_ICON_RESOURCE_ID_KEY, -1); + } + } + + return largeIconResourceID; + } + + /** + * Sets the resource Id for large icon. + * + * @param resourceID the resource Id of the icon + */ + static void setLargeIconResourceID(int resourceID) { + largeIconResourceID = resourceID; + AndroidLocalStorageService localStorageService = new AndroidLocalStorageService(); + LocalStorageService.DataStore dataStore = localStorageService.getDataStore(DATASTORE_NAME); + + if (dataStore != null) { + dataStore.setInt(LARGE_ICON_RESOURCE_ID_KEY, largeIconResourceID); + } + } + + /** + * For testing. + * Clear this App of all held variables and object references. + * Method clears the {@link Application}, {@link Context}, {@link Activity}, + * notification icon resources and clears the icons in local persistent storage. + */ + static void clearAppResources() { + if (appContext != null) { + appContext = null; + } + + if (application != null) { + Application app = application.get(); + + if (app != null) { + app.unregisterActivityLifecycleCallbacks(AppLifecycleListener.getInstance()); + } + + application.clear(); + application = null; + } + + if (currentActivity != null) { + currentActivity.clear(); + currentActivity = null; + } + + AndroidLocalStorageService localStorageService = new AndroidLocalStorageService(); + LocalStorageService.DataStore dataStore = localStorageService.getDataStore(DATASTORE_NAME); + + if (dataStore != null) { + dataStore.remove(SMALL_ICON_RESOURCE_ID_KEY); + dataStore.remove(LARGE_ICON_RESOURCE_ID_KEY); + } + + smallIconResourceID = -1; + largeIconResourceID = -1; + } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Core.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Core.java deleted file mode 100644 index 00ea1772e..000000000 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Core.java +++ /dev/null @@ -1,597 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import java.util.HashMap; -import java.util.Map; - -class Core { - private static final String LOG_TAG = Core.class.getSimpleName(); - private boolean startActionCalled; - - EventHub eventHub; - - Core(final PlatformServices platformServices) { - this(platformServices, "undefined"); - } - - Core(final PlatformServices platformServices, final String coreVersion) { - Log.setLoggingService(platformServices.getLoggingService()); - eventHub = new EventHub("AMSEventHub", platformServices, coreVersion); - - try { - eventHub.registerModule(ConfigurationExtension.class, new ConfigurationModuleDetails(coreVersion)); - } catch (InvalidModuleException e) { - Log.error(LOG_TAG, "Failed to register Configuration extension (%s)", e); - } - - Log.trace(LOG_TAG, "Core initialization was successful"); - } - - Core(final PlatformServices platformServices, final EventHub eventHub) { - Log.setLoggingService(platformServices.getLoggingService()); - this.eventHub = eventHub; - Log.trace(LOG_TAG, "Core initialization was successful"); - } - - - // ------------------------------------ 3rd party extensions -------------------------------------- - - /** - * Registers an extension class which has {@code Extension} as parent. - * - * @param extensionClass a class whose parent is {@link Extension} - * @param errorCallback an optional {@link ExtensionErrorCallback} for the eventuality of an error, - * called when this method returns false - */ - void registerExtension(final Class extensionClass, - final ExtensionErrorCallback errorCallback) { - eventHub.registerExtensionWithCallback(extensionClass, errorCallback); - } - - /** - * Clones the provided {@code event} and dispatches it through the {@code EventHub}. - * Passes an {@link ExtensionError} to {@code errorCallback} if the event is null. - * - * @param event provided {@link Event} from the public API - * @param errorCallback callback to return {@link ExtensionError} if an error occurs - * @return {@code boolean} indicating if the event was dispatched or not - */ - boolean dispatchEvent(final Event event, final ExtensionErrorCallback errorCallback) { - if (event == null) { - Log.debug(LOG_TAG, "%s (Core.dispatchEvent) - The event was not dispatched", Log.UNEXPECTED_NULL_VALUE); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.EVENT_NULL); - } - - return false; - } - - eventHub.dispatch(event); - return true; - } - - /** - * This method will be used when the provided {@code Event} is used as a trigger and a response event is expected in return. - * The returned event needs to be sent using {@link #dispatchResponseEvent(Event, Event, ExtensionErrorCallback)} method. - *

    - * Clones the provided {@code event}, registers a {@link OneTimeListener} for the response and dispatches it through the - * {@code EventHub}. Passes an {@link ExtensionError} to {@code errorCallback} if the event is null. - * - * @param event provided {@link Event} from the public API - * @param responseCallback {@link AdobeCallback} to be called with the response event received in the {@link OneTimeListener} - * @param errorCallback callback to return {@link ExtensionError} if an error occurs - * @return {@code boolean} indicating if the paired event was dispatched or not - * @see #dispatchResponseEvent - */ - boolean dispatchEventWithResponseCallback(final Event event, - final AdobeCallback responseCallback, - final ExtensionErrorCallback errorCallback) { - if (responseCallback == null) { - Log.debug(LOG_TAG, - "%s (Core.dispatchEventWithResponseCallback) - The event was not dispatched", Log.UNEXPECTED_NULL_VALUE); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.CALLBACK_NULL); - } - - return false; - } - - if (event == null) { - Log.debug(LOG_TAG, "%s (Core.dispatchEventWithResponseCallback) - The event was not dispatched", - Log.UNEXPECTED_NULL_VALUE); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.EVENT_NULL); - } - - return false; - } - - eventHub.registerOneTimeListener(event.getResponsePairID(), new Module.OneTimeListenerBlock() { - @Override - public void call(final Event e) { - responseCallback.call(e); - } - }); - eventHub.dispatch(event); - return true; - } - - /** - * This method will be used when the provided {@code Event} is used as a trigger and a response event is expected in return. - * The returned event needs to be sent using {@link #dispatchResponseEvent(Event, Event, ExtensionErrorCallback)} method. - *

    - * Clones the provided {@code event}, registers a {@link OneTimeListener} for the response and dispatches it through the - * {@code EventHub} if the given {@link Event} and {@link AdobeCallbackWithError} are not null. - * The one-time listener will be unregistered by the event hub after the response event is received, or when event processing timeout (5000ms) occurs. - * - * @param event provided {@link Event} from the public API - * @param responseCallback {@link AdobeCallback} to be called with the response event received in the {@link OneTimeListener} - * @see #dispatchResponseEvent - */ - void dispatchEventWithResponseCallback(final Event event, - final AdobeCallbackWithError responseCallback) { - if (event == null || responseCallback == null) { - Log.debug(LOG_TAG, - "(Core.dispatchEventWithResponseCallback) - The event was not dispatched, the given Event or AdobeCallbackWithError is null"); - return; - } - - eventHub.registerOneTimeListener(event.getResponsePairID(), new Module.OneTimeListenerBlock() { - @Override - public void call(final Event e) { - responseCallback.call(e); - } - }, responseCallback); - eventHub.dispatch(event); - } - - /** - * This method will be used when a response event should be dispatched for a paired event that was previously sent - * using {@code dispatchEventWithResponseCallback} - *

    - * Clones the provided {@code event}, sets the pair id associated with the request event's pair id and dispatches - * it through the {@link EventHub}. Passes an {@link ExtensionError} to {@code errorCallback} if the event is null. - * - * @param responseEvent provided response {@link Event} from the public API - * @param requestEvent the trigger {@link Event} for the dispatched event - * @param errorCallback callback to return {@link ExtensionError} if an error occurs - * @return {@code boolean} indicating if the response event was dispatched or not - * @see #dispatchEventWithResponseCallback - */ - boolean dispatchResponseEvent(final Event responseEvent, final Event requestEvent, - final ExtensionErrorCallback errorCallback) { - if (requestEvent == null) { - Log.debug(LOG_TAG, - "%s (Core.dispatchResponseEvent) - The response event was not dispatched", Log.UNEXPECTED_NULL_VALUE); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.EVENT_NULL); - } - - return false; - } - - if (responseEvent == null) { - Log.warning(LOG_TAG, "%s (Core.dispatchResponseEvent) - The response event was not dispatched", - Log.UNEXPECTED_NULL_VALUE); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.EVENT_NULL); - } - - return false; - } - - responseEvent.setPairId(requestEvent.getResponsePairID()); - eventHub.dispatch(responseEvent); - return true; - } - - /** - * Registers an event listener for the provided event type and source. - * - * @param eventType required parameter, the event type as a valid string (not null or empty) - * @param eventSource required parameter, the event source as a valid string (not null or empty) - * @param callback required parameter, {@link AdobeCallbackWithError#call(Object)} will be called when the event is heard - */ - void registerEventListener(final String eventType, final String eventSource, - final AdobeCallbackWithError callback) { - eventHub.registerEventListener(EventType.get(eventType), EventSource.get(eventSource), callback); - } - - - // ------------------------------ Configuration methods ------------------------------ - - /** - * Load remote configuration specified by the given application ID. - *

    - * Configure the SDK by downloading the remote configuration file hosted on Adobe servers - * specified by the given application ID. The configuration file is cached once downloaded - * and used in subsequent calls to this API. If the remote file is updated after the first - * download, the updated file is downloaded and replaces the cached file. - *

    - * The {@code appId} is preserved, and on application restarts, the remote configuration file specified by {@code appId} - * is downloaded and applied to the SDK. - *

    - * On failure to download the remote configuration file, the SDK is configured using the cached - * file if it exists, or if no cache file exists then the existing configuration remains unchanged. - *

    - * Calls to this API will replace any existing SDK configuration except those set using - * {@link #updateConfiguration(Map)} or {@link #setPrivacyStatus(MobilePrivacyStatus)}. Configuration updates - * made using {@link #updateConfiguration(Map)} and {@link #setPrivacyStatus(MobilePrivacyStatus)} - * are always applied on top of configuration changes made using this API. - * - * @param appId a unique identifier assigned to the app instance by the Adobe Mobile Services. It is automatically - * added to the Mobile configuration JSON file when downloaded from the Adobe Mobile Services UI and can be - * found in Manage App Settings. A value of {@code null} or empty {@code String} will clear the preserved value. - */ - void configureWithAppID(final String appId) { - EventData eventData = new EventData(); - eventData.putString(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID, appId); - final Event event = new Event.Builder("Configure with AppID", EventType.CONFIGURATION, - EventSource.REQUEST_CONTENT).setData(eventData).build(); - eventHub.dispatch(event); - } - - /** - * Load configuration from local file. - *

    - * Configure the SDK by reading a local file containing the JSON configuration. On application relaunch, - * the configuration from the file at {@code filepath} is not preserved and this method must be called again if desired. - *

    - * On failure to read the file or parse the JSON contents, the existing configuration remains unchanged. - *

    - * Calls to this API will replace any existing SDK configuration except those set using - * {@link #updateConfiguration(Map)} or {@link #setPrivacyStatus(MobilePrivacyStatus)}. - * Configuration updates made using {@link #updateConfiguration(Map)} and {@link #setPrivacyStatus(MobilePrivacyStatus)} - * are always applied on top of configuration changes made using this API. - * - * @param filepath absolute path to a local configuration file. A value of {@code null} has no effect. - */ - void configureWithFileInPath(final String filepath) { - if (StringUtils.isNullOrEmpty(filepath)) { - Log.warning("Configuration", "Unable to configure with null or empty remoteURL"); - return; - } - - EventData eventData = new EventData(); - eventData.putString(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_FILE_PATH, filepath); - final Event event = new Event.Builder("Configure with FilePath", EventType.CONFIGURATION, - EventSource.REQUEST_CONTENT).setData(eventData).build(); - eventHub.dispatch(event); - } - - /** - * Load configuration from an asset file. - * @param fileName the name of the configure file in the assets folder. A value of {@code null} has no effect. - */ - void configureWithFileInAssets(final String fileName) { - if (StringUtils.isNullOrEmpty(fileName)) { - Log.warning("Configuration", "Unable to configure with null or empty file name"); - return; - } - - EventData eventData = new EventData(); - eventData.putString(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_ASSET_FILE, fileName); - final Event event = new Event.Builder("Configure with FilePath", EventType.CONFIGURATION, - EventSource.REQUEST_CONTENT).setData(eventData).build(); - eventHub.dispatch(event); - } - - /** - * Update specific configuration parameters. - *

    - * Update the current SDK configuration with specific key/value pairs. Keys not found in the current - * configuration are added. Configuration updates are preserved and applied over existing or new - * configurations set by calling {@link #configureWithAppID(String)} or {@link #configureWithFileInPath(String)}, - * even across application restarts. - *

    - * Using {@code null} values is allowed and effectively removes the configuration parameter from the current configuration. - * - * @param configMap configuration key/value pairs to be updated or added. A value of {@code null} has no effect. - */ - void updateConfiguration(final Map configMap) { - // Create a EventData Map - HashMap eventDataMap = new HashMap(); - Variant configMapVariant = Variant.fromTypedMap(configMap, PermissiveVariantSerializer.DEFAULT_INSTANCE); - eventDataMap.put(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_UPDATE_CONFIG, - configMapVariant); - EventData eventData = new EventData(eventDataMap); - final Event event = new Event.Builder("Configuration Update", EventType.CONFIGURATION, - EventSource.REQUEST_CONTENT).setData(eventData).build(); - eventHub.dispatch(event); - } - - /** - * Clear the changes made by {@link #updateConfiguration(Map)} to the initial configuration provided either by - * {@link #configureWithAppID(String)} or {@link #configureWithFileInPath(String)} or {@link #configureWithFileInAssets(String)} - */ - void clearUpdatedConfiguration() { - EventData eventData = new EventData(); - eventData.putBoolean(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_CLEAR_UPDATED_CONFIG, - true); - final Event event = new Event.Builder("Clear updated configuration", EventType.CONFIGURATION, - EventSource.REQUEST_CONTENT).setData(eventData).build(); - eventHub.dispatch(event); - } - /** - * Gets the SDK's current version with wrapper type. - */ - String getSdkVersion() { - return eventHub.getSdkVersion(); - } - - /** - * Sets the SDK's current wrapper type. This API should only be used if - * being developed on platforms such as React Native. - * - * @param wrapperType the type of wrapper being used. - */ - void setWrapperType(final WrapperType wrapperType) { - eventHub.setWrapperType(wrapperType); - } - - /** - * Set the Adobe Mobile Privacy status. - *

    - * Sets the {@link MobilePrivacyStatus} for this SDK. The set privacy status is preserved and applied over any new - * configuration changes from calls to {@link #configureWithAppID(String)} or {@link #configureWithFileInPath(String)}, - * even across application restarts. - * - * @param privacyStatus {@link MobilePrivacyStatus} to be set to the SDK - * @see MobilePrivacyStatus - */ - void setPrivacyStatus(final MobilePrivacyStatus privacyStatus) { - final Map privacyStatusUpdateConfig = new HashMap(); - final String privacyStatusString = (privacyStatus == null ? null : privacyStatus.getValue()); - privacyStatusUpdateConfig.put(CoreConstants.EventDataKeys.Configuration.GLOBAL_CONFIG_PRIVACY, privacyStatusString); - updateConfiguration(privacyStatusUpdateConfig); - } - - /** - * Get the current Adobe Mobile Privacy Status. - *

    - * Gets the currently configured {@link MobilePrivacyStatus} and passes it as a parameter to the given - * {@link AdobeCallback#call(Object)} function. - * - * @param callback {@link AdobeCallback} instance which is invoked with the configured privacy status as a parameter - */ - void getPrivacyStatus(final AdobeCallback callback) { - if (callback == null) { - return; - } - - EventData eventData = new EventData(); - eventData.putBoolean(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_RETRIEVE_CONFIG, true); - Event event = new Event.Builder("PrivacyStatusRequest", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) - .setData(eventData).build(); - - - final AdobeCallbackWithError adobeCallbackWithError = callback instanceof AdobeCallbackWithError ? - (AdobeCallbackWithError) callback : null; - - eventHub.registerOneTimeListener(event.getResponsePairID(), new Module.OneTimeListenerBlock() { - @Override - public void call(final Event e) { - EventData eventData = e.getData(); - callback.call(MobilePrivacyStatus.fromString(eventData.getString(ConfigurationConstants.EventDataKeys - .Configuration.GLOBAL_CONFIG_PRIVACY))); - } - }, adobeCallbackWithError); - - eventHub.dispatch(event); - - } - - /** - * Retrieve all identities stored by/known to the SDK in a JSON {@code String} format. - *

    - * Dispatches an {@link EventType#CONFIGURATION} - {@link EventSource#REQUEST_IDENTITY} {@code Event}. - *

    - * Returns an empty string if the SDK is unable to retrieve any identifiers. - * - * @param callback {@link AdobeCallback} instance which is invoked with all the known identifier in JSON {@link String} format - * @see AdobeCallback - */ - void getSdkIdentities(final AdobeCallback callback) { - if (callback == null) { - return; - } - - final AdobeCallbackWithError adobeCallbackWithError = callback instanceof AdobeCallbackWithError ? - (AdobeCallbackWithError) callback : null; - - Event event = new Event.Builder("getSdkIdentities", EventType.CONFIGURATION, EventSource.REQUEST_IDENTITY).build(); - eventHub.registerOneTimeListener(event.getResponsePairID(), new Module.OneTimeListenerBlock() { - @Override - public void call(final Event e) { - EventData eventData = e.getData(); - callback.call(eventData.optString( - ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_RESPONSE_IDENTITY_ALL_IDENTIFIERS, "{}")); - } - }, adobeCallbackWithError); - - eventHub.dispatch(event); - } - - /** - * Dispatches a track action request event. - * - * @param action The action string - * @param contextData Any context data that needs to be associated with the {@code action} or {@code state} - */ - void trackAction(final String action, final Map contextData) { - EventData trackData = new EventData(); - trackData.putString(CoreConstants.EventDataKeys.Analytics.TRACK_ACTION, action); - trackData.putStringMap(CoreConstants.EventDataKeys.Analytics.CONTEXT_DATA, - contextData == null ? new HashMap() : contextData); - Event event = new Event.Builder("Analytics Track", EventType.GENERIC_TRACK, EventSource.REQUEST_CONTENT) - .setData(trackData).build(); - - eventHub.dispatch(event); - } - - /** - * Dispatches a track state request event. - * - * @param state The state string - * @param contextData Any context data that needs to be associated with the {@code action} or {@code state} - */ - void trackState(final String state, final Map contextData) { - EventData trackData = new EventData(); - trackData.putString(CoreConstants.EventDataKeys.Analytics.TRACK_STATE, state); - trackData.putStringMap(CoreConstants.EventDataKeys.Analytics.CONTEXT_DATA, - contextData == null ? new HashMap() : contextData); - Event event = new Event.Builder("Analytics Track", EventType.GENERIC_TRACK, EventSource.REQUEST_CONTENT) - .setData(trackData).build(); - - eventHub.dispatch(event); - } - - /** - * Dispatches an event with the Advertising Identifier - * - * @param adid the advertising idenifier string. - */ - void setAdvertisingIdentifier(final String adid) { - EventData eventData = new EventData(); - eventData.putString(CoreConstants.EventDataKeys.Identity.ADVERTISING_IDENTIFIER, adid); - - Event event = new Event.Builder("SetAdvertisingIdentifier", EventType.GENERIC_IDENTITY, EventSource.REQUEST_CONTENT) - .setData(eventData) - .build(); - - eventHub.dispatch(event); - - } - - /** - * Dispatches an event with the push token - * - * @param registrationID push token that needs to be set. - */ - void setPushIdentifier(final String registrationID) { - EventData eventData = new EventData(); - eventData.putString(CoreConstants.EventDataKeys.Identity.PUSH_IDENTIFIER, registrationID); - - Event event = new Event.Builder("SetPushIdentifier", EventType.GENERIC_IDENTITY, EventSource.REQUEST_CONTENT) - .setData(eventData) - .build(); - - eventHub.dispatch(event); - } - - /** - * Dispatches an event to resume/start a lifecycle session - * - * @param additionalContextData {@code Map} context data - */ - void lifecycleStart(final Map additionalContextData) { - EventData eventData = new EventData(); - eventData.putString(CoreConstants.EventDataKeys.Lifecycle.LIFECYCLE_ACTION_KEY, - CoreConstants.EventDataKeys.Lifecycle.LIFECYCLE_START); - - eventData.putStringMap(CoreConstants.EventDataKeys.Lifecycle.ADDITIONAL_CONTEXT_DATA, additionalContextData); - Event event = new Event.Builder("LifecycleResume", EventType.GENERIC_LIFECYLE, EventSource.REQUEST_CONTENT) - .setData(eventData) - .build(); - - eventHub.dispatch(event); - } - - /** - * Dispatches an event to pause/stop a lifecycle session - */ - void lifecyclePause() { - EventData eventData = new EventData(); - eventData.putString(CoreConstants.EventDataKeys.Lifecycle.LIFECYCLE_ACTION_KEY, - CoreConstants.EventDataKeys.Lifecycle.LIFECYCLE_PAUSE); - - Event event = new Event.Builder("LifecyclePause", EventType.GENERIC_LIFECYLE, EventSource.REQUEST_CONTENT) - .setData(eventData) - .build(); - - eventHub.dispatch(event); - } - - /** - * Create collect PII event, which is listened by Rules Engine module to determine if the data matches any PII request. - * - * @param data the PII data to be collected, which will be used in Rules Engine comparison and request token replacement. - */ - void collectPii(final Map data) { - if (data == null || data.isEmpty()) { - Log.debug(LOG_TAG, "Could not trigger PII, the data is null or empty."); - return; - } - - final EventData eventData = new EventData() - .putStringMap(CoreConstants.EventDataKeys.Signal.SIGNAL_CONTEXT_DATA, data); - eventHub.dispatch(new Event.Builder("CollectPII", EventType.GENERIC_PII, - EventSource.REQUEST_CONTENT).setData(eventData).build()); - Log.trace(LOG_TAG, "Collect PII event was sent"); - } - - /** - * Create collect data event, which may contain deep link information, messages info or referrer data. - * Dispatches an {@link EventType#GENERIC_DATA} {@link EventSource#OS} event to the {@link EventHub}. - * - * @param marshalledData OS launch data marshalled as {@code Map} - */ - void collectData(final Map marshalledData) { - if (marshalledData == null || marshalledData.isEmpty()) { - Log.debug(LOG_TAG, "collectData: Could not dispatch generic data event, data is null or empty."); - return; - } - - Event event = new Event.Builder("CollectData", EventType.GENERIC_DATA, EventSource.OS) - .setEventData(marshalledData) - .build(); - eventHub.dispatch(event); - Log.trace(LOG_TAG, "collectData: generic data OS event dispatched."); - } - - /** - * Dispatches a generic identity event to notify extensions to reset their stored identities - */ - void resetIdentities() { - Event event = new Event.Builder("Reset Identities Request", EventType.GENERIC_IDENTITY, EventSource.REQUEST_RESET) - .build(); - - eventHub.dispatch(event); - } - - /** - * Start the Core processing. This should be called after the initial set of extensions have been registered. - *

    - * This call will wait for any outstanding registrations to complete and then start event processing. - * You can use the callback to kickoff additional operations immediately after any operations kicked off during registration. - * - * @param completionCallback An optional {@link AdobeCallback} invoked after registrations are completed - */ - void start(final AdobeCallback completionCallback) { - if (startActionCalled) { - Log.debug(LOG_TAG, "Can't start Core more than once."); - return; - } - - startActionCalled = true; - eventHub.finishModulesRegistration(completionCallback); - } - - - -} diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java index 7f2fbe0fe..670c61546 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java @@ -16,804 +16,1023 @@ import android.content.Context; import java.util.Date; +import java.util.HashMap; import java.util.Map; public class MobileCore { - private final static String VERSION = "1.10.0"; - private final static String TAG = MobileCore.class.getSimpleName(); - private static final String NULL_CONTEXT_MESSAGE = "Context must be set before calling SDK methods"; - - private static Core core; - private static PlatformServices platformServices; - private static final Object mutex = new Object(); - - private MobileCore() { - } - - // For test - static void setCore(final Core core) { - synchronized (mutex) { - MobileCore.core = core; - } - } - - // For test - static void setPlatformServices(final PlatformServices platformServices) { - synchronized (mutex) { - MobileCore.platformServices = platformServices; - } - } - - static Core getCore() { - synchronized (mutex) { - return core; - } - } - - /** - * Returns the version for the {@code MobileCore} extension - * @return The version string - */ - public static String extensionVersion() { - synchronized (mutex) { - if (core == null) { - Log.warning(TAG, "Returning version without wrapper type info. Make sure setApplication API is called."); - return VERSION; - } - - return core.getSdkVersion(); - } - } - - /** - * Set the current {@link Application}, which enables the SDK get the app {@code Context}, - * register a {@link Application.ActivityLifecycleCallbacks} - * to monitor the lifecycle of the app and get the {@link android.app.Activity} on top of the screen. - *

    - * NOTE: This method should be called right after the app starts, so it gives the SDK all the - * contexts it needed. - * - * @param app the current {@code Application} - */ - public static void setApplication(final Application app) { - // AMSDK-8502 - // workaround to prevent a crash happening on Android 8.0/8.1 related to TimeZoneNamesImpl - // https://issuetracker.google.com/issues/110848122 - try { - new Date().toString(); - } catch (AssertionError e) { - // Workaround for a bug in Android that can cause crashes on Android 8.0 and 8.1 - } catch (Exception e) { - // Workaround for a bug in Android that can cause crashes on Android 8.0 and 8.1 - } - - App.setApplication(app); - V4ToV5Migration migrationTool = new V4ToV5Migration(); - migrationTool.migrate(); - - if (core == null) { - synchronized (mutex) { - if (platformServices == null) { - platformServices = new AndroidPlatformServices(); - } - - core = new Core(platformServices, VERSION); - } - } - - com.adobe.marketing.mobile.internal.context.App.getInstance().initializeApp(new - com.adobe.marketing.mobile.internal.context.App.AppContextProvider() { - @Override - public Context getAppContext() { - return App.getAppContext(); - } - - @Override - public Activity getCurrentActivity() { - return App.getCurrentActivity(); - } - }); - - } - - /** - * Get the global {@link Application} object of the current process. - *

    - * NOTE: {@link #setApplication(Application)} must be called before calling this method. - * - * @return the current {@code Application}, or null if no {@code Application} was set or - * the {@code Application} process was destroyed. - */ - public static Application getApplication() { - return App.getApplication(); - } - - /** - * Set the {@link LoggingMode} level for the Mobile SDK. - * - * @param mode the logging mode - */ - public static void setLogLevel(LoggingMode mode) { - Log.setLogLevel(mode); - } - - /** - * Get the {@link LoggingMode} level for the Mobile SDK - * @return the set {@code LoggingMode} - */ - public static LoggingMode getLogLevel() { - return Log.getLogLevel(); - } - - /** - * Sends a log message of the given {@code LoggingMode}. If the specified {@code mode} is - * more verbose than the current {@link LoggingMode} set from {@link #setLogLevel(LoggingMode)} - * then the message is not printed. - * - * @param mode the {@link LoggingMode} used to print the message - * @param tag used to identify the source of the log message - * @param message the message to log - */ - public static void log(final LoggingMode mode, final String tag, final String message) { - if (mode == null) { - return; - } - - switch (mode) { - case ERROR: - Log.error(tag, message); - break; - - case WARNING: - Log.warning(tag, message); - break; - - case DEBUG: - Log.debug(tag, message); - break; - - case VERBOSE: - Log.trace(tag, message); - break; - } - } - - /** - * Start the Core processing. This should be called after the initial set of extensions have been registered. - *

    - * This call will wait for any outstanding registrations to complete and then start event processing. - * You can use the callback to kickoff additional operations immediately after any operations kicked off during registration. - * You shouldn't call this method more than once in your app, if so, sdk will ignore it and print error log. - * - * @param completionCallback An optional {@link AdobeCallback} invoked after registrations are completed - */ - public static void start(final AdobeCallback completionCallback) { - synchronized (mutex) { - if (core == null) { - Log.debug(TAG, "Failed to start SDK (%s)", NULL_CONTEXT_MESSAGE); - - if (completionCallback != null & completionCallback instanceof AdobeCallbackWithError) { - ((AdobeCallbackWithError) completionCallback).fail(AdobeError.EXTENSION_NOT_INITIALIZED); - } - - return; - } - - // initialize the AndroidEventHistory service and set it within the EventHistoryProvider instance - try { - if (EventHistoryProvider.getEventHistory() == null) { - EventHistoryProvider.setEventHistory(new AndroidEventHistory()); - Log.trace(TAG, "Android EventHistory created and set in the EventHistoryProvider"); - } - } catch (final EventHistoryDatabaseCreationException exception) { - Log.warning(TAG, "Failed to create the android event history service: %s", - exception.getMessage()); - } - - core.start(completionCallback); - } - } - - // ======================================================== - // Configuration methods - // ======================================================== - - - public static void configureWithAppID(final String appId) { - if (core == null) { - Log.debug(TAG, "Failed to set Adobe App ID (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.configureWithAppID(appId); - } - - - /** - * Registers an extension class which has {@code Extension} as parent. - *

    - * In order to ensure that your extension receives all the internal events, this method needs - * to be called after {@link MobileCore#setApplication(Application)} is called, but before - * any other method in this class. - * - * @param extensionClass a class whose parent is {@link Extension} - * @param errorCallback an optional {@link ExtensionErrorCallback} for the eventuality of an error, - * called when this method returns false - * @return {@code boolean} indicating if the provided parameters are valid and no error occurs - */ - public static boolean registerExtension(final Class extensionClass, - final ExtensionErrorCallback errorCallback) { - if (core == null) { - Log.debug(TAG, "Failed to register the extension. (%s)", NULL_CONTEXT_MESSAGE); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.UNEXPECTED_ERROR); - } - - return false; - } - - if (extensionClass == null) { - if (errorCallback != null) { - errorCallback.error(ExtensionError.UNEXPECTED_ERROR); - } - - return false; - } - - core.registerExtension(extensionClass, errorCallback); - return true; - } - - /** - * Called by the extension public API to dispatch an event for other extensions or the internal SDK to consume. - * - * @param event required parameter, {@link Event} instance to be dispatched, should not be null - * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred during dispatching - * @return {@code boolean} indicating if the the event dispatching operation succeeded - */ - public static boolean dispatchEvent(final Event event, final ExtensionErrorCallback errorCallback) { - if (core == null) { - Log.debug(TAG, "Failed to dispatch event. (%s)", NULL_CONTEXT_MESSAGE); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.UNEXPECTED_ERROR); - } - - return false; - } - - return core.dispatchEvent(event, errorCallback); - } - - /** - * This method will be used when the provided {@code Event} is used as a trigger and a response event - * is expected in return. The returned event needs to be sent using - * {@link #dispatchResponseEvent(Event, Event, ExtensionErrorCallback)}. - *

    - * Passes an {@link ExtensionError} to {@code errorCallback} if {@code event} or - * {@code responseCallback} are null. - * - * @param event required parameter, {@link Event} instance to be dispatched, used as a trigger - * @param responseCallback required parameters, {@link AdobeCallback} to be called with the response event received - * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred during dispatching - * @return {@code boolean} indicating if the the event dispatching operation succeeded - * - * @see MobileCore#dispatchResponseEvent(Event, Event, ExtensionErrorCallback) - */ - public static boolean dispatchEventWithResponseCallback(final Event event, - final AdobeCallback responseCallback, - final ExtensionErrorCallback errorCallback) { - if (core == null) { - Log.debug(TAG, "Failed to dispatch event with a response callback. (%s)", NULL_CONTEXT_MESSAGE); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.UNEXPECTED_ERROR); - } - - return false; - } - - // the core will validate and copy this event - return core.dispatchEventWithResponseCallback(event, responseCallback, errorCallback); - } - - /** - * This method will be used when the provided {@code Event} is used as a trigger and a response event - * is expected in return. The returned event needs to be sent using - * {@link #dispatchResponseEvent(Event, Event, ExtensionErrorCallback)}. - *

    - * Passes an {@link AdobeError} to {@link AdobeCallbackWithError#fail(AdobeError)} if {@code event} is null. - * Passes an {@link AdobeError} to {@link AdobeCallbackWithError#fail(AdobeError)} if {@code event} processing timeout occurs. - * - * @param event required parameter, {@link Event} instance to be dispatched, used as a trigger - * @param responseCallback required parameters, {@link AdobeCallback} to be called with the response event received - * - * @see MobileCore#dispatchResponseEvent(Event, Event, ExtensionErrorCallback) - */ - public static void dispatchEventWithResponseCallback(final Event event, - final AdobeCallbackWithError responseCallback) { - if (core == null) { - Log.debug(TAG, "Failed to dispatch event with a response callback. (%s)", NULL_CONTEXT_MESSAGE); - - if (responseCallback != null) { - responseCallback.fail(AdobeError.UNEXPECTED_ERROR); - } - - return; - } - - if (event == null) { - Log.debug(TAG, "Failed to dispatch event with a response callback: the given event is null "); - - if (responseCallback != null) { - responseCallback.fail(AdobeError.UNEXPECTED_ERROR); - } - - return; - } - - if (responseCallback == null) { - Log.warning(TAG, - "Failed to dispatch event with a response callback: the given callback (AdobeCallbackWithError) object is null "); - return; - } - - // the core will validate and copy this event - core.dispatchEventWithResponseCallback(event, responseCallback); - } - - /** - * Dispatches a response event for a paired event that was sent to {@code dispatchEventWithResponseCallback} - * and received by an extension listener {@code hear} method. - *

    - * Passes an {@link ExtensionError} to {@code errorCallback} if {@code responseEvent} or {@code requestEvent} are null. - *

    - * Note: The {@code responseEvent} will not be sent to any listeners, it is sent only to the response callback registered - * using {@link MobileCore#dispatchEventWithResponseCallback(Event, AdobeCallback, ExtensionErrorCallback)}. - * - * @param responseEvent required parameter, {@link Event} instance to be dispatched as a response for the - * event sent using {@link MobileCore#dispatchEventWithResponseCallback(Event, AdobeCallback, ExtensionErrorCallback)} - * @param requestEvent required parameter, the event sent using - * {@link MobileCore#dispatchEventWithResponseCallback(Event, AdobeCallback, ExtensionErrorCallback)} - * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred during dispatching - * @return {@code boolean} indicating if the the event dispatching operation succeeded - * - * @see MobileCore#dispatchEventWithResponseCallback(Event, AdobeCallback, ExtensionErrorCallback) - */ - public static boolean dispatchResponseEvent(final Event responseEvent, final Event requestEvent, - final ExtensionErrorCallback errorCallback) { - - if (core == null) { - Log.debug(TAG, "Failed to dispatch the response event. (%s)", NULL_CONTEXT_MESSAGE); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.UNEXPECTED_ERROR); - } - - return false; - } - - // the core will validate and copy this events - return core.dispatchResponseEvent(responseEvent, requestEvent, errorCallback); - } - - /** - * Load configuration from the file in the assets folder. SDK automatically reads config from `ADBMobileConfig.json` file if - * it exists in the assets folder. Use this API only if the config needs to be read from a different file. - *

    - * On application relaunch, the configuration from the file at {@code filepath} is not preserved and this method must be called - * again if desired. - *

    - * On failure to read the file or parse the JSON contents, the existing configuration remains unchanged. - *

    - * Calls to this API will replace any existing SDK configuration except those set using - * {@link #updateConfiguration(Map)} or {@link #setPrivacyStatus(MobilePrivacyStatus)}. - * Configuration updates made using {@link #updateConfiguration(Map)} and {@link #setPrivacyStatus(MobilePrivacyStatus)} - * are always applied on top of configuration changes made using this API. - *absolute - * @param fileName the name of the configure file in the assets folder. A value of {@code null} has no effect. - */ - public static void configureWithFileInAssets(final String fileName) { - if (core == null) { - Log.debug(TAG, "Failed to load configuration with asset file (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.configureWithFileInAssets(fileName); - } - - /** - * Load configuration from local file. - *

    - * Configure the SDK by reading a local file containing the JSON configuration. On application relaunch, - * the configuration from the file at {@code filepath} is not preserved and this method must be called again if desired. - *

    - * On failure to read the file or parse the JSON contents, the existing configuration remains unchanged. - *

    - * Calls to this API will replace any existing SDK configuration except those set using - * {@link #updateConfiguration(Map)} or {@link #setPrivacyStatus(MobilePrivacyStatus)}. - * Configuration updates made using {@link #updateConfiguration(Map)} and {@link #setPrivacyStatus(MobilePrivacyStatus)} - * are always applied on top of configuration changes made using this API. - * - * @param filepath absolute path to a local configuration file. A value of {@code null} has no effect. - */ - public static void configureWithFileInPath(final String filepath) { - if (core == null) { - Log.debug(TAG, "Failed to load configuration with file path (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.configureWithFileInPath(filepath); - } - - /** - * Update specific configuration parameters. - *

    - * Update the current SDK configuration with specific key/value pairs. Keys not found in the current - * configuration are added. Configuration updates are preserved and applied over existing or new - * configurations set by calling {@link #configureWithAppID(String)} or {@link #configureWithFileInPath(String)}, - * even across application restarts. - *

    - * Using {@code null} values is allowed and effectively removes the configuration parameter from the current configuration. - * - * @param configMap configuration key/value pairs to be updated or added. A value of {@code null} has no effect. - */ - public static void updateConfiguration(final Map configMap) { - if (core == null) { - Log.debug(TAG, "Failed to update configuration (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.updateConfiguration(configMap); - } - - /** - * Clear the changes made by {@link #updateConfiguration(Map)} and {@link #setPrivacyStatus(MobilePrivacyStatus)} - * to the initial configuration provided either by {@link #configureWithAppID(String)} - * or {@link #configureWithFileInPath(String)} or {@link #configureWithFileInAssets(String)} - */ - public static void clearUpdatedConfiguration() { - if (core == null) { - Log.debug(TAG, "Failed to clear updated configuration (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.clearUpdatedConfiguration(); - } - - /** - * Set the Adobe Mobile Privacy status. - *

    - * Sets the {@link MobilePrivacyStatus} for this SDK. The set privacy status is preserved and applied over any new - * configuration changes from calls to {@link #configureWithAppID(String)} or {@link #configureWithFileInPath(String)}, - * even across application restarts. - * - * @param privacyStatus {@link MobilePrivacyStatus} to be set to the SDK - * @see MobilePrivacyStatus - */ - public static void setPrivacyStatus(final MobilePrivacyStatus privacyStatus) { - if (core == null) { - Log.debug(TAG, "Failed to set privacy status (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.setPrivacyStatus(privacyStatus); - } - - /** - * Get the current Adobe Mobile Privacy Status. - *

    - * Gets the currently configured {@link MobilePrivacyStatus} and passes it as a parameter to the given - * {@link AdobeCallback#call(Object)} function. - * - * @param callback {@link AdobeCallback} instance which is invoked with the configured privacy status as a parameter - * @see AdobeCallback - * @see MobilePrivacyStatus - */ - public static void getPrivacyStatus(final AdobeCallback callback) { - if (core == null) { - Log.debug(TAG, "Failed to retrieve the privacy status (%s)", NULL_CONTEXT_MESSAGE); - - if (callback != null & callback instanceof AdobeCallbackWithError) { - ((AdobeCallbackWithError) callback).fail(AdobeError.EXTENSION_NOT_INITIALIZED); - } - - return; - } - - core.getPrivacyStatus(callback); - } - - /** - * Retrieve all identities stored by/known to the SDK in a JSON {@code String} format. - * - * @param callback {@link AdobeCallback} instance which is invoked with all the known identifier in JSON {@link String} format - * @see AdobeCallback - */ - public static void getSdkIdentities(final AdobeCallback callback) { - if (callback == null) { - Log.debug(TAG, "%s (Callback), provide a callback to retrieve the all SDK identities", Log.UNEXPECTED_NULL_VALUE); - return; - } - - if (core == null) { - Log.debug(TAG, "Failed to retrieve the all SDK identities (%s)", NULL_CONTEXT_MESSAGE); - - if (callback != null & callback instanceof AdobeCallbackWithError) { - ((AdobeCallbackWithError) callback).fail(AdobeError.EXTENSION_NOT_INITIALIZED); - } - - return; - } - - core.getSdkIdentities(callback); - } - - // ======================================================== - // Generic methods - // ======================================================== - - - /** - * This method dispatches an Analytics track {@code action} event - *

    - * Actions represent events that occur in your application that you want to measure; the corresponding metrics will - * be incremented each time the event occurs. For example, you may want to track when an user click on the login - * button or a certain article was viewed. - *

    - * - * @param action {@code String} containing the name of the action to track - * @param contextData {@code Map} containing context data to attach on this hit - */ - public static void trackAction(final String action, final Map contextData) { - if (core == null) { - Log.debug(TAG, "Failed to track action %s (%s)", action, NULL_CONTEXT_MESSAGE); - return; - } - - core.trackAction(action, contextData); - } - - /** - * This method dispatches an Analytics track {@code state} event - *

    - * States represent different screens or views of your application. When the user navigates between application pages, - * a new track call should be sent with current state name. Tracking state name is typically called from an - * Activity in the onResume method. - *

    - * - * @param state {@code String} containing the name of the state to track - * @param contextData contextData {@code Map} containing context data to attach on this hit - */ - public static void trackState(final String state, final Map contextData) { - if (core == null) { - Log.debug(TAG, "Failed to track state %s (%s)", state, NULL_CONTEXT_MESSAGE); - return; - } - - core.trackState(state, contextData); - } - - /** - * This method dispatches an event to notify the SDK of a new {@code advertisingIdentifier} - * - * @param advertisingIdentifier {@code String} representing Android advertising identifier - */ - public static void setAdvertisingIdentifier(final String advertisingIdentifier) { - if (core == null) { - Log.debug(TAG, "Failed to set advertising identifier (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.setAdvertisingIdentifier(advertisingIdentifier); - } - - /** - * This method dispatches an event to notify the SDK of a new {@code pushIdentifier} - * - * @param pushIdentifier {@code String} representing the new push identifier - */ - public static void setPushIdentifier(final String pushIdentifier) { - if (core == null) { - Log.debug(TAG, "Failed to set push identifier (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.setPushIdentifier(pushIdentifier); - } - - - /** - * Start/resume lifecycle session. - *

    - * Start a new lifecycle session or resume a previously paused lifecycle session. If a previously paused session - * timed out, then a new session is created. If a current session is running, then calling this method does nothing. - *

    - * Additional context data may be passed when calling this method. Lifecycle data and any additional data are - * sent as context data parameters to Analytics, to Target as mbox parameters, and for Audience Manager they are - * sent as customer variables. Any additional data is also used by the Rules Engine when processing rules. - *

    - * This method should be called from the Activity onResume method. - * - * @param additionalContextData optional additional context for this session. - */ - public static void lifecycleStart(final Map additionalContextData) { - if (core == null) { - Log.debug(TAG, "Failed to start lifecycle session (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.lifecycleStart(additionalContextData); - } - - /** - * Pause/stop lifecycle session. - *

    - * Pauses the current lifecycle session. Calling pause on an already paused session updates the paused timestamp, - * having the effect of resetting the session timeout timer. If no lifecycle session is running, then calling - * this method does nothing. - *

    - * A paused session is resumed if {@link #lifecycleStart(Map)} is called before the session timeout. After - * the session timeout, a paused session is closed and calling {@link #lifecycleStart(Map)} will create - * a new session. The session timeout is defined by the {@code lifecycle.sessionTimeout} configuration parameter. - * If not defined, the default session timeout is five minutes. - *

    - * This method should be called from the Activity onPause method. - */ - public static void lifecyclePause() { - if (core == null) { - Log.debug(TAG, "Failed to pause lifecycle session (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.lifecyclePause(); - } - - /** - * Collect PII data. Although using this call enables collection of PII data, the SDK does not - * automatically send the data to any Adobe endpoint. - * - * @param data the map containing the PII data to be collected - */ - public static void collectPii(final Map data) { - if (core == null) { - Log.debug(TAG, "Failed to collect PII (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.collectPii(data); - } - - /** - * Sets the resource Id for small icon. - * - * @param resourceID the resource Id of the icon - */ - public static void setSmallIconResourceID(final int resourceID) { - App.setSmallIconResourceID(resourceID); - } - - /** - * Sets the resource Id for small icon. - * - * @param resourceID the resource Id of the icon - */ - public static void setLargeIconResourceID(final int resourceID) { - App.setLargeIconResourceID(resourceID); - } - - /** - * Collects message data from various points in the application. - * - * This method can be invoked to support the following use cases: - *

      - *
    1. Tracking Push Message receive and click.
    2. - *
    3. Tracking Local Notification receive and click.
    4. - *
    - *

    - * The message tracking information can be supplied in the {@code messageInfo} Map. For scenarios where the application - * is launched as a result of notification click, {@link #collectLaunchInfo(Activity)} will be invoked with the target - * Activity and message data will be extracted from the Intent extras. - * - * @param messageInfo {@code Map} containing message tracking information - */ - public static void collectMessageInfo(final Map messageInfo) { - if (core == null) { - Log.debug(TAG, "Failed to collect Message Info (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.collectData(messageInfo); - } - - /** - * Collects data from the Activity / context to be used later by the SDK. - * - * This method marshals the {@code activity} instance and extracts the intent data / extras. It should be called to support - * the following use cases: - *

      - *
    1. Tracking Deep Link click-through - *
        - *
      • Update AndroidManifest.xml to support intent-filter in the activity with the intended action and type of data.
      • - *
      • Handle the intent in the activity.
      • - *
      • Pass activity with deepLink intent to SDK in {@code collectLaunchInfo}.
      • - *
      - *
    2. - *
    3. Tracking Push Message click-through - *
        - *
      • Push message data must be added to the Intent used to open target activity on click-through.
      • - *
      • The data can be added in intent extras which is then collected by SDK when target activity is passed in {@code collectedLaunchInfo}.
      • - *
      - *
    4. - *
    5. Tracking Local Notification click-through - *
        - *
      • Add manifest-declared broadcast receiver {@code } in your app.
      • - *
      • Pass notifications activity reference in {@code collectLaunchInfo}.
      • - *
      - *
    6. - *
    - *

    - * Invoke this method from {@link Activity#onResume} callback in your activity. - * - * @param activity current {@link Activity} reference. - */ - static void collectLaunchInfo(Activity activity) { - if (core == null) { - Log.debug(TAG, "Failed to collect Activity data (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - DataMarshaller marshaller = new DataMarshaller(); - marshaller.marshal(activity); - core.collectData(marshaller.getData()); - } - - /** - * Sets the SDK's current wrapper type. This API should only be used if - * being developed on platforms such as React Native. - *

    - * NOTE: {@link #setApplication(Application)} must be called before calling this method. - * - * @param wrapperType the type of wrapper being used. - */ - public static void setWrapperType(WrapperType wrapperType) { - if (core == null) { - Log.warning(TAG, "Cannot set wrapper type, core is null. Make sure setApplication API is called."); - return; - } - - core.setWrapperType(wrapperType); - } - - /** - * Registers an event listener for the provided event type and source. - * - * @param eventType required parameter, the event type as a valid string (not null or empty) - * @param eventSource required parameter, the event source as a valid string (not null or empty) - * @param callback required parameter, {@link AdobeCallbackWithError#call(Object)} will be called when the event is heard - */ - public static void registerEventListener(final String eventType, final String eventSource, - final AdobeCallbackWithError callback) { - if (core == null) { - Log.debug(TAG, "Failed to register the event listener (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.registerEventListener(eventType, eventSource, callback); - } - - /** - * Clears all identifiers from Edge extensions and generates a new Experience Cloud ID (ECID). - */ - public static void resetIdentities() { - if (core == null) { - Log.debug(TAG, "Failed to reset identities (%s)", NULL_CONTEXT_MESSAGE); - return; - } - - core.resetIdentities(); - } + private final static String VERSION = "2.0.0"; + private final static String LOG_TAG = "MobileCore"; + private static final String NULL_CONTEXT_MESSAGE = "Context must be set before calling SDK methods"; + private static final Object mutex = new Object(); + private static boolean startActionCalled; + private static EventHub eventHub; + + private MobileCore() { + } + + /** + * Returns the version for the {@code MobileCore} extension + * + * @return The version string + */ + public static String extensionVersion() { + synchronized (mutex) { + if (eventHub == null) { + Log.warning(LOG_TAG, "Returning version without wrapper type info. Make sure setApplication API is called."); + return VERSION; + } + + return eventHub.getSdkVersion(); + } + } + + /** + * Set the current {@link Application}, which enables the SDK get the app {@code Context}, + * register a {@link Application.ActivityLifecycleCallbacks} + * to monitor the lifecycle of the app and get the {@link android.app.Activity} on top of the screen. + *

    + * NOTE: This method should be called right after the app starts, so it gives the SDK all the + * contexts it needed. + * + * @param app the current {@code Application} + */ + public static void setApplication(final Application app) { + if (eventHub != null) { + Log.debug(LOG_TAG, "The Eventhub has already initialized"); + return; + } + + // AMSDK-8502 + // workaround to prevent a crash happening on Android 8.0/8.1 related to TimeZoneNamesImpl + // https://issuetracker.google.com/issues/110848122 + try { + new Date().toString(); + } catch (AssertionError e) { + // Workaround for a bug in Android that can cause crashes on Android 8.0 and 8.1 + } catch (Exception e) { + // Workaround for a bug in Android that can cause crashes on Android 8.0 and 8.1 + } + + App.setApplication(app); + if (App.getPlatformServices() == null) { + App.setPlatformServices(new AndroidPlatformServices()); + } + com.adobe.marketing.mobile.internal.context.App.getInstance().initializeApp( + new com.adobe.marketing.mobile.internal.context.App.AppContextProvider() { + @Override + public Context getAppContext() { + return App.getAppContext(); + } + + @Override + public Activity getCurrentActivity() { + return App.getCurrentActivity(); + } + } + ); + + V4ToV5Migration migrationTool = new V4ToV5Migration(); + migrationTool.migrate(); + + Log.setLoggingService(App.getPlatformServices().getLoggingService()); + eventHub = new EventHub("AMSEventHub", App.getPlatformServices(), VERSION); + + try { + eventHub.registerModule(ConfigurationExtension.class, new ConfigurationModuleDetails(VERSION)); + } catch (InvalidModuleException e) { + Log.error(LOG_TAG, "Failed to register Configuration extension (%s)", e); + } + Log.trace(LOG_TAG, "Eventhub initialization was successful"); + } + + /** + * Get the global {@link Application} object of the current process. + *

    + * NOTE: {@link #setApplication(Application)} must be called before calling this method. + * + * @return the current {@code Application}, or null if no {@code Application} was set or + * the {@code Application} process was destroyed. + */ + public static Application getApplication() { + return App.getApplication(); + } + + /** + * Set the {@link LoggingMode} level for the Mobile SDK. + * + * @param mode the logging mode + */ + public static void setLogLevel(LoggingMode mode) { + Log.setLogLevel(mode); + } + + /** + * Get the {@link LoggingMode} level for the Mobile SDK + * + * @return the set {@code LoggingMode} + */ + public static LoggingMode getLogLevel() { + return Log.getLogLevel(); + } + + /** + * Sends a log message of the given {@code LoggingMode}. If the specified {@code mode} is + * more verbose than the current {@link LoggingMode} set from {@link #setLogLevel(LoggingMode)} + * then the message is not printed. + * + * @param mode the {@link LoggingMode} used to print the message + * @param tag used to identify the source of the log message + * @param message the message to log + */ + public static void log(final LoggingMode mode, final String tag, final String message) { + if (mode == null) { + return; + } + + switch (mode) { + case ERROR: + Log.error(tag, message); + break; + + case WARNING: + Log.warning(tag, message); + break; + + case DEBUG: + Log.debug(tag, message); + break; + + case VERBOSE: + Log.trace(tag, message); + break; + } + } + + /** + * Start the Core processing. This should be called after the initial set of extensions have been registered. + *

    + * This call will wait for any outstanding registrations to complete and then start event processing. + * You can use the callback to kickoff additional operations immediately after any operations kicked off during registration. + * You shouldn't call this method more than once in your app, if so, sdk will ignore it and print error log. + * + * @param completionCallback An optional {@link AdobeCallback} invoked after registrations are completed + */ + public static void start(final AdobeCallback completionCallback) { + synchronized (mutex) { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to start SDK (%s)", NULL_CONTEXT_MESSAGE); + + if (completionCallback != null & completionCallback instanceof AdobeCallbackWithError) { + ((AdobeCallbackWithError) completionCallback).fail(AdobeError.EXTENSION_NOT_INITIALIZED); + } + + return; + } + + // initialize the AndroidEventHistory service and set it within the EventHistoryProvider instance + try { + if (EventHistoryProvider.getEventHistory() == null) { + EventHistoryProvider.setEventHistory(new AndroidEventHistory()); + Log.trace(LOG_TAG, "Android EventHistory created and set in the EventHistoryProvider"); + } + } catch (final EventHistoryDatabaseCreationException exception) { + Log.warning(LOG_TAG, "Failed to create the android event history service: %s", + exception.getMessage()); + } + + if (startActionCalled) { + Log.debug(LOG_TAG, "Can't start Core more than once."); + return; + } + + startActionCalled = true; + eventHub.finishModulesRegistration(completionCallback); + } + } + + // ======================================================== + // Configuration methods + // ======================================================== + + + public static void configureWithAppID(final String appId) { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to set Adobe App ID (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + EventData eventData = new EventData(); + eventData.putString(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID, appId); + final Event event = new Event.Builder("Configure with AppID", EventType.CONFIGURATION, + EventSource.REQUEST_CONTENT).setData(eventData).build(); + eventHub.dispatch(event); + } + + + /** + * Registers an extension class which has {@code Extension} as parent. + *

    + * In order to ensure that your extension receives all the internal events, this method needs + * to be called after {@link MobileCore#setApplication(Application)} is called, but before + * any other method in this class. + * + * @param extensionClass a class whose parent is {@link Extension} + * @param errorCallback an optional {@link ExtensionErrorCallback} for the eventuality of an error, + * called when this method returns false + * @return {@code boolean} indicating if the provided parameters are valid and no error occurs + */ + public static boolean registerExtension(final Class extensionClass, + final ExtensionErrorCallback errorCallback) { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to register the extension. (%s)", NULL_CONTEXT_MESSAGE); + + if (errorCallback != null) { + errorCallback.error(ExtensionError.UNEXPECTED_ERROR); + } + + return false; + } + + if (extensionClass == null) { + if (errorCallback != null) { + errorCallback.error(ExtensionError.UNEXPECTED_ERROR); + } + + return false; + } + + eventHub.registerExtensionWithCallback(extensionClass, errorCallback); + return true; + } + + /** + * Called by the extension public API to dispatch an event for other extensions or the internal SDK to consume. + * + * @param event required parameter, {@link Event} instance to be dispatched, should not be null + * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred during dispatching + * @return {@code boolean} indicating if the the event dispatching operation succeeded + */ + public static boolean dispatchEvent(final Event event, final ExtensionErrorCallback errorCallback) { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to dispatch event. (%s)", NULL_CONTEXT_MESSAGE); + + if (errorCallback != null) { + errorCallback.error(ExtensionError.UNEXPECTED_ERROR); + } + + return false; + } + + if (event == null) { + Log.debug(LOG_TAG, "%s (Core.dispatchEvent) - The event was not dispatched", Log.UNEXPECTED_NULL_VALUE); + + if (errorCallback != null) { + errorCallback.error(ExtensionError.EVENT_NULL); + } + + return false; + } + + eventHub.dispatch(event); + return true; + } + + /** + * This method will be used when the provided {@code Event} is used as a trigger and a response event + * is expected in return. The returned event needs to be sent using + * {@link #dispatchResponseEvent(Event, Event, ExtensionErrorCallback)}. + *

    + * Passes an {@link ExtensionError} to {@code errorCallback} if {@code event} or + * {@code responseCallback} are null. + * + * @param event required parameter, {@link Event} instance to be dispatched, used as a trigger + * @param responseCallback required parameters, {@link AdobeCallback} to be called with the response event received + * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred during dispatching + * @return {@code boolean} indicating if the the event dispatching operation succeeded + * @see MobileCore#dispatchResponseEvent(Event, Event, ExtensionErrorCallback) + */ + public static boolean dispatchEventWithResponseCallback(final Event event, + final AdobeCallback responseCallback, + final ExtensionErrorCallback errorCallback) { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to dispatch event with a response callback. (%s)", NULL_CONTEXT_MESSAGE); + + if (errorCallback != null) { + errorCallback.error(ExtensionError.UNEXPECTED_ERROR); + } + + return false; + } + + // the core will validate and copy this event + if (responseCallback == null) { + Log.debug(LOG_TAG, + "%s (Core.dispatchEventWithResponseCallback) - The event was not dispatched", Log.UNEXPECTED_NULL_VALUE); + + if (errorCallback != null) { + errorCallback.error(ExtensionError.CALLBACK_NULL); + } + + return false; + } + + if (event == null) { + Log.debug(LOG_TAG, "%s (Core.dispatchEventWithResponseCallback) - The event was not dispatched", + Log.UNEXPECTED_NULL_VALUE); + + if (errorCallback != null) { + errorCallback.error(ExtensionError.EVENT_NULL); + } + + return false; + } + + eventHub.registerOneTimeListener(event.getResponsePairID(), new Module.OneTimeListenerBlock() { + @Override + public void call(final Event e) { + responseCallback.call(e); + } + }); + eventHub.dispatch(event); + return true; + } + + /** + * This method will be used when the provided {@code Event} is used as a trigger and a response event + * is expected in return. The returned event needs to be sent using + * {@link #dispatchResponseEvent(Event, Event, ExtensionErrorCallback)}. + *

    + * Passes an {@link AdobeError} to {@link AdobeCallbackWithError#fail(AdobeError)} if {@code event} is null. + * Passes an {@link AdobeError} to {@link AdobeCallbackWithError#fail(AdobeError)} if {@code event} processing timeout occurs. + * + * @param event required parameter, {@link Event} instance to be dispatched, used as a trigger + * @param responseCallback required parameters, {@link AdobeCallback} to be called with the response event received + * @see MobileCore#dispatchResponseEvent(Event, Event, ExtensionErrorCallback) + */ + public static void dispatchEventWithResponseCallback(final Event event, + final AdobeCallbackWithError responseCallback) { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to dispatch event with a response callback. (%s)", NULL_CONTEXT_MESSAGE); + + if (responseCallback != null) { + responseCallback.fail(AdobeError.UNEXPECTED_ERROR); + } + + return; + } + + if (event == null) { + Log.debug(LOG_TAG, "Failed to dispatch event with a response callback: the given event is null "); + + if (responseCallback != null) { + responseCallback.fail(AdobeError.UNEXPECTED_ERROR); + } + + return; + } + + if (responseCallback == null) { + Log.warning(LOG_TAG, + "Failed to dispatch event with a response callback: the given callback (AdobeCallbackWithError) object is null "); + return; + } + + // the core will validate and copy this event + if (event == null || responseCallback == null) { + Log.debug(LOG_TAG, + "(Core.dispatchEventWithResponseCallback) - The event was not dispatched, the given Event or AdobeCallbackWithError is null"); + return; + } + + eventHub.registerOneTimeListener(event.getResponsePairID(), new Module.OneTimeListenerBlock() { + @Override + public void call(final Event e) { + responseCallback.call(e); + } + }, responseCallback); + eventHub.dispatch(event); + } + + /** + * Dispatches a response event for a paired event that was sent to {@code dispatchEventWithResponseCallback} + * and received by an extension listener {@code hear} method. + *

    + * Passes an {@link ExtensionError} to {@code errorCallback} if {@code responseEvent} or {@code requestEvent} are null. + *

    + * Note: The {@code responseEvent} will not be sent to any listeners, it is sent only to the response callback registered + * using {@link MobileCore#dispatchEventWithResponseCallback(Event, AdobeCallback, ExtensionErrorCallback)}. + * + * @param responseEvent required parameter, {@link Event} instance to be dispatched as a response for the + * event sent using {@link MobileCore#dispatchEventWithResponseCallback(Event, AdobeCallback, ExtensionErrorCallback)} + * @param requestEvent required parameter, the event sent using + * {@link MobileCore#dispatchEventWithResponseCallback(Event, AdobeCallback, ExtensionErrorCallback)} + * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred during dispatching + * @return {@code boolean} indicating if the the event dispatching operation succeeded + * @see MobileCore#dispatchEventWithResponseCallback(Event, AdobeCallback, ExtensionErrorCallback) + */ + public static boolean dispatchResponseEvent(final Event responseEvent, final Event requestEvent, + final ExtensionErrorCallback errorCallback) { + + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to dispatch the response event. (%s)", NULL_CONTEXT_MESSAGE); + + if (errorCallback != null) { + errorCallback.error(ExtensionError.UNEXPECTED_ERROR); + } + + return false; + } + + // the core will validate and copy this events + if (requestEvent == null) { + Log.debug(LOG_TAG, + "%s (Core.dispatchResponseEvent) - The response event was not dispatched", Log.UNEXPECTED_NULL_VALUE); + + if (errorCallback != null) { + errorCallback.error(ExtensionError.EVENT_NULL); + } + + return false; + } + + if (responseEvent == null) { + Log.warning(LOG_TAG, "%s (Core.dispatchResponseEvent) - The response event was not dispatched", + Log.UNEXPECTED_NULL_VALUE); + + if (errorCallback != null) { + errorCallback.error(ExtensionError.EVENT_NULL); + } + + return false; + } + + responseEvent.setPairId(requestEvent.getResponsePairID()); + eventHub.dispatch(responseEvent); + return true; + } + + /** + * Load configuration from the file in the assets folder. SDK automatically reads config from `ADBMobileConfig.json` file if + * it exists in the assets folder. Use this API only if the config needs to be read from a different file. + *

    + * On application relaunch, the configuration from the file at {@code filepath} is not preserved and this method must be called + * again if desired. + *

    + * On failure to read the file or parse the JSON contents, the existing configuration remains unchanged. + *

    + * Calls to this API will replace any existing SDK configuration except those set using + * {@link #updateConfiguration(Map)} or {@link #setPrivacyStatus(MobilePrivacyStatus)}. + * Configuration updates made using {@link #updateConfiguration(Map)} and {@link #setPrivacyStatus(MobilePrivacyStatus)} + * are always applied on top of configuration changes made using this API. + * absolute + * + * @param fileName the name of the configure file in the assets folder. A value of {@code null} has no effect. + */ + public static void configureWithFileInAssets(final String fileName) { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to load configuration with asset file (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + if (StringUtils.isNullOrEmpty(fileName)) { + Log.warning("Configuration", "Unable to configure with null or empty file name"); + return; + } + + EventData eventData = new EventData(); + eventData.putString(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_ASSET_FILE, fileName); + final Event event = new Event.Builder("Configure with FilePath", EventType.CONFIGURATION, + EventSource.REQUEST_CONTENT).setData(eventData).build(); + eventHub.dispatch(event); + } + + /** + * Load configuration from local file. + *

    + * Configure the SDK by reading a local file containing the JSON configuration. On application relaunch, + * the configuration from the file at {@code filepath} is not preserved and this method must be called again if desired. + *

    + * On failure to read the file or parse the JSON contents, the existing configuration remains unchanged. + *

    + * Calls to this API will replace any existing SDK configuration except those set using + * {@link #updateConfiguration(Map)} or {@link #setPrivacyStatus(MobilePrivacyStatus)}. + * Configuration updates made using {@link #updateConfiguration(Map)} and {@link #setPrivacyStatus(MobilePrivacyStatus)} + * are always applied on top of configuration changes made using this API. + * + * @param filepath absolute path to a local configuration file. A value of {@code null} has no effect. + */ + public static void configureWithFileInPath(final String filepath) { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to load configuration with file path (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + if (StringUtils.isNullOrEmpty(filepath)) { + Log.warning("Configuration", "Unable to configure with null or empty remoteURL"); + return; + } + + EventData eventData = new EventData(); + eventData.putString(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_FILE_PATH, filepath); + final Event event = new Event.Builder("Configure with FilePath", EventType.CONFIGURATION, + EventSource.REQUEST_CONTENT).setData(eventData).build(); + eventHub.dispatch(event); + } + + /** + * Update specific configuration parameters. + *

    + * Update the current SDK configuration with specific key/value pairs. Keys not found in the current + * configuration are added. Configuration updates are preserved and applied over existing or new + * configurations set by calling {@link #configureWithAppID(String)} or {@link #configureWithFileInPath(String)}, + * even across application restarts. + *

    + * Using {@code null} values is allowed and effectively removes the configuration parameter from the current configuration. + * + * @param configMap configuration key/value pairs to be updated or added. A value of {@code null} has no effect. + */ + public static void updateConfiguration(final Map configMap) { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to update configuration (%s)", NULL_CONTEXT_MESSAGE); + return; + } + // Create a EventData Map + HashMap eventDataMap = new HashMap(); + Variant configMapVariant = Variant.fromTypedMap(configMap, PermissiveVariantSerializer.DEFAULT_INSTANCE); + eventDataMap.put(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_UPDATE_CONFIG, + configMapVariant); + EventData eventData = new EventData(eventDataMap); + final Event event = new Event.Builder("Configuration Update", EventType.CONFIGURATION, + EventSource.REQUEST_CONTENT).setData(eventData).build(); + eventHub.dispatch(event); + } + + /** + * Clear the changes made by {@link #updateConfiguration(Map)} and {@link #setPrivacyStatus(MobilePrivacyStatus)} + * to the initial configuration provided either by {@link #configureWithAppID(String)} + * or {@link #configureWithFileInPath(String)} or {@link #configureWithFileInAssets(String)} + */ + public static void clearUpdatedConfiguration() { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to clear updated configuration (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + EventData eventData = new EventData(); + eventData.putBoolean(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_CLEAR_UPDATED_CONFIG, + true); + final Event event = new Event.Builder("Clear updated configuration", EventType.CONFIGURATION, + EventSource.REQUEST_CONTENT).setData(eventData).build(); + eventHub.dispatch(event); + } + + /** + * Set the Adobe Mobile Privacy status. + *

    + * Sets the {@link MobilePrivacyStatus} for this SDK. The set privacy status is preserved and applied over any new + * configuration changes from calls to {@link #configureWithAppID(String)} or {@link #configureWithFileInPath(String)}, + * even across application restarts. + * + * @param privacyStatus {@link MobilePrivacyStatus} to be set to the SDK + * @see MobilePrivacyStatus + */ + public static void setPrivacyStatus(final MobilePrivacyStatus privacyStatus) { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to set privacy status (%s)", NULL_CONTEXT_MESSAGE); + return; + } + final Map privacyStatusUpdateConfig = new HashMap(); + final String privacyStatusString = (privacyStatus == null ? null : privacyStatus.getValue()); + privacyStatusUpdateConfig.put(CoreConstants.EventDataKeys.Configuration.GLOBAL_CONFIG_PRIVACY, privacyStatusString); + updateConfiguration(privacyStatusUpdateConfig); + } + + /** + * Get the current Adobe Mobile Privacy Status. + *

    + * Gets the currently configured {@link MobilePrivacyStatus} and passes it as a parameter to the given + * {@link AdobeCallback#call(Object)} function. + * + * @param callback {@link AdobeCallback} instance which is invoked with the configured privacy status as a parameter + * @see AdobeCallback + * @see MobilePrivacyStatus + */ + public static void getPrivacyStatus(final AdobeCallback callback) { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to retrieve the privacy status (%s)", NULL_CONTEXT_MESSAGE); + + if (callback != null & callback instanceof AdobeCallbackWithError) { + ((AdobeCallbackWithError) callback).fail(AdobeError.EXTENSION_NOT_INITIALIZED); + } + + return; + } + + if (callback == null) { + return; + } + + EventData eventData = new EventData(); + eventData.putBoolean(CoreConstants.EventDataKeys.Configuration.CONFIGURATION_REQUEST_CONTENT_RETRIEVE_CONFIG, true); + Event event = new Event.Builder("PrivacyStatusRequest", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) + .setData(eventData).build(); + + + final AdobeCallbackWithError adobeCallbackWithError = callback instanceof AdobeCallbackWithError ? + (AdobeCallbackWithError) callback : null; + + eventHub.registerOneTimeListener(event.getResponsePairID(), new Module.OneTimeListenerBlock() { + @Override + public void call(final Event e) { + EventData eventData = e.getData(); + callback.call(MobilePrivacyStatus.fromString(eventData.getString(ConfigurationConstants.EventDataKeys + .Configuration.GLOBAL_CONFIG_PRIVACY))); + } + }, adobeCallbackWithError); + + eventHub.dispatch(event); + } + + /** + * Retrieve all identities stored by/known to the SDK in a JSON {@code String} format. + * + * @param callback {@link AdobeCallback} instance which is invoked with all the known identifier in JSON {@link String} format + * @see AdobeCallback + */ + public static void getSdkIdentities(final AdobeCallback callback) { + if (callback == null) { + Log.debug(LOG_TAG, "%s (Callback), provide a callback to retrieve the all SDK identities", Log.UNEXPECTED_NULL_VALUE); + return; + } + + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to retrieve the all SDK identities (%s)", NULL_CONTEXT_MESSAGE); + + if (callback != null & callback instanceof AdobeCallbackWithError) { + ((AdobeCallbackWithError) callback).fail(AdobeError.EXTENSION_NOT_INITIALIZED); + } + + return; + } + + if (callback == null) { + return; + } + + final AdobeCallbackWithError adobeCallbackWithError = callback instanceof AdobeCallbackWithError ? + (AdobeCallbackWithError) callback : null; + + Event event = new Event.Builder("getSdkIdentities", EventType.CONFIGURATION, EventSource.REQUEST_IDENTITY).build(); + eventHub.registerOneTimeListener(event.getResponsePairID(), new Module.OneTimeListenerBlock() { + @Override + public void call(final Event e) { + EventData eventData = e.getData(); + callback.call(eventData.optString( + ConfigurationConstants.EventDataKeys.Configuration.CONFIGURATION_RESPONSE_IDENTITY_ALL_IDENTIFIERS, "{}")); + } + }, adobeCallbackWithError); + + eventHub.dispatch(event); + } + + // ======================================================== + // Generic methods + // ======================================================== + + + /** + * This method dispatches an Analytics track {@code action} event + *

    + * Actions represent events that occur in your application that you want to measure; the corresponding metrics will + * be incremented each time the event occurs. For example, you may want to track when an user click on the login + * button or a certain article was viewed. + *

    + * + * @param action {@code String} containing the name of the action to track + * @param contextData {@code Map} containing context data to attach on this hit + */ + public static void trackAction(final String action, final Map contextData) { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to track action %s (%s)", action, NULL_CONTEXT_MESSAGE); + return; + } + + EventData trackData = new EventData(); + trackData.putString(CoreConstants.EventDataKeys.Analytics.TRACK_ACTION, action); + trackData.putStringMap(CoreConstants.EventDataKeys.Analytics.CONTEXT_DATA, + contextData == null ? new HashMap() : contextData); + Event event = new Event.Builder("Analytics Track", EventType.GENERIC_TRACK, EventSource.REQUEST_CONTENT) + .setData(trackData).build(); + + eventHub.dispatch(event); + } + + /** + * This method dispatches an Analytics track {@code state} event + *

    + * States represent different screens or views of your application. When the user navigates between application pages, + * a new track call should be sent with current state name. Tracking state name is typically called from an + * Activity in the onResume method. + *

    + * + * @param state {@code String} containing the name of the state to track + * @param contextData contextData {@code Map} containing context data to attach on this hit + */ + public static void trackState(final String state, final Map contextData) { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to track state %s (%s)", state, NULL_CONTEXT_MESSAGE); + return; + } + EventData trackData = new EventData(); + trackData.putString(CoreConstants.EventDataKeys.Analytics.TRACK_STATE, state); + trackData.putStringMap(CoreConstants.EventDataKeys.Analytics.CONTEXT_DATA, + contextData == null ? new HashMap() : contextData); + Event event = new Event.Builder("Analytics Track", EventType.GENERIC_TRACK, EventSource.REQUEST_CONTENT) + .setData(trackData).build(); + + eventHub.dispatch(event); + } + + /** + * This method dispatches an event to notify the SDK of a new {@code advertisingIdentifier} + * + * @param advertisingIdentifier {@code String} representing Android advertising identifier + */ + public static void setAdvertisingIdentifier(final String advertisingIdentifier) { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to set advertising identifier (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + EventData eventData = new EventData(); + eventData.putString(CoreConstants.EventDataKeys.Identity.ADVERTISING_IDENTIFIER, advertisingIdentifier); + + Event event = new Event.Builder("SetAdvertisingIdentifier", EventType.GENERIC_IDENTITY, EventSource.REQUEST_CONTENT) + .setData(eventData) + .build(); + + eventHub.dispatch(event); + } + + /** + * This method dispatches an event to notify the SDK of a new {@code pushIdentifier} + * + * @param pushIdentifier {@code String} representing the new push identifier + */ + public static void setPushIdentifier(final String pushIdentifier) { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to set push identifier (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + EventData eventData = new EventData(); + eventData.putString(CoreConstants.EventDataKeys.Identity.PUSH_IDENTIFIER, pushIdentifier); + + Event event = new Event.Builder("SetPushIdentifier", EventType.GENERIC_IDENTITY, EventSource.REQUEST_CONTENT) + .setData(eventData) + .build(); + + eventHub.dispatch(event); + } + + + /** + * Start/resume lifecycle session. + *

    + * Start a new lifecycle session or resume a previously paused lifecycle session. If a previously paused session + * timed out, then a new session is created. If a current session is running, then calling this method does nothing. + *

    + * Additional context data may be passed when calling this method. Lifecycle data and any additional data are + * sent as context data parameters to Analytics, to Target as mbox parameters, and for Audience Manager they are + * sent as customer variables. Any additional data is also used by the Rules Engine when processing rules. + *

    + * This method should be called from the Activity onResume method. + * + * @param additionalContextData optional additional context for this session. + */ + public static void lifecycleStart(final Map additionalContextData) { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to start lifecycle session (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + EventData eventData = new EventData(); + eventData.putString(CoreConstants.EventDataKeys.Lifecycle.LIFECYCLE_ACTION_KEY, + CoreConstants.EventDataKeys.Lifecycle.LIFECYCLE_START); + + eventData.putStringMap(CoreConstants.EventDataKeys.Lifecycle.ADDITIONAL_CONTEXT_DATA, additionalContextData); + Event event = new Event.Builder("LifecycleResume", EventType.GENERIC_LIFECYLE, EventSource.REQUEST_CONTENT) + .setData(eventData) + .build(); + + eventHub.dispatch(event); + } + + /** + * Pause/stop lifecycle session. + *

    + * Pauses the current lifecycle session. Calling pause on an already paused session updates the paused timestamp, + * having the effect of resetting the session timeout timer. If no lifecycle session is running, then calling + * this method does nothing. + *

    + * A paused session is resumed if {@link #lifecycleStart(Map)} is called before the session timeout. After + * the session timeout, a paused session is closed and calling {@link #lifecycleStart(Map)} will create + * a new session. The session timeout is defined by the {@code lifecycle.sessionTimeout} configuration parameter. + * If not defined, the default session timeout is five minutes. + *

    + * This method should be called from the Activity onPause method. + */ + public static void lifecyclePause() { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to pause lifecycle session (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + EventData eventData = new EventData(); + eventData.putString(CoreConstants.EventDataKeys.Lifecycle.LIFECYCLE_ACTION_KEY, + CoreConstants.EventDataKeys.Lifecycle.LIFECYCLE_PAUSE); + + Event event = new Event.Builder("LifecyclePause", EventType.GENERIC_LIFECYLE, EventSource.REQUEST_CONTENT) + .setData(eventData) + .build(); + + eventHub.dispatch(event); + } + + /** + * Collect PII data. Although using this call enables collection of PII data, the SDK does not + * automatically send the data to any Adobe endpoint. + * + * @param data the map containing the PII data to be collected + */ + public static void collectPii(final Map data) { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to collect PII (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + if (data == null || data.isEmpty()) { + Log.debug(LOG_TAG, "Could not trigger PII, the data is null or empty."); + return; + } + + final EventData eventData = new EventData() + .putStringMap(CoreConstants.EventDataKeys.Signal.SIGNAL_CONTEXT_DATA, data); + eventHub.dispatch(new Event.Builder("CollectPII", EventType.GENERIC_PII, + EventSource.REQUEST_CONTENT).setData(eventData).build()); + Log.trace(LOG_TAG, "Collect PII event was sent"); + } + + /** + * Sets the resource Id for small icon. + * + * @param resourceID the resource Id of the icon + */ + public static void setSmallIconResourceID(final int resourceID) { + App.setSmallIconResourceID(resourceID); + } + + /** + * Sets the resource Id for small icon. + * + * @param resourceID the resource Id of the icon + */ + public static void setLargeIconResourceID(final int resourceID) { + App.setLargeIconResourceID(resourceID); + } + + /** + * Collects message data from various points in the application. + *

    + * This method can be invoked to support the following use cases: + *

      + *
    1. Tracking Push Message receive and click.
    2. + *
    3. Tracking Local Notification receive and click.
    4. + *
    + *

    + * The message tracking information can be supplied in the {@code messageInfo} Map. For scenarios where the application + * is launched as a result of notification click, {@link #collectLaunchInfo(Activity)} will be invoked with the target + * Activity and message data will be extracted from the Intent extras. + * + * @param messageInfo {@code Map} containing message tracking information + */ + public static void collectMessageInfo(final Map messageInfo) { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to collect Message Info (%s)", NULL_CONTEXT_MESSAGE); + return; + } + if (messageInfo == null || messageInfo.isEmpty()) { + Log.debug(LOG_TAG, "collectData: Could not dispatch generic data event, data is null or empty."); + return; + } + + Event event = new Event.Builder("CollectData", EventType.GENERIC_DATA, EventSource.OS) + .setEventData(messageInfo) + .build(); + eventHub.dispatch(event); + Log.trace(LOG_TAG, "collectData: generic data OS event dispatched."); + } + + /** + * Collects data from the Activity / context to be used later by the SDK. + *

    + * This method marshals the {@code activity} instance and extracts the intent data / extras. It should be called to support + * the following use cases: + *

      + *
    1. Tracking Deep Link click-through + *
        + *
      • Update AndroidManifest.xml to support intent-filter in the activity with the intended action and type of data.
      • + *
      • Handle the intent in the activity.
      • + *
      • Pass activity with deepLink intent to SDK in {@code collectLaunchInfo}.
      • + *
      + *
    2. + *
    3. Tracking Push Message click-through + *
        + *
      • Push message data must be added to the Intent used to open target activity on click-through.
      • + *
      • The data can be added in intent extras which is then collected by SDK when target activity is passed in {@code collectedLaunchInfo}.
      • + *
      + *
    4. + *
    5. Tracking Local Notification click-through + *
        + *
      • Add manifest-declared broadcast receiver {@code } in your app.
      • + *
      • Pass notifications activity reference in {@code collectLaunchInfo}.
      • + *
      + *
    6. + *
    + *

    + * Invoke this method from Activity.onResume() callback in your activity. + * + * @param activity current {@link Activity} reference. + */ + static void collectLaunchInfo(final Activity activity) { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to collect Activity data (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + DataMarshaller marshaller = new DataMarshaller(); + marshaller.marshal(activity); + final Map marshalledData = marshaller.getData(); + if (marshalledData == null || marshalledData.isEmpty()) { + Log.debug(LOG_TAG, "collectData: Could not dispatch generic data event, data is null or empty."); + return; + } + + Event event = new Event.Builder("CollectData", EventType.GENERIC_DATA, EventSource.OS) + .setEventData(marshalledData) + .build(); + eventHub.dispatch(event); + Log.trace(LOG_TAG, "collectData: generic data OS event dispatched."); + } + + /** + * Sets the SDK's current wrapper type. This API should only be used if + * being developed on platforms such as React Native. + *

    + * NOTE: {@link #setApplication(Application)} must be called before calling this method. + * + * @param wrapperType the type of wrapper being used. + */ + public static void setWrapperType(final WrapperType wrapperType) { + if (eventHub == null) { + Log.warning(LOG_TAG, "Cannot set wrapper type, core is null. Make sure setApplication API is called."); + return; + } + + eventHub.setWrapperType(wrapperType); + } + + /** + * Registers an event listener for the provided event type and source. + * + * @param eventType required parameter, the event type as a valid string (not null or empty) + * @param eventSource required parameter, the event source as a valid string (not null or empty) + * @param callback required parameter, {@link AdobeCallbackWithError#call(Object)} will be called when the event is heard + */ + public static void registerEventListener(final String eventType, final String eventSource, + final AdobeCallbackWithError callback) { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to register the event listener (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + eventHub.registerEventListener(EventType.get(eventType), EventSource.get(eventSource), callback); + } + + /** + * Clears all identifiers from Edge extensions and generates a new Experience Cloud ID (ECID). + */ + public static void resetIdentities() { + if (eventHub == null) { + Log.debug(LOG_TAG, "Failed to reset identities (%s)", NULL_CONTEXT_MESSAGE); + return; + } + + Event event = new Event.Builder("Reset Identities Request", EventType.GENERIC_IDENTITY, EventSource.REQUEST_RESET) + .build(); + + eventHub.dispatch(event); + } } From ef6e2b67522d9d4c9486b889c2f477027019bd1b Mon Sep 17 00:00:00 2001 From: Spoorthi Pujari <63024083+spoorthipujariadobe@users.noreply.github.com> Date: Thu, 2 Jun 2022 11:20:56 -0700 Subject: [PATCH 061/476] replace map flattening logic with existing utility method (#86) --- .../mobile/launch/rulesengine/LaunchTokenFinder.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt index edf8b3a8f..38bfd4e28 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt @@ -130,17 +130,17 @@ internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionAp return null } val (sharedStateName, dataKeyName) = sharedStateKeyString.split(SHARED_STATE_KEY_DELIMITER) - // TODO uncomment once map flattening logic is finalized - /* val sharedStateMap = extensionApi.getSharedEventState(sharedStateName, event) { + // TODO change once map flattening logic is finalized + val sharedStateMap = extensionApi.getSharedEventState(sharedStateName, event) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Unable to replace the token %s, token not found in shared state for the event", key) ) - }?.getFlattenedDataMap() + }?.flattening() if (sharedStateMap == null || sharedStateMap.isEmpty() || StringUtils.isNullOrEmpty(dataKeyName) || !sharedStateMap.containsKey(dataKeyName)) { return null } - return sharedStateMap[dataKeyName] */ + return sharedStateMap[dataKeyName] return null } @@ -161,9 +161,8 @@ internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionAp return EMPTY_STRING } // TODO uncomment once map flattening logic is finalized - /* val eventDataMap = event.eventData.getFlattenedDataMap() + val eventDataMap = event.eventData.flattening() return eventDataMap[key] - */ return EMPTY_STRING } } From 5561deac7f464871cf3935ad218c036bcbcd6088 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Thu, 2 Jun 2022 12:20:01 -0700 Subject: [PATCH 062/476] comment out signal extension code which uses HitQueue --- .../marketing/mobile/SignalExtension.java | 17 +++++++++----- .../com/adobe/marketing/mobile/SignalHit.java | 9 ++++---- .../marketing/mobile/SignalHitSchema.java | 4 ++++ .../marketing/mobile/SignalHitsDatabase.java | 22 ++++++++++--------- .../marketing/mobile/SignalTemplate.java | 5 +++-- .../mobile/MockSignalHitsDatabase.java | 5 +++-- .../marketing/mobile/SignalHitSchemaTest.java | 4 +++- .../marketing/mobile/SignalTemplateTest.java | 4 +++- 8 files changed, 44 insertions(+), 26 deletions(-) diff --git a/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalExtension.java b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalExtension.java index 51cd43ea1..250ea6c62 100644 --- a/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalExtension.java +++ b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalExtension.java @@ -17,7 +17,8 @@ class SignalExtension extends InternalModule { private static final String LOGTAG = "SignalExtension"; private final ConcurrentLinkedQueue unprocessedEvents; - private SignalHitsDatabase signalHitsDatabase; + // TODO refactor to use public hit queue + // private SignalHitsDatabase signalHitsDatabase; /** * Constructor for an internal module, must be called by inheritors. @@ -30,7 +31,8 @@ class SignalExtension extends InternalModule { registerListener(EventType.RULES_ENGINE, EventSource.RESPONSE_CONTENT, ListenerRulesEngineResponseContentSignal.class); registerListener(EventType.CONFIGURATION, EventSource.RESPONSE_CONTENT, ListenerConfigurationResponseContentSignal.class); - this.signalHitsDatabase = new SignalHitsDatabase(services); + // TODO refactor to use public hit queue + // this.signalHitsDatabase = new SignalHitsDatabase(services); this.unprocessedEvents = new ConcurrentLinkedQueue(); } @@ -41,10 +43,11 @@ class SignalExtension extends InternalModule { * @param services PlatformServices instance * @param database SignalHitsDatabase instance */ - SignalExtension(final EventHub hub, final PlatformServices services, final SignalHitsDatabase database) { + // TODO refactor to use public hit queue + /* SignalExtension(final EventHub hub, final PlatformServices services, final SignalHitsDatabase database) { this(hub, services); this.signalHitsDatabase = database; - } + } */ /** * queue the signal event (postback or pii), triggered by Rules Engine, and try to process the events in queue. @@ -139,7 +142,8 @@ public void run() { unprocessedEvents.clear(); } - signalHitsDatabase.updatePrivacyStatus(privacyStatus); + // TODO refactor to use public hit queue + // signalHitsDatabase.updatePrivacyStatus(privacyStatus); tryProcessQueuedEvent(); } }); @@ -203,7 +207,8 @@ boolean processSignalEvent(final Event event) { SignalTemplate signalTemplate = SignalTemplate.createSignalTemplateFromConsequence(signalConsequence); if (signalTemplate != null) { - signalHitsDatabase.queue(signalTemplate.getSignalHit(), event.getTimestamp(), privacyStatus); + // TODO refactor to use public hit queue + // signalHitsDatabase.queue(signalTemplate.getSignalHit(), event.getTimestamp(), privacyStatus); } } diff --git a/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHit.java b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHit.java index f55528979..82911704f 100644 --- a/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHit.java +++ b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHit.java @@ -14,18 +14,19 @@ /* Define the fields for signal request */ -class SignalHit extends AbstractHit { +// TODO refactor to use public hit queue +/*class SignalHit extends AbstractHit { String url; String body; String contentType; int timeout; - /** + *//** * Determine the Http command based off the request body * * @return HttpCommand.POST if the body has content, otherwise HttpCommand.GET - */ + *//* NetworkService.HttpCommand getHttpCommand() { return StringUtils.isNullOrEmpty(body) ? NetworkService.HttpCommand.GET : NetworkService.HttpCommand.POST; } -} +}*/ diff --git a/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHitSchema.java b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHitSchema.java index ff6da3ede..5d809cd7e 100644 --- a/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHitSchema.java +++ b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHitSchema.java @@ -18,6 +18,9 @@ /** Define the database schema for signal request */ + +//TODO refactor to use public hit queue +/* class SignalHitSchema extends AbstractHitSchema { private static final String LOG_TAG = "SignalHitType"; @@ -96,3 +99,4 @@ Map generateDataMap(final SignalHit hit) { } } +*/ diff --git a/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHitsDatabase.java b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHitsDatabase.java index aed954416..9f309b9af 100644 --- a/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHitsDatabase.java +++ b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalHitsDatabase.java @@ -20,7 +20,9 @@ /** * The database queue for signal requests */ -class SignalHitsDatabase implements HitQueue.IHitProcessor { + +// TODO refactor to use public HitQueue +/*class SignalHitsDatabase implements HitQueue.IHitProcessor { private static final String LOG_TAG = "SignalHitsDatabase"; private static final String SIGNAL_FILENAME = "ADBMobileSignalDataCache.sqlite"; @@ -31,19 +33,19 @@ class SignalHitsDatabase implements HitQueue.IHitProcessor { private final static int HTTP_SUCCESS_RESPONSE_CODE_LOWER_LIMIT = 200; private final static int HTTP_SUCCESS_RESPONSE_CODE_UPPER_LIMIT = 299; - /** + *//** * Default Constructor * @param services PlatformServices - */ + *//* SignalHitsDatabase(final PlatformServices services) { this(services, null); } - /** + *//** * Constructor for test * @param services PlatformServices * @param hitQueue HitQueue for test - */ + *//* SignalHitsDatabase(final PlatformServices services, final HitQueue hitQueue) { this.networkService = services.getNetworkService(); @@ -108,11 +110,11 @@ public HitQueue.RetryType process(final SignalHit hit) { return retryType; } - /** + *//** * update the privacy status. Resume the queue when optin. Clear the queue when optout. Suspend the queue when unkonwn. * * @param privacyStatus the new privacy status - */ + *//* void updatePrivacyStatus(final MobilePrivacyStatus privacyStatus) { switch (privacyStatus) { case OPT_IN: @@ -130,12 +132,12 @@ void updatePrivacyStatus(final MobilePrivacyStatus privacyStatus) { } } - /** + *//** * Add a signal hit to the queue * @param signalHit the signal hit * @param timestampMillis event timestamp to be associated with the signal hit * @param privacyStatus the current privacy status - */ + *//* void queue(final SignalHit signalHit, final long timestampMillis, final MobilePrivacyStatus privacyStatus) { if (signalHit != null) { signalHit.timestamp = TimeUnit.MILLISECONDS.toSeconds(timestampMillis); @@ -148,4 +150,4 @@ void queue(final SignalHit signalHit, final long timestampMillis, final MobilePr } } -} +}*/ diff --git a/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalTemplate.java b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalTemplate.java index 105bb826d..8dcb4485e 100644 --- a/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalTemplate.java +++ b/code/android-signal-library/src/main/java/com/adobe/marketing/mobile/SignalTemplate.java @@ -37,7 +37,8 @@ class SignalTemplate { * create a new signal hit. * @return a new signal hit object */ - SignalHit getSignalHit() { + // TODO refactor to use public hit queue + /* SignalHit getSignalHit() { SignalHit signalHit = new SignalHit(); signalHit.url = this.urlTemplate; signalHit.body = this.bodyTemplate; @@ -45,7 +46,7 @@ SignalHit getSignalHit() { signalHit.timeout = this.timeout; return signalHit; - } + } */ /** * return the id of the signal template diff --git a/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/MockSignalHitsDatabase.java b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/MockSignalHitsDatabase.java index 7c36867dc..51eae4ff6 100644 --- a/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/MockSignalHitsDatabase.java +++ b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/MockSignalHitsDatabase.java @@ -10,7 +10,8 @@ */ package com.adobe.marketing.mobile; -public class MockSignalHitsDatabase extends SignalHitsDatabase { +// TODO refactor to use public hit queue +/* public class MockSignalHitsDatabase extends SignalHitsDatabase { MockSignalHitsDatabase(PlatformServices services) { super(services); } @@ -31,4 +32,4 @@ void queue(final SignalHit signalHit, final long timestamp, final MobilePrivacyS this.queueParametersMobilePrivacyStatus = privacyStatus; this.queueWasCalled = true; } -} +} */ diff --git a/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalHitSchemaTest.java b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalHitSchemaTest.java index 4ce81525d..f7fb51bba 100644 --- a/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalHitSchemaTest.java +++ b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalHitSchemaTest.java @@ -19,7 +19,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -public class SignalHitSchemaTest { +// TODO refactor to use public hit queue +/* public class SignalHitSchemaTest { private static final String HIT_ID_COL_NAME = "ID"; private static final int HIT_ID_COL_INDEX = 0; private static final String HIT_URL_COL_NAME = "URL"; @@ -52,3 +53,4 @@ public void testGenerateDataMap() { assertEquals("url", values.get(HIT_URL_COL_NAME)); } } +*/ diff --git a/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalTemplateTest.java b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalTemplateTest.java index 3e230e042..0e98cc4a7 100644 --- a/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalTemplateTest.java +++ b/code/android-signal-library/src/test/java/com/adobe/marketing/mobile/SignalTemplateTest.java @@ -80,7 +80,8 @@ public void returnNullSignalTemplate_when_UrlIsHttpWithPiiType() { Assert.assertNull(SignalTemplate.createSignalTemplateFromConsequence(map)); } - @Test + // TODO refactor to use public hit queue + /* @Test public void returnValidSignalTemplate_when_UrlIsHttpsWithPiiType() { Map detail = new HashMap(); detail.put("templateurl", "https://xyz.com"); @@ -248,4 +249,5 @@ public void signalHitUseConfiguredTimeout_when_ContainsTimeoutInJson() throws Ex SignalTemplate signal = SignalTemplate.createSignalTemplateFromConsequence(map); Assert.assertEquals(5, signal.getSignalHit().timeout); } + */ } From 21f58e0af921b2ea62be68bb0565009b03f6bae6 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Thu, 2 Jun 2022 15:15:11 -0700 Subject: [PATCH 063/476] Fix ExtensionContainer retrieval logic [Problem] For shared state operations, ExtensionContainer that is to be operated on is being fetched from the registered extension map directly using the "extensionName". This is incorrect because registered extension map maintains a mapping between the class name of the extension and the container, resulting in a no-op always. [Solution] Add a utility method to get the extension container form the registered extension map based on the extension name. --- .../mobile/internal/eventhub/EventHub.kt | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index 29f230916..ecb5ad0af 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -123,15 +123,15 @@ internal class EventHub { return@submit } - val extensionName = extensionClass.extensionTypeName - if (registeredExtensions.containsKey(extensionName)) { + val extensionTypeName = extensionClass.extensionTypeName + if (registeredExtensions.containsKey(extensionTypeName)) { completion(EventHubError.DuplicateExtensionName) return@submit } val executor = Executors.newSingleThreadExecutor() val container = ExtensionContainer(extensionClass, ExtensionRuntime(), executor, completion) - registeredExtensions[extensionName] = container + registeredExtensions[extensionTypeName] = container } } @@ -187,7 +187,7 @@ internal class EventHub { return@Callable false } - val extensionContainer: ExtensionContainer? = registeredExtensions[extensionName] + val extensionContainer: ExtensionContainer? = registeredExtensions[getExtensionTypeName(extensionName)] if (extensionContainer == null) { MobileCore.log(LoggingMode.ERROR, LOG_TAG, "Error setting SharedState for extension: [$extensionName]. Extension may not have been registered.") @@ -250,7 +250,7 @@ internal class EventHub { return@Callable null } - val extensionContainer: ExtensionContainer? = registeredExtensions[extensionName] + val extensionContainer: ExtensionContainer? = registeredExtensions[getExtensionTypeName(extensionName)] if (extensionContainer == null) { MobileCore.log( @@ -298,7 +298,7 @@ internal class EventHub { return@Callable null } - val extensionContainer: ExtensionContainer? = registeredExtensions[extensionName] + val extensionContainer: ExtensionContainer? = registeredExtensions[getExtensionTypeName(extensionName)] if (extensionContainer == null) { MobileCore.log( @@ -348,6 +348,19 @@ internal class EventHub { val eventUUID = event?.uniqueIdentifier return eventNumberMap[eventUUID] } + + /** + * Retrieves the [extensionTypeName] for the provided [extensionName] + * This is required because [registeredExtensions] maintains a mapping between [extensionTypeName] + * and [ExtensionContainer] and most state based operations rely on the name of an extension. + * + * @param [extensionName] the name of the extension for which an extensionTypeName should be fetched. + * This should match [Extension.name] of an extension registered with the event hub. + * @return the [extensionTypeName] for the provided [extensionName] + */ + private fun getExtensionTypeName(extensionName: String): String? { + return registeredExtensions.entries.firstOrNull { extensionName == it.value.sharedStateName }?.key + } } /** From ede005ef7757a9b7f30384eb460e9f05681655ba Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Mon, 6 Jun 2022 09:30:44 -0700 Subject: [PATCH 064/476] handle rules consequences part 1 --- .../com/adobe/marketing/mobile/Event.java | 11 + .../launch/rulesengine/LaunchRulesEngine.java | 12 +- .../rulesengine/LaunchRulesEvaluator.kt | 298 +++++++++++++++++- .../launch/rulesengine/RuleConsequence.kt | 7 +- .../mobile/rulesengine/DelimiterPair.java | 4 +- 5 files changed, 309 insertions(+), 23 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java index 8d28cb27b..13493a655 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java @@ -284,6 +284,17 @@ public Event copy() { return this; } + public Event copyWithNewData(final Map newData) { + Event newEvent = new Event.Builder(this.name, this.type, this.source, this.mask) + .setEventData(newData) + .build(); + newEvent.uniqueIdentifier = this.uniqueIdentifier; + newEvent.timestamp = this.timestamp; + newEvent.pairID = this.pairID; + newEvent.responsePairID = this.responsePairID; + return newEvent; + } + /** * Returns the {@code Event} name * @return {@code String} representing the {@link Event} name diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngine.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngine.java index f41bb9bef..c94723459 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngine.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngine.java @@ -35,13 +35,11 @@ public void replaceRules(final List rules) { } /** - * Evaluates all the current rules against the supplied {@link Event}. - * @param event the {@link Event} against which to evaluate the rules - * @return the processed {@link Event} + * Evaluates all the current rules using the supplied {@link TokenFinder}. + * @param tokenFinder the {@link TokenFinder} used to evaluate the rules + * @return the {@link List} of {@link LaunchRule} that have been matched */ - public Event process(final Event event) { - // TODO pass in extensionApi in call to LaunchTokenFinder - // ruleRulesEngine.evaluate(new LaunchTokenFinder(event)); - return event; + public List process(TokenFinder tokenFinder) { + return ruleRulesEngine.evaluate(tokenFinder); } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt index 76a208c2e..8c1af63f8 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt @@ -12,43 +12,137 @@ package com.adobe.marketing.mobile.launch.rulesengine import com.adobe.marketing.mobile.Event import com.adobe.marketing.mobile.EventPreprocessor +import com.adobe.marketing.mobile.ExtensionApi +import com.adobe.marketing.mobile.ExtensionErrorCallback import com.adobe.marketing.mobile.LoggingMode import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.internal.utility.EventDataMerger +import com.adobe.marketing.mobile.rulesengine.DelimiterPair +import com.adobe.marketing.mobile.rulesengine.Template +import com.adobe.marketing.mobile.rulesengine.TokenFinder +import com.adobe.marketing.mobile.rulesengine.Transforming internal class LaunchRulesEvaluator( private val name: String, - private val launchRulesEngine: LaunchRulesEngine + private val launchRulesEngine: LaunchRulesEngine, + private val extensionApi: ExtensionApi ) : EventPreprocessor { - constructor(name: String) : this(name, LaunchRulesEngine()) + constructor(name: String, extensionApi: ExtensionApi) : this(name, LaunchRulesEngine(), extensionApi) private var cachedEvents: MutableList? = mutableListOf() private val logTag = "LaunchRulesEvaluator_$name" + private val transformer: Transforming = LaunchRuleTransformer.createTransforming() + private var dispatchChainedEventsCount = mutableMapOf() companion object { const val CACHED_EVENT_MAX = 99 // TODO: we should move the following event type/event source values to the public EventType/EventSource classes once we have those. - const val EVENT_SOURCE = "com.adobe.eventsource.requestreset" - const val EVENT_TYPE = "com.adobe.eventtype.rulesengine" + const val EVENT_SOURCE_REQUEST_RESET = "com.adobe.eventsource.requestreset" + const val EVENT_SOURCE_RESPONSE_CONTENT = "com.adobe.eventSource.responseContent" + const val EVENT_TYPE_RULES_ENGINE = "com.adobe.eventtype.rulesengine" + const val LAUNCH_RULE_TOKEN_LEFT_DELIMITER = "{%" + const val LAUNCH_RULE_TOKEN_RIGHT_DELIMITER = "%}" + const val CONSEQUENCE_TYPE_ADD = "add" + const val CONSEQUENCE_TYPE_MOD = "mod" + const val CONSEQUENCE_TYPE_DISPATCH = "dispatch" + const val CONSEQUENCE_DETAIL_ACTION_COPY = "copy" + const val CONSEQUENCE_DETAIL_ACTION_NEW = "new" + /// Do not process Dispatch consequence if chained event count is greater than max + const val MAX_CHAINED_CONSEQUENCE_COUNT = 1 + const val CONSEQUENCE_DISPATCH_EVENT_NAME = "Dispatch Consequence Result" + const val CONSEQUENCE_EVENT_DATA_KEY_ID = "id" + const val CONSEQUENCE_EVENT_DATA_KEY_TYPE = "type" + const val CONSEQUENCE_EVENT_DATA_KEY_DETAIL = "detail" + const val CONSEQUENCE_EVENT_DATA_KEY_CONSEQUENCE = "triggeredconsequence" + const val CONSEQUENCE_EVENT_NAME = "Rules Consequence Event" } override fun process(event: Event?): Event? { if (event == null) return null - if (event.type == EVENT_TYPE && event.source == EVENT_SOURCE) { - reprocessCachedEvents() + val dispatchChainCount = dispatchChainedEventsCount.remove(event.uniqueIdentifier) + val launchTokenFinder = LaunchTokenFinder(event, extensionApi) + if (event.type == EVENT_TYPE_RULES_ENGINE && event.source == EVENT_SOURCE_REQUEST_RESET) { + reprocessCachedEvents(launchTokenFinder) + return event } else { cacheEvent(event) - launchRulesEngine.process(event) - // TODO: handle rules consequence + val matchedRules = launchRulesEngine.process(launchTokenFinder) + var processedEvent: Event = event + for (rule in matchedRules) { + for (consequence in rule.consequenceList) { + val consequenceWithConcreteValue = replaceToken(consequence, launchTokenFinder) + when(consequenceWithConcreteValue.type){ + CONSEQUENCE_TYPE_ADD -> { + val attachedEventData = processAttachDataConsequence( + consequenceWithConcreteValue, + processedEvent.eventData + ) ?: continue + processedEvent = processedEvent.copyWithNewData(attachedEventData) + } + CONSEQUENCE_TYPE_MOD -> { + val modifiedEventData = processModifyDataConsequence( + consequenceWithConcreteValue, + processedEvent.eventData + ) ?: continue + processedEvent = processedEvent.copyWithNewData(modifiedEventData) + } + CONSEQUENCE_TYPE_DISPATCH -> { + if (dispatchChainCount == null || dispatchChainCount == 0 || + dispatchChainCount >= MAX_CHAINED_CONSEQUENCE_COUNT) { + MobileCore.log( + LoggingMode.VERBOSE, + logTag, + "Unable to process dispatch consequence, max chained " + + "dispatch consequences limit of $MAX_CHAINED_CONSEQUENCE_COUNT" + + "met for this event uuid ${event.uniqueIdentifier}" + ) + continue + } + val dispatchEvent = processDispatchConsequence( + consequenceWithConcreteValue, + processedEvent.eventData + ) ?: continue + MobileCore.log( + LoggingMode.VERBOSE, + logTag, + " Generating new dispatch consequence result event $dispatchEvent" + ) + MobileCore.dispatchEvent(event) { + MobileCore.log( + LoggingMode.WARNING, + logTag, + "An error occurred when dispatching dispatch consequence result event" + ) + } + } + else -> { + val consequenceEvent = generateConsequenceEvent(consequenceWithConcreteValue) + MobileCore.log( + LoggingMode.VERBOSE, + logTag, + "Generating new consequence event $consequenceEvent" + ) + MobileCore.dispatchEvent(event) { + MobileCore.log( + LoggingMode.WARNING, + logTag, + "An error occurred when dispatching consequence result event" + ) + } + } + } + } + } + return processedEvent } - return event } - private fun reprocessCachedEvents() { + private fun reprocessCachedEvents(tokenFinder: TokenFinder) { cachedEvents?.forEach { event -> - launchRulesEngine.process(event) + launchRulesEngine.process(tokenFinder) // TODO: handle rules consequence } clearCachedEvents() @@ -84,8 +178,8 @@ internal class LaunchRulesEvaluator( MobileCore.dispatchEvent( Event.Builder( name, - EVENT_TYPE, - EVENT_SOURCE + EVENT_TYPE_RULES_ENGINE, + EVENT_SOURCE_REQUEST_RESET ).build() ) { extensionError -> MobileCore.log( @@ -95,4 +189,182 @@ internal class LaunchRulesEvaluator( ) } } + + /** + * Replace tokens inside the provided [RuleConsequence] with the right value + * + * @param consequence [RuleConsequence] instance that may contain tokens + * @param tokenFinder [TokenFinder] instance which replaces the tokens with values + * @return the [RuleConsequence] with replaced tokens + */ + fun replaceToken(consequence: RuleConsequence, tokenFinder: TokenFinder): RuleConsequence { + val tokenReplacedMap = replaceToken(consequence.detail, tokenFinder) + return RuleConsequence(consequence.id, consequence.type, tokenReplacedMap) + } + + @Suppress("UNCHECKED_CAST") + private fun replaceToken(detail: Map?, tokenFinder: TokenFinder): Map? { + if (detail.isNullOrEmpty()) + return null + val mutableDetail = detail.toMutableMap() + for((key, value) in detail) { + when(value) { + is String -> mutableDetail[key] = replaceToken(value, tokenFinder) + is Map<*, *> -> replaceToken(mutableDetail[key] as Map, tokenFinder) + else -> break + } + } + return mutableDetail + } + + private fun replaceToken(value: String, tokenFinder: TokenFinder): String { + val template = Template(value, DelimiterPair(LAUNCH_RULE_TOKEN_LEFT_DELIMITER, LAUNCH_RULE_TOKEN_RIGHT_DELIMITER)) + return template.render(tokenFinder, transformer) + } + + /** + * Process an attach data consequence event. Attaches the triggering event data from the [RuleConsequence] to the + * triggering event data without overwriting the original event data. If either the event data + * from the [RuleConsequence] or the triggering event data is null then the processing is aborted. + * + * @param consequence the [RuleConsequence] which contains the event data to attach + * @param eventData the event data of the triggering [Event] + * @return [Map] with the [RuleConsequence] data attached to the triggering event data, or + * null if the processing fails + */ + private fun processAttachDataConsequence(consequence: RuleConsequence, eventData: Map?): Map? { + val from = consequence.eventData ?: run { + MobileCore.log( + LoggingMode.ERROR, + logTag, + "Unable to process an AttachDataConsequence Event, 'eventData' is missing from 'details'" + ) + return null + } + val to = eventData ?: run { + MobileCore.log( + LoggingMode.ERROR, + logTag, + "Unable to process an AttachDataConsequence Event, 'eventData' is missing from original event" + ) + return null + } + // TODO add utility function for map pretty print + MobileCore.log( + LoggingMode.VERBOSE, + logTag, + "Attaching event data with $from" + ) + return EventDataMerger.merge(from, to, false) + } + + /** + * Process a modify data consequence event. Modifies the triggering event data by merging the + * event data from the [RuleConsequence] onto it. If either the event data + * from the [RuleConsequence] or the triggering event data is null then the processing is aborted. + * + * @param consequence the [RuleConsequence] which contains the event data to attach + * @param eventData the event data of the triggering [Event] + * @return [Map] with the Event data modified with the [RuleConsequence] data, or + * null if the processing fails + */ + private fun processModifyDataConsequence(consequence: RuleConsequence, eventData: Map?): Map? { + val from = consequence.eventData ?: run { + MobileCore.log( + LoggingMode.ERROR, + logTag, + "Unable to process a ModifyDataConsequence Event, 'eventData' is missing from 'details'" + ) + return null + } + val to = eventData ?: run { + MobileCore.log( + LoggingMode.ERROR, + logTag, + "Unable to process a ModifyDataConsequence Event, 'eventData' is missing from original event" + ) + return null + } + // TODO add utility function for map pretty print + MobileCore.log( + LoggingMode.VERBOSE, + logTag, + "Modifying event data with $from" + ) + return EventDataMerger.merge(from, to, true) + } + + /** + * Process a dispatch consequence event. Generates a new [Event] from the details contained within the [RuleConsequence] + * + * @param consequence the [RuleConsequence] which contains details on the new [Event] to generate + * @param eventData the triggering Event data + * @return a new [Event] to be dispatched to the [EventHub], or null if the processing failed. + */ + private fun processDispatchConsequence(consequence: RuleConsequence, eventData: Map?): Event? { + val type = consequence.eventType ?: run { + MobileCore.log( + LoggingMode.ERROR, + logTag, + "Unable to process a DispatchConsequence Event, 'type' is missing from 'details'" + ) + return null + } + val source = consequence.eventSource ?: run { + MobileCore.log( + LoggingMode.ERROR, + logTag, + "Unable to process a DispatchConsequence Event, 'source' is missing from 'details'" + ) + return null + } + val action = consequence.eventDataAction ?: run { + MobileCore.log( + LoggingMode.ERROR, + logTag, + "Unable to process a DispatchConsequence Event, 'eventdataaction' is missing from 'details'" + ) + return null + } + val dispatchEventData: Map? + when (action) { + CONSEQUENCE_DETAIL_ACTION_COPY -> { + dispatchEventData = eventData + } + CONSEQUENCE_DETAIL_ACTION_NEW -> { + dispatchEventData = consequence.eventData?.filterValues { it != null } + } + else -> { + MobileCore.log( + LoggingMode.ERROR, + logTag, + "Unable to process a DispatchConsequence Event, unsupported 'eventdataaction', expected values copy/new" + ) + return null + } + } + return Event.Builder(CONSEQUENCE_DISPATCH_EVENT_NAME, type, source) + .setEventData(dispatchEventData) + .build() + } + + private fun generateConsequenceEvent(consequence: RuleConsequence) : Event? { + val eventData = mutableMapOf() + eventData[CONSEQUENCE_EVENT_DATA_KEY_DETAIL] = consequence.detail + eventData[CONSEQUENCE_EVENT_DATA_KEY_ID] = consequence.id + eventData[CONSEQUENCE_EVENT_DATA_KEY_TYPE] = consequence.type + return Event.Builder(CONSEQUENCE_EVENT_NAME, EVENT_TYPE_RULES_ENGINE, EVENT_SOURCE_RESPONSE_CONTENT) + .setEventData(mapOf(CONSEQUENCE_EVENT_DATA_KEY_CONSEQUENCE to eventData)) + .build() + } } + +// Extend RuleConsequence with helper methods for processing Dispatch Consequence events. +val RuleConsequence.eventSource: String? +get() = detail?.get("source") as? String + +val RuleConsequence.eventType: String? + get() = detail?.get("type") as? String + +val RuleConsequence.eventDataAction: String? + get() = detail?.get("eventdataaction") as? String \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence.kt index 82d679378..d084e840b 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence.kt @@ -19,8 +19,13 @@ package com.adobe.marketing.mobile.launch.rulesengine * @property detail the meta data of the consequence object * @constructor Constructs a new [RuleConsequence] */ + +@Suppress("UNCHECKED_CAST") data class RuleConsequence( val id: String, val type: String, var detail: Map? = null -) +) { + val eventData: Map? + get() = detail?.get("eventData") as? Map? +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/DelimiterPair.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/DelimiterPair.java index bda8b9403..85dd02833 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/DelimiterPair.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/DelimiterPair.java @@ -18,7 +18,7 @@ * The default delimiter pair from launch rules are "{%" "%}" * eg token: {%region.cityName%} */ -class DelimiterPair { +public class DelimiterPair { private final String startTag; private final String endTag; @@ -28,7 +28,7 @@ class DelimiterPair { * @param startString * @param endString */ - DelimiterPair(final String startString, final String endString) { + public DelimiterPair(final String startString, final String endString) { this.startTag = startString; this.endTag = endString; } From f4e50c2770d6882835f2ddad46ae79eeeec805fb Mon Sep 17 00:00:00 2001 From: Yansong Yang Date: Mon, 6 Jun 2022 12:37:19 -0600 Subject: [PATCH 065/476] add module tests for the LaunchRulesEngine (#89) --- .../mobile/ConfigurationExtension.java | 8 +- .../launch/rulesengine/LaunchRulesEngine.java | 44 +- .../rulesengine/LaunchRulesEvaluator.kt | 2 - .../launch/rulesengine/LaunchTokenFinder.kt | 1 - .../rulesengine/json/HistoricalCondition.kt | 7 +- .../rulesengine/json/MatcherCondition.kt | 41 +- .../mobile/rulesengine/LogicalExpression.java | 76 +-- .../mobile/rulesengine/RulesEngine.java | 6 +- .../LaunchRulesEngineModuleTests.kt | 462 ++++++++++++++++++ .../rulesengine/json/JSONConditionTests.kt | 3 +- .../rulesengine/json/JSONConsequenceTests.kt | 1 + .../rulesengine/json/JSONRuleRootTests.kt | 1 + .../launch/rulesengine/json/JSONRuleTests.kt | 1 + .../utility}/JSONRuleUtilities.kt | 2 +- .../test/utility/TestResourceUtilities.kt | 5 + .../consequence_rules_1.json | 173 +++++++ .../consequence_rules_testAttachData.json | 91 ++++ ...ence_rules_testAttachData_invalidJson.json | 91 ++++ ...sequence_rules_testDispatchEventChain.json | 158 ++++++ ...nsequence_rules_testDispatchEventCopy.json | 62 +++ ..._rules_testDispatchEventInvalidAction.json | 62 +++ ...quence_rules_testDispatchEventNewData.json | 67 +++ ...ence_rules_testDispatchEventNewNoData.json | 62 +++ ...uence_rules_testDispatchEventNoAction.json | 61 +++ ...uence_rules_testDispatchEventNoSource.json | 61 +++ ...equence_rules_testDispatchEventNoType.json | 61 +++ .../consequence_rules_testModifyData.json | 73 +++ ...ence_rules_testModifyData_invalidJson.json | 73 +++ .../consequence_rules_testUrlenc.json | 68 +++ ...quence_rules_testUrlenc_invalidFnName.json | 68 +++ .../rules_testGroupLogicalOperators.json | 87 ++++ .../rules_module_tests/rules_testHistory.json | 33 ++ .../rules_testMatcherCo.json | 87 ++++ .../rules_testMatcherGe.json | 87 ++++ .../rules_testMatcherGt.json | 87 ++++ .../rules_testMatcherGt_2_types.json | 69 +++ .../rules_testMatcherLe.json | 87 ++++ .../rules_testMatcherLt.json | 87 ++++ .../rules_testMatcherNc.json | 87 ++++ .../rules_testMatcherNe.json | 87 ++++ .../rules_testMatcherNx.json | 85 ++++ ...MatcherWithDifferentTypesOfParameters.json | 87 ++++ .../rules_testTransform.json | 148 ++++++ 43 files changed, 2941 insertions(+), 68 deletions(-) create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngineModuleTests.kt rename code/android-core-library/src/test/java/com/adobe/marketing/mobile/{launch/rulesengine/json => test/utility}/JSONRuleUtilities.kt (95%) create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/test/utility/TestResourceUtilities.kt create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_1.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testAttachData.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testAttachData_invalidJson.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventChain.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventCopy.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventInvalidAction.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventNewData.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventNewNoData.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventNoAction.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventNoSource.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventNoType.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testModifyData.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testModifyData_invalidJson.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testUrlenc.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testUrlenc_invalidFnName.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/rules_testGroupLogicalOperators.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/rules_testHistory.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherCo.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherGe.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherGt.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherGt_2_types.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherLe.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherLt.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherNc.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherNe.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherNx.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherWithDifferentTypesOfParameters.json create mode 100644 code/android-core-library/src/test/resources/rules_module_tests/rules_testTransform.json diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ConfigurationExtension.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ConfigurationExtension.java index 858e4ae29..e6b39f676 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ConfigurationExtension.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ConfigurationExtension.java @@ -12,6 +12,7 @@ package com.adobe.marketing.mobile; import com.adobe.marketing.mobile.internal.utility.TimeUtil; +import com.adobe.marketing.mobile.launch.rulesengine.LaunchRulesEngine; import com.adobe.marketing.mobile.launch.rulesengine.LaunchRulesEvaluator; import com.adobe.marketing.mobile.launch.rulesengine.json.JSONRulesParser; @@ -136,8 +137,11 @@ public ConfigurationExtension(final EventHub eventHub, final PlatformServices se rulesDownloadExecutor = Executors.newSingleThreadExecutor(); this.cachedEvents = Collections.synchronizedList(new ArrayList()); - launchRulesEvaluator = new LaunchRulesEvaluator(LAUNCH_RULES_ENGINE); - eventHub.registerPreprocessor(launchRulesEvaluator); + //TODO: need to pass an instance of ExtensionAPi to initialize LaunchRulesEngine, will do it later after we converted Configuration to a 3th party extension. + LaunchRulesEngine launchRulesEngine = new LaunchRulesEngine(null); + launchRulesEvaluator = new LaunchRulesEvaluator(LAUNCH_RULES_ENGINE, launchRulesEngine); + //TODO: enable pre-processor to utilize the new RulesEngine after the Configuration is converted to a 3th party extension. + //eventHub.registerPreprocessor(launchRulesEvaluator); } ConfigurationDispatcherConfigurationResponseIdentity createResponseIdentityDispatcher() { diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngine.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngine.java index f41bb9bef..ef164dbb9 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngine.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngine.java @@ -11,23 +11,50 @@ package com.adobe.marketing.mobile.launch.rulesengine; import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.ExtensionApi; +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; import com.adobe.marketing.mobile.rulesengine.ConditionEvaluator; +import com.adobe.marketing.mobile.rulesengine.Log; +import com.adobe.marketing.mobile.rulesengine.LogLevel; +import com.adobe.marketing.mobile.rulesengine.Logging; import com.adobe.marketing.mobile.rulesengine.RulesEngine; -import com.adobe.marketing.mobile.rulesengine.TokenFinder; import java.util.List; public class LaunchRulesEngine { private final RulesEngine ruleRulesEngine; + private final ExtensionApi extensionApi; - // TODO pass in extensionApi to the constructor @SuppressWarnings("rawtypes") - public LaunchRulesEngine() { - ruleRulesEngine = new RulesEngine<>(new ConditionEvaluator(), LaunchRuleTransformer.INSTANCE.createTransforming()); + public LaunchRulesEngine(final ExtensionApi extensionApi) { + ruleRulesEngine = new RulesEngine<>(new ConditionEvaluator(ConditionEvaluator.Option.CASE_INSENSITIVE), LaunchRuleTransformer.INSTANCE.createTransforming()); + Log.setLogging(new Logging() { + @Override + public void log(LogLevel level, String tag, String message) { + LoggingMode loggingMode; + switch (level) { + case DEBUG: + loggingMode = LoggingMode.DEBUG; + break; + case ERROR: + loggingMode = LoggingMode.ERROR; + break; + case WARNING: + loggingMode = LoggingMode.WARNING; + break; + default: + loggingMode = LoggingMode.VERBOSE; + } + MobileCore.log(loggingMode, tag, message); + } + }); + this.extensionApi = extensionApi; } /** * Set a new set of rules, the new rules replace the current rules. + * * @param rules a list of {@link LaunchRule}s */ public void replaceRules(final List rules) { @@ -36,12 +63,11 @@ public void replaceRules(final List rules) { /** * Evaluates all the current rules against the supplied {@link Event}. + * * @param event the {@link Event} against which to evaluate the rules - * @return the processed {@link Event} + * @return the matched {@link List} */ - public Event process(final Event event) { - // TODO pass in extensionApi in call to LaunchTokenFinder - // ruleRulesEngine.evaluate(new LaunchTokenFinder(event)); - return event; + public List process(final Event event) { + return ruleRulesEngine.evaluate(new LaunchTokenFinder(event, extensionApi)); } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt index 76a208c2e..0e354268a 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt @@ -20,8 +20,6 @@ internal class LaunchRulesEvaluator( private val launchRulesEngine: LaunchRulesEngine ) : EventPreprocessor { - constructor(name: String) : this(name, LaunchRulesEngine()) - private var cachedEvents: MutableList? = mutableListOf() private val logTag = "LaunchRulesEvaluator_$name" diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt index 38bfd4e28..f2280e06a 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt @@ -141,7 +141,6 @@ internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionAp return null } return sharedStateMap[dataKeyName] - return null } /** diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt index bd14f5f17..6cea1588b 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt @@ -26,11 +26,10 @@ internal class HistoricalCondition(private val definition: JSONDefinition) : JSO } override fun toEvaluable(): Evaluable? { - val matcher = definition.matcher val valueAsInt = definition.value + val operationName = MatcherCondition.MATCHER_MAPPING[definition.matcher] if (definition.events !is List<*> || - matcher !is String || - matcher !in MatcherCondition.MATCHER_MAPPING || + operationName !is String || valueAsInt !is Int ) { MobileCore.log( @@ -55,7 +54,7 @@ internal class HistoricalCondition(private val definition: JSONDefinition) : JSO 0 } }, requestEvents, searchType), - matcher, + operationName, OperandLiteral(valueAsInt) ) } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/MatcherCondition.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/MatcherCondition.kt index e5faa8216..8de2e6675 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/MatcherCondition.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/MatcherCondition.kt @@ -13,11 +13,13 @@ package com.adobe.marketing.mobile.launch.rulesengine.json import com.adobe.marketing.mobile.LoggingMode import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.launch.rulesengine.LaunchRulesConstants import com.adobe.marketing.mobile.rulesengine.ComparisonExpression import com.adobe.marketing.mobile.rulesengine.Evaluable import com.adobe.marketing.mobile.rulesengine.LogicalExpression import com.adobe.marketing.mobile.rulesengine.OperandLiteral import com.adobe.marketing.mobile.rulesengine.OperandMustacheToken +import com.adobe.marketing.mobile.rulesengine.UnaryExpression /** * The class representing a matcher condition @@ -55,7 +57,7 @@ internal class MatcherCondition(private val definition: JSONDefinition) : JSONCo } val values: List = definition.values ?: listOf() return when (values.size) { - 0 -> convert(definition.key, definition.matcher, "") + 0 -> convert(definition.key, definition.matcher, null) 1 -> convert(definition.key, definition.matcher, values[0]) in 2..Int.MAX_VALUE -> { val operands = values.map { convert(definition.key, definition.matcher, it) } @@ -75,18 +77,39 @@ internal class MatcherCondition(private val definition: JSONDefinition) : JSONCo ) return null } - val javaClass: Any? = when (value) { - is String -> String::class.java - is Int -> Number::class.java - is Double -> Number::class.java + if (value == null) { + return UnaryExpression( + OperandMustacheToken("{{$key}}", Any::class.java as Class<*>), + operationName + ) + } + val (javaClass: Any?, token: String) = when (value) { + is String -> Pair( + String::class.java, + "{{${LaunchRulesConstants.Transform.TRANSFORM_TO_STRING}($key)}}" + ) + is Int -> Pair( + Number::class.java, + "{{${LaunchRulesConstants.Transform.TRANSFORM_TO_INT}($key)}}" + ) + is Double -> Pair( + Number::class.java, + "{{${LaunchRulesConstants.Transform.TRANSFORM_TO_DOUBLE}($key)}}" + ) // note: Kotlin.Boolean is not mapped to java.lang.Boolean correctly - is Boolean -> java.lang.Boolean::class.java - is Float -> Number::class.java - else -> null + is Boolean -> Pair( + java.lang.Boolean::class.java, + "{{${LaunchRulesConstants.Transform.TRANSFORM_TO_BOOL}($key)}}" + ) + is Float -> Pair( + Number::class.java, + "{{${LaunchRulesConstants.Transform.TRANSFORM_TO_DOUBLE}($key)}}" + ) + else -> Pair(Any::class.java, "{{$key}}") } return if (javaClass != null) { ComparisonExpression( - OperandMustacheToken("{{$key}}", javaClass as Class<*>), + OperandMustacheToken(token, javaClass as Class<*>), operationName, OperandLiteral(value) ) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/LogicalExpression.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/LogicalExpression.java index f6bdbcb4a..a50de76e7 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/LogicalExpression.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/LogicalExpression.java @@ -15,52 +15,52 @@ import java.util.List; public class LogicalExpression implements Evaluable { - public final List operands; - public final String operationName; + public final List operands; + public final String operationName; - public LogicalExpression(List operands, String operationName) { - this.operands = operands; - this.operationName = operationName; - } + public LogicalExpression(List operands, String operationName) { + this.operands = operands; + this.operationName = operationName; + } - @Override - public RulesResult evaluate(Context context) { - ArrayList resolvedOperands = new ArrayList(); + @Override + public RulesResult evaluate(Context context) { + ArrayList resolvedOperands = new ArrayList(); - for (Evaluable evaluable : operands) { - resolvedOperands.add(evaluable.evaluate(context)); - } + for (Evaluable evaluable : operands) { + resolvedOperands.add(evaluable.evaluate(context)); + } - switch (operationName) { - case "and": - return performAndOperation(resolvedOperands); + switch (operationName) { + case "and": + return performAndOperation(resolvedOperands); - case "or": - return performOrOperation(resolvedOperands); + case "or": + return performOrOperation(resolvedOperands); - default: - return new RulesResult(RulesResult.FailureType.MISSING_OPERATOR, String.format("Unknown conjunction operator - %s.", - operationName)); - } - } + default: + return new RulesResult(RulesResult.FailureType.MISSING_OPERATOR, String.format("Unknown conjunction operator - %s.", + operationName)); + } + } - private RulesResult performAndOperation(final List resolvedOperands) { - for (RulesResult rulesResult : resolvedOperands) { - if (!rulesResult.isSuccess()) { - return new RulesResult(RulesResult.FailureType.CONDITION_FAILED, "AND operation returned false."); - } - } + private RulesResult performAndOperation(final List resolvedOperands) { + for (RulesResult rulesResult : resolvedOperands) { + if (!rulesResult.isSuccess()) { + return new RulesResult(RulesResult.FailureType.CONDITION_FAILED, "AND operation returned false."); + } + } - return RulesResult.SUCCESS; - } + return RulesResult.SUCCESS; + } - private RulesResult performOrOperation(final List resolvedOperands) { - for (RulesResult rulesResult : resolvedOperands) { - if (rulesResult.isSuccess()) { - return RulesResult.SUCCESS; - } - } + private RulesResult performOrOperation(final List resolvedOperands) { + for (RulesResult rulesResult : resolvedOperands) { + if (rulesResult.isSuccess()) { + return RulesResult.SUCCESS; + } + } - return new RulesResult(RulesResult.FailureType.CONDITION_FAILED, "OR operation returned false."); - } + return new RulesResult(RulesResult.FailureType.CONDITION_FAILED, "OR operation returned false."); + } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/RulesEngine.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/RulesEngine.java index 1614712da..0e301d941 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/RulesEngine.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/rulesengine/RulesEngine.java @@ -15,6 +15,7 @@ import java.util.List; public class RulesEngine { + private final static String LOG_TAG = "RulesEngine"; private final Object rulesEngineMutex = new Object(); private final Evaluating evaluator; private final Transforming transformer; @@ -32,8 +33,11 @@ public List evaluate(final TokenFinder tokenFinder) { List triggerRules = new ArrayList<>(); for (final T rule : rules) { - if (rule.getEvaluable().evaluate(context).isSuccess()) { + RulesResult result = rule.getEvaluable().evaluate(context); + if (result.isSuccess()) { triggerRules.add(rule); + } else { + Log.debug(LOG_TAG, result.getFailureMessage()); } } return triggerRules; diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngineModuleTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngineModuleTests.kt new file mode 100644 index 000000000..b90b8fca3 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngineModuleTests.kt @@ -0,0 +1,462 @@ +package com.adobe.marketing.mobile.launch.rulesengine + +import com.adobe.marketing.mobile.Event +import com.adobe.marketing.mobile.ExtensionApi +import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.internal.eventhub.history.EventHistory +import com.adobe.marketing.mobile.internal.eventhub.history.EventHistoryRequest +import com.adobe.marketing.mobile.internal.eventhub.history.EventHistoryResultHandler +import com.adobe.marketing.mobile.launch.rulesengine.json.JSONRulesParser +import com.adobe.marketing.mobile.test.utility.readTestResources +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyString +import org.mockito.BDDMockito +import org.powermock.api.mockito.PowerMockito +import org.powermock.core.classloader.annotations.PrepareForTest +import org.powermock.modules.junit4.PowerMockRunner + +@RunWith(PowerMockRunner::class) +@PrepareForTest(ExtensionApi::class, MobileCore::class) +class LaunchRulesEngineModuleTests { + private lateinit var extensionApi: ExtensionApi + + private lateinit var launchRulesEngine: LaunchRulesEngine + + private val defaultEvent = Event.Builder( + "event", + "com.adobe.eventType.lifecycle", + "com.adobe.eventSource.responseContent" + ).setEventData( + mapOf( + "lifecyclecontextdata" to mapOf( + "launchevent" to "LaunchEvent" + ) + ) + ).build() + + @Before + fun setup() { + extensionApi = PowerMockito.mock(ExtensionApi::class.java) + PowerMockito.mockStatic(MobileCore::class.java) + launchRulesEngine = LaunchRulesEngine(extensionApi) + } + + @Test + fun `Test group condition`() { + val json = readTestResources("rules_module_tests/rules_testGroupLogicalOperators.json") + assertNotNull(json) + val rules = JSONRulesParser.parse(json) + assertNotNull(rules) + launchRulesEngine.replaceRules(rules) + PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "AT&T" + ) + ) + ) + val matchedRules = launchRulesEngine.process(defaultEvent) + assertEquals(1, matchedRules.size) + assertEquals(1, matchedRules[0].consequenceList.size) + assertEquals("pb", matchedRules[0].consequenceList[0].type) + } + + @Test + fun `Test historical condition`() { + val eventHistory = object : EventHistory { + override fun recordEvent(event: Event?, handler: EventHistoryResultHandler?) { + TODO("Not yet implemented") + } + + override fun getEvents( + eventHistoryRequests: Array?, + enforceOrder: Boolean, + handler: EventHistoryResultHandler? + ) { + handler?.call(1) + } + + override fun deleteEvents( + eventHistoryRequests: Array?, + handler: EventHistoryResultHandler? + ) { + TODO("Not yet implemented") + } + } + BDDMockito.given(MobileCore.getEventHistory()).willReturn(eventHistory) + val json = readTestResources("rules_module_tests/rules_testHistory.json") + assertNotNull(json) + val rules = JSONRulesParser.parse(json) + assertNotNull(rules) + launchRulesEngine.replaceRules(rules) + assertEquals(1, launchRulesEngine.process(defaultEvent).size) + } + + @Test + fun `Test matcher condition (co) - negative `() { + val json = readTestResources("rules_module_tests/rules_testMatcherCo.json") + assertNotNull(json) + val rules = JSONRulesParser.parse(json) + assertNotNull(rules) + launchRulesEngine.replaceRules(rules) + PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "Verizon" + ) + ) + ) + assertEquals(0, launchRulesEngine.process(defaultEvent).size) + } + + @Test + fun `Test matcher condition (co) - positive `() { + val json = readTestResources("rules_module_tests/rules_testMatcherCo.json") + assertNotNull(json) + val rules = JSONRulesParser.parse(json) + assertNotNull(rules) + launchRulesEngine.replaceRules(rules) + PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "AT&T" + ) + ) + ) + val matchedRules = launchRulesEngine.process(defaultEvent) + assertEquals(1, matchedRules.size) + assertEquals(1, matchedRules[0].consequenceList.size) + assertEquals("pb", matchedRules[0].consequenceList[0].type) + } + + @Test + fun `Test matcher condition (ge) - negative `() { + val json = readTestResources("rules_module_tests/rules_testMatcherGe.json") + assertNotNull(json) + val rules = JSONRulesParser.parse(json) + assertNotNull(rules) + launchRulesEngine.replaceRules(rules) + PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "launches" to 1 + ) + ) + ) + assertEquals(0, launchRulesEngine.process(defaultEvent).size) + } + + @Test + fun `Test matcher condition (ge) - positive `() { + val json = readTestResources("rules_module_tests/rules_testMatcherGe.json") + assertNotNull(json) + val rules = JSONRulesParser.parse(json) + assertNotNull(rules) + launchRulesEngine.replaceRules(rules) + PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "launches" to 2 + ) + ) + ) + val matchedRules = launchRulesEngine.process(defaultEvent) + assertEquals(1, matchedRules.size) + assertEquals(1, matchedRules[0].consequenceList.size) + assertEquals("pb", matchedRules[0].consequenceList[0].type) + } + + @Test + fun `Test matcher condition (gt) - negative `() { + val json = readTestResources("rules_module_tests/rules_testMatcherGt.json") + assertNotNull(json) + val rules = JSONRulesParser.parse(json) + assertNotNull(rules) + launchRulesEngine.replaceRules(rules) + PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "launches" to 2 + ) + ) + ) + assertEquals(0, launchRulesEngine.process(defaultEvent).size) + } + + @Test + fun `Test matcher condition (gt) - positive `() { + val json = readTestResources("rules_module_tests/rules_testMatcherGt.json") + assertNotNull(json) + val rules = JSONRulesParser.parse(json) + assertNotNull(rules) + launchRulesEngine.replaceRules(rules) + PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "launches" to 3 + ) + ) + ) + val matchedRules = launchRulesEngine.process(defaultEvent) + assertEquals(1, matchedRules.size) + assertEquals(1, matchedRules[0].consequenceList.size) + assertEquals("pb", matchedRules[0].consequenceList[0].type) + } + + @Test + fun `Test matcher condition (gt) with different types - String vs Int `() { + val json = readTestResources("rules_module_tests/rules_testMatcherGt_2_types.json") + assertNotNull(json) + val rules = JSONRulesParser.parse(json) + assertNotNull(rules) + launchRulesEngine.replaceRules(rules) + PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "launches" to 2 + ) + ) + ) + val matchedRules = launchRulesEngine.process(defaultEvent) + assertEquals(1, matchedRules.size) + assertEquals(1, matchedRules[0].consequenceList.size) + assertEquals("pb", matchedRules[0].consequenceList[0].type) + } + + @Test + fun `Test matcher condition (le) - negative `() { + val json = readTestResources("rules_module_tests/rules_testMatcherLe.json") + assertNotNull(json) + val rules = JSONRulesParser.parse(json) + assertNotNull(rules) + launchRulesEngine.replaceRules(rules) + PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "launches" to 3 + ) + ) + ) + assertEquals(0, launchRulesEngine.process(defaultEvent).size) + } + + @Test + fun `Test matcher condition (le) - positive `() { + val json = readTestResources("rules_module_tests/rules_testMatcherLe.json") + assertNotNull(json) + val rules = JSONRulesParser.parse(json) + assertNotNull(rules) + launchRulesEngine.replaceRules(rules) + PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "launches" to 2 + ) + ) + ) + val matchedRules = launchRulesEngine.process(defaultEvent) + assertEquals(1, matchedRules.size) + assertEquals(1, matchedRules[0].consequenceList.size) + assertEquals("pb", matchedRules[0].consequenceList[0].type) + } + + @Test + fun `Test matcher condition (lt) - negative `() { + val json = readTestResources("rules_module_tests/rules_testMatcherLt.json") + assertNotNull(json) + val rules = JSONRulesParser.parse(json) + assertNotNull(rules) + launchRulesEngine.replaceRules(rules) + PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "launches" to 2 + ) + ) + ) + assertEquals(0, launchRulesEngine.process(defaultEvent).size) + } + + @Test + fun `Test matcher condition (lt) - positive `() { + val json = readTestResources("rules_module_tests/rules_testMatcherLt.json") + assertNotNull(json) + val rules = JSONRulesParser.parse(json) + assertNotNull(rules) + launchRulesEngine.replaceRules(rules) + PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "launches" to 1 + ) + ) + ) + val matchedRules = launchRulesEngine.process(defaultEvent) + assertEquals(1, matchedRules.size) + assertEquals(1, matchedRules[0].consequenceList.size) + assertEquals("pb", matchedRules[0].consequenceList[0].type) + } + + @Test + fun `Test matcher condition (nc) - negative `() { + val json = readTestResources("rules_module_tests/rules_testMatcherNc.json") + assertNotNull(json) + val rules = JSONRulesParser.parse(json) + assertNotNull(rules) + launchRulesEngine.replaceRules(rules) + PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "AT&T" + ) + ) + ) + assertEquals(0, launchRulesEngine.process(defaultEvent).size) + } + + @Test + fun `Test matcher condition (nc) - positive `() { + val json = readTestResources("rules_module_tests/rules_testMatcherNc.json") + assertNotNull(json) + val rules = JSONRulesParser.parse(json) + assertNotNull(rules) + launchRulesEngine.replaceRules(rules) + PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "Verizon" + ) + ) + ) + val matchedRules = launchRulesEngine.process(defaultEvent) + assertEquals(1, matchedRules.size) + assertEquals(1, matchedRules[0].consequenceList.size) + assertEquals("pb", matchedRules[0].consequenceList[0].type) + } + + @Test + fun `Test matcher condition (ne) - negative `() { + val json = readTestResources("rules_module_tests/rules_testMatcherNe.json") + assertNotNull(json) + val rules = JSONRulesParser.parse(json) + assertNotNull(rules) + launchRulesEngine.replaceRules(rules) + PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "AT&T" + ) + ) + ) + assertEquals(0, launchRulesEngine.process(defaultEvent).size) + } + + @Test + fun `Test matcher condition (ne) - positive `() { + val json = readTestResources("rules_module_tests/rules_testMatcherNe.json") + assertNotNull(json) + val rules = JSONRulesParser.parse(json) + assertNotNull(rules) + launchRulesEngine.replaceRules(rules) + PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "Verizon" + ) + ) + ) + val matchedRules = launchRulesEngine.process(defaultEvent) + assertEquals(1, matchedRules.size) + assertEquals(1, matchedRules[0].consequenceList.size) + assertEquals("pb", matchedRules[0].consequenceList[0].type) + } + + @Test + fun `Test matcher condition (nx) - negative `() { + val json = readTestResources("rules_module_tests/rules_testMatcherNx.json") + assertNotNull(json) + val rules = JSONRulesParser.parse(json) + assertNotNull(rules) + launchRulesEngine.replaceRules(rules) + PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "AT&T" + ) + ) + ) + assertEquals(0, launchRulesEngine.process(defaultEvent).size) + } + + @Test + fun `Test matcher condition (nx) - positive `() { + val json = readTestResources("rules_module_tests/rules_testMatcherNx.json") + assertNotNull(json) + val rules = JSONRulesParser.parse(json) + assertNotNull(rules) + launchRulesEngine.replaceRules(rules) + PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "key" to "value" + ) + ) + ) + val matchedRules = launchRulesEngine.process(defaultEvent) + assertEquals(1, matchedRules.size) + assertEquals(1, matchedRules[0].consequenceList.size) + assertEquals("pb", matchedRules[0].consequenceList[0].type) + } + + @Test + fun `Test matcher condition - with different types`() { + val json = + readTestResources("rules_module_tests/rules_testMatcherWithDifferentTypesOfParameters.json") + assertNotNull(json) + val rules = JSONRulesParser.parse(json) + assertNotNull(rules) + launchRulesEngine.replaceRules(rules) + PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "launches" to 3 + ) + ) + ) + val matchedRules = launchRulesEngine.process(defaultEvent) + assertEquals(1, matchedRules.size) + } + + @Test + fun `Test transformer`() { + val json = readTestResources("rules_module_tests/rules_testTransform.json") + assertNotNull(json) + val rules = JSONRulesParser.parse(json) + assertNotNull(rules) + launchRulesEngine.replaceRules(rules) + val matchedRules = launchRulesEngine.process( + Event.Builder( + "event", + "com.adobe.eventType.lifecycle", + "com.adobe.eventSource.responseContent" + ).setEventData( + mapOf( + "lifecyclecontextdata" to mapOf( + "numberString" to "3", + "booleanValue" to true, + "intValue" to 5, + "doubleValue" to 10.3 + ) + ) + ).build() + ) + assertEquals(1, matchedRules.size) + assertEquals(1, matchedRules[0].consequenceList.size) + assertEquals("url", matchedRules[0].consequenceList[0].type) + } +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConditionTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConditionTests.kt index a9ee4b6a9..96ee843c8 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConditionTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConditionTests.kt @@ -13,6 +13,7 @@ package com.adobe.marketing.mobile.launch.rulesengine.json import com.adobe.marketing.mobile.rulesengine.ComparisonExpression import com.adobe.marketing.mobile.rulesengine.Evaluable import com.adobe.marketing.mobile.rulesengine.LogicalExpression +import com.adobe.marketing.mobile.test.utility.buildJSONObject import kotlin.test.assertTrue import org.junit.Test @@ -85,7 +86,7 @@ class JSONConditionTests { "definition": { "key": "~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches", "matcher": "ge", - "values": [] + "values": [1] } } """.trimIndent() diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequenceTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequenceTests.kt index a1e503a66..a070347f5 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequenceTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequenceTests.kt @@ -10,6 +10,7 @@ */ package com.adobe.marketing.mobile.launch.rulesengine.json +import com.adobe.marketing.mobile.test.utility.buildJSONObject import kotlin.test.assertEquals import kotlin.test.assertNotNull import org.junit.Test diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRootTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRootTests.kt index 51dd4231e..2ed0e8f2d 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRootTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRootTests.kt @@ -11,6 +11,7 @@ package com.adobe.marketing.mobile.launch.rulesengine.json +import com.adobe.marketing.mobile.test.utility.buildJSONObject import kotlin.test.assertEquals import kotlin.test.assertTrue import org.junit.Test diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleTests.kt index 3ba631894..d8049a848 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleTests.kt @@ -13,6 +13,7 @@ package com.adobe.marketing.mobile.launch.rulesengine.json import com.adobe.marketing.mobile.launch.rulesengine.LaunchRule import com.adobe.marketing.mobile.rulesengine.ComparisonExpression +import com.adobe.marketing.mobile.test.utility.buildJSONObject import kotlin.test.assertEquals import kotlin.test.assertNull import kotlin.test.assertTrue diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleUtilities.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/test/utility/JSONRuleUtilities.kt similarity index 95% rename from code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleUtilities.kt rename to code/android-core-library/src/test/java/com/adobe/marketing/mobile/test/utility/JSONRuleUtilities.kt index f61fdea42..ac73085de 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleUtilities.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/test/utility/JSONRuleUtilities.kt @@ -8,7 +8,7 @@ OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.launch.rulesengine.json +package com.adobe.marketing.mobile.test.utility import org.json.JSONArray import org.json.JSONObject diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/test/utility/TestResourceUtilities.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/test/utility/TestResourceUtilities.kt new file mode 100644 index 000000000..d1b6406a0 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/test/utility/TestResourceUtilities.kt @@ -0,0 +1,5 @@ +package com.adobe.marketing.mobile.test.utility + +internal fun readTestResources(filePath: String): String? { + return object {}.javaClass.classLoader.getResource(filePath)?.readText() +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_1.json b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_1.json new file mode 100644 index 000000000..c973a8caf --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_1.json @@ -0,0 +1,173 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.responseContent" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.launchevent", + "matcher": "ex", + "values": [] + } + } + ] + } + } + ] + } + }, + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~state.com.adobe.module.lifecycle/lifecyclecontextdata.carriername", + "matcher": "eq", + "values": [ + "AT&T" + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "RCd6959d7b48da42709b442c52b74b0e3c", + "type": "url", + "detail": { + "url": "http://adobe.com/device={%~state.com.adobe.module.lifecycle/lifecyclecontextdata.devicename%}" + } + } + ] + }, + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.responseContent" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.launchevent", + "matcher": "ex", + "values": [] + } + } + ] + } + } + ] + } + }, + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~state.com.adobe.module.lifecycle/lifecyclecontextdata.installevent", + "matcher": "ex", + "values": [] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "48181acd22b3edaebc8a447868a7df7ce629920a", + "type": "add", + "detail": { + "eventdata": { + "key": "value", + "key.subkey": "subvalue", + "ecid": "{%%ECID DATA ELEMENT%%}", + "mySharedKey": "{%~state.com.myExtension/sharedKey%}", + "myObject": { + "objKey": "objValue" + } + } + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testAttachData.json b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testAttachData.json new file mode 100644 index 000000000..bb288297a --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testAttachData.json @@ -0,0 +1,91 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.responseContent" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.launchevent", + "matcher": "ex", + "values": [] + } + } + ] + } + } + ] + } + }, + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~state.com.adobe.module.lifecycle/lifecyclecontextdata.carriername", + "matcher": "eq", + "values": [ + "AT&T" + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "RCa839e401f54a459a9049328f9b609a07", + "type": "add", + "detail": { + "eventdata": { + "attached_data": { + "key1": "value1", + "launches": "{%~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches%}" + } + } + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testAttachData_invalidJson.json b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testAttachData_invalidJson.json new file mode 100644 index 000000000..d2bff6801 --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testAttachData_invalidJson.json @@ -0,0 +1,91 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.responseContent" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.launchevent", + "matcher": "ex", + "values": [] + } + } + ] + } + } + ] + } + }, + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~state.com.adobe.module.lifecycle/lifecyclecontextdata.carriername", + "matcher": "eq", + "values": [ + "AT&T" + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "RCa839e401f54a459a9049328f9b609a07", + "type": "add", + "detail": { + "eventdata_xyz": { + "attached_data": { + "key1": "value1", + "launches": "{%~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches%}" + } + } + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventChain.json b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventChain.json new file mode 100644 index 000000000..aa21f85c9 --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventChain.json @@ -0,0 +1,158 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.edge" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.requestContent" + ] + } + } + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "48181acd22b3edaebc8a447868a7df7ce629920a", + "type": "dispatch", + "detail": { + "type" : "com.adobe.eventType.edge", + "source" : "com.adobe.eventSource.requestContent", + "eventdataaction" : "copy" + } + } + ] + }, + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.applicationLaunch" + ] + } + } + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "48181acd22b3edaebc8a447868a7df7ce629920a", + "type": "dispatch", + "detail": { + "type" : "com.adobe.eventType.edge", + "source" : "com.adobe.eventSource.requestContent", + "eventdataaction" : "copy" + } + } + ] + }, + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "dispatch", + "matcher": "eq", + "values": [ + "yes" + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "48181acd22b3edaebc8a447868a7df7ce629920a", + "type": "dispatch", + "detail": { + "type" : "com.adobe.eventType.edge", + "source" : "com.adobe.eventSource.requestContent", + "eventdataaction" : "copy" + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventCopy.json b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventCopy.json new file mode 100644 index 000000000..80146fb11 --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventCopy.json @@ -0,0 +1,62 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.applicationLaunch" + ] + } + } + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "48181acd22b3edaebc8a447868a7df7ce629920a", + "type": "dispatch", + "detail": { + "type" : "com.adobe.eventType.edge", + "source" : "com.adobe.eventSource.requestContent", + "eventdataaction" : "copy" + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventInvalidAction.json b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventInvalidAction.json new file mode 100644 index 000000000..25fe50f73 --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventInvalidAction.json @@ -0,0 +1,62 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.applicationLaunch" + ] + } + } + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "48181acd22b3edaebc8a447868a7df7ce629920a", + "type": "dispatch", + "detail": { + "type" : "com.adobe.eventType.edge", + "source" : "com.adobe.eventSource.requestContent", + "eventdataaction" : "invalid" + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventNewData.json b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventNewData.json new file mode 100644 index 000000000..d5cdf70c1 --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventNewData.json @@ -0,0 +1,67 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.applicationLaunch" + ] + } + } + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "48181acd22b3edaebc8a447868a7df7ce629920a", + "type": "dispatch", + "detail": { + "type" : "com.adobe.eventType.edge", + "source" : "com.adobe.eventSource.requestContent", + "eventdataaction" : "new", + "eventdata" : { + "key" : "value", + "key.subkey" : "subvalue", + "launches": "{%~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches%}" + } + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventNewNoData.json b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventNewNoData.json new file mode 100644 index 000000000..a6ee0d015 --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventNewNoData.json @@ -0,0 +1,62 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.applicationLaunch" + ] + } + } + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "48181acd22b3edaebc8a447868a7df7ce629920a", + "type": "dispatch", + "detail": { + "type" : "com.adobe.eventType.edge", + "source" : "com.adobe.eventSource.requestContent", + "eventdataaction" : "new" + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventNoAction.json b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventNoAction.json new file mode 100644 index 000000000..d08d9e3c8 --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventNoAction.json @@ -0,0 +1,61 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.applicationLaunch" + ] + } + } + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "48181acd22b3edaebc8a447868a7df7ce629920a", + "type": "dispatch", + "detail": { + "type" : "com.adobe.eventType.edge", + "source" : "com.adobe.eventSource.requestContent" + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventNoSource.json b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventNoSource.json new file mode 100644 index 000000000..50d551fe7 --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventNoSource.json @@ -0,0 +1,61 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.applicationLaunch" + ] + } + } + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "48181acd22b3edaebc8a447868a7df7ce629920a", + "type": "dispatch", + "detail": { + "type" : "com.adobe.eventType.edge", + "eventdataaction" : "copy" + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventNoType.json b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventNoType.json new file mode 100644 index 000000000..c1ba6d5c7 --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testDispatchEventNoType.json @@ -0,0 +1,61 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.applicationLaunch" + ] + } + } + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "48181acd22b3edaebc8a447868a7df7ce629920a", + "type": "dispatch", + "detail": { + "source" : "com.adobe.eventSource.requestContent", + "eventdataaction" : "copy" + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testModifyData.json b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testModifyData.json new file mode 100644 index 000000000..c5a6ee36d --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testModifyData.json @@ -0,0 +1,73 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.responseContent" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.launchevent", + "matcher": "ex", + "values": [] + } + } + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "RC056814a218c5424e92dc16cb29cb11bd", + "type": "mod", + "detail": { + "eventdata": { + "lifecyclecontextdata": { + "launches": "{%~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches%}", + "launchevent": null + } + } + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testModifyData_invalidJson.json b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testModifyData_invalidJson.json new file mode 100644 index 000000000..7264f4466 --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testModifyData_invalidJson.json @@ -0,0 +1,73 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.responseContent" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.launchevent", + "matcher": "ex", + "values": [] + } + } + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "RC056814a218c5424e92dc16cb29cb11bd", + "type": "mod", + "detail": { + "eventdata_xyz": { + "lifecyclecontextdata": { + "launches": "{%~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches%}", + "launchevent": null + } + } + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testUrlenc.json b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testUrlenc.json new file mode 100644 index 000000000..d0b3b6643 --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testUrlenc.json @@ -0,0 +1,68 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.responseContent" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.launchevent", + "matcher": "ex", + "values": [] + } + } + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "RC48ef3f5e83c84405a3da6cc5128c090c", + "type": "url", + "detail": { + "url": "http://www.adobe.com/a={%urlenc(~state.com.adobe.module.lifecycle/lifecyclecontextdata.carriername)%}" + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testUrlenc_invalidFnName.json b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testUrlenc_invalidFnName.json new file mode 100644 index 000000000..ccf65171e --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/consequence_rules_testUrlenc_invalidFnName.json @@ -0,0 +1,68 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.responseContent" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.launchevent", + "matcher": "ex", + "values": [] + } + } + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "RC48ef3f5e83c84405a3da6cc5128c090c", + "type": "url", + "detail": { + "url": "http://www.adobe.com/a={%urlenc1(~state.com.adobe.module.lifecycle/lifecyclecontextdata.carriername)%}" + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/rules_testGroupLogicalOperators.json b/code/android-core-library/src/test/resources/rules_module_tests/rules_testGroupLogicalOperators.json new file mode 100644 index 000000000..30bc43f42 --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/rules_testGroupLogicalOperators.json @@ -0,0 +1,87 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.responseContent" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.launchevent", + "matcher": "ex", + "values": [] + } + } + ] + } + } + ] + } + }, + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~state.com.adobe.module.lifecycle/lifecyclecontextdata.carriername", + "matcher": "eq", + "values": [ + "AT&T" + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "RC50fb35a797484dcda01eb68627de9a26", + "type": "pb", + "detail": { + "timeout": 0, + "templateurl": "https://www.adobe.com" + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/rules_testHistory.json b/code/android-core-library/src/test/resources/rules_module_tests/rules_testHistory.json new file mode 100644 index 000000000..b5491291c --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/rules_testHistory.json @@ -0,0 +1,33 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "historical", + "definition": { + "events": [ + { + "key": "value", + "key2": 3 + } + ], + "searchType": "any", + "from": 123456789, + "to": 234567890, + "matcher": "eq", + "value": 1 + } + }, + "consequences": [ + { + "id": "RC2500e6b0140744d49e6fd503a55a66d4", + "type": "pb", + "detail": { + "timeout": 0, + "templateurl": "http://www.adobe.com" + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherCo.json b/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherCo.json new file mode 100644 index 000000000..efc476290 --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherCo.json @@ -0,0 +1,87 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.responseContent" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.launchevent", + "matcher": "ex", + "values": [] + } + } + ] + } + } + ] + } + }, + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~state.com.adobe.module.lifecycle/lifecyclecontextdata.carriername", + "matcher": "co", + "values": [ + "AT" + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "RCc223ec648df44fbbaab737e6cc6da50e", + "type": "pb", + "detail": { + "timeout": 0, + "templateurl": "http://www.adobe.com" + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherGe.json b/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherGe.json new file mode 100644 index 000000000..df9902ee0 --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherGe.json @@ -0,0 +1,87 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.responseContent" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.launchevent", + "matcher": "ex", + "values": [] + } + } + ] + } + } + ] + } + }, + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches", + "matcher": "ge", + "values": [ + 2 + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "RCff10c34c93f3423a8842cf88a578c52d", + "type": "pb", + "detail": { + "timeout": 0, + "templateurl": "http://www.adobe.com" + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherGt.json b/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherGt.json new file mode 100644 index 000000000..60655928b --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherGt.json @@ -0,0 +1,87 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.responseContent" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.launchevent", + "matcher": "ex", + "values": [] + } + } + ] + } + } + ] + } + }, + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches", + "matcher": "gt", + "values": [ + 2 + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "RCad46454dd97043b68ef781eebebd9b2c", + "type": "pb", + "detail": { + "timeout": 0, + "templateurl": "http://www.adobe.com" + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherGt_2_types.json b/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherGt_2_types.json new file mode 100644 index 000000000..94697a033 --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherGt_2_types.json @@ -0,0 +1,69 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + } + ] + } + } + ] + } + }, + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~timestampu", + "matcher": "gt", + "values": [ + 1622098800 + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "RCad46454dd97043b68ef781eebebd9b2c", + "type": "pb", + "detail": { + "timeout": 0, + "templateurl": "http://www.adobe.com" + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherLe.json b/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherLe.json new file mode 100644 index 000000000..d4493b4f7 --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherLe.json @@ -0,0 +1,87 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.responseContent" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.launchevent", + "matcher": "ex", + "values": [] + } + } + ] + } + } + ] + } + }, + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches", + "matcher": "le", + "values": [ + 2 + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "RC41cd28bbf4564162855c114271b5b56b", + "type": "pb", + "detail": { + "timeout": 0, + "templateurl": "http://www.adobe.com" + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherLt.json b/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherLt.json new file mode 100644 index 000000000..e1b2ebb85 --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherLt.json @@ -0,0 +1,87 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.responseContent" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.launchevent", + "matcher": "ex", + "values": [] + } + } + ] + } + } + ] + } + }, + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches", + "matcher": "lt", + "values": [ + 2 + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "RC41cd28bbf4564162855c114271b5b56b", + "type": "pb", + "detail": { + "timeout": 0, + "templateurl": "http://www.adobe.com" + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherNc.json b/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherNc.json new file mode 100644 index 000000000..b44180540 --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherNc.json @@ -0,0 +1,87 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.responseContent" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.launchevent", + "matcher": "ex", + "values": [] + } + } + ] + } + } + ] + } + }, + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~state.com.adobe.module.lifecycle/lifecyclecontextdata.carriername", + "matcher": "nc", + "values": [ + "AT" + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "RCc223ec648df44fbbaab737e6cc6da50e", + "type": "pb", + "detail": { + "timeout": 0, + "templateurl": "http://www.adobe.com" + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherNe.json b/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherNe.json new file mode 100644 index 000000000..db4245f3a --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherNe.json @@ -0,0 +1,87 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.responseContent" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.launchevent", + "matcher": "ex", + "values": [] + } + } + ] + } + } + ] + } + }, + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~state.com.adobe.module.lifecycle/lifecyclecontextdata.carriername", + "matcher": "ne", + "values": [ + "AT&T" + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "RC2500e6b0140744d49e6fd503a55a66d4", + "type": "pb", + "detail": { + "timeout": 0, + "templateurl": "http://www.adobe.com" + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherNx.json b/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherNx.json new file mode 100644 index 000000000..4683a2bd4 --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherNx.json @@ -0,0 +1,85 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.responseContent" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.launchevent", + "matcher": "ex", + "values": [] + } + } + ] + } + } + ] + } + }, + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~state.com.adobe.module.lifecycle/lifecyclecontextdata.carriername", + "matcher": "nx", + "values": [] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "RC5f742d53b4af48d0bee02f9e18ff1bf3", + "type": "pb", + "detail": { + "timeout": 0, + "templateurl": "http://www.adobe.com" + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherWithDifferentTypesOfParameters.json b/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherWithDifferentTypesOfParameters.json new file mode 100644 index 000000000..195652567 --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/rules_testMatcherWithDifferentTypesOfParameters.json @@ -0,0 +1,87 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.responseContent" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.launchevent", + "matcher": "ex", + "values": [] + } + } + ] + } + } + ] + } + }, + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches", + "matcher": "gt", + "values": [ + "2" + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "RCad46454dd97043b68ef781eebebd9b2c", + "type": "pb", + "detail": { + "timeout": 0, + "templateurl": "http://www.adobe.com" + } + } + ] + } + ] +} diff --git a/code/android-core-library/src/test/resources/rules_module_tests/rules_testTransform.json b/code/android-core-library/src/test/resources/rules_module_tests/rules_testTransform.json new file mode 100644 index 000000000..01335858c --- /dev/null +++ b/code/android-core-library/src/test/resources/rules_module_tests/rules_testTransform.json @@ -0,0 +1,148 @@ +{ + "version": 1, + "rules": [ + { + "condition": { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "or", + "conditions": [ + { + "type": "group", + "definition": { + "logic": "and", + "conditions": [ + { + "type": "matcher", + "definition": { + "key": "~type", + "matcher": "eq", + "values": [ + "com.adobe.eventType.lifecycle" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "~source", + "matcher": "eq", + "values": [ + "com.adobe.eventSource.responseContent" + ] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.numberString", + "matcher": "eq", + "values": [3] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.numberString", + "matcher": "eq", + "values": ["3"] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.numberString", + "matcher": "eq", + "values": [false] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.numberString", + "matcher": "eq", + "values": [false] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.booleanValue", + "matcher": "eq", + "values": [true] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.booleanValue", + "matcher": "eq", + "values": [1] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.booleanValue", + "matcher": "eq", + "values": ["true"] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.intValue", + "matcher": "eq", + "values": ["5"] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.intValue", + "matcher": "eq", + "values": [5] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.doubleValue", + "matcher": "eq", + "values": [10.3] + } + }, + { + "type": "matcher", + "definition": { + "key": "lifecyclecontextdata.doubleValue", + "matcher": "co", + "values": ["10."] + } + } + ] + } + } + ] + } + } + ] + } + }, + "consequences": [ + { + "id": "RC48ef3f5e83c84405a3da6cc5128c090c", + "type": "url", + "detail": { + "url": "http://www.adobe.com" + } + } + ] + } + ] +} From d3d17b0a9a011683520f44c4e4eab2463ff66130 Mon Sep 17 00:00:00 2001 From: Yansong Date: Mon, 6 Jun 2022 16:29:57 -0600 Subject: [PATCH 066/476] add tests --- code/android-core-library/build.gradle | 4 +- ...idThirdPartyExtensionsFunctionalTests.java | 2906 ++++++++--------- .../marketing/mobile/AbstractE2ETest.java | 4 +- .../mobile/ExtensionTestingHelper.java | 561 ++-- .../adobe/marketing/mobile/TestHelper.java | 7 - .../adobe/marketing/mobile/MobileCore.java | 7 +- .../com/adobe/marketing/mobile/CoreTests.java | 703 ---- .../marketing/mobile/MobileCoreTests.java | 812 +---- .../adobe/marketing/mobile/MobileCoreTests.kt | 732 +++++ .../com/adobe/marketing/mobile/MockCore.java | 150 - .../adobe/marketing/mobile/TestableCore.java | 21 - 11 files changed, 2591 insertions(+), 3316 deletions(-) delete mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/CoreTests.java create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/MobileCoreTests.kt delete mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/MockCore.java delete mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/TestableCore.java diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index fb3b21d71..4cd69bddf 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -118,8 +118,8 @@ dependencies { testImplementation "junit:junit:4.13.2" //noinspection GradleDependency testImplementation "org.mockito:mockito-core:2.22.0" - testImplementation 'org.powermock:powermock-api-mockito2:2.0.0' - testImplementation 'org.powermock:powermock-module-junit4:2.0.0' + testImplementation 'org.powermock:powermock-api-mockito2:2.0.9' + testImplementation 'org.powermock:powermock-module-junit4:2.0.9' testImplementation 'commons-codec:commons-codec:1.15' testImplementation 'org.robolectric:robolectric:3.6.2' //noinspection GradleDependency diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java index 813a4afb7..fa8aa788e 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java @@ -1,1453 +1,1453 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; - -import android.support.test.runner.AndroidJUnit4; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - - -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -/** - * Class {@link AndroidThirdPartyExtensionsFunctionalTests} that defines all the necessary test cases to test a third party extension {@link TestableExtension} that extends {@link ExtensionListener} class of the Adobe Experience Platform SDK. - * - * This covers all the test cases listed in the document to run as part of the CI build system. - * - * https://wiki.corp.adobe.com/pages/viewpage.action?spaceKey=adms&title=Mobile+SDK+v5+Extensions+Module+Test+Plan - * - * @author Adobe - * @version 5.0 - */ - -@RunWith(AndroidJUnit4.class) -public class AndroidThirdPartyExtensionsFunctionalTests extends AbstractE2ETest { - - private static final String LOG_TAG = AndroidThirdPartyExtensionsFunctionalTests.class.getSimpleName(); - - private TestHelper testHelper = new TestHelper(); - private ExtensionTestingHelper extensionTestingHelper = new ExtensionTestingHelper(); - private AsyncHelper asyncHelper = new AsyncHelper(); - - private List configListenerTypes = new ArrayList(); - private List identityListenerTypes = new ArrayList(); - private List customListenerTypes = new ArrayList(); - private List analyticsListenerTypes = new ArrayList(); - private List wildcardListener = new ArrayList(); - - static final String CONFIGURATION_SHARED_STATE = "com.adobe.module.configuration"; - static final String EVENT_SOURCE_SHARED_STATE = "com.adobe.eventSource.sharedstate"; - static final String EVENT_TYPE_CONFIGURATION = "com.adobe.eventType.configuration"; - - private final Object executorMutex = new Object(); - private ExecutorService executor; - private ConcurrentLinkedQueue unprocessedEvents; - private long noProcessedEvents; - - private Map eventData = new HashMap(); - Map subEventData = new HashMap(); - Map pairedEventData = new HashMap(); - ExtensionError extensionError; - Map configMap = new HashMap(); - - @Before - public void setUp() { - super.setUp(); - testHelper.cleanCache(defaultContext); - MobileCore.setLogLevel(LoggingMode.DEBUG); - MobileCore.start(null); - testableNetworkService.resetTestableNetworkService(); - - configListenerTypes.add(new ListenerType("com.adobe.eventType.configuration", "com.adobe.eventSource.responseContent")); - configListenerTypes.add(new ListenerType("com.adobe.eventType.configuration", "com.adobe.eventSource.requestContent")); - - identityListenerTypes.add(new ListenerType("com.adobe.eventType.identity", "com.adobe.eventSource.requestIdentity")); - identityListenerTypes.add(new ListenerType("com.adobe.eventType.identity", "com.adobe.eventSource.responseIdentity")); - - analyticsListenerTypes.add(new ListenerType("com.adobe.eventType.analytics", "com.adobe.eventSource.requestContent")); - analyticsListenerTypes.add(new ListenerType("com.adobe.eventType.analytics", "com.adobe.eventSource.requestIdentity")); - analyticsListenerTypes.add(new ListenerType("com.adobe.eventType.analytics", "com.adobe.eventSource.responseContent")); - analyticsListenerTypes.add(new ListenerType("com.adobe.eventType.analytics", "com.adobe.eventSource.responseIdentity")); - analyticsListenerTypes.add(new ListenerType("com.adobe.eventType.generic.track", - "com.adobe.eventSource.requestContent")); - - wildcardListener.add(new ListenerType("com.adobe.eventtype._wildcard_", "com.adobe.eventsource._wildcard_")); - - customListenerTypes.add(new ListenerType("com.example.testable.custom", "com.example.testable.request")); - - subEventData.put("key0", "value0"); - subEventData.put("key1", "value1"); - subEventData.put("key2", "value2"); - Map customElement = new HashMap(); - // customElement.put("customByte", (byte)127); - // customElement.put("customFloat", new Float(3.14)); - // customElement.put("customShort", (short)300); - customElement.put("customInt", new Integer(123)); - customElement.put("customLong", (long) 300); - customElement.put("customChar", 'c'); - customElement.put("customString", "string1"); - customElement.put("customNull", null); - customElement.put("customBoolean", true); - customElement.put("customList", customListenerTypes); - customElement.put("customDouble", new Double(3.14)); - customElement.put("customMap", subEventData); - Map customData = new HashMap(); - customData.put("customElement", customElement); - eventData.put("customData", customData); - - pairedEventData.put("ResponseKey1", "ResponseData1"); - pairedEventData.put("ResponseKey2", "ResponseData2"); - pairedEventData.put("ResponseKey3", "ResponseData3"); - } - - @After - public void tearDown() { - asyncHelper.waitForAppThreads(500, true); - super.tearDown(); - } - - public void updateConfiguration() { - configMap.put("build.environment", "dev"); - configMap.put("myExtension.server", "mydomain.dev.com"); - configMap.put("global.privacy", "optedin"); - configMap.put("experienceCloud.org", "972C898555E9F7BC7F000101@AdobeOrg"); - configMap.put("analytics.server", "obumobile5.sc.omtrdc.net"); - configMap.put("analytics.rsids", "mobile5the.v5.show"); - configMap.put("analytics.offlineEnabled", true); - configMap.put("analytics.referrerTimeout", 15); - configMap.put("analytics.backdatePreviousSessionInfo", true); - configMap.put("identity.adidEnabled", true); - configMap.put("acquisition.server", "c00.adobe.com"); - configMap.put("rules.url", "https://s3.amazonaws.com/ams-qe/ios-sdk-test-app-rules.zip"); - configMap.put("target.clientCode", "yourclientcode"); - configMap.put("target.timeout", 5); - configMap.put("audience.server", "omniture.demdex.net"); - configMap.put("audience.timeout", 5); - configMap.put("analytics.aamForwardingEnabled", false); - configMap.put("analytics.batchLimit", 0); - configMap.put("lifecycle.sessionTimeout", 300); - MobileCore.updateConfiguration(configMap); - } - - public ExecutorService getExecutor() { - synchronized (executorMutex) { - if (executor == null) { - executor = Executors.newFixedThreadPool(2); - } - - return executor; - } - } - - // Test Case No : 1 - // Register A Third Party Extension using registerExtension API - @Test - public void testRegisterExtension_whenValidData_returnsNoError() { - - // setup - String extensionName = "ThirdPartyExtension01"; - // test - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, - configListenerTypes); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - } - - // Test Case No : 2 & 20 - // Get the Name Of A ThirdParty Extension using getName API in Android and name in iOS - @Test - public void testGetName_whenExtensionRegistered_returnsName() { - - // setup - String extensionName = "ThirdPartyExtension02"; - // test - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, - configListenerTypes); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertEquals(returnStatus.extensionName, extensionName); - } - - // Test Case No : 2a - @Test - public void testGetExtensionInstance_whenExtensionNotRegistered_returnsNull() { - - // setup - String extensionName = "ThirdPartyExtension02a"; - // test - Extension extension = extensionTestingHelper.getExtensionInstance(extensionName); - // verify - assertNull(extension); - } - - // Test Case No : 3 & 20 - // Get the Version Of A ThirdParty Extension using getVersion API in Android and version in iOS - @Test - public void testGetVersion_whenExtensionRegistered_returnsVersion() { - - // setup - String extensionName = "ThirdPartyExtension03"; - // test - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(returnStatus.extensionVersion.contains("1.0.0")); - } - - // Test Case No : 4 - // Unregister A Third Party Extension using unregisterExtension API - @Test - public void testUnRegisterExtension_whenExtensionRegistered_returnsTrue() { - - // setup - String extensionName = "ThirdPartyExtension04"; - // test - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); - boolean unregisterStatus = extensionTestingHelper.unregisterExtension(extensionName); - // verify - assertTrue(unregisterStatus); - assertFalse(extensionTestingHelper.isRegistered(extensionName)); - } - - // Test Case No : 5 - // OnUnregistered Call Of A Third Party Extension using unregisterExtension API in Android and onUnregister in IOS - @Test - public void testOnUnregistered_whenExtensionUnRegistered_OnUnregisteredIsCalled() { - - // setup - String extensionName = "ThirdPartyExtension05"; - // test - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); - extensionTestingHelper.confirmExtensionUnregisteredCall = ""; - boolean unregisterStatus = extensionTestingHelper.unregisterExtension(extensionName); - // verify - assertTrue(unregisterStatus); - assertEquals(returnStatus.extensionName, extensionName); - assertFalse(extensionTestingHelper.isRegistered(extensionName)); - assertTrue(extensionTestingHelper.confirmExtensionUnregisteredCall.contains("ConfirmedByTestableExtension")); - } - - // Test Case No : 6 - // onUnexpectedError Call Of A Third Party Extension when trying - // to a register Extension API for various errors mentioned in the ExtensionError Class - @Test - public void testRegisterExtension_whenNullName_returnsError() { - - // setup - String extensionName = null; - // test - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); - // verify - assertEquals(1, returnStatus.extensionUnexpectedError.getErrorCode().getErrorCode()); - assertEquals("extension.bad_extension_name", returnStatus.extensionUnexpectedError.getErrorCode().getErrorName()); - } - - // Test Case No : 6a - @Test - public void testRegisterExtension_whenEmptyName_returnsError() { - - // setup - String extensionName = ""; - // test - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); - // verify - assertEquals(1, returnStatus.extensionUnexpectedError.getErrorCode().getErrorCode()); - assertEquals("extension.bad_extension_name", returnStatus.extensionUnexpectedError.getErrorCode().getErrorName()); - } - - // Test Case No : 7 & 21 - // Register a listener to listen an identity event using registerEventListener API for stateowner like com.adobe.module.identity - @Test - public void testRegisterListeners_whenEventsDispatched_eventsReceivedByRightListener() { - - // setup - String extensionName = "ThirdPartyExtension07"; - // test - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, identityListenerTypes); - extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(0), eventData); - extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(1), subEventData); - Event event1 = extensionTestingHelper.getLastEventHeardByListener(extensionName, identityListenerTypes.get(0)); - Event event2 = extensionTestingHelper.getLastEventHeardByListener(extensionName, identityListenerTypes.get(1)); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - - assertTrue(identityListenerTypes.get(0).eventType.equalsIgnoreCase(event1.getType())); - assertTrue(identityListenerTypes.get(0).eventSource.equalsIgnoreCase(event1.getSource())); - assertEquals(eventData, event1.getEventData()); - - assertTrue(identityListenerTypes.get(1).eventType.equalsIgnoreCase(event2.getType())); - assertTrue(identityListenerTypes.get(1).eventSource.equalsIgnoreCase(event2.getSource())); - assertEquals(subEventData, event2.getEventData()); - } - - // Test Case No : 8 - // OnUnregistered Call in A registered listener will be called while an extension - // gets unregistered using unregisterExtension API - @Test - public void testOnUnregisteredCall_whenUnregisterExtension_OnUnregisteredCallOfListenerCalled() { - - // setup - String extensionName = "ThirdPartyExtension08"; - // test - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, identityListenerTypes); - extensionTestingHelper.confirmListenerUnregisteredCall = ""; - extensionTestingHelper.confirmExtensionUnregisteredCall = ""; - boolean unregisterStatus = extensionTestingHelper.unregisterExtension(extensionName); - // verify - assertTrue(unregisterStatus); - assertFalse(extensionTestingHelper.isRegistered(extensionName)); - assertTrue(extensionTestingHelper.confirmExtensionUnregisteredCall.contains("ConfirmedByTestableExtension")); - assertTrue(extensionTestingHelper.confirmListenerUnregisteredCall.contains("ConfirmedByTestableListener")); - } - - // Test Case No : 9 - // Register A Listener to listen a custom event using registerEventListener API - @Test - public void testRegisterAListener_whenACustomEvent_returnsNoError() { - - // setup - String extensionName = "ThirdPartyExtension07"; - // test - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); - EventListener eventListener = extensionTestingHelper.getListenerInstance(extensionName, customListenerTypes.get(0)); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertTrue(eventListener.getEventType().getName().equalsIgnoreCase("com.example.testable.custom")); - assertTrue(eventListener.getEventSource().getName().equalsIgnoreCase("com.example.testable.request")); - } - - // Test Case No : 10 - // Register A Wildcard Listener to listen all the events using registerWildcardListener API - @Test - public void testRegisterWildcardListener_whenRegistered_returnsNoError() { - - // setup - String extensionName = "ThirdPartyExtension10"; - // test - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, wildcardListener); - EventListener eventListener = extensionTestingHelper.getListenerInstance(extensionName, wildcardListener.get(0)); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertEquals(eventListener.getEventType().getName(), wildcardListener.get(0).eventType); - assertEquals(eventListener.getEventSource().getName(), wildcardListener.get(0).eventSource); - } - - // Test Case No : 10a - // Dispatch events of types identity, analytics, custom, and config using ACPCore dispatchEvent API - // And confirm that the wildcard listener is able to hear all those dispatched. - @Test - public void testDispatchEvent_whenWildcardListenerRegistered_hearsAllTheEventsAndreturnsNoError() { - - // setup - String extensionName = "ThirdPartyExtension11a"; - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, wildcardListener); - EventListener eventListener = extensionTestingHelper.getListenerInstance(extensionName, wildcardListener.get(0)); - // test - extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(0), eventData); - extensionTestingHelper.dispatchAnEvent(analyticsListenerTypes.get(0), subEventData); - extensionTestingHelper.dispatchAnEvent(customListenerTypes.get(0), eventData); - extensionTestingHelper.dispatchAnEvent(configListenerTypes.get(0), subEventData); - List eventsHearedByWildcardListener = extensionTestingHelper.getAllEventsHeardByListener(extensionName, - wildcardListener.get(0)); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertEquals(eventListener.getEventType().getName(), wildcardListener.get(0).eventType); - assertEquals(eventListener.getEventSource().getName(), wildcardListener.get(0).eventSource); - assertTrue(eventsHearedByWildcardListener.get(1).getEventType().getName().equalsIgnoreCase(identityListenerTypes.get( - 0).eventType)); - assertTrue(eventsHearedByWildcardListener.get(1).getEventSource().getName().equalsIgnoreCase(identityListenerTypes.get( - 0).eventSource)); - assertTrue(eventsHearedByWildcardListener.get(1).getEventData().equals(eventData)); - assertTrue(eventsHearedByWildcardListener.get(2).getEventType().getName().equalsIgnoreCase(analyticsListenerTypes.get( - 0).eventType)); - assertTrue(eventsHearedByWildcardListener.get(2).getEventSource().getName().equalsIgnoreCase(analyticsListenerTypes.get( - 0).eventSource)); - assertTrue(eventsHearedByWildcardListener.get(2).getEventData().equals(subEventData)); - assertTrue(eventsHearedByWildcardListener.get(3).getEventType().getName().equalsIgnoreCase(customListenerTypes.get( - 0).eventType)); - assertTrue(eventsHearedByWildcardListener.get(3).getEventSource().getName().equalsIgnoreCase(customListenerTypes.get( - 0).eventSource)); - assertTrue(eventsHearedByWildcardListener.get(3).getEventData().equals(eventData)); - assertTrue(eventsHearedByWildcardListener.get(4).getEventType().getName().equalsIgnoreCase(configListenerTypes.get( - 0).eventType)); - assertTrue(eventsHearedByWildcardListener.get(4).getEventSource().getName().equalsIgnoreCase(configListenerTypes.get( - 0).eventSource)); - assertEquals(eventsHearedByWildcardListener.get(4).getEventData(), subEventData); - } - - // Test Case No : 11 & 12 - // Dispatch A Custom Event using ACPCore dispatchEvent API - @Test - public void testDispatchEvent_whenCustomEvent_returnsTrue() { - - // setup - ListenerType listenerType = customListenerTypes.get(0); - // test - boolean dispatchStatus = extensionTestingHelper.dispatchAnEvent(listenerType, eventData); - // verify - assertTrue(dispatchStatus); - } - - - // Test Case No : 13 & 14 - // Dispatch an event using ACPCore dispatchResponseEvent API - @Test - public void testDispatchEventWithResponseCallback_whenPaired_returnsPairedEventAndData() throws InterruptedException { - - // setup - String extensionName = "ThirdPartyExtension13"; - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); - TestableListener eventListener = (TestableListener) extensionTestingHelper.getListenerInstance(extensionName, - customListenerTypes.get(0)); - eventListener.setDispatchBehavior("doDispatchResponseEvent"); - ListenerType listenerType = customListenerTypes.get(0); - // test - Event responseEvent = new Event.Builder("DispatchedEvent", listenerType.eventType, - listenerType.eventSource).setEventData(eventData).build(); - final List result = new ArrayList(); - final CountDownLatch latch = new CountDownLatch(1); - AdobeCallback dispatchCallback = new AdobeCallback() { - @Override - public void call(Event value) { - result.add(value); - latch.countDown(); - } - }; - boolean dispatchStatus = MobileCore.dispatchEventWithResponseCallback(responseEvent, dispatchCallback, null); - latch.await(10, TimeUnit.SECONDS); - // verify - assertTrue(dispatchStatus); - assertTrue(result.get(0).getType().equalsIgnoreCase("com.adobe.eventtype.pairedresponse")); - assertTrue(result.get(0).getSource().equalsIgnoreCase("com.example.testable.pairedrequest")); - assertEquals(pairedEventData, result.get(0).getEventData()); - } - - // Test Case No : 15, 17 & 36 - // Get Shared Event State Owned By A Configuration Event using getSharedEventState API - // with the extension name as the stateowner like - // com.adobe.module.identity, com.adobe.module.configuration - @Test - public void testGetSharedEventState_whenConfigEvent_returnsAppropriateSharedState() { - - // setup - String extensionName = "ThirdPartyExtension15"; - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, - configListenerTypes); - TestableExtension testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); - - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - } - }; - // test - updateConfiguration(); - Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, - configListenerTypes.get(0).eventSource).setEventData(null).setEventNumber(100).build(); - asyncHelper.waitForAppThreads(500, true); - Map configurationSharedState = - testableExtension.getApi().getSharedEventState(CONFIGURATION_SHARED_STATE, event, errorCallback); - Event eventHeard = extensionTestingHelper.getLastEventHeardByListener(extensionName, configListenerTypes.get(0)); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertEquals(configMap, configurationSharedState); - assertEquals(configMap, eventHeard.getEventData()); - - } - - // Test Case No : 16 - // Get Shared Event State Owned By A Custom Event using getSharedEventState API for stateowner - // as extensionName. - @Test - public void testGetSharedEventState_whenItIsSet_returnsAppropriateSharedState() { - - // setup - String extensionName = "Extension18"; - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, - customListenerTypes); - TestableExtension testableExtension = (TestableExtension) - extensionTestingHelper.getExtensionInstance(extensionName); - - Event event = new Event.Builder("DispatchedEvent", customListenerTypes.get(0).eventType, - customListenerTypes.get(0).eventSource).setEventData(eventData).build(); - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - } - }; - boolean status1 = testableExtension.getApi().setSharedEventState(subEventData, event, errorCallback); - - Map sharedState = testableExtension.getApi().getSharedEventState( - testableExtension.getName(), event, errorCallback); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertEquals(subEventData, sharedState); - } - - // Test Case No : 18 - // Set Shared Event State that is not tied to an event using setSharedEventState API - @Test - public void testSetAndGetSharedEventState_whenNullEvent_setsAndGetsAppropriateSharedState() { - - // setup - String extensionName = "Extension18"; - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, - configListenerTypes); - TestableExtension testableExtension = (TestableExtension) - extensionTestingHelper.getExtensionInstance(extensionName); - - Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, - configListenerTypes.get(0).eventSource).setEventData(eventData).build(); - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - } - }; - boolean status1 = testableExtension.getApi().setSharedEventState(subEventData, event, errorCallback); - - Map sharedState = testableExtension.getApi().getSharedEventState( - testableExtension.getName(), event, errorCallback); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertEquals(subEventData, sharedState); - } - - // Test Case No : 19 - // Clear Shared Event States Of A Third Party Extension without affecting - // the Shared Event States Of other extensions using clearSharedEventStates API - @Test - public void testClearSharedEventStates_whenMultipleRegisteredExtensions_doesNotAffectSharedStateOfOtherExtensions() { - - // setup - CreateExtensionResponse returnStatus1 = extensionTestingHelper.registerExtension("ThirdPartyExtensionOne", - configListenerTypes); - TestableExtension testableExtension1 = (TestableExtension) - extensionTestingHelper.getExtensionInstance("ThirdPartyExtensionOne"); - CreateExtensionResponse returnStatus2 = extensionTestingHelper.registerExtension("ThirdPartyExtensionTwo", - configListenerTypes); - TestableExtension testableExtension2 = (TestableExtension) - extensionTestingHelper.getExtensionInstance("ThirdPartyExtensionTwo"); - Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, - configListenerTypes.get(0).eventSource).setEventData(eventData).build(); - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - } - }; - boolean status1 = testableExtension1.getApi().setSharedEventState(subEventData, event, errorCallback); - boolean status2 = testableExtension2.getApi().setSharedEventState(subEventData, event, errorCallback); - ExtensionErrorCallback errorCallback1 = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - } - }; - Map sharedStateBeforeClearing1 = testableExtension1.getApi().getSharedEventState( - testableExtension1.getName(), event, errorCallback1); - Map sharedStateBeforeClearing2 = testableExtension2.getApi().getSharedEventState( - testableExtension2.getName(), event, errorCallback1); - ExtensionErrorCallback errorCallback2 = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - Log.debug(LOG_TAG, String.format("An error occurred while clearing the shared states %d %s", - extensionError.getErrorCode(), extensionError.getErrorName())); - } - }; - // test - testableExtension1.getApi().clearSharedEventStates(errorCallback2); - Map getSharedStateAfterClearing1 = testableExtension1.getApi().getSharedEventState( - testableExtension1.getName(), event, errorCallback1); - Map getSharedStateAfterClearing2 = testableExtension2.getApi().getSharedEventState( - testableExtension2.getName(), event, errorCallback1); - // verify - assertNull(returnStatus1.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionOne")); - assertNull(returnStatus2.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionTwo")); - assertEquals(subEventData, sharedStateBeforeClearing1); - assertEquals(subEventData, sharedStateBeforeClearing2); - assertNull(getSharedStateAfterClearing1); - assertEquals(subEventData, getSharedStateAfterClearing2); - } - - // Test Case No : 22 - // Make a copy of the event object received using event.copy() method, - // and the correctness of the copied event object. - @Test - public void testCopyEvent_whenCopied_returnsTheCopiedEvent() { - - // setup - ListenerType listenerType = customListenerTypes.get(0); - Event event = new Event.Builder("DispatchedEvent", listenerType.eventType, - listenerType.eventSource).setEventData(eventData).build(); - // setup - Event copiedEvent = event.copy(); - // verify - assertEquals(eventData, copiedEvent.getEventData()); - assertEquals(listenerType.eventType, copiedEvent.getType()); - assertEquals(listenerType.eventSource, copiedEvent.getSource()); - } - - // Test Case No : 23 - // Register the same extension multiple times - should not register, the initial extension - // should still be registered and receive events, - @Test - public void testCreateExtension_whenDuplicateName_returnsError() { - - // setup - String extensionName = "ThirdPartyExtension23"; - // test - CreateExtensionResponse returnStatus1 = extensionTestingHelper.registerExtension(extensionName, - configListenerTypes); - CreateExtensionResponse returnStatus2 = extensionTestingHelper.registerExtension(extensionName, - configListenerTypes); - // verify - assertNull(returnStatus1.extensionUnexpectedError); - assertEquals(2, returnStatus2.extensionUnexpectedError.getErrorCode().getErrorCode()); - assertTrue(returnStatus2.extensionUnexpectedError.getMessage().contains("Failed to register extension with name " + - extensionName)); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - } - - // Test Case No : 26 - // Register an extension with null version - should be ok, no NPE should be thrown in logs - @Test - public void testCreateExtension_whenNullVersion_returnsNoError() { - - // setup - String extensionName = "ExtensionWithNullVersion"; - // test - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, - configListenerTypes); - TestableExtension testableExtension = (TestableExtension) - extensionTestingHelper.getExtensionInstance(extensionName); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertNull(testableExtension.getVersion()); - } - - // Test Case No : 27 - // Same as Test case No : 19, but checking with the same extension by re-registering it. - @Test - public void testGetSharedEventState_whenExtensionReRegistered_theSharedStateIsUnaffected() { - - // setup - String extensionName = "ThirdPartyExtension27"; - CreateExtensionResponse returnStatus1 = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); - TestableExtension testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); - Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, - configListenerTypes.get(0).eventSource).setEventData(eventData).build(); - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - } - }; - boolean status = testableExtension.getApi().setSharedEventState(subEventData, event, errorCallback); - Map sharedStateBeforeUnregister = testableExtension.getApi().getSharedEventState( - testableExtension.getName(), event, errorCallback); - // test - boolean unregisterStatus = extensionTestingHelper.unregisterExtension(extensionName); - CreateExtensionResponse returnStatus2 = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); - testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); - Map sharedStateAfterReregister = testableExtension.getApi().getSharedEventState( - testableExtension.getName(), event, errorCallback); - // verify - assertNull(returnStatus1.extensionUnexpectedError); - assertTrue(unregisterStatus); - assertNull(returnStatus2.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertEquals(subEventData, sharedStateBeforeUnregister); - assertEquals(subEventData, sharedStateAfterReregister); - } - - // Test Case No : 28 - // Register a listener with null eventType - should be ok, no NPE should be thrown in logs - @Test - public void testRegisterListener_whenNullEventType_returnsErrorCode() { - - // setup - String extensionName = "ThirdPartyExtension28a"; - // setup - List listenerType = new ArrayList(); - listenerType.add(new ListenerType(null, "com.example.testable.request")); - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, - listenerType); - // test - Map listenerRegistrationStatus = TestableExtension.getListenerRegistrationStatus(); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorCode(), 3); - assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorName(), "extension.event_type_not_supported"); - } - - // Test Case No : 28a - // Register a listener with null eventSource - should be ok, no NPE should be thrown in logs - @Test - public void testRegisterListener_whenNullEventSource_returnsErrorCode() { - - // setup - String extensionName = "ThirdPartyExtension28b"; - // test - List listenerType = new ArrayList(); - listenerType.add(new ListenerType("com.example.testable.custom", null)); - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, listenerType); - Map listenerRegistrationStatus = TestableExtension.getListenerRegistrationStatus(); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorCode(), 4); - assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorName(), - "extension.event_source_not_supported"); - } - - // Test Case No : 28b - // Register a listener with null for both eventType and eventSource - should be ok, no NPE should be thrown in logs - @Test - public void testRegisterListener_whenNullEventTypeAndSource_returnsErrorCode() { - - // setup - String extensionName = "ThirdPartyExtension28c"; - // test - List listenerType = new ArrayList(); - listenerType.add(new ListenerType(null, null)); - CreateExtensionResponse returnStatus = - extensionTestingHelper.registerExtension(extensionName, listenerType); - Map listenerRegistrationStatus = TestableExtension.getListenerRegistrationStatus(); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorCode(), 3); - assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorName(), "extension.event_type_not_supported"); - } - - // Test Case No : 29 - // Register listener which does busy work on the event hub thread - // Expected : It should not block the event hub in dispatching new events - @Test - @Ignore - public void testWithAListenerDoingBusyWorkOnTheHub_whenAnotherEventIsDispatched_eventHubShouldDispatchIt() throws - InterruptedException { - - // setup - final String customExtension = "CustomExtension29"; - final String busyWorkExtension = "BusyWorkExtension29"; - - final List listenerTypes = new ArrayList(); - - final List eventsHeard = new ArrayList(); - final List result = new ArrayList(); - final ArrayList[] eventsHeardByListener = new ArrayList[4]; - listenerTypes.add(new ListenerType("com.adobe.eventtype.busywork", "com.example.testable.busywork")); - listenerTypes.add(new ListenerType("com.adobe.eventtype.customevent", "com.example.testable.customevent")); - CreateExtensionResponse registrationStatus1 = extensionTestingHelper.registerExtension(customExtension, listenerTypes); - CreateExtensionResponse registrationStatus2 = extensionTestingHelper.registerExtension(busyWorkExtension, - listenerTypes); - - ExtensionErrorCallback dispatchCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError ec) { - Log.debug(LOG_TAG, String.format("[dispatchAnEvent] Dispatch failed with error %s ", ec.getErrorCode())); - } - }; - - Event busyEvent = new Event.Builder("DispatchedBusyEvent", listenerTypes.get(0).eventType, - listenerTypes.get(0).eventSource).setEventData(subEventData).build(); - Event customEvent = new Event.Builder("DispatchedCustomEvent", listenerTypes.get(1).eventType, - listenerTypes.get(1).eventSource).setEventData(subEventData).build(); - - MobileCore.dispatchEvent(customEvent, dispatchCallback); - MobileCore.dispatchEvent(customEvent, dispatchCallback); - MobileCore.dispatchEvent(busyEvent, dispatchCallback); - MobileCore.dispatchEvent(customEvent, dispatchCallback); - MobileCore.dispatchEvent(customEvent, dispatchCallback); - asyncHelper.waitForAppThreads(500, true); - eventsHeardByListener[0] = (ArrayList) extensionTestingHelper.getAllEventsHeardByListener(customExtension, - listenerTypes.get(0)); - eventsHeardByListener[1] = (ArrayList) extensionTestingHelper.getAllEventsHeardByListener(customExtension, - listenerTypes.get(1)); - eventsHeardByListener[2] = (ArrayList) extensionTestingHelper.getAllEventsHeardByListener(busyWorkExtension, - listenerTypes.get(0)); - eventsHeardByListener[3] = (ArrayList) extensionTestingHelper.getAllEventsHeardByListener(busyWorkExtension, - listenerTypes.get(1)); - // verify - assertEquals(1, eventsHeardByListener[0].size()); - assertEquals(4, eventsHeardByListener[1].size()); - assertEquals(1, eventsHeardByListener[2].size()); - assertEquals(4, eventsHeardByListener[3].size()); - } - - - // Test Case No : 30 - // Build a custom event with null event data should not crash - @Test - public void testDispatchEvent_whenNullEventData_returnsNoError() { - - // setup - String extensionName = "ThirdPartyExtension30"; - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, - identityListenerTypes); - TestableExtension testableExtension = (TestableExtension) - extensionTestingHelper.getExtensionInstance(extensionName); - ListenerType listenerType = new ListenerType("com.adobe.eventType.configuration", - "com.adobe.eventSource.requestContent"); - Event event = new Event.Builder("DispatchedEvent", listenerType.eventType, - listenerType.eventSource).setEventData(null).build(); - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - } - }; - boolean status = testableExtension.getApi().setSharedEventState(null, event, errorCallback); - // test - ExtensionErrorCallback errorCallback1 = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - } - }; - Map configurationSharedState = testableExtension.getApi().getSharedEventState( - testableExtension.getName(), event, errorCallback1); - ExtensionErrorCallback dispatchCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError ec) { - } - }; - boolean dispatchStatus = MobileCore.dispatchEvent(event, dispatchCallback); - asyncHelper.waitForAppThreads(500, true); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertNull(configurationSharedState); - assertTrue(dispatchStatus); - } - - // Test Case No : 31 Skipped due to the bug AMSDK-7597 - // SDK v5 does not have support for Short, Float, and Byte Data types - // Check under bourbon-core-java-core/code/shared/src/main/java/com/adobe/marketing/mobile - // Build a custom event data with byte data type - // dispatch it and check the returned values are the same. - @Test - public void testDispatchEvent_whenEventDataWithByteTypeValue_returnsNoError() { - - // setup - String extensionName = "ThirdPartyExtension31a"; - Map testEventData = new HashMap(); - int byteData = 0b0010_0101; - testEventData.put("customByte", byteData); - // tes - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, - identityListenerTypes); - extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(0), testEventData); - Event event = extensionTestingHelper.getLastEventHeardByListener(extensionName, - identityListenerTypes.get(0)); - - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertTrue(identityListenerTypes.get(0).eventType.equalsIgnoreCase(event.getType())); - assertTrue(identityListenerTypes.get(0).eventSource.equalsIgnoreCase(event.getSource())); - assertEquals(testEventData, event.getEventData()); - } - - // Test Case No : 31a Skipped due to the bug AMSDK-7596 - // Build a custom event data with Float data type - // dispatch it and check the returned values are the same. - @Ignore - public void testDispatchEvent_whenEventDataWithFloatTypeValue_returnsNoError() { - - // setup - String extensionName = "ThirdPartyExtension31b"; - Map testEventData = new HashMap(); - testEventData.put("customFloat", new Float(3.14)); - // test - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, - identityListenerTypes); - extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(0), testEventData); - Event event = extensionTestingHelper.getLastEventHeardByListener(extensionName, - identityListenerTypes.get(0)); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertTrue(identityListenerTypes.get(0).eventType.equalsIgnoreCase(event.getType())); - assertTrue(identityListenerTypes.get(0).eventSource.equalsIgnoreCase(event.getSource())); - assertEquals(testEventData, event.getEventData()); - } - - // Test Case No : 31b Skipped due to the bug AMSDK-7595 - // Build a custom event data with Short data type - // dispatch it and check the returned values are the same. - @Ignore - public void testDispatchEvent_whenEventDataWithShortTypeValue_returnsNoError() { - - // setup - String extensionName = "ThirdPartyExtension31c"; - Map testEventData = new HashMap(); - testEventData.put("customShort", (short)300); - // test - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, - identityListenerTypes); - extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(0), testEventData); - Event event = extensionTestingHelper.getLastEventHeardByListener(extensionName, - identityListenerTypes.get(0)); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertTrue(identityListenerTypes.get(0).eventType.equalsIgnoreCase(event.getType())); - assertTrue(identityListenerTypes.get(0).eventSource.equalsIgnoreCase(event.getSource())); - assertEquals(testEventData, event.getEventData()); - } - - // Test Case No : 32 - // Dispatch with null event, should return false and error in the callback - @Test - public void testDispatchEvent_whenNullEvent_returnsError() throws InterruptedException { - - // test - ExtensionErrorCallback dispatchCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError ec) { - extensionError = ec; - } - }; - boolean dispatchStatus = MobileCore.dispatchEvent(null, dispatchCallback); - asyncHelper.waitForAppThreads(500, true); - // verify - assertFalse(dispatchStatus); - assertEquals("extension.event_null", extensionError.getErrorName()); - assertEquals(6, extensionError.getErrorCode()); - } - - // Test Case No : 33 - // Dispatch response event with null event, should return false and error in the callback - @Test - public void testDispatchEventWithResponseCallback_whenNullResponseEvent_returnsError() throws InterruptedException { - - // setup - String extensionName = "ThirdPartyExtension33"; - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); - TestableListener eventListener = (TestableListener) extensionTestingHelper.getListenerInstance(extensionName, - customListenerTypes.get(0)); - eventListener.setDispatchBehavior("doDispatchResponseEventWithNullEvent"); - ListenerType listenerType = customListenerTypes.get(0); - // test - Event responseEvent = new Event.Builder("DispatchedEvent", listenerType.eventType, - listenerType.eventSource).setEventData(eventData).build(); - final List result = new ArrayList(); - final CountDownLatch latch = new CountDownLatch(1); - AdobeCallback dispatchCallback = new AdobeCallback() { - @Override - public void call(Event value) { - result.add(value); - latch.countDown(); - } - }; - boolean dispatchStatus = MobileCore.dispatchEventWithResponseCallback(responseEvent, dispatchCallback, null); - latch.await(500, TimeUnit.MILLISECONDS); - // verify - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertTrue(dispatchStatus); - assertTrue(result.isEmpty()); - assertEquals("extension.event_null", eventListener.getExtensionError().getErrorName()); - assertEquals(6, eventListener.getExtensionError().getErrorCode()); - } - - // Test Case No : 34 - // Get the shared state for the custom extension, should be null if not set, should be valid if set before - @Test - public void testGetSharedEventState_whenItIsNotSet_returnsNull() { - - // setup - String extensionName = "ThirdPartyExtension34"; - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); - TestableExtension testableExtension = (TestableExtension) - extensionTestingHelper.getExtensionInstance(extensionName); - ListenerType listenerType = customListenerTypes.get(0); - Event event = new Event.Builder("DispatchedEvent", listenerType.eventType, - listenerType.eventSource).setEventData(eventData).build(); - - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - } - }; - // test - Map sharedStateBeforeItWasSet = testableExtension.getApi().getSharedEventState( - testableExtension.getName(), - event, errorCallback); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertNull(sharedStateBeforeItWasSet); - } - - // Test Case No : 35 - // SetSharedState with null state, null event, should not crash - @Test - public void testSetAndGetSharedEventState_whenNullStateAndEvent_returnsNoError() { - - // setup - String extensionName = "ThirdPartyExtension35"; - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); - TestableExtension testableExtension = (TestableExtension) - extensionTestingHelper.getExtensionInstance(extensionName); - - final ExtensionError[] extenError = new ExtensionError[1]; - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - extenError[0] = extensionError; - } - }; - boolean status = testableExtension.getApi().setSharedEventState(null, null, errorCallback); - // test - Map customSharedState = testableExtension.getApi().getSharedEventState(testableExtension.getName(), - null, errorCallback); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertNull(customSharedState); - assertNull(extenError[0]); - } - - // Test Case No : 38 - // Simulate an analytics 3rd party extension - dispatching & receiving generic events when trackAction called - @Test - public void testTrackAction_whenDispatched_receivedByGenericTrackEventType() { - // setup - String extensionName = "AnalyticsExtension38a"; - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, - analyticsListenerTypes); - TestableExtension testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - } - }; - // test - Map additionalContextData = new HashMap(); - additionalContextData.put("customKey", "value"); - MobileCore.trackAction("eventDispatched", additionalContextData); - asyncHelper.waitForAppThreads(500, true); - Event eventHeard = extensionTestingHelper.getLastEventHeardByListener(extensionName, analyticsListenerTypes.get(4)); - // verify - assertEquals("eventDispatched", eventHeard.getEventData().get("action")); - assertEquals(eventHeard.getEventData().get("contextdata"), additionalContextData); - assertTrue(eventHeard.getEventSource().getName().equalsIgnoreCase(analyticsListenerTypes.get(4).eventSource)); - assertTrue(eventHeard.getEventType().getName().equalsIgnoreCase(analyticsListenerTypes.get(4).eventType)); - - MobileCore.trackState("trackState", additionalContextData); - } - - // Test Case No : 38a - // Simulate an analytics 3rd party extension - dispatching & receiving generic events when TrackState called - @Test - public void testTrackState_whenDispatched_receivedByGenericTrackEventType() { - // setup - String extensionName = "AnalyticsExtension38a"; - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, - analyticsListenerTypes); - TestableExtension testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - } - }; - // test - Map additionalContextData = new HashMap(); - additionalContextData.put("customKey", "value"); - MobileCore.trackState("trackState", additionalContextData); - asyncHelper.waitForAppThreads(500, true); - Event eventHeard = extensionTestingHelper.getLastEventHeardByListener(extensionName, analyticsListenerTypes.get(4)); - // verify - assertEquals("trackState", eventHeard.getEventData().get("state")); - assertEquals(additionalContextData, eventHeard.getEventData().get("contextdata")); - assertTrue(eventHeard.getEventSource().getName().equalsIgnoreCase(analyticsListenerTypes.get(4).eventSource)); - assertTrue(eventHeard.getEventType().getName().equalsIgnoreCase(analyticsListenerTypes.get(4).eventType)); - } - // Test Case No : 38b - // Dispatch different types of Analytics Events and - // Confirm if they are all received by the appropriate listeners registered. - @Test - public void testAnalyticsExtension_whenAnalyticsEventsDispatched_receivesAllDispatchedEvents() { - // setup - String extensionName = "AnalyticsExtension38"; - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, - analyticsListenerTypes); - TestableExtension testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); - - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - } - }; - // test - Event[] eventHeard = new Event[analyticsListenerTypes.size()]; - - for (int i = 0; i < analyticsListenerTypes.size(); i++) { - subEventData.put("newKey" + i, "newValue" + i); - Event event = new Event.Builder("DispatchedEvent", analyticsListenerTypes.get(i).eventType, - analyticsListenerTypes.get(i).eventSource).setEventData(subEventData).build(); - ExtensionErrorCallback dispatchCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError ec) { - Log.debug(LOG_TAG, String.format("[dispatchAnEvent] Dispatch failed with error %s ", ec.getErrorCode())); - } - }; - MobileCore.dispatchEvent(event, dispatchCallback); - asyncHelper.waitForAppThreads(500, true); - eventHeard[i] = extensionTestingHelper.getLastEventHeardByListener(extensionName, analyticsListenerTypes.get(i)); - // verify - assertEquals(subEventData, eventHeard[i].getEventData()); - assertTrue(eventHeard[i].getEventSource().getName().equalsIgnoreCase(analyticsListenerTypes.get(i).eventSource)); - assertTrue(eventHeard[i].getEventType().getName().equalsIgnoreCase(analyticsListenerTypes.get(i).eventType)); - } - } - - // Test Case No : 39 - // Test communication between two 3rd party extensions - dispatch events and set shared states from one of them, - // Check updates are received in the other extension - @Test - public void testCommunicationBetweenExtensions_whenOneDispatchesEventsAndSetsSharedState_otherExtensionReceivesThem() { - - // setup - String extensionName1 = "Extension39One"; - CreateExtensionResponse returnStatus1 = extensionTestingHelper.registerExtension(extensionName1, customListenerTypes); - TestableExtension testableExtension1 = (TestableExtension) - extensionTestingHelper.getExtensionInstance(extensionName1); - - String extensionName2 = "Extension39two"; - CreateExtensionResponse returnStatus2 = extensionTestingHelper.registerExtension(extensionName2, customListenerTypes); - TestableExtension testableExtension2 = (TestableExtension) - extensionTestingHelper.getExtensionInstance(extensionName2); - - - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - Log.debug(LOG_TAG, String.format("An error occurred while setting the shared state %d %s", - extensionError.getErrorCode(), extensionError.getErrorName())); - } - }; - - // test - Event event = new Event.Builder("DispatchedEvent", customListenerTypes.get(0).eventType, - customListenerTypes.get(0).eventSource).setEventData(eventData).build(); - MobileCore.dispatchEvent(event, errorCallback); - testableExtension1.getApi().setSharedEventState(subEventData, event, errorCallback); - asyncHelper.waitForAppThreads(500, true); - - Event eventHeard = extensionTestingHelper.getLastEventHeardByListener(extensionName2, customListenerTypes.get(0)); - Map sharedState = testableExtension2.getApi().getSharedEventState( - testableExtension1.getName(), event, errorCallback); - - // verify - assertEquals(eventData, eventHeard.getEventData()); - assertTrue(eventHeard.getEventSource().getName().equalsIgnoreCase(customListenerTypes.get(0).eventSource)); - assertTrue(eventHeard.getEventType().getName().equalsIgnoreCase(customListenerTypes.get(0).eventType)); - assertEquals(subEventData, sharedState); - } - - // Test Case No : 40 - // Check that paired response events are received by another extension in a wildcard listener, but not by a regular listener. - // The response should be received in the callback by the first extension. - @Ignore - public void testDispatchEventWithResponseCallback_whenAnotherExtensionListens_OnlyItsWildcordListenersCanHearIt() throws - InterruptedException { - - // setup - String extensionName1 = "Extension40One"; - CreateExtensionResponse registrationStatus1 = extensionTestingHelper.registerExtension(extensionName1, - customListenerTypes); - Extension extensionInstance1 = extensionTestingHelper.getExtensionInstance(extensionName1); - TestableListener eventListener1 = (TestableListener) extensionTestingHelper.getListenerInstance(extensionName1, - customListenerTypes.get(0)); - eventListener1.setDispatchBehavior("doDispatchResponseEvent"); - - String extensionName2 = "Extension40Two"; - List listenerTypesOfExtension2 = new ArrayList<>(); - listenerTypesOfExtension2.add(new ListenerType("com.adobe.eventtype._wildcard_", "com.adobe.eventsource._wildcard_")); - listenerTypesOfExtension2.add(new ListenerType("com.adobe.eventtype.pairedresponse", - "com.example.testable.pairedrequest")); - CreateExtensionResponse registrationStatus2 = extensionTestingHelper.registerExtension(extensionName2, - listenerTypesOfExtension2); - - asyncHelper.waitForAppThreads(500, true); - - // test - Event responseEvent = new Event.Builder("DispatchedEvent", customListenerTypes.get(0).eventType, - customListenerTypes.get(0).eventSource).setEventData(subEventData).build(); - final List result = new ArrayList(); - final CountDownLatch latch = new CountDownLatch(1); - AdobeCallback dispatchCallback = new AdobeCallback() { - @Override - public void call(Event value) { - result.add(value); - latch.countDown(); - } - }; - boolean dispatchStatus = MobileCore.dispatchEventWithResponseCallback(responseEvent, dispatchCallback, null); - latch.await(500, TimeUnit.MILLISECONDS); - - List eventsHeardByWildcardListener = extensionTestingHelper.getAllEventsHeardByListener(extensionName2, - listenerTypesOfExtension2.get(0)); - List eventsHeardByPairedResponseListener = extensionTestingHelper.getAllEventsHeardByListener(extensionName2, - listenerTypesOfExtension2.get(1)); - // verify - assertEquals(eventsHeardByWildcardListener.size(), 2); - assertEquals(eventsHeardByPairedResponseListener.size(), 0); - assertTrue(eventsHeardByWildcardListener.get(0).getEventType().getName().equalsIgnoreCase(customListenerTypes.get( - 0).eventType)); - assertTrue(eventsHeardByWildcardListener.get(0).getEventSource().getName().equalsIgnoreCase(customListenerTypes.get( - 0).eventSource)); - assertEquals(subEventData, eventsHeardByWildcardListener.get(0).getEventData()); - assertTrue(eventsHeardByWildcardListener.get( - 1).getEventType().getName().equalsIgnoreCase("com.adobe.eventtype.pairedresponse")); - assertTrue(eventsHeardByWildcardListener.get( - 1).getEventSource().getName().equalsIgnoreCase("com.example.testable.pairedrequest")); - assertEquals(pairedEventData, eventsHeardByWildcardListener.get(1).getEventData()); - assertTrue(dispatchStatus); - assertTrue(result.get(0).getType().equalsIgnoreCase("com.adobe.eventtype.pairedresponse")); - assertTrue(result.get(0).getSource().equalsIgnoreCase("com.example.testable.pairedrequest")); - assertEquals(pairedEventData, result.get(0).getEventData()); - } - - // Test Case No : 41 - // Set XDM Shared Event State that is not tied to an event using setXDMSharedEventState API - @Test - public void testSetAndGetXDMSharedEventState_whenDanglingEvent_setsAndGetsAppropriateXDMSharedState() { - // setup - String extensionName = "Extension41"; - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, - configListenerTypes); - TestableExtension testableExtension = (TestableExtension) - extensionTestingHelper.getExtensionInstance(extensionName); - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertNotNull(testableExtension); - - Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, - configListenerTypes.get(0).eventSource).setEventData(eventData).build(); - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - } - }; - assertTrue(testableExtension.getApi().setXDMSharedEventState(subEventData, event, errorCallback)); - - Map sharedState = testableExtension.getApi().getXDMSharedEventState( - testableExtension.getName(), event, errorCallback); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertEquals(subEventData, sharedState); - } - - // Test Case No : 42 - // Clear XDM Shared Event States Of A Third Party Extension without affecting - // the XDM Shared Event States Of other extensions using clearXDMSharedEventStates API - @Test - public void - testClearXDMSharedEventStates_whenMultipleRegisteredExtensions_doesNotAffectXDMSharedStateOfOtherExtensions() { - - // setup - CreateExtensionResponse returnStatus1 = extensionTestingHelper.registerExtension("ThirdPartyExtensionOne", - configListenerTypes); - TestableExtension testableExtension1 = (TestableExtension) - extensionTestingHelper.getExtensionInstance("ThirdPartyExtensionOne"); - assertNull(returnStatus1.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionOne")); - assertNotNull(testableExtension1); - - CreateExtensionResponse returnStatus2 = extensionTestingHelper.registerExtension("ThirdPartyExtensionTwo", - configListenerTypes); - TestableExtension testableExtension2 = (TestableExtension) - extensionTestingHelper.getExtensionInstance("ThirdPartyExtensionTwo"); - assertNull(returnStatus2.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionTwo")); - assertNotNull(testableExtension2); - - Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, - configListenerTypes.get(0).eventSource).setEventData(eventData).build(); - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - } - }; - assertTrue(testableExtension1.getApi().setXDMSharedEventState(subEventData, event, errorCallback)); - assertTrue(testableExtension2.getApi().setXDMSharedEventState(subEventData, event, errorCallback)); - ExtensionErrorCallback errorCallback1 = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - } - }; - Map sharedStateBeforeClearing1 = testableExtension1.getApi().getXDMSharedEventState( - testableExtension1.getName(), event, errorCallback1); - Map sharedStateBeforeClearing2 = testableExtension2.getApi().getXDMSharedEventState( - testableExtension2.getName(), event, errorCallback1); - ExtensionErrorCallback errorCallback2 = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - Log.debug(LOG_TAG, String.format("An error occurred while clearing the XDM shared states %d %s", - extensionError.getErrorCode(), extensionError.getErrorName())); - } - }; - // test - testableExtension1.getApi().clearXDMSharedEventStates(errorCallback2); - Map getSharedStateAfterClearing1 = testableExtension1.getApi().getXDMSharedEventState( - testableExtension1.getName(), event, errorCallback1); - Map getSharedStateAfterClearing2 = testableExtension2.getApi().getXDMSharedEventState( - testableExtension2.getName(), event, errorCallback1); - // verify - assertNull(returnStatus1.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionOne")); - assertNull(returnStatus2.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionTwo")); - assertEquals(subEventData, sharedStateBeforeClearing1); - assertEquals(subEventData, sharedStateBeforeClearing2); - assertNull(getSharedStateAfterClearing1); - assertEquals(subEventData, getSharedStateAfterClearing2); - } - - // Test Case No : 43 - // Get the XDM shared state for the custom extension, should be null if not set, should be valid if set before - @Test - public void testGetXDMSharedEventState_whenItIsNotSet_returnsNull() { - - // setup - String extensionName = "ThirdPartyExtension43"; - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); - TestableExtension testableExtension = (TestableExtension) - extensionTestingHelper.getExtensionInstance(extensionName); - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertNotNull(testableExtension); - - ListenerType listenerType = customListenerTypes.get(0); - Event event = new Event.Builder("DispatchedEvent", listenerType.eventType, - listenerType.eventSource).setEventData(eventData).build(); - - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - } - }; - // test - Map sharedStateBeforeItWasSet = testableExtension.getApi().getXDMSharedEventState( - testableExtension.getName(), - event, errorCallback); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertNull(sharedStateBeforeItWasSet); - } - - // Test Case No : 44 - // SetXDMSharedState with valid state, null event, should not crash - @Test - public void testSetAndGetXDMSharedEventState_whenValidStateAndNullEvent_returnsNoError() { - - // setup - String extensionName = "ThirdPartyExtension44"; - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); - TestableExtension testableExtension = (TestableExtension) - extensionTestingHelper.getExtensionInstance(extensionName); - - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertNotNull(testableExtension); - - final ExtensionError[] extenError = new ExtensionError[1]; - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - extenError[0] = extensionError; - } - }; - Map state = new HashMap(); - state.put("testKey", "testVal"); - assertTrue(testableExtension.getApi().setXDMSharedEventState(state, null, errorCallback)); - // test - Map customSharedState = testableExtension.getApi().getXDMSharedEventState(testableExtension.getName(), - null, errorCallback); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertEquals(state, customSharedState); - assertNull(extenError[0]); - } - - // Test Case No : 45 - // SetXDMSharedState with null state, valid event, should not crash - @Test - public void testSetAndGetXDMSharedEventState_whenNullStateAndValidEvent_returnsNoError() { - - // setup - String extensionName = "ThirdPartyExtension44"; - CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); - TestableExtension testableExtension = (TestableExtension) - extensionTestingHelper.getExtensionInstance(extensionName); - - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertNotNull(testableExtension); - - final ExtensionError[] extenError = new ExtensionError[1]; - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - extenError[0] = extensionError; - } - }; - Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, - configListenerTypes.get(0).eventSource).setEventData(eventData).build(); - assertTrue(testableExtension.getApi().setXDMSharedEventState(null, event, errorCallback)); - // test - Map customSharedState = testableExtension.getApi().getXDMSharedEventState(testableExtension.getName(), - event, errorCallback); - // verify - assertNull(returnStatus.extensionUnexpectedError); - assertTrue(extensionTestingHelper.isRegistered(extensionName)); - assertNull(customSharedState); - assertNull(extenError[0]); - } - -} - - - - +///* +// Copyright 2022 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may obtain a copy +// of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under +// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +// OF ANY KIND, either express or implied. See the License for the specific language +// governing permissions and limitations under the License. +// */ +// +//package com.adobe.marketing.mobile; +// +//import org.junit.After; +//import org.junit.Before; +//import org.junit.Ignore; +//import org.junit.Test; +//import org.junit.runner.RunWith; +// +//import android.support.test.runner.AndroidJUnit4; +// +//import java.util.ArrayList; +//import java.util.HashMap; +//import java.util.List; +//import java.util.Map; +// +// +//import java.util.concurrent.ConcurrentLinkedQueue; +//import java.util.concurrent.CountDownLatch; +//import java.util.concurrent.ExecutorService; +//import java.util.concurrent.Executors; +//import java.util.concurrent.TimeUnit; +// +//import static org.junit.Assert.assertNotNull; +//import static org.junit.Assert.assertEquals; +//import static org.junit.Assert.assertFalse; +//import static org.junit.Assert.assertNull; +//import static org.junit.Assert.assertTrue; +// +///** +// * Class {@link AndroidThirdPartyExtensionsFunctionalTests} that defines all the necessary test cases to test a third party extension {@link TestableExtension} that extends {@link ExtensionListener} class of the Adobe Experience Platform SDK. +// * +// * This covers all the test cases listed in the document to run as part of the CI build system. +// * +// * https://wiki.corp.adobe.com/pages/viewpage.action?spaceKey=adms&title=Mobile+SDK+v5+Extensions+Module+Test+Plan +// * +// * @author Adobe +// * @version 5.0 +// */ +// +//@RunWith(AndroidJUnit4.class) +//public class AndroidThirdPartyExtensionsFunctionalTests extends AbstractE2ETest { +// +// private static final String LOG_TAG = AndroidThirdPartyExtensionsFunctionalTests.class.getSimpleName(); +// +// private TestHelper testHelper = new TestHelper(); +// private ExtensionTestingHelper extensionTestingHelper = new ExtensionTestingHelper(); +// private AsyncHelper asyncHelper = new AsyncHelper(); +// +// private List configListenerTypes = new ArrayList(); +// private List identityListenerTypes = new ArrayList(); +// private List customListenerTypes = new ArrayList(); +// private List analyticsListenerTypes = new ArrayList(); +// private List wildcardListener = new ArrayList(); +// +// static final String CONFIGURATION_SHARED_STATE = "com.adobe.module.configuration"; +// static final String EVENT_SOURCE_SHARED_STATE = "com.adobe.eventSource.sharedstate"; +// static final String EVENT_TYPE_CONFIGURATION = "com.adobe.eventType.configuration"; +// +// private final Object executorMutex = new Object(); +// private ExecutorService executor; +// private ConcurrentLinkedQueue unprocessedEvents; +// private long noProcessedEvents; +// +// private Map eventData = new HashMap(); +// Map subEventData = new HashMap(); +// Map pairedEventData = new HashMap(); +// ExtensionError extensionError; +// Map configMap = new HashMap(); +// +// @Before +// public void setUp() { +// super.setUp(); +// testHelper.cleanCache(defaultContext); +// MobileCore.setLogLevel(LoggingMode.DEBUG); +// MobileCore.start(null); +// testableNetworkService.resetTestableNetworkService(); +// +// configListenerTypes.add(new ListenerType("com.adobe.eventType.configuration", "com.adobe.eventSource.responseContent")); +// configListenerTypes.add(new ListenerType("com.adobe.eventType.configuration", "com.adobe.eventSource.requestContent")); +// +// identityListenerTypes.add(new ListenerType("com.adobe.eventType.identity", "com.adobe.eventSource.requestIdentity")); +// identityListenerTypes.add(new ListenerType("com.adobe.eventType.identity", "com.adobe.eventSource.responseIdentity")); +// +// analyticsListenerTypes.add(new ListenerType("com.adobe.eventType.analytics", "com.adobe.eventSource.requestContent")); +// analyticsListenerTypes.add(new ListenerType("com.adobe.eventType.analytics", "com.adobe.eventSource.requestIdentity")); +// analyticsListenerTypes.add(new ListenerType("com.adobe.eventType.analytics", "com.adobe.eventSource.responseContent")); +// analyticsListenerTypes.add(new ListenerType("com.adobe.eventType.analytics", "com.adobe.eventSource.responseIdentity")); +// analyticsListenerTypes.add(new ListenerType("com.adobe.eventType.generic.track", +// "com.adobe.eventSource.requestContent")); +// +// wildcardListener.add(new ListenerType("com.adobe.eventtype._wildcard_", "com.adobe.eventsource._wildcard_")); +// +// customListenerTypes.add(new ListenerType("com.example.testable.custom", "com.example.testable.request")); +// +// subEventData.put("key0", "value0"); +// subEventData.put("key1", "value1"); +// subEventData.put("key2", "value2"); +// Map customElement = new HashMap(); +// // customElement.put("customByte", (byte)127); +// // customElement.put("customFloat", new Float(3.14)); +// // customElement.put("customShort", (short)300); +// customElement.put("customInt", new Integer(123)); +// customElement.put("customLong", (long) 300); +// customElement.put("customChar", 'c'); +// customElement.put("customString", "string1"); +// customElement.put("customNull", null); +// customElement.put("customBoolean", true); +// customElement.put("customList", customListenerTypes); +// customElement.put("customDouble", new Double(3.14)); +// customElement.put("customMap", subEventData); +// Map customData = new HashMap(); +// customData.put("customElement", customElement); +// eventData.put("customData", customData); +// +// pairedEventData.put("ResponseKey1", "ResponseData1"); +// pairedEventData.put("ResponseKey2", "ResponseData2"); +// pairedEventData.put("ResponseKey3", "ResponseData3"); +// } +// +// @After +// public void tearDown() { +// asyncHelper.waitForAppThreads(500, true); +// super.tearDown(); +// } +// +// public void updateConfiguration() { +// configMap.put("build.environment", "dev"); +// configMap.put("myExtension.server", "mydomain.dev.com"); +// configMap.put("global.privacy", "optedin"); +// configMap.put("experienceCloud.org", "972C898555E9F7BC7F000101@AdobeOrg"); +// configMap.put("analytics.server", "obumobile5.sc.omtrdc.net"); +// configMap.put("analytics.rsids", "mobile5the.v5.show"); +// configMap.put("analytics.offlineEnabled", true); +// configMap.put("analytics.referrerTimeout", 15); +// configMap.put("analytics.backdatePreviousSessionInfo", true); +// configMap.put("identity.adidEnabled", true); +// configMap.put("acquisition.server", "c00.adobe.com"); +// configMap.put("rules.url", "https://s3.amazonaws.com/ams-qe/ios-sdk-test-app-rules.zip"); +// configMap.put("target.clientCode", "yourclientcode"); +// configMap.put("target.timeout", 5); +// configMap.put("audience.server", "omniture.demdex.net"); +// configMap.put("audience.timeout", 5); +// configMap.put("analytics.aamForwardingEnabled", false); +// configMap.put("analytics.batchLimit", 0); +// configMap.put("lifecycle.sessionTimeout", 300); +// MobileCore.updateConfiguration(configMap); +// } +// +// public ExecutorService getExecutor() { +// synchronized (executorMutex) { +// if (executor == null) { +// executor = Executors.newFixedThreadPool(2); +// } +// +// return executor; +// } +// } +// +// // Test Case No : 1 +// // Register A Third Party Extension using registerExtension API +// @Test +// public void testRegisterExtension_whenValidData_returnsNoError() { +// +// // setup +// String extensionName = "ThirdPartyExtension01"; +// // test +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, +// configListenerTypes); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// } +// +// // Test Case No : 2 & 20 +// // Get the Name Of A ThirdParty Extension using getName API in Android and name in iOS +// @Test +// public void testGetName_whenExtensionRegistered_returnsName() { +// +// // setup +// String extensionName = "ThirdPartyExtension02"; +// // test +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, +// configListenerTypes); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertEquals(returnStatus.extensionName, extensionName); +// } +// +// // Test Case No : 2a +// @Test +// public void testGetExtensionInstance_whenExtensionNotRegistered_returnsNull() { +// +// // setup +// String extensionName = "ThirdPartyExtension02a"; +// // test +// Extension extension = extensionTestingHelper.getExtensionInstance(extensionName); +// // verify +// assertNull(extension); +// } +// +// // Test Case No : 3 & 20 +// // Get the Version Of A ThirdParty Extension using getVersion API in Android and version in iOS +// @Test +// public void testGetVersion_whenExtensionRegistered_returnsVersion() { +// +// // setup +// String extensionName = "ThirdPartyExtension03"; +// // test +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(returnStatus.extensionVersion.contains("1.0.0")); +// } +// +// // Test Case No : 4 +// // Unregister A Third Party Extension using unregisterExtension API +// @Test +// public void testUnRegisterExtension_whenExtensionRegistered_returnsTrue() { +// +// // setup +// String extensionName = "ThirdPartyExtension04"; +// // test +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); +// boolean unregisterStatus = extensionTestingHelper.unregisterExtension(extensionName); +// // verify +// assertTrue(unregisterStatus); +// assertFalse(extensionTestingHelper.isRegistered(extensionName)); +// } +// +// // Test Case No : 5 +// // OnUnregistered Call Of A Third Party Extension using unregisterExtension API in Android and onUnregister in IOS +// @Test +// public void testOnUnregistered_whenExtensionUnRegistered_OnUnregisteredIsCalled() { +// +// // setup +// String extensionName = "ThirdPartyExtension05"; +// // test +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); +// extensionTestingHelper.confirmExtensionUnregisteredCall = ""; +// boolean unregisterStatus = extensionTestingHelper.unregisterExtension(extensionName); +// // verify +// assertTrue(unregisterStatus); +// assertEquals(returnStatus.extensionName, extensionName); +// assertFalse(extensionTestingHelper.isRegistered(extensionName)); +// assertTrue(extensionTestingHelper.confirmExtensionUnregisteredCall.contains("ConfirmedByTestableExtension")); +// } +// +// // Test Case No : 6 +// // onUnexpectedError Call Of A Third Party Extension when trying +// // to a register Extension API for various errors mentioned in the ExtensionError Class +// @Test +// public void testRegisterExtension_whenNullName_returnsError() { +// +// // setup +// String extensionName = null; +// // test +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); +// // verify +// assertEquals(1, returnStatus.extensionUnexpectedError.getErrorCode().getErrorCode()); +// assertEquals("extension.bad_extension_name", returnStatus.extensionUnexpectedError.getErrorCode().getErrorName()); +// } +// +// // Test Case No : 6a +// @Test +// public void testRegisterExtension_whenEmptyName_returnsError() { +// +// // setup +// String extensionName = ""; +// // test +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); +// // verify +// assertEquals(1, returnStatus.extensionUnexpectedError.getErrorCode().getErrorCode()); +// assertEquals("extension.bad_extension_name", returnStatus.extensionUnexpectedError.getErrorCode().getErrorName()); +// } +// +// // Test Case No : 7 & 21 +// // Register a listener to listen an identity event using registerEventListener API for stateowner like com.adobe.module.identity +// @Test +// public void testRegisterListeners_whenEventsDispatched_eventsReceivedByRightListener() { +// +// // setup +// String extensionName = "ThirdPartyExtension07"; +// // test +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, identityListenerTypes); +// extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(0), eventData); +// extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(1), subEventData); +// Event event1 = extensionTestingHelper.getLastEventHeardByListener(extensionName, identityListenerTypes.get(0)); +// Event event2 = extensionTestingHelper.getLastEventHeardByListener(extensionName, identityListenerTypes.get(1)); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// +// assertTrue(identityListenerTypes.get(0).eventType.equalsIgnoreCase(event1.getType())); +// assertTrue(identityListenerTypes.get(0).eventSource.equalsIgnoreCase(event1.getSource())); +// assertEquals(eventData, event1.getEventData()); +// +// assertTrue(identityListenerTypes.get(1).eventType.equalsIgnoreCase(event2.getType())); +// assertTrue(identityListenerTypes.get(1).eventSource.equalsIgnoreCase(event2.getSource())); +// assertEquals(subEventData, event2.getEventData()); +// } +// +// // Test Case No : 8 +// // OnUnregistered Call in A registered listener will be called while an extension +// // gets unregistered using unregisterExtension API +// @Test +// public void testOnUnregisteredCall_whenUnregisterExtension_OnUnregisteredCallOfListenerCalled() { +// +// // setup +// String extensionName = "ThirdPartyExtension08"; +// // test +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, identityListenerTypes); +// extensionTestingHelper.confirmListenerUnregisteredCall = ""; +// extensionTestingHelper.confirmExtensionUnregisteredCall = ""; +// boolean unregisterStatus = extensionTestingHelper.unregisterExtension(extensionName); +// // verify +// assertTrue(unregisterStatus); +// assertFalse(extensionTestingHelper.isRegistered(extensionName)); +// assertTrue(extensionTestingHelper.confirmExtensionUnregisteredCall.contains("ConfirmedByTestableExtension")); +// assertTrue(extensionTestingHelper.confirmListenerUnregisteredCall.contains("ConfirmedByTestableListener")); +// } +// +// // Test Case No : 9 +// // Register A Listener to listen a custom event using registerEventListener API +// @Test +// public void testRegisterAListener_whenACustomEvent_returnsNoError() { +// +// // setup +// String extensionName = "ThirdPartyExtension07"; +// // test +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); +// EventListener eventListener = extensionTestingHelper.getListenerInstance(extensionName, customListenerTypes.get(0)); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertTrue(eventListener.getEventType().getName().equalsIgnoreCase("com.example.testable.custom")); +// assertTrue(eventListener.getEventSource().getName().equalsIgnoreCase("com.example.testable.request")); +// } +// +// // Test Case No : 10 +// // Register A Wildcard Listener to listen all the events using registerWildcardListener API +// @Test +// public void testRegisterWildcardListener_whenRegistered_returnsNoError() { +// +// // setup +// String extensionName = "ThirdPartyExtension10"; +// // test +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, wildcardListener); +// EventListener eventListener = extensionTestingHelper.getListenerInstance(extensionName, wildcardListener.get(0)); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertEquals(eventListener.getEventType().getName(), wildcardListener.get(0).eventType); +// assertEquals(eventListener.getEventSource().getName(), wildcardListener.get(0).eventSource); +// } +// +// // Test Case No : 10a +// // Dispatch events of types identity, analytics, custom, and config using ACPCore dispatchEvent API +// // And confirm that the wildcard listener is able to hear all those dispatched. +// @Test +// public void testDispatchEvent_whenWildcardListenerRegistered_hearsAllTheEventsAndreturnsNoError() { +// +// // setup +// String extensionName = "ThirdPartyExtension11a"; +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, wildcardListener); +// EventListener eventListener = extensionTestingHelper.getListenerInstance(extensionName, wildcardListener.get(0)); +// // test +// extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(0), eventData); +// extensionTestingHelper.dispatchAnEvent(analyticsListenerTypes.get(0), subEventData); +// extensionTestingHelper.dispatchAnEvent(customListenerTypes.get(0), eventData); +// extensionTestingHelper.dispatchAnEvent(configListenerTypes.get(0), subEventData); +// List eventsHearedByWildcardListener = extensionTestingHelper.getAllEventsHeardByListener(extensionName, +// wildcardListener.get(0)); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertEquals(eventListener.getEventType().getName(), wildcardListener.get(0).eventType); +// assertEquals(eventListener.getEventSource().getName(), wildcardListener.get(0).eventSource); +// assertTrue(eventsHearedByWildcardListener.get(1).getEventType().getName().equalsIgnoreCase(identityListenerTypes.get( +// 0).eventType)); +// assertTrue(eventsHearedByWildcardListener.get(1).getEventSource().getName().equalsIgnoreCase(identityListenerTypes.get( +// 0).eventSource)); +// assertTrue(eventsHearedByWildcardListener.get(1).getEventData().equals(eventData)); +// assertTrue(eventsHearedByWildcardListener.get(2).getEventType().getName().equalsIgnoreCase(analyticsListenerTypes.get( +// 0).eventType)); +// assertTrue(eventsHearedByWildcardListener.get(2).getEventSource().getName().equalsIgnoreCase(analyticsListenerTypes.get( +// 0).eventSource)); +// assertTrue(eventsHearedByWildcardListener.get(2).getEventData().equals(subEventData)); +// assertTrue(eventsHearedByWildcardListener.get(3).getEventType().getName().equalsIgnoreCase(customListenerTypes.get( +// 0).eventType)); +// assertTrue(eventsHearedByWildcardListener.get(3).getEventSource().getName().equalsIgnoreCase(customListenerTypes.get( +// 0).eventSource)); +// assertTrue(eventsHearedByWildcardListener.get(3).getEventData().equals(eventData)); +// assertTrue(eventsHearedByWildcardListener.get(4).getEventType().getName().equalsIgnoreCase(configListenerTypes.get( +// 0).eventType)); +// assertTrue(eventsHearedByWildcardListener.get(4).getEventSource().getName().equalsIgnoreCase(configListenerTypes.get( +// 0).eventSource)); +// assertEquals(eventsHearedByWildcardListener.get(4).getEventData(), subEventData); +// } +// +// // Test Case No : 11 & 12 +// // Dispatch A Custom Event using ACPCore dispatchEvent API +// @Test +// public void testDispatchEvent_whenCustomEvent_returnsTrue() { +// +// // setup +// ListenerType listenerType = customListenerTypes.get(0); +// // test +// boolean dispatchStatus = extensionTestingHelper.dispatchAnEvent(listenerType, eventData); +// // verify +// assertTrue(dispatchStatus); +// } +// +// +// // Test Case No : 13 & 14 +// // Dispatch an event using ACPCore dispatchResponseEvent API +// @Test +// public void testDispatchEventWithResponseCallback_whenPaired_returnsPairedEventAndData() throws InterruptedException { +// +// // setup +// String extensionName = "ThirdPartyExtension13"; +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); +// TestableListener eventListener = (TestableListener) extensionTestingHelper.getListenerInstance(extensionName, +// customListenerTypes.get(0)); +// eventListener.setDispatchBehavior("doDispatchResponseEvent"); +// ListenerType listenerType = customListenerTypes.get(0); +// // test +// Event responseEvent = new Event.Builder("DispatchedEvent", listenerType.eventType, +// listenerType.eventSource).setEventData(eventData).build(); +// final List result = new ArrayList(); +// final CountDownLatch latch = new CountDownLatch(1); +// AdobeCallback dispatchCallback = new AdobeCallback() { +// @Override +// public void call(Event value) { +// result.add(value); +// latch.countDown(); +// } +// }; +// boolean dispatchStatus = MobileCore.dispatchEventWithResponseCallback(responseEvent, dispatchCallback, null); +// latch.await(10, TimeUnit.SECONDS); +// // verify +// assertTrue(dispatchStatus); +// assertTrue(result.get(0).getType().equalsIgnoreCase("com.adobe.eventtype.pairedresponse")); +// assertTrue(result.get(0).getSource().equalsIgnoreCase("com.example.testable.pairedrequest")); +// assertEquals(pairedEventData, result.get(0).getEventData()); +// } +// +// // Test Case No : 15, 17 & 36 +// // Get Shared Event State Owned By A Configuration Event using getSharedEventState API +// // with the extension name as the stateowner like +// // com.adobe.module.identity, com.adobe.module.configuration +// @Test +// public void testGetSharedEventState_whenConfigEvent_returnsAppropriateSharedState() { +// +// // setup +// String extensionName = "ThirdPartyExtension15"; +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, +// configListenerTypes); +// TestableExtension testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); +// +// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// } +// }; +// // test +// updateConfiguration(); +// Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, +// configListenerTypes.get(0).eventSource).setEventData(null).setEventNumber(100).build(); +// asyncHelper.waitForAppThreads(500, true); +// Map configurationSharedState = +// testableExtension.getApi().getSharedEventState(CONFIGURATION_SHARED_STATE, event, errorCallback); +// Event eventHeard = extensionTestingHelper.getLastEventHeardByListener(extensionName, configListenerTypes.get(0)); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertEquals(configMap, configurationSharedState); +// assertEquals(configMap, eventHeard.getEventData()); +// +// } +// +// // Test Case No : 16 +// // Get Shared Event State Owned By A Custom Event using getSharedEventState API for stateowner +// // as extensionName. +// @Test +// public void testGetSharedEventState_whenItIsSet_returnsAppropriateSharedState() { +// +// // setup +// String extensionName = "Extension18"; +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, +// customListenerTypes); +// TestableExtension testableExtension = (TestableExtension) +// extensionTestingHelper.getExtensionInstance(extensionName); +// +// Event event = new Event.Builder("DispatchedEvent", customListenerTypes.get(0).eventType, +// customListenerTypes.get(0).eventSource).setEventData(eventData).build(); +// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// } +// }; +// boolean status1 = testableExtension.getApi().setSharedEventState(subEventData, event, errorCallback); +// +// Map sharedState = testableExtension.getApi().getSharedEventState( +// testableExtension.getName(), event, errorCallback); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertEquals(subEventData, sharedState); +// } +// +// // Test Case No : 18 +// // Set Shared Event State that is not tied to an event using setSharedEventState API +// @Test +// public void testSetAndGetSharedEventState_whenNullEvent_setsAndGetsAppropriateSharedState() { +// +// // setup +// String extensionName = "Extension18"; +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, +// configListenerTypes); +// TestableExtension testableExtension = (TestableExtension) +// extensionTestingHelper.getExtensionInstance(extensionName); +// +// Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, +// configListenerTypes.get(0).eventSource).setEventData(eventData).build(); +// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// } +// }; +// boolean status1 = testableExtension.getApi().setSharedEventState(subEventData, event, errorCallback); +// +// Map sharedState = testableExtension.getApi().getSharedEventState( +// testableExtension.getName(), event, errorCallback); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertEquals(subEventData, sharedState); +// } +// +// // Test Case No : 19 +// // Clear Shared Event States Of A Third Party Extension without affecting +// // the Shared Event States Of other extensions using clearSharedEventStates API +// @Test +// public void testClearSharedEventStates_whenMultipleRegisteredExtensions_doesNotAffectSharedStateOfOtherExtensions() { +// +// // setup +// CreateExtensionResponse returnStatus1 = extensionTestingHelper.registerExtension("ThirdPartyExtensionOne", +// configListenerTypes); +// TestableExtension testableExtension1 = (TestableExtension) +// extensionTestingHelper.getExtensionInstance("ThirdPartyExtensionOne"); +// CreateExtensionResponse returnStatus2 = extensionTestingHelper.registerExtension("ThirdPartyExtensionTwo", +// configListenerTypes); +// TestableExtension testableExtension2 = (TestableExtension) +// extensionTestingHelper.getExtensionInstance("ThirdPartyExtensionTwo"); +// Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, +// configListenerTypes.get(0).eventSource).setEventData(eventData).build(); +// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// } +// }; +// boolean status1 = testableExtension1.getApi().setSharedEventState(subEventData, event, errorCallback); +// boolean status2 = testableExtension2.getApi().setSharedEventState(subEventData, event, errorCallback); +// ExtensionErrorCallback errorCallback1 = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// } +// }; +// Map sharedStateBeforeClearing1 = testableExtension1.getApi().getSharedEventState( +// testableExtension1.getName(), event, errorCallback1); +// Map sharedStateBeforeClearing2 = testableExtension2.getApi().getSharedEventState( +// testableExtension2.getName(), event, errorCallback1); +// ExtensionErrorCallback errorCallback2 = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// Log.debug(LOG_TAG, String.format("An error occurred while clearing the shared states %d %s", +// extensionError.getErrorCode(), extensionError.getErrorName())); +// } +// }; +// // test +// testableExtension1.getApi().clearSharedEventStates(errorCallback2); +// Map getSharedStateAfterClearing1 = testableExtension1.getApi().getSharedEventState( +// testableExtension1.getName(), event, errorCallback1); +// Map getSharedStateAfterClearing2 = testableExtension2.getApi().getSharedEventState( +// testableExtension2.getName(), event, errorCallback1); +// // verify +// assertNull(returnStatus1.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionOne")); +// assertNull(returnStatus2.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionTwo")); +// assertEquals(subEventData, sharedStateBeforeClearing1); +// assertEquals(subEventData, sharedStateBeforeClearing2); +// assertNull(getSharedStateAfterClearing1); +// assertEquals(subEventData, getSharedStateAfterClearing2); +// } +// +// // Test Case No : 22 +// // Make a copy of the event object received using event.copy() method, +// // and the correctness of the copied event object. +// @Test +// public void testCopyEvent_whenCopied_returnsTheCopiedEvent() { +// +// // setup +// ListenerType listenerType = customListenerTypes.get(0); +// Event event = new Event.Builder("DispatchedEvent", listenerType.eventType, +// listenerType.eventSource).setEventData(eventData).build(); +// // setup +// Event copiedEvent = event.copy(); +// // verify +// assertEquals(eventData, copiedEvent.getEventData()); +// assertEquals(listenerType.eventType, copiedEvent.getType()); +// assertEquals(listenerType.eventSource, copiedEvent.getSource()); +// } +// +// // Test Case No : 23 +// // Register the same extension multiple times - should not register, the initial extension +// // should still be registered and receive events, +// @Test +// public void testCreateExtension_whenDuplicateName_returnsError() { +// +// // setup +// String extensionName = "ThirdPartyExtension23"; +// // test +// CreateExtensionResponse returnStatus1 = extensionTestingHelper.registerExtension(extensionName, +// configListenerTypes); +// CreateExtensionResponse returnStatus2 = extensionTestingHelper.registerExtension(extensionName, +// configListenerTypes); +// // verify +// assertNull(returnStatus1.extensionUnexpectedError); +// assertEquals(2, returnStatus2.extensionUnexpectedError.getErrorCode().getErrorCode()); +// assertTrue(returnStatus2.extensionUnexpectedError.getMessage().contains("Failed to register extension with name " + +// extensionName)); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// } +// +// // Test Case No : 26 +// // Register an extension with null version - should be ok, no NPE should be thrown in logs +// @Test +// public void testCreateExtension_whenNullVersion_returnsNoError() { +// +// // setup +// String extensionName = "ExtensionWithNullVersion"; +// // test +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, +// configListenerTypes); +// TestableExtension testableExtension = (TestableExtension) +// extensionTestingHelper.getExtensionInstance(extensionName); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertNull(testableExtension.getVersion()); +// } +// +// // Test Case No : 27 +// // Same as Test case No : 19, but checking with the same extension by re-registering it. +// @Test +// public void testGetSharedEventState_whenExtensionReRegistered_theSharedStateIsUnaffected() { +// +// // setup +// String extensionName = "ThirdPartyExtension27"; +// CreateExtensionResponse returnStatus1 = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); +// TestableExtension testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); +// Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, +// configListenerTypes.get(0).eventSource).setEventData(eventData).build(); +// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// } +// }; +// boolean status = testableExtension.getApi().setSharedEventState(subEventData, event, errorCallback); +// Map sharedStateBeforeUnregister = testableExtension.getApi().getSharedEventState( +// testableExtension.getName(), event, errorCallback); +// // test +// boolean unregisterStatus = extensionTestingHelper.unregisterExtension(extensionName); +// CreateExtensionResponse returnStatus2 = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); +// testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); +// Map sharedStateAfterReregister = testableExtension.getApi().getSharedEventState( +// testableExtension.getName(), event, errorCallback); +// // verify +// assertNull(returnStatus1.extensionUnexpectedError); +// assertTrue(unregisterStatus); +// assertNull(returnStatus2.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertEquals(subEventData, sharedStateBeforeUnregister); +// assertEquals(subEventData, sharedStateAfterReregister); +// } +// +// // Test Case No : 28 +// // Register a listener with null eventType - should be ok, no NPE should be thrown in logs +// @Test +// public void testRegisterListener_whenNullEventType_returnsErrorCode() { +// +// // setup +// String extensionName = "ThirdPartyExtension28a"; +// // setup +// List listenerType = new ArrayList(); +// listenerType.add(new ListenerType(null, "com.example.testable.request")); +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, +// listenerType); +// // test +// Map listenerRegistrationStatus = TestableExtension.getListenerRegistrationStatus(); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorCode(), 3); +// assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorName(), "extension.event_type_not_supported"); +// } +// +// // Test Case No : 28a +// // Register a listener with null eventSource - should be ok, no NPE should be thrown in logs +// @Test +// public void testRegisterListener_whenNullEventSource_returnsErrorCode() { +// +// // setup +// String extensionName = "ThirdPartyExtension28b"; +// // test +// List listenerType = new ArrayList(); +// listenerType.add(new ListenerType("com.example.testable.custom", null)); +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, listenerType); +// Map listenerRegistrationStatus = TestableExtension.getListenerRegistrationStatus(); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorCode(), 4); +// assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorName(), +// "extension.event_source_not_supported"); +// } +// +// // Test Case No : 28b +// // Register a listener with null for both eventType and eventSource - should be ok, no NPE should be thrown in logs +// @Test +// public void testRegisterListener_whenNullEventTypeAndSource_returnsErrorCode() { +// +// // setup +// String extensionName = "ThirdPartyExtension28c"; +// // test +// List listenerType = new ArrayList(); +// listenerType.add(new ListenerType(null, null)); +// CreateExtensionResponse returnStatus = +// extensionTestingHelper.registerExtension(extensionName, listenerType); +// Map listenerRegistrationStatus = TestableExtension.getListenerRegistrationStatus(); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorCode(), 3); +// assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorName(), "extension.event_type_not_supported"); +// } +// +// // Test Case No : 29 +// // Register listener which does busy work on the event hub thread +// // Expected : It should not block the event hub in dispatching new events +// @Test +// @Ignore +// public void testWithAListenerDoingBusyWorkOnTheHub_whenAnotherEventIsDispatched_eventHubShouldDispatchIt() throws +// InterruptedException { +// +// // setup +// final String customExtension = "CustomExtension29"; +// final String busyWorkExtension = "BusyWorkExtension29"; +// +// final List listenerTypes = new ArrayList(); +// +// final List eventsHeard = new ArrayList(); +// final List result = new ArrayList(); +// final ArrayList[] eventsHeardByListener = new ArrayList[4]; +// listenerTypes.add(new ListenerType("com.adobe.eventtype.busywork", "com.example.testable.busywork")); +// listenerTypes.add(new ListenerType("com.adobe.eventtype.customevent", "com.example.testable.customevent")); +// CreateExtensionResponse registrationStatus1 = extensionTestingHelper.registerExtension(customExtension, listenerTypes); +// CreateExtensionResponse registrationStatus2 = extensionTestingHelper.registerExtension(busyWorkExtension, +// listenerTypes); +// +// ExtensionErrorCallback dispatchCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError ec) { +// Log.debug(LOG_TAG, String.format("[dispatchAnEvent] Dispatch failed with error %s ", ec.getErrorCode())); +// } +// }; +// +// Event busyEvent = new Event.Builder("DispatchedBusyEvent", listenerTypes.get(0).eventType, +// listenerTypes.get(0).eventSource).setEventData(subEventData).build(); +// Event customEvent = new Event.Builder("DispatchedCustomEvent", listenerTypes.get(1).eventType, +// listenerTypes.get(1).eventSource).setEventData(subEventData).build(); +// +// MobileCore.dispatchEvent(customEvent, dispatchCallback); +// MobileCore.dispatchEvent(customEvent, dispatchCallback); +// MobileCore.dispatchEvent(busyEvent, dispatchCallback); +// MobileCore.dispatchEvent(customEvent, dispatchCallback); +// MobileCore.dispatchEvent(customEvent, dispatchCallback); +// asyncHelper.waitForAppThreads(500, true); +// eventsHeardByListener[0] = (ArrayList) extensionTestingHelper.getAllEventsHeardByListener(customExtension, +// listenerTypes.get(0)); +// eventsHeardByListener[1] = (ArrayList) extensionTestingHelper.getAllEventsHeardByListener(customExtension, +// listenerTypes.get(1)); +// eventsHeardByListener[2] = (ArrayList) extensionTestingHelper.getAllEventsHeardByListener(busyWorkExtension, +// listenerTypes.get(0)); +// eventsHeardByListener[3] = (ArrayList) extensionTestingHelper.getAllEventsHeardByListener(busyWorkExtension, +// listenerTypes.get(1)); +// // verify +// assertEquals(1, eventsHeardByListener[0].size()); +// assertEquals(4, eventsHeardByListener[1].size()); +// assertEquals(1, eventsHeardByListener[2].size()); +// assertEquals(4, eventsHeardByListener[3].size()); +// } +// +// +// // Test Case No : 30 +// // Build a custom event with null event data should not crash +// @Test +// public void testDispatchEvent_whenNullEventData_returnsNoError() { +// +// // setup +// String extensionName = "ThirdPartyExtension30"; +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, +// identityListenerTypes); +// TestableExtension testableExtension = (TestableExtension) +// extensionTestingHelper.getExtensionInstance(extensionName); +// ListenerType listenerType = new ListenerType("com.adobe.eventType.configuration", +// "com.adobe.eventSource.requestContent"); +// Event event = new Event.Builder("DispatchedEvent", listenerType.eventType, +// listenerType.eventSource).setEventData(null).build(); +// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// } +// }; +// boolean status = testableExtension.getApi().setSharedEventState(null, event, errorCallback); +// // test +// ExtensionErrorCallback errorCallback1 = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// } +// }; +// Map configurationSharedState = testableExtension.getApi().getSharedEventState( +// testableExtension.getName(), event, errorCallback1); +// ExtensionErrorCallback dispatchCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError ec) { +// } +// }; +// boolean dispatchStatus = MobileCore.dispatchEvent(event, dispatchCallback); +// asyncHelper.waitForAppThreads(500, true); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertNull(configurationSharedState); +// assertTrue(dispatchStatus); +// } +// +// // Test Case No : 31 Skipped due to the bug AMSDK-7597 +// // SDK v5 does not have support for Short, Float, and Byte Data types +// // Check under bourbon-core-java-core/code/shared/src/main/java/com/adobe/marketing/mobile +// // Build a custom event data with byte data type +// // dispatch it and check the returned values are the same. +// @Test +// public void testDispatchEvent_whenEventDataWithByteTypeValue_returnsNoError() { +// +// // setup +// String extensionName = "ThirdPartyExtension31a"; +// Map testEventData = new HashMap(); +// int byteData = 0b0010_0101; +// testEventData.put("customByte", byteData); +// // tes +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, +// identityListenerTypes); +// extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(0), testEventData); +// Event event = extensionTestingHelper.getLastEventHeardByListener(extensionName, +// identityListenerTypes.get(0)); +// +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertTrue(identityListenerTypes.get(0).eventType.equalsIgnoreCase(event.getType())); +// assertTrue(identityListenerTypes.get(0).eventSource.equalsIgnoreCase(event.getSource())); +// assertEquals(testEventData, event.getEventData()); +// } +// +// // Test Case No : 31a Skipped due to the bug AMSDK-7596 +// // Build a custom event data with Float data type +// // dispatch it and check the returned values are the same. +// @Ignore +// public void testDispatchEvent_whenEventDataWithFloatTypeValue_returnsNoError() { +// +// // setup +// String extensionName = "ThirdPartyExtension31b"; +// Map testEventData = new HashMap(); +// testEventData.put("customFloat", new Float(3.14)); +// // test +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, +// identityListenerTypes); +// extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(0), testEventData); +// Event event = extensionTestingHelper.getLastEventHeardByListener(extensionName, +// identityListenerTypes.get(0)); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertTrue(identityListenerTypes.get(0).eventType.equalsIgnoreCase(event.getType())); +// assertTrue(identityListenerTypes.get(0).eventSource.equalsIgnoreCase(event.getSource())); +// assertEquals(testEventData, event.getEventData()); +// } +// +// // Test Case No : 31b Skipped due to the bug AMSDK-7595 +// // Build a custom event data with Short data type +// // dispatch it and check the returned values are the same. +// @Ignore +// public void testDispatchEvent_whenEventDataWithShortTypeValue_returnsNoError() { +// +// // setup +// String extensionName = "ThirdPartyExtension31c"; +// Map testEventData = new HashMap(); +// testEventData.put("customShort", (short)300); +// // test +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, +// identityListenerTypes); +// extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(0), testEventData); +// Event event = extensionTestingHelper.getLastEventHeardByListener(extensionName, +// identityListenerTypes.get(0)); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertTrue(identityListenerTypes.get(0).eventType.equalsIgnoreCase(event.getType())); +// assertTrue(identityListenerTypes.get(0).eventSource.equalsIgnoreCase(event.getSource())); +// assertEquals(testEventData, event.getEventData()); +// } +// +// // Test Case No : 32 +// // Dispatch with null event, should return false and error in the callback +// @Test +// public void testDispatchEvent_whenNullEvent_returnsError() throws InterruptedException { +// +// // test +// ExtensionErrorCallback dispatchCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError ec) { +// extensionError = ec; +// } +// }; +// boolean dispatchStatus = MobileCore.dispatchEvent(null, dispatchCallback); +// asyncHelper.waitForAppThreads(500, true); +// // verify +// assertFalse(dispatchStatus); +// assertEquals("extension.event_null", extensionError.getErrorName()); +// assertEquals(6, extensionError.getErrorCode()); +// } +// +// // Test Case No : 33 +// // Dispatch response event with null event, should return false and error in the callback +// @Test +// public void testDispatchEventWithResponseCallback_whenNullResponseEvent_returnsError() throws InterruptedException { +// +// // setup +// String extensionName = "ThirdPartyExtension33"; +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); +// TestableListener eventListener = (TestableListener) extensionTestingHelper.getListenerInstance(extensionName, +// customListenerTypes.get(0)); +// eventListener.setDispatchBehavior("doDispatchResponseEventWithNullEvent"); +// ListenerType listenerType = customListenerTypes.get(0); +// // test +// Event responseEvent = new Event.Builder("DispatchedEvent", listenerType.eventType, +// listenerType.eventSource).setEventData(eventData).build(); +// final List result = new ArrayList(); +// final CountDownLatch latch = new CountDownLatch(1); +// AdobeCallback dispatchCallback = new AdobeCallback() { +// @Override +// public void call(Event value) { +// result.add(value); +// latch.countDown(); +// } +// }; +// boolean dispatchStatus = MobileCore.dispatchEventWithResponseCallback(responseEvent, dispatchCallback, null); +// latch.await(500, TimeUnit.MILLISECONDS); +// // verify +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertTrue(dispatchStatus); +// assertTrue(result.isEmpty()); +// assertEquals("extension.event_null", eventListener.getExtensionError().getErrorName()); +// assertEquals(6, eventListener.getExtensionError().getErrorCode()); +// } +// +// // Test Case No : 34 +// // Get the shared state for the custom extension, should be null if not set, should be valid if set before +// @Test +// public void testGetSharedEventState_whenItIsNotSet_returnsNull() { +// +// // setup +// String extensionName = "ThirdPartyExtension34"; +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); +// TestableExtension testableExtension = (TestableExtension) +// extensionTestingHelper.getExtensionInstance(extensionName); +// ListenerType listenerType = customListenerTypes.get(0); +// Event event = new Event.Builder("DispatchedEvent", listenerType.eventType, +// listenerType.eventSource).setEventData(eventData).build(); +// +// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// } +// }; +// // test +// Map sharedStateBeforeItWasSet = testableExtension.getApi().getSharedEventState( +// testableExtension.getName(), +// event, errorCallback); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertNull(sharedStateBeforeItWasSet); +// } +// +// // Test Case No : 35 +// // SetSharedState with null state, null event, should not crash +// @Test +// public void testSetAndGetSharedEventState_whenNullStateAndEvent_returnsNoError() { +// +// // setup +// String extensionName = "ThirdPartyExtension35"; +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); +// TestableExtension testableExtension = (TestableExtension) +// extensionTestingHelper.getExtensionInstance(extensionName); +// +// final ExtensionError[] extenError = new ExtensionError[1]; +// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// extenError[0] = extensionError; +// } +// }; +// boolean status = testableExtension.getApi().setSharedEventState(null, null, errorCallback); +// // test +// Map customSharedState = testableExtension.getApi().getSharedEventState(testableExtension.getName(), +// null, errorCallback); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertNull(customSharedState); +// assertNull(extenError[0]); +// } +// +// // Test Case No : 38 +// // Simulate an analytics 3rd party extension - dispatching & receiving generic events when trackAction called +// @Test +// public void testTrackAction_whenDispatched_receivedByGenericTrackEventType() { +// // setup +// String extensionName = "AnalyticsExtension38a"; +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, +// analyticsListenerTypes); +// TestableExtension testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); +// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// } +// }; +// // test +// Map additionalContextData = new HashMap(); +// additionalContextData.put("customKey", "value"); +// MobileCore.trackAction("eventDispatched", additionalContextData); +// asyncHelper.waitForAppThreads(500, true); +// Event eventHeard = extensionTestingHelper.getLastEventHeardByListener(extensionName, analyticsListenerTypes.get(4)); +// // verify +// assertEquals("eventDispatched", eventHeard.getEventData().get("action")); +// assertEquals(eventHeard.getEventData().get("contextdata"), additionalContextData); +// assertTrue(eventHeard.getEventSource().getName().equalsIgnoreCase(analyticsListenerTypes.get(4).eventSource)); +// assertTrue(eventHeard.getEventType().getName().equalsIgnoreCase(analyticsListenerTypes.get(4).eventType)); +// +// MobileCore.trackState("trackState", additionalContextData); +// } +// +// // Test Case No : 38a +// // Simulate an analytics 3rd party extension - dispatching & receiving generic events when TrackState called +// @Test +// public void testTrackState_whenDispatched_receivedByGenericTrackEventType() { +// // setup +// String extensionName = "AnalyticsExtension38a"; +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, +// analyticsListenerTypes); +// TestableExtension testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); +// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// } +// }; +// // test +// Map additionalContextData = new HashMap(); +// additionalContextData.put("customKey", "value"); +// MobileCore.trackState("trackState", additionalContextData); +// asyncHelper.waitForAppThreads(500, true); +// Event eventHeard = extensionTestingHelper.getLastEventHeardByListener(extensionName, analyticsListenerTypes.get(4)); +// // verify +// assertEquals("trackState", eventHeard.getEventData().get("state")); +// assertEquals(additionalContextData, eventHeard.getEventData().get("contextdata")); +// assertTrue(eventHeard.getEventSource().getName().equalsIgnoreCase(analyticsListenerTypes.get(4).eventSource)); +// assertTrue(eventHeard.getEventType().getName().equalsIgnoreCase(analyticsListenerTypes.get(4).eventType)); +// } +// // Test Case No : 38b +// // Dispatch different types of Analytics Events and +// // Confirm if they are all received by the appropriate listeners registered. +// @Test +// public void testAnalyticsExtension_whenAnalyticsEventsDispatched_receivesAllDispatchedEvents() { +// // setup +// String extensionName = "AnalyticsExtension38"; +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, +// analyticsListenerTypes); +// TestableExtension testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); +// +// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// } +// }; +// // test +// Event[] eventHeard = new Event[analyticsListenerTypes.size()]; +// +// for (int i = 0; i < analyticsListenerTypes.size(); i++) { +// subEventData.put("newKey" + i, "newValue" + i); +// Event event = new Event.Builder("DispatchedEvent", analyticsListenerTypes.get(i).eventType, +// analyticsListenerTypes.get(i).eventSource).setEventData(subEventData).build(); +// ExtensionErrorCallback dispatchCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError ec) { +// Log.debug(LOG_TAG, String.format("[dispatchAnEvent] Dispatch failed with error %s ", ec.getErrorCode())); +// } +// }; +// MobileCore.dispatchEvent(event, dispatchCallback); +// asyncHelper.waitForAppThreads(500, true); +// eventHeard[i] = extensionTestingHelper.getLastEventHeardByListener(extensionName, analyticsListenerTypes.get(i)); +// // verify +// assertEquals(subEventData, eventHeard[i].getEventData()); +// assertTrue(eventHeard[i].getEventSource().getName().equalsIgnoreCase(analyticsListenerTypes.get(i).eventSource)); +// assertTrue(eventHeard[i].getEventType().getName().equalsIgnoreCase(analyticsListenerTypes.get(i).eventType)); +// } +// } +// +// // Test Case No : 39 +// // Test communication between two 3rd party extensions - dispatch events and set shared states from one of them, +// // Check updates are received in the other extension +// @Test +// public void testCommunicationBetweenExtensions_whenOneDispatchesEventsAndSetsSharedState_otherExtensionReceivesThem() { +// +// // setup +// String extensionName1 = "Extension39One"; +// CreateExtensionResponse returnStatus1 = extensionTestingHelper.registerExtension(extensionName1, customListenerTypes); +// TestableExtension testableExtension1 = (TestableExtension) +// extensionTestingHelper.getExtensionInstance(extensionName1); +// +// String extensionName2 = "Extension39two"; +// CreateExtensionResponse returnStatus2 = extensionTestingHelper.registerExtension(extensionName2, customListenerTypes); +// TestableExtension testableExtension2 = (TestableExtension) +// extensionTestingHelper.getExtensionInstance(extensionName2); +// +// +// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// Log.debug(LOG_TAG, String.format("An error occurred while setting the shared state %d %s", +// extensionError.getErrorCode(), extensionError.getErrorName())); +// } +// }; +// +// // test +// Event event = new Event.Builder("DispatchedEvent", customListenerTypes.get(0).eventType, +// customListenerTypes.get(0).eventSource).setEventData(eventData).build(); +// MobileCore.dispatchEvent(event, errorCallback); +// testableExtension1.getApi().setSharedEventState(subEventData, event, errorCallback); +// asyncHelper.waitForAppThreads(500, true); +// +// Event eventHeard = extensionTestingHelper.getLastEventHeardByListener(extensionName2, customListenerTypes.get(0)); +// Map sharedState = testableExtension2.getApi().getSharedEventState( +// testableExtension1.getName(), event, errorCallback); +// +// // verify +// assertEquals(eventData, eventHeard.getEventData()); +// assertTrue(eventHeard.getEventSource().getName().equalsIgnoreCase(customListenerTypes.get(0).eventSource)); +// assertTrue(eventHeard.getEventType().getName().equalsIgnoreCase(customListenerTypes.get(0).eventType)); +// assertEquals(subEventData, sharedState); +// } +// +// // Test Case No : 40 +// // Check that paired response events are received by another extension in a wildcard listener, but not by a regular listener. +// // The response should be received in the callback by the first extension. +// @Ignore +// public void testDispatchEventWithResponseCallback_whenAnotherExtensionListens_OnlyItsWildcordListenersCanHearIt() throws +// InterruptedException { +// +// // setup +// String extensionName1 = "Extension40One"; +// CreateExtensionResponse registrationStatus1 = extensionTestingHelper.registerExtension(extensionName1, +// customListenerTypes); +// Extension extensionInstance1 = extensionTestingHelper.getExtensionInstance(extensionName1); +// TestableListener eventListener1 = (TestableListener) extensionTestingHelper.getListenerInstance(extensionName1, +// customListenerTypes.get(0)); +// eventListener1.setDispatchBehavior("doDispatchResponseEvent"); +// +// String extensionName2 = "Extension40Two"; +// List listenerTypesOfExtension2 = new ArrayList<>(); +// listenerTypesOfExtension2.add(new ListenerType("com.adobe.eventtype._wildcard_", "com.adobe.eventsource._wildcard_")); +// listenerTypesOfExtension2.add(new ListenerType("com.adobe.eventtype.pairedresponse", +// "com.example.testable.pairedrequest")); +// CreateExtensionResponse registrationStatus2 = extensionTestingHelper.registerExtension(extensionName2, +// listenerTypesOfExtension2); +// +// asyncHelper.waitForAppThreads(500, true); +// +// // test +// Event responseEvent = new Event.Builder("DispatchedEvent", customListenerTypes.get(0).eventType, +// customListenerTypes.get(0).eventSource).setEventData(subEventData).build(); +// final List result = new ArrayList(); +// final CountDownLatch latch = new CountDownLatch(1); +// AdobeCallback dispatchCallback = new AdobeCallback() { +// @Override +// public void call(Event value) { +// result.add(value); +// latch.countDown(); +// } +// }; +// boolean dispatchStatus = MobileCore.dispatchEventWithResponseCallback(responseEvent, dispatchCallback, null); +// latch.await(500, TimeUnit.MILLISECONDS); +// +// List eventsHeardByWildcardListener = extensionTestingHelper.getAllEventsHeardByListener(extensionName2, +// listenerTypesOfExtension2.get(0)); +// List eventsHeardByPairedResponseListener = extensionTestingHelper.getAllEventsHeardByListener(extensionName2, +// listenerTypesOfExtension2.get(1)); +// // verify +// assertEquals(eventsHeardByWildcardListener.size(), 2); +// assertEquals(eventsHeardByPairedResponseListener.size(), 0); +// assertTrue(eventsHeardByWildcardListener.get(0).getEventType().getName().equalsIgnoreCase(customListenerTypes.get( +// 0).eventType)); +// assertTrue(eventsHeardByWildcardListener.get(0).getEventSource().getName().equalsIgnoreCase(customListenerTypes.get( +// 0).eventSource)); +// assertEquals(subEventData, eventsHeardByWildcardListener.get(0).getEventData()); +// assertTrue(eventsHeardByWildcardListener.get( +// 1).getEventType().getName().equalsIgnoreCase("com.adobe.eventtype.pairedresponse")); +// assertTrue(eventsHeardByWildcardListener.get( +// 1).getEventSource().getName().equalsIgnoreCase("com.example.testable.pairedrequest")); +// assertEquals(pairedEventData, eventsHeardByWildcardListener.get(1).getEventData()); +// assertTrue(dispatchStatus); +// assertTrue(result.get(0).getType().equalsIgnoreCase("com.adobe.eventtype.pairedresponse")); +// assertTrue(result.get(0).getSource().equalsIgnoreCase("com.example.testable.pairedrequest")); +// assertEquals(pairedEventData, result.get(0).getEventData()); +// } +// +// // Test Case No : 41 +// // Set XDM Shared Event State that is not tied to an event using setXDMSharedEventState API +// @Test +// public void testSetAndGetXDMSharedEventState_whenDanglingEvent_setsAndGetsAppropriateXDMSharedState() { +// // setup +// String extensionName = "Extension41"; +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, +// configListenerTypes); +// TestableExtension testableExtension = (TestableExtension) +// extensionTestingHelper.getExtensionInstance(extensionName); +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertNotNull(testableExtension); +// +// Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, +// configListenerTypes.get(0).eventSource).setEventData(eventData).build(); +// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// } +// }; +// assertTrue(testableExtension.getApi().setXDMSharedEventState(subEventData, event, errorCallback)); +// +// Map sharedState = testableExtension.getApi().getXDMSharedEventState( +// testableExtension.getName(), event, errorCallback); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertEquals(subEventData, sharedState); +// } +// +// // Test Case No : 42 +// // Clear XDM Shared Event States Of A Third Party Extension without affecting +// // the XDM Shared Event States Of other extensions using clearXDMSharedEventStates API +// @Test +// public void +// testClearXDMSharedEventStates_whenMultipleRegisteredExtensions_doesNotAffectXDMSharedStateOfOtherExtensions() { +// +// // setup +// CreateExtensionResponse returnStatus1 = extensionTestingHelper.registerExtension("ThirdPartyExtensionOne", +// configListenerTypes); +// TestableExtension testableExtension1 = (TestableExtension) +// extensionTestingHelper.getExtensionInstance("ThirdPartyExtensionOne"); +// assertNull(returnStatus1.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionOne")); +// assertNotNull(testableExtension1); +// +// CreateExtensionResponse returnStatus2 = extensionTestingHelper.registerExtension("ThirdPartyExtensionTwo", +// configListenerTypes); +// TestableExtension testableExtension2 = (TestableExtension) +// extensionTestingHelper.getExtensionInstance("ThirdPartyExtensionTwo"); +// assertNull(returnStatus2.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionTwo")); +// assertNotNull(testableExtension2); +// +// Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, +// configListenerTypes.get(0).eventSource).setEventData(eventData).build(); +// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// } +// }; +// assertTrue(testableExtension1.getApi().setXDMSharedEventState(subEventData, event, errorCallback)); +// assertTrue(testableExtension2.getApi().setXDMSharedEventState(subEventData, event, errorCallback)); +// ExtensionErrorCallback errorCallback1 = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// } +// }; +// Map sharedStateBeforeClearing1 = testableExtension1.getApi().getXDMSharedEventState( +// testableExtension1.getName(), event, errorCallback1); +// Map sharedStateBeforeClearing2 = testableExtension2.getApi().getXDMSharedEventState( +// testableExtension2.getName(), event, errorCallback1); +// ExtensionErrorCallback errorCallback2 = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// Log.debug(LOG_TAG, String.format("An error occurred while clearing the XDM shared states %d %s", +// extensionError.getErrorCode(), extensionError.getErrorName())); +// } +// }; +// // test +// testableExtension1.getApi().clearXDMSharedEventStates(errorCallback2); +// Map getSharedStateAfterClearing1 = testableExtension1.getApi().getXDMSharedEventState( +// testableExtension1.getName(), event, errorCallback1); +// Map getSharedStateAfterClearing2 = testableExtension2.getApi().getXDMSharedEventState( +// testableExtension2.getName(), event, errorCallback1); +// // verify +// assertNull(returnStatus1.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionOne")); +// assertNull(returnStatus2.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionTwo")); +// assertEquals(subEventData, sharedStateBeforeClearing1); +// assertEquals(subEventData, sharedStateBeforeClearing2); +// assertNull(getSharedStateAfterClearing1); +// assertEquals(subEventData, getSharedStateAfterClearing2); +// } +// +// // Test Case No : 43 +// // Get the XDM shared state for the custom extension, should be null if not set, should be valid if set before +// @Test +// public void testGetXDMSharedEventState_whenItIsNotSet_returnsNull() { +// +// // setup +// String extensionName = "ThirdPartyExtension43"; +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); +// TestableExtension testableExtension = (TestableExtension) +// extensionTestingHelper.getExtensionInstance(extensionName); +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertNotNull(testableExtension); +// +// ListenerType listenerType = customListenerTypes.get(0); +// Event event = new Event.Builder("DispatchedEvent", listenerType.eventType, +// listenerType.eventSource).setEventData(eventData).build(); +// +// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// } +// }; +// // test +// Map sharedStateBeforeItWasSet = testableExtension.getApi().getXDMSharedEventState( +// testableExtension.getName(), +// event, errorCallback); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertNull(sharedStateBeforeItWasSet); +// } +// +// // Test Case No : 44 +// // SetXDMSharedState with valid state, null event, should not crash +// @Test +// public void testSetAndGetXDMSharedEventState_whenValidStateAndNullEvent_returnsNoError() { +// +// // setup +// String extensionName = "ThirdPartyExtension44"; +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); +// TestableExtension testableExtension = (TestableExtension) +// extensionTestingHelper.getExtensionInstance(extensionName); +// +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertNotNull(testableExtension); +// +// final ExtensionError[] extenError = new ExtensionError[1]; +// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// extenError[0] = extensionError; +// } +// }; +// Map state = new HashMap(); +// state.put("testKey", "testVal"); +// assertTrue(testableExtension.getApi().setXDMSharedEventState(state, null, errorCallback)); +// // test +// Map customSharedState = testableExtension.getApi().getXDMSharedEventState(testableExtension.getName(), +// null, errorCallback); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertEquals(state, customSharedState); +// assertNull(extenError[0]); +// } +// +// // Test Case No : 45 +// // SetXDMSharedState with null state, valid event, should not crash +// @Test +// public void testSetAndGetXDMSharedEventState_whenNullStateAndValidEvent_returnsNoError() { +// +// // setup +// String extensionName = "ThirdPartyExtension44"; +// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); +// TestableExtension testableExtension = (TestableExtension) +// extensionTestingHelper.getExtensionInstance(extensionName); +// +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertNotNull(testableExtension); +// +// final ExtensionError[] extenError = new ExtensionError[1]; +// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError extensionError) { +// extenError[0] = extensionError; +// } +// }; +// Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, +// configListenerTypes.get(0).eventSource).setEventData(eventData).build(); +// assertTrue(testableExtension.getApi().setXDMSharedEventState(null, event, errorCallback)); +// // test +// Map customSharedState = testableExtension.getApi().getXDMSharedEventState(testableExtension.getName(), +// event, errorCallback); +// // verify +// assertNull(returnStatus.extensionUnexpectedError); +// assertTrue(extensionTestingHelper.isRegistered(extensionName)); +// assertNull(customSharedState); +// assertNull(extenError[0]); +// } +// +//} +// +// +// +// diff --git a/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/AbstractE2ETest.java b/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/AbstractE2ETest.java index 8f4957e68..a3f685407 100755 --- a/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/AbstractE2ETest.java +++ b/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/AbstractE2ETest.java @@ -35,15 +35,13 @@ public class AbstractE2ETest { public void setUp() { TestingPlatform testingPlatform = new TestingPlatform(); testableNetworkService = testingPlatform.e2EAndroidNetworkService; - MobileCore.setCore(null); - MobileCore.setPlatformServices(testingPlatform); MobileCore.setApplication(defaultApplication); testHelper.cleanCache(defaultApplication.getApplicationContext()); TestHelper.cleanLocalStorage(); } public void tearDown() { - MobileCore.setCore(null); + } public class LogCat implements TestRule { diff --git a/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/ExtensionTestingHelper.java b/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/ExtensionTestingHelper.java index bfe045580..3e6bea11d 100644 --- a/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/ExtensionTestingHelper.java +++ b/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/ExtensionTestingHelper.java @@ -1,277 +1,284 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentLinkedQueue; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; - -/** - * Class {@link ExtensionTestingHelper} that defines the necessary helper methods to work with the third party extension {@link TestableExtension} that extends {@link ExtensionListener} class of the Adobe Experience Platform SDK. - * - * This class provides the following features to write automated tests for a third party extension - * - * 1. necessary helper methods for the automated tests cases to work with an extension instance. The instance will be captured using the eventHub getActiveModules() method. - * 2. necessary helper methods to get access to the listeners registered by the TestableExtension class. The listeners that are registered in the eventHub will be accessed with the getModuleListeners(Module) method. - * 3. uses static countdown latch to wait for all the listeners to be registered before dispatching events - * 4. has provisions for setting the shared state, retrieving the shared state, clearing the shared state - - * @author Adobe - * @version 5.0 - */ - - - -public class ExtensionTestingHelper { - - private static final String LOG_TAG = ExtensionTestingHelper.class.getSimpleName(); - static AsyncHelper asyncHelper = new AsyncHelper(); - static boolean isDispatched = false; - static String confirmExtensionUnregisteredCall; - static String confirmListenerUnregisteredCall; - - - - /** - * Returns an CreateExtensionResponse object - *

    - * This method helps registering a third party extension with a list of required listeners and - * retuns the status of creation as an CreateExtensionResponse object - * @param tExtensionName Name of the extension to be created - * @param listenerTypes The list of listeners as a ListenerType to be registered as part of creating the third party extension - * @return returns an CreateExtensionResponse object as a result of creating the extension. - */ - - public CreateExtensionResponse registerExtension(String tExtensionName, List listenerTypes) { - final String extensionName = tExtensionName; - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError adbExtensionError) { - Log.debug(LOG_TAG, String.format("[registerExtension] Registration failed with error %s ", - adbExtensionError.getErrorName())); - } - }; - TestableExtension.setListOfListeners(listenerTypes); - TestableExtension.setNameCallback(new Callback() { - public String call() { - return extensionName; - }; - }); - MobileCore.registerExtension(TestableExtension.class, errorCallback); - asyncHelper.waitForAppThreads(500, true); - - if (TestableExtension.getExtensionUnexpectedError() != null) { - Log.debug(LOG_TAG, String.format("[registerExtension] Registration failed with error %s ", - TestableExtension.getExtensionUnexpectedError().getMessage())); - } - - return new CreateExtensionResponse(TestableExtension.getExtensionUnexpectedError(), - TestableExtension.createdExtensionName, TestableExtension.createdExtensionVersion); - } - - /** - * Returns an Map object - *

    - * This method helps finding out all the third party extensions currently registered at the EventHub - * @return returns the list of Extensions registered as a Map object. - */ - - public static Map getAllThirdpartyExtensions() { - - Map thirdPartyExtensions = new HashMap(); - Collection allExtensions = MobileCore.getCore().eventHub.getActiveModules(); - Iterator iterator = allExtensions.iterator(); - - while (iterator.hasNext()) { - Module currentModule = iterator.next(); - - if (currentModule instanceof ExtensionApi) { - ExtensionApi extensionApi = (ExtensionApi) currentModule; - Extension ext = extensionApi.getExtension(); - thirdPartyExtensions.put(ext.getName(), ext); - } - } - - return thirdPartyExtensions; - } - - /** - * Returns a boolean value - *

    - * This method helps to check if an extension is currently registered at the EventHub - * @param extensionName Name of the extension to be checked - * @return returns true if the given extension is registered otherwise false. - */ - - public static boolean isRegistered(String extensionName) { - return (getExtensionInstance(extensionName) != null); - } - - /** - * Returns an Extension Object - *

    - * This method helps to get the instance of an extension currently registered at the EventHub - * @param extensionName Name of the extension - * @return returns instance of an extension if the given extension is registered otherwise a null object. - */ - - public static Extension getExtensionInstance(String extensionName) { - Map thirdPartyExtensions = getAllThirdpartyExtensions(); - return thirdPartyExtensions.get(extensionName); - } - - /** - * Returns a boolean value - *

    - * This method helps to unregister an extension that's currently registered at the EventHub - * @param extensionName Name of the extension to be checked - * @return returns true if the given extension got registered at the EventHub and successfully unregistered otherwise false. - */ - - public static boolean unregisterExtension(String extensionName) { - boolean status = false; - TestableExtension.confirmExtensionUnregisteredCall = ""; - TestableListener.confirmListenerUnregisteredCall = ""; - Extension testableExtension = getExtensionInstance(extensionName); - - if (testableExtension != null) { - testableExtension.getApi().unregisterExtension(); - asyncHelper.waitForAppThreads(500, true); - status = true; - } - - confirmExtensionUnregisteredCall = TestableExtension.confirmExtensionUnregisteredCall; - confirmListenerUnregisteredCall = TestableListener.confirmListenerUnregisteredCall; - return status; - } - - /** - * Returns all the registered listeners of an extension as a Collection Object - *

    - * This method helps finding out the list of listeners that are registered as part of creating a third party extension. - * @param extensionName The name of the 3rd party extension as a String - * @return returns all the registered listeners of an extension as a Collection Object - */ - public static Collection getRegisteredListeners(String extensionName) { - ConcurrentLinkedQueue listeners = new ConcurrentLinkedQueue(); - Extension testableExtension = getExtensionInstance(extensionName); - - if (testableExtension != null) { - listeners = MobileCore.getCore().eventHub.getModuleListeners(testableExtension.getApi()); - } - - return listeners; - } - - /** - * Returns the instance of the EventListener from the list of listeners registered by the extension. - *

    - * @param extensionName The name of the extension. - * @param listenerType The listerType that contains the EventType and EventSource of the listener. - * @return returns the instance of the EventListener from the list of listeners registered by the extension. - * If the expected listener type is not registered this method will return null. - */ - - public static EventListener getListenerInstance(String extensionName, ListenerType listenerType) { - - Collection registeredListeners = getRegisteredListeners(extensionName); - Iterator listenersIterator = registeredListeners.iterator(); - - while (listenersIterator.hasNext()) { - EventListener eventListener = listenersIterator.next(); - - if ((eventListener.getEventType().getName().equalsIgnoreCase(listenerType.eventType)) - && (eventListener.getEventSource().getName().equalsIgnoreCase(listenerType.eventSource))) { - return eventListener; - } - - } - - return null; - } - - - /** - * Returns a boolean value as the status of the dispatching of an Event specified. - *

    - * This method helps dispatching an event to the EventHub. - * @param listenerType The listerType that contains the EventType and EventSource of the Event to be dispatched. - * @param data EventData to be dispatched. - * @return returns the status of the dispatch of an Event as a boolean. - */ - public static boolean dispatchAnEvent(ListenerType listenerType, Map data) { - isDispatched = true; - Event event = new Event.Builder("DispatchedEvent", listenerType.eventType, - listenerType.eventSource).setEventData(data).build(); - ExtensionErrorCallback dispatchCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError ec) { - Log.debug(LOG_TAG, String.format("[dispatchAnEvent] Dispatch failed with error %s ", ec.getErrorCode())); - isDispatched = false; - } - }; - MobileCore.dispatchEvent(event, dispatchCallback); - asyncHelper.waitForAppThreads(500, true); - return isDispatched; - } - - /** - * Returns an Event Object of the Listener that's registered by a Third party extension. - *

    - * This method helps confirming if the Event that's dispatched was listened by the specified listener. - * returns an Event Object of the Listener that's registered by a Third party extension. - * @param extensionName The name of the extension as a String type that owns the listener that's being checked. - * @param listener The name of the listener as a ListenerType which is being checked to confirm whether it received the event that gets published with dispatchEvent call - * @return returns an Event Object of the Listener that's registered by a Third party extension. - * The caller can access the Event Type, Event Source, and Event Data with getEventType().getName(), getEventSource().getName() + getEventData() methods - */ - public static Event getLastEventHeardByListener(String extensionName, ListenerType listener) { - List events = getAllEventsHeardByListener(extensionName, listener); - return ((events.size() > 0) ? events.get(events.size() - 1) : null); - } - - /** - * Returns an Event Object of the Listener that's registered by a Third party extension. - *

    - * This method helps confirming if the Event that's dispatched was listened by the specified listener. - * returns an Event Object of the Listener that's registered by a Third party extension. - * @param extensionName The name of the extension as a String type that owns the listener that's being checked. - * @param listener The name of the listener as a ListenerType which is being checked to confirm whether it received the event that gets published with dispatchEvent call - * @return returns an Event Object of the Listener that's registered by a Third party extension. - * The caller can access the Event Type, Event Source, and Event Data with getEventType().getName(), getEventSource().getName() + getEventData() methods - */ - public static List getAllEventsHeardByListener(String extensionName, ListenerType listener) { - Collection registeredListeners = new ConcurrentLinkedQueue(); - registeredListeners = getRegisteredListeners(extensionName); - Iterator listenersIterator = registeredListeners.iterator(); - TestableListener listenerType = null; - - while (listenersIterator.hasNext()) { - listenerType = (TestableListener) listenersIterator.next(); - - if ((listenerType != null) && - (listenerType.getEventType().getName().equalsIgnoreCase(listener.eventType) - && listenerType.getEventSource().getName().equalsIgnoreCase(listener.eventSource))) { - return listenerType.getReceivedEvents(); - } - } - - return new ArrayList<>(); - } -} +///* +// Copyright 2022 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may obtain a copy +// of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under +// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +// OF ANY KIND, either express or implied. See the License for the specific language +// governing permissions and limitations under the License. +// */ +// +//package com.adobe.marketing.mobile; +// +//import java.util.ArrayList; +//import java.util.Collection; +//import java.util.HashMap; +//import java.util.Iterator; +//import java.util.List; +//import java.util.Map; +//import java.util.concurrent.ConcurrentLinkedQueue; +// +///** +// * Class {@link ExtensionTestingHelper} that defines the necessary helper methods to work with the third party extension {@link TestableExtension} that extends {@link ExtensionListener} class of the Adobe Experience Platform SDK. +// *

    +// * This class provides the following features to write automated tests for a third party extension +// *

    +// * 1. necessary helper methods for the automated tests cases to work with an extension instance. The instance will be captured using the eventHub getActiveModules() method. +// * 2. necessary helper methods to get access to the listeners registered by the TestableExtension class. The listeners that are registered in the eventHub will be accessed with the getModuleListeners(Module) method. +// * 3. uses static countdown latch to wait for all the listeners to be registered before dispatching events +// * 4. has provisions for setting the shared state, retrieving the shared state, clearing the shared state +// * +// * @author Adobe +// * @version 5.0 +// */ +// +// +//public class ExtensionTestingHelper { +// +// private static final String LOG_TAG = ExtensionTestingHelper.class.getSimpleName(); +// static AsyncHelper asyncHelper = new AsyncHelper(); +// static boolean isDispatched = false; +// static String confirmExtensionUnregisteredCall; +// static String confirmListenerUnregisteredCall; +// +// +// /** +// * Returns an CreateExtensionResponse object +// *

    +// * This method helps registering a third party extension with a list of required listeners and +// * retuns the status of creation as an CreateExtensionResponse object +// * +// * @param tExtensionName Name of the extension to be created +// * @param listenerTypes The list of listeners as a ListenerType to be registered as part of creating the third party extension +// * @return returns an CreateExtensionResponse object as a result of creating the extension. +// */ +// +// public CreateExtensionResponse registerExtension(String tExtensionName, List listenerTypes) { +// final String extensionName = tExtensionName; +// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError adbExtensionError) { +// Log.debug(LOG_TAG, String.format("[registerExtension] Registration failed with error %s ", +// adbExtensionError.getErrorName())); +// } +// }; +// TestableExtension.setListOfListeners(listenerTypes); +// TestableExtension.setNameCallback(new Callback() { +// public String call() { +// return extensionName; +// } +// +// ; +// }); +// MobileCore.registerExtension(TestableExtension.class, errorCallback); +// asyncHelper.waitForAppThreads(500, true); +// +// if (TestableExtension.getExtensionUnexpectedError() != null) { +// Log.debug(LOG_TAG, String.format("[registerExtension] Registration failed with error %s ", +// TestableExtension.getExtensionUnexpectedError().getMessage())); +// } +// +// return new CreateExtensionResponse(TestableExtension.getExtensionUnexpectedError(), +// TestableExtension.createdExtensionName, TestableExtension.createdExtensionVersion); +// } +// +// /** +// * Returns an Map object +// *

    +// * This method helps finding out all the third party extensions currently registered at the EventHub +// * +// * @return returns the list of Extensions registered as a Map object. +// */ +// +// public static Map getAllThirdpartyExtensions(EventHub eventhub) { +// +// Map thirdPartyExtensions = new HashMap(); +// Collection allExtensions = eventhub.getActiveModules(); +// Iterator iterator = allExtensions.iterator(); +// +// while (iterator.hasNext()) { +// Module currentModule = iterator.next(); +// +// if (currentModule instanceof Extension) { +// ExtensionApi extensionApi = (ExtensionApi) currentModule; +// Extension ext = extensionApi.getExtension(); +// thirdPartyExtensions.put(ext.getName(), ext); +// } +// } +// +// return thirdPartyExtensions; +// } +// +// /** +// * Returns a boolean value +// *

    +// * This method helps to check if an extension is currently registered at the EventHub +// * +// * @param extensionName Name of the extension to be checked +// * @return returns true if the given extension is registered otherwise false. +// */ +// +// public static boolean isRegistered(String extensionName) { +// return (getExtensionInstance(extensionName) != null); +// } +// +// /** +// * Returns an Extension Object +// *

    +// * This method helps to get the instance of an extension currently registered at the EventHub +// * +// * @param extensionName Name of the extension +// * @return returns instance of an extension if the given extension is registered otherwise a null object. +// */ +// +// public static Extension getExtensionInstance(String extensionName) { +// Map thirdPartyExtensions = getAllThirdpartyExtensions(); +// return thirdPartyExtensions.get(extensionName); +// } +// +// /** +// * Returns a boolean value +// *

    +// * This method helps to unregister an extension that's currently registered at the EventHub +// * +// * @param extensionName Name of the extension to be checked +// * @return returns true if the given extension got registered at the EventHub and successfully unregistered otherwise false. +// */ +// +// public static boolean unregisterExtension(String extensionName) { +// boolean status = false; +// TestableExtension.confirmExtensionUnregisteredCall = ""; +// TestableListener.confirmListenerUnregisteredCall = ""; +// Extension testableExtension = getExtensionInstance(extensionName); +// +// if (testableExtension != null) { +// testableExtension.getApi().unregisterExtension(); +// asyncHelper.waitForAppThreads(500, true); +// status = true; +// } +// +// confirmExtensionUnregisteredCall = TestableExtension.confirmExtensionUnregisteredCall; +// confirmListenerUnregisteredCall = TestableListener.confirmListenerUnregisteredCall; +// return status; +// } +// +// /** +// * Returns all the registered listeners of an extension as a Collection Object +// *

    +// * This method helps finding out the list of listeners that are registered as part of creating a third party extension. +// * +// * @param extensionName The name of the 3rd party extension as a String +// * @return returns all the registered listeners of an extension as a Collection Object +// */ +// public static Collection getRegisteredListeners(EventHub eventhub, String extensionName) { +// ConcurrentLinkedQueue listeners = new ConcurrentLinkedQueue(); +// Extension testableExtension = getExtensionInstance(extensionName); +// +// if (testableExtension != null) { +// listeners = eventhub.getModuleListeners(testableExtension.getApi()); +// } +// +// return listeners; +// } +// +// /** +// * Returns the instance of the EventListener from the list of listeners registered by the extension. +// *

    +// * +// * @param extensionName The name of the extension. +// * @param listenerType The listerType that contains the EventType and EventSource of the listener. +// * @return returns the instance of the EventListener from the list of listeners registered by the extension. +// * If the expected listener type is not registered this method will return null. +// */ +// +// public static EventListener getListenerInstance(String extensionName, ListenerType listenerType) { +// +// Collection registeredListeners = getRegisteredListeners(extensionName); +// Iterator listenersIterator = registeredListeners.iterator(); +// +// while (listenersIterator.hasNext()) { +// EventListener eventListener = listenersIterator.next(); +// +// if ((eventListener.getEventType().getName().equalsIgnoreCase(listenerType.eventType)) +// && (eventListener.getEventSource().getName().equalsIgnoreCase(listenerType.eventSource))) { +// return eventListener; +// } +// +// } +// +// return null; +// } +// +// +// /** +// * Returns a boolean value as the status of the dispatching of an Event specified. +// *

    +// * This method helps dispatching an event to the EventHub. +// * +// * @param listenerType The listerType that contains the EventType and EventSource of the Event to be dispatched. +// * @param data EventData to be dispatched. +// * @return returns the status of the dispatch of an Event as a boolean. +// */ +// public static boolean dispatchAnEvent(ListenerType listenerType, Map data) { +// isDispatched = true; +// Event event = new Event.Builder("DispatchedEvent", listenerType.eventType, +// listenerType.eventSource).setEventData(data).build(); +// ExtensionErrorCallback dispatchCallback = new ExtensionErrorCallback() { +// @Override +// public void error(final ExtensionError ec) { +// Log.debug(LOG_TAG, String.format("[dispatchAnEvent] Dispatch failed with error %s ", ec.getErrorCode())); +// isDispatched = false; +// } +// }; +// MobileCore.dispatchEvent(event, dispatchCallback); +// asyncHelper.waitForAppThreads(500, true); +// return isDispatched; +// } +// +// /** +// * Returns an Event Object of the Listener that's registered by a Third party extension. +// *

    +// * This method helps confirming if the Event that's dispatched was listened by the specified listener. +// * returns an Event Object of the Listener that's registered by a Third party extension. +// * +// * @param extensionName The name of the extension as a String type that owns the listener that's being checked. +// * @param listener The name of the listener as a ListenerType which is being checked to confirm whether it received the event that gets published with dispatchEvent call +// * @return returns an Event Object of the Listener that's registered by a Third party extension. +// * The caller can access the Event Type, Event Source, and Event Data with getEventType().getName(), getEventSource().getName() + getEventData() methods +// */ +// public static Event getLastEventHeardByListener(String extensionName, ListenerType listener) { +// List events = getAllEventsHeardByListener(extensionName, listener); +// return ((events.size() > 0) ? events.get(events.size() - 1) : null); +// } +// +// /** +// * Returns an Event Object of the Listener that's registered by a Third party extension. +// *

    +// * This method helps confirming if the Event that's dispatched was listened by the specified listener. +// * returns an Event Object of the Listener that's registered by a Third party extension. +// * +// * @param extensionName The name of the extension as a String type that owns the listener that's being checked. +// * @param listener The name of the listener as a ListenerType which is being checked to confirm whether it received the event that gets published with dispatchEvent call +// * @return returns an Event Object of the Listener that's registered by a Third party extension. +// * The caller can access the Event Type, Event Source, and Event Data with getEventType().getName(), getEventSource().getName() + getEventData() methods +// */ +// public static List getAllEventsHeardByListener(String extensionName, ListenerType listener) { +// Collection registeredListeners = new ConcurrentLinkedQueue(); +// registeredListeners = getRegisteredListeners(extensionName); +// Iterator listenersIterator = registeredListeners.iterator(); +// TestableListener listenerType = null; +// +// while (listenersIterator.hasNext()) { +// listenerType = (TestableListener) listenersIterator.next(); +// +// if ((listenerType != null) && +// (listenerType.getEventType().getName().equalsIgnoreCase(listener.eventType) +// && listenerType.getEventSource().getName().equalsIgnoreCase(listener.eventSource))) { +// return listenerType.getReceivedEvents(); +// } +// } +// +// return new ArrayList<>(); +// } +//} diff --git a/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/TestHelper.java b/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/TestHelper.java index 4c017064a..24f6eb020 100644 --- a/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/TestHelper.java +++ b/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/TestHelper.java @@ -55,13 +55,6 @@ public void sleep(int milliseconds) { } } - public void bootEventHub() { - // a hack before we fix the module registration issue - //trigger boot event to force loading bundled config - Event bootTrigger = new Event.Builder("EventHub", EventType.HUB, EventSource.BOOTED).build(); - MobileCore.getCore().eventHub.dispatch(bootTrigger); - } - public void setAudienceServer() { HashMap data = new HashMap (); data.put("audience.server", "audience.com"); diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java index 670c61546..bed13d32e 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java @@ -14,17 +14,19 @@ import android.app.Activity; import android.app.Application; import android.content.Context; +import android.support.annotation.VisibleForTesting; import java.util.Date; import java.util.HashMap; import java.util.Map; -public class MobileCore { +final public class MobileCore { private final static String VERSION = "2.0.0"; private final static String LOG_TAG = "MobileCore"; private static final String NULL_CONTEXT_MESSAGE = "Context must be set before calling SDK methods"; private static final Object mutex = new Object(); private static boolean startActionCalled; + @VisibleForTesting private static EventHub eventHub; private MobileCore() { @@ -94,7 +96,7 @@ public Activity getCurrentActivity() { V4ToV5Migration migrationTool = new V4ToV5Migration(); migrationTool.migrate(); - Log.setLoggingService(App.getPlatformServices().getLoggingService()); + Log.setLoggingService(new AndroidLoggingService()); eventHub = new EventHub("AMSEventHub", App.getPlatformServices(), VERSION); try { @@ -969,7 +971,6 @@ static void collectLaunchInfo(final Activity activity) { Log.debug(LOG_TAG, "Failed to collect Activity data (%s)", NULL_CONTEXT_MESSAGE); return; } - DataMarshaller marshaller = new DataMarshaller(); marshaller.marshal(activity); final Map marshalledData = marshaller.getData(); diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/CoreTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/CoreTests.java deleted file mode 100644 index 35e7c2f27..000000000 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/CoreTests.java +++ /dev/null @@ -1,703 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import static junit.framework.TestCase.assertEquals; -import static org.junit.Assert.*; - -public class CoreTests { - private TestableCore core; - private MockEventHubUnitTest eventHub; - private ExtensionErrorCallback errorCallback; - - private static final String ADOBE_PREFIX = "com.adobe.eventType."; - public static final EventType GENERIC_TRACK = EventType.get(ADOBE_PREFIX + "generic.track"); - public static final EventType GENERIC_LIFECYLE = EventType.get(ADOBE_PREFIX + "generic.lifecycle"); - public static final EventType GENERIC_IDENTITY = EventType.get(ADOBE_PREFIX + "generic.identity"); - public static final EventType GENERIC_PII = EventType.get(ADOBE_PREFIX + "generic.pii"); - private final ExtensionError[] resultError = new ExtensionError[1]; - private final boolean[] callbackCalled = new boolean[1]; - final CountDownLatch latch = new CountDownLatch(1); - final CountDownLatch latch2 = new CountDownLatch(1); - - @Before - public void testSetup() { - PlatformServices fakePlatformServices = new FakePlatformServices(); - eventHub = new MockEventHubUnitTest("MockEventHubUnitTest", fakePlatformServices); - core = new TestableCore(fakePlatformServices, eventHub); - resultError[0] = null; - callbackCalled[0] = false; - - errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - resultError[0] = extensionError; - callbackCalled[0] = true; - latch.countDown(); - } - }; - } - - @Test - public void testConfigureWithAppId_should_dispatch_Configuration_Request_Event() { - // Test - core.configureWithAppID("someAppID"); - // Verify - assertTrue(eventHub.isDispatchedCalled); - assertEquals("someAppID", eventHub.dispatchedEvent.getData().optString( - CoreTestConstants.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID, null)); - assertEquals("Configure with AppID", eventHub.dispatchedEvent.getName()); - assertEquals(EventType.CONFIGURATION, eventHub.dispatchedEvent.getEventType()); - assertEquals(EventSource.REQUEST_CONTENT, eventHub.dispatchedEvent.getEventSource()); - } - - @Test - public void testConfigureWithAppId_when_NullAppID_should_dispatch_Configuration_Request_Event() { - // Test - core.configureWithAppID(null); - // Verify - assertTrue(eventHub.isDispatchedCalled); - assertEquals(null, eventHub.dispatchedEvent.getData().optString( - CoreTestConstants.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID, null)); - assertEquals("Configure with AppID", eventHub.dispatchedEvent.getName()); - assertEquals(EventType.CONFIGURATION, eventHub.dispatchedEvent.getEventType()); - assertEquals(EventSource.REQUEST_CONTENT, eventHub.dispatchedEvent.getEventSource()); - } - - @Test - public void testConfigureWithAppId_when_EmptyAppID_should_dispatch_Configuration_Request_Event() { - // Test - core.configureWithAppID(""); - // Verify - assertTrue(eventHub.isDispatchedCalled); - assertEquals("", eventHub.dispatchedEvent.getData().optString( - CoreTestConstants.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_APP_ID, null)); - assertEquals("Configure with AppID", eventHub.dispatchedEvent.getName()); - assertEquals(EventType.CONFIGURATION, eventHub.dispatchedEvent.getEventType()); - assertEquals(EventSource.REQUEST_CONTENT, eventHub.dispatchedEvent.getEventSource()); - - } - - @Test - public void testConfigureWithFileInPath_should_dispatch_Configuration_Request_Event() { - // Test - core.configureWithFileInPath("fakePath"); - // Verify - assertTrue(eventHub.isDispatchedCalled); - assertEquals("fakePath", eventHub.dispatchedEvent.getData().optString( - CoreTestConstants.Configuration.CONFIGURATION_REQUEST_CONTENT_JSON_FILE_PATH, null)); - assertEquals("Configure with FilePath", eventHub.dispatchedEvent.getName()); - assertEquals(EventType.CONFIGURATION, eventHub.dispatchedEvent.getEventType()); - assertEquals(EventSource.REQUEST_CONTENT, eventHub.dispatchedEvent.getEventSource()); - } - - @Test - public void testConfigureWithFileInPath_when_NullURL_shouldNot_dispatch_Configuration_Request_Event() { - // Test - core.configureWithFileInPath(null); - // Verify - assertFalse(eventHub.isDispatchedCalled); - } - - @Test - public void testConfigureWithFileInPath_when_EmptyURL_shouldNot_dispatch_Configuration_Request_Event() { - // Test - core.configureWithFileInPath(""); - // Verify - assertFalse(eventHub.isDispatchedCalled); - } - - @Test - public void testUpdateConfiguration_should_dispatch_Configuration_Request_Event() throws VariantException { - // Test - HashMap configMap = new HashMap(); - configMap.put("configKey", "configValue"); - core.updateConfiguration(configMap); - // Verify - assertTrue(eventHub.isDispatchedCalled); - - - Map configVariantMap = Variant.fromTypedMap(configMap, - PermissiveVariantSerializer.DEFAULT_INSTANCE).getVariantMap(); - assertEquals(configVariantMap, eventHub.dispatchedEvent.getData().optVariantMap( - CoreTestConstants.Configuration.CONFIGURATION_REQUEST_CONTENT_UPDATE_CONFIG, null)); - assertEquals("Configuration Update", eventHub.dispatchedEvent.getName()); - assertEquals(EventType.CONFIGURATION, eventHub.dispatchedEvent.getEventType()); - assertEquals(EventSource.REQUEST_CONTENT, eventHub.dispatchedEvent.getEventSource()); - } - - @Test - public void testUpdateConfiguration_whenNullConfigMap_should_dispatch_Configuration_Request_Event() { - // Test - core.updateConfiguration(null); - // Verify - assertTrue(eventHub.isDispatchedCalled); - assertEquals(null, eventHub.dispatchedEvent.getData() - .optVariantMap(CoreTestConstants.Configuration.CONFIGURATION_REQUEST_CONTENT_UPDATE_CONFIG, null)); - assertEquals("Configuration Update", eventHub.dispatchedEvent.getName()); - assertEquals(EventType.CONFIGURATION, eventHub.dispatchedEvent.getEventType()); - assertEquals(EventSource.REQUEST_CONTENT, eventHub.dispatchedEvent.getEventSource()); - } - - @Test - public void testClearConfiguration_should_dispatch_Configuration_Request_Event() throws VariantException { - // Test; - core.clearUpdatedConfiguration(); - // Verify - assertTrue(eventHub.isDispatchedCalled); - assertEquals("Clear updated configuration", eventHub.dispatchedEvent.getName()); - assertEquals(EventType.CONFIGURATION, eventHub.dispatchedEvent.getEventType()); - assertEquals(EventSource.REQUEST_CONTENT, eventHub.dispatchedEvent.getEventSource()); - assertEquals(true, eventHub.dispatchedEvent.getData().optBoolean( - CoreTestConstants.Configuration.CONFIGURATION_REQUEST_CONTENT_CLEAR_UPDATED_CONFIG, false)); - } - - - @Test - public void testDispatchEvent_whenNullEvent_noCallback_doesNotThrow() { - // Test - try { - core.dispatchEvent(null, null); - } catch (Exception e) { - Assert.fail("No error should be thrown when the event is null"); - } - - // Verify - assertFalse(eventHub.isDispatchedCalled); - } - - @Test - public void testDispatchEvent_whenNullEvent_shouldReturnFalse() { - // Test - boolean dispatchResult = true; - - try { - dispatchResult = core.dispatchEvent(null, null); - } catch (Exception e) { - Assert.fail("No error should be thrown when the event is null"); - } - - // Verify - assertFalse(dispatchResult); - assertFalse(eventHub.isDispatchedCalled); - } - - @Test - public void testDispatchEvent_whenValidEvent_retrunsTrue() { - // Test - boolean dispatchResult = false; - dispatchResult = core.dispatchEvent(new Event.Builder("test", EventType.ANALYTICS, EventSource.REQUEST_CONTENT).build(), - errorCallback); - - // Verify - assertTrue(dispatchResult); - assertTrue(eventHub.isDispatchedCalled); - } - - - @Test - public void testDispatchEvent_whenNullEvent_withCallback_returnsErrorInCallback() throws Exception { - final CountDownLatch latch = new CountDownLatch(1); - final ExtensionError[] resultError = new ExtensionError[1]; - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - resultError[0] = extensionError; - latch.countDown(); - } - }; - - // Test - try { - core.dispatchEvent(null, errorCallback); - } catch (Exception e) { - Assert.fail("No error should be thrown when the event is null"); - } - - // Verify - latch.await(500, TimeUnit.MICROSECONDS); - assertFalse(eventHub.isDispatchedCalled); - assertEquals(ExtensionError.EVENT_NULL, resultError[0]); - } - - @Test - public void testDispatchEvent_whenValidEvent_withCallback_happy() throws Exception { - // Test - core.dispatchEvent(new Event.Builder("test", EventType.ANALYTICS, EventSource.REQUEST_CONTENT).build(), errorCallback); - - // Verify - latch.await(500, TimeUnit.MICROSECONDS); - assertTrue(eventHub.isDispatchedCalled); - assertFalse(callbackCalled[0]); - } - - @Test - public void testDispatchEventWithResponseCallback_whenNullEvent_returnsErrorInCallback() throws Exception { - final Event[] responseEvent = new Event[1]; - AdobeCallback responseCallback = new AdobeCallback() { - @Override - public void call(final Event value) { - responseEvent[0] = value; - } - }; - - // Test - try { - core.dispatchEventWithResponseCallback(null, responseCallback, errorCallback); - } catch (Exception e) { - Assert.fail("No error should be thrown when the event is null"); - } - - // Verify - latch.await(500, TimeUnit.MICROSECONDS); - assertFalse(eventHub.isDispatchedCalled); - assertEquals(ExtensionError.EVENT_NULL, resultError[0]); - } - - @Test - public void testDispatchEventWithResponseCallback_whenValidEvent_andValidResponseCallback_registersOneTimeListener() - throws Exception { - final Event[] responseEvent = new Event[1]; - AdobeCallback responseCallback = new AdobeCallback() { - @Override - public void call(final Event value) { - responseEvent[0] = value; - } - }; - - Event requestEvent = new Event.Builder("test", EventType.ANALYTICS, EventSource.REQUEST_CONTENT).build(); - - // Test - try { - core.dispatchEventWithResponseCallback(requestEvent, responseCallback, errorCallback); - } catch (Exception e) { - Assert.fail("No error should be thrown when the event is valid"); - } - - // Verify - latch.await(500, TimeUnit.MICROSECONDS); - assertTrue(eventHub.isDispatchedCalled); - assertTrue(eventHub.registerOneTimeListenerCalled); - assertEquals(requestEvent.getResponsePairID(), eventHub.registerOneTimeListenerParamPairId); - assertFalse(callbackCalled[0]); - } - - @Test - public void testDispatchEventWithResponseCallbackWithError_whenNullEventOrNullCallback_notDispatchEvent() throws - Exception { - Event requestEvent = new Event.Builder("test", EventType.ANALYTICS, EventSource.REQUEST_CONTENT).build(); - - // Test - try { - core.dispatchEventWithResponseCallback(null, new AdobeCallbackWithError() { - @Override - public void fail(AdobeError error) { - } - - @Override - public void call(Event value) { - } - }); - core.dispatchEventWithResponseCallback(requestEvent, null); - } catch (Exception e) { - Assert.fail("No error should be thrown when the event is null"); - } - - // Verify - latch.await(500, TimeUnit.MICROSECONDS); - assertFalse(eventHub.isDispatchedCalled); - } - - @Test - public void - testDispatchEventWithResponseCallbackWithError_whenValidEvent_andValidResponseCallback_registersOneTimeListener() - throws Exception { - final Event[] responseEvent = new Event[1]; - Event requestEvent = new Event.Builder("test", EventType.ANALYTICS, EventSource.REQUEST_CONTENT).build(); - - // Test - try { - core.dispatchEventWithResponseCallback(requestEvent, new AdobeCallbackWithError() { - @Override - public void fail(AdobeError error) { - - } - - @Override - public void call(Event value) { - responseEvent[0] = value; - } - }); - } catch (Exception e) { - Assert.fail("No error should be thrown when the event is valid"); - } - - // Verify - latch.await(500, TimeUnit.MICROSECONDS); - assertTrue(eventHub.isDispatchedCalled); - assertTrue(eventHub.registerOneTimeListenerWithErrorCalled); - assertEquals(requestEvent.getResponsePairID(), eventHub.registerOneTimeListenerWithErrorParamPairId); - assertFalse(callbackCalled[0]); - } - - @Test - public void testDispatchResponseEvent_whenValidResponse_andValidRequest_dispatchesEvent() throws Exception { - final CountDownLatch latch = new CountDownLatch(1); - final boolean[] callbackCalled = new boolean[1]; - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - callbackCalled[0] = true; - latch.countDown(); - } - }; - - Event responseEvent = new Event.Builder("testResponse", EventType.ANALYTICS, EventSource.RESPONSE_CONTENT).build(); - - Event requestEvent = new Event.Builder("testRequest", EventType.ANALYTICS, EventSource.REQUEST_CONTENT).build(); - - // Test - try { - core.dispatchResponseEvent(responseEvent, requestEvent, errorCallback); - } catch (Exception e) { - Assert.fail("No error should be thrown when the event is valid"); - } - - // Verify - latch.await(500, TimeUnit.MICROSECONDS); - assertTrue(eventHub.isDispatchedCalled); - assertEquals(requestEvent.getResponsePairID(), eventHub.dispatchedEvent.getPairID()); - assertFalse(callbackCalled[0]); - } - - @Test - public void testDispatchResponseEvent_whenValidResponse_andNullRequest_returnErrorInCallback() throws Exception { - Event responseEvent = new Event.Builder("testResponse", EventType.ANALYTICS, EventSource.RESPONSE_CONTENT).build(); - - // Test - try { - core.dispatchResponseEvent(responseEvent, null, errorCallback); - } catch (Exception e) { - Assert.fail("No error should be thrown when the request event is null"); - } - - // Verify - latch.await(500, TimeUnit.MICROSECONDS); - assertFalse(eventHub.isDispatchedCalled); - assertEquals(ExtensionError.EVENT_NULL, resultError[0]); - } - - @Test - public void testDispatchResponseEvent_whenNullResponse_andValidRequest_returnErrorInCallback() throws Exception { - Event requestEvent = new Event.Builder("testRequest", EventType.ANALYTICS, EventSource.REQUEST_CONTENT).build(); - - // Test - try { - core.dispatchResponseEvent(null, requestEvent, errorCallback); - } catch (Exception e) { - Assert.fail("No error should be thrown when the request event is null"); - } - - // Verify - latch.await(500, TimeUnit.MICROSECONDS); - assertFalse(eventHub.isDispatchedCalled); - assertEquals(ExtensionError.EVENT_NULL, resultError[0]); - } - - @Test - public void testDispatchResponseEvent_whenNullResponse_andNullRequest_returnErrorInCallback() throws Exception { - // Test - try { - core.dispatchResponseEvent(null, null, errorCallback); - } catch (Exception e) { - Assert.fail("No error should be thrown when the request event is null"); - } - - // Verify - latch.await(500, TimeUnit.MICROSECONDS); - assertFalse(eventHub.isDispatchedCalled); - assertEquals(ExtensionError.EVENT_NULL, resultError[0]); - } - - @Test - public void testTrack_Action_Happy() { - // Test - Map c = new HashMap(); - c.put("key", "value"); - core.trackAction("action", c); - // Verify - assertTrue(eventHub.isDispatchedCalled); - assertEquals("action", eventHub.dispatchedEvent.getData().optString( - CoreTestConstants.Analytics.TRACK_ACTION, null)); - assertEquals(c, eventHub.dispatchedEvent.getData().optStringMap( - CoreTestConstants.Analytics.CONTEXT_DATA, null)); - assertEquals(GENERIC_TRACK, eventHub.dispatchedEvent.getEventType()); - assertEquals(EventSource.REQUEST_CONTENT, eventHub.dispatchedEvent.getEventSource()); - } - - @Test - public void testTrack_State_Happy() { - // Test - Map c = new HashMap(); - c.put("key", "value"); - core.trackState("state", c); - // Verify - assertTrue(eventHub.isDispatchedCalled); - assertEquals("state", eventHub.dispatchedEvent.getData().optString( - CoreTestConstants.Analytics.TRACK_STATE, null)); - assertEquals(c, eventHub.dispatchedEvent.getData().optStringMap( - CoreTestConstants.Analytics.CONTEXT_DATA, null)); - assertEquals(GENERIC_TRACK, eventHub.dispatchedEvent.getEventType()); - assertEquals(EventSource.REQUEST_CONTENT, eventHub.dispatchedEvent.getEventSource()); - } - - @Test - public void testTrack_State_No_ContextData() { - // Test - core.trackState("state", null); - // Verify - assertTrue(eventHub.isDispatchedCalled); - assertEquals("state", eventHub.dispatchedEvent.getData().optString( - CoreTestConstants.Analytics.TRACK_STATE, null)); - assertEquals(0, eventHub.dispatchedEvent.getData().optStringMap( - CoreTestConstants.Analytics.CONTEXT_DATA, null).size()); - assertEquals(GENERIC_TRACK, eventHub.dispatchedEvent.getEventType()); - assertEquals(EventSource.REQUEST_CONTENT, eventHub.dispatchedEvent.getEventSource()); - } - - @Test - public void testLifecycleStart_Happy() { - // Test - Map c = new HashMap(); - c.put("key", "value"); - core.lifecycleStart(c); - // Verify - assertTrue(eventHub.isDispatchedCalled); - assertEquals("start", eventHub.dispatchedEvent.getData().optString( - CoreTestConstants.Lifecycle.LIFECYCLE_ACTION_KEY, null)); - assertEquals(c, eventHub.dispatchedEvent.getData().optStringMap( - CoreTestConstants.Lifecycle.ADDITIONAL_CONTEXT_DATA, null)); - assertEquals(GENERIC_LIFECYLE, eventHub.dispatchedEvent.getEventType()); - assertEquals(EventSource.REQUEST_CONTENT, eventHub.dispatchedEvent.getEventSource()); - } - - @Test - public void testLifecycleStart_No_ContextData() { - // Test - core.lifecycleStart(null); - // Verify - assertTrue(eventHub.isDispatchedCalled); - assertEquals("start", eventHub.dispatchedEvent.getData().optString( - CoreTestConstants.Lifecycle.LIFECYCLE_ACTION_KEY, null)); - assertNull(eventHub.dispatchedEvent.getData().optStringMap( - CoreTestConstants.Lifecycle.ADDITIONAL_CONTEXT_DATA, null)); - assertEquals(GENERIC_LIFECYLE, eventHub.dispatchedEvent.getEventType()); - assertEquals(EventSource.REQUEST_CONTENT, eventHub.dispatchedEvent.getEventSource()); - } - - - @Test - public void testLifecyclePause_Happy() { - // Test - core.lifecyclePause(); - // Verify - assertTrue(eventHub.isDispatchedCalled); - assertEquals("pause", eventHub.dispatchedEvent.getData().optString( - CoreTestConstants.Lifecycle.LIFECYCLE_ACTION_KEY, null)); - assertNull(eventHub.dispatchedEvent.getData().optStringMap( - CoreTestConstants.Lifecycle.ADDITIONAL_CONTEXT_DATA, null)); - assertEquals(GENERIC_LIFECYLE, eventHub.dispatchedEvent.getEventType()); - assertEquals(EventSource.REQUEST_CONTENT, eventHub.dispatchedEvent.getEventSource()); - } - - - @Test - public void testIdentitySetPushIdentifier_Happy() { - // Test - core.setPushIdentifier("pushid"); - // Verify - assertTrue(eventHub.isDispatchedCalled); - assertEquals("pushid", eventHub.dispatchedEvent.getData().optString( - CoreTestConstants.Identity.PUSH_ID, null)); - assertEquals(GENERIC_IDENTITY, eventHub.dispatchedEvent.getEventType()); - assertEquals(EventSource.REQUEST_CONTENT, eventHub.dispatchedEvent.getEventSource()); - } - - @Test - public void testIdentitySetAdId_Happy() { - // Test - core.setAdvertisingIdentifier("adid"); - // Verify - assertTrue(eventHub.isDispatchedCalled); - assertEquals("adid", eventHub.dispatchedEvent.getData().optString( - CoreTestConstants.Identity.ADV_ID, null)); - assertEquals(GENERIC_IDENTITY, eventHub.dispatchedEvent.getEventType()); - assertEquals(EventSource.REQUEST_CONTENT, eventHub.dispatchedEvent.getEventSource()); - } - - @Test - public void testCollectPii_Happy() { - // Test - Map c = new HashMap(); - c.put("key", "value"); - core.collectPii(c); - // Verify - assertTrue(eventHub.isDispatchedCalled); - assertEquals(c, eventHub.dispatchedEvent.getData().optStringMap( - CoreTestConstants.Signal.SIGNAL_CONTEXT_DATA, null)); - assertEquals(GENERIC_PII, eventHub.dispatchedEvent.getEventType()); - assertEquals(EventSource.REQUEST_CONTENT, eventHub.dispatchedEvent.getEventSource()); - } - - @Test - public void testCollectPii_No_ContextData() { - // Test - core.collectPii(null); - // Verify - assertFalse(eventHub.isDispatchedCalled); - } - - @Test - public void testCollectData_Happy() { - Map data = new HashMap(); - data.put("deeplink", "deeplink://data"); - core.collectData(data); - assertTrue(eventHub.isDispatchedCalled); - EventData dispatchedData = eventHub.dispatchedEvent.getData(); - assertNotNull(dispatchedData); - assertTrue(dispatchedData.containsKey("deeplink")); - assertEquals(data.get("deeplink"), dispatchedData.optString("deeplink", null)); - - assertEquals(EventType.GENERIC_DATA, eventHub.dispatchedEvent.getEventType()); - assertEquals(EventSource.OS, eventHub.dispatchedEvent.getEventSource()); - } - - @Test - public void testCollectData_EmptyData() { - Map data = new HashMap(); - core.collectData(data); - assertFalse(eventHub.isDispatchedCalled); - } - - @Test - public void testCollectData_NullData() { - core.collectData(null); - assertFalse(eventHub.isDispatchedCalled); - } - - @Test - public void testStart_SecondCall() { - eventHub.finishModulesRegistrationCalled = false; - core.start(null); - assertTrue(eventHub.finishModulesRegistrationCalled); - eventHub.finishModulesRegistrationCalled = false; - core.start(null); - assertFalse(eventHub.finishModulesRegistrationCalled); - } - - @Test - public void testConstructor_WithVersion() { - final Core coreWithVersion = new Core(new FakePlatformServices(), "1.2.3"); - - assertNotNull(coreWithVersion.eventHub); - } - - @Test - public void testRegisterEventListener_Happy() throws Exception { - String eventType = "a.b.c"; - String eventSource = "x.y.z"; - core.registerEventListener(eventType, eventSource, new AdobeCallbackWithError() { - private int counter = 0; - @Override - public void fail(AdobeError error) { - - } - - @Override - public void call(Event value) { - assertEquals("value_test", value.getEventData().get("key_test")); - counter ++; - - if (counter == 3) { - latch.countDown(); - } - } - }); - Map data = new HashMap<>(); - data.put("key_test", "value_test"); - core.dispatchEvent(new Event.Builder("test1", eventType, eventSource).setEventData(data).build(), null); - core.dispatchEvent(new Event.Builder("unexpected event", EventType.ANALYTICS, EventSource.REQUEST_CONTENT).build(), - null); - core.dispatchEvent(new Event.Builder("test2", eventType, eventSource).setEventData(data).build(), null); - core.dispatchEvent(new Event.Builder("test3", eventType, eventSource).setEventData(data).build(), null); - latch.await(100, TimeUnit.MICROSECONDS); - } - - @Test - public void testRegisterEventListener_MultipleListenersForSameEvent() throws Exception { - String eventType = "a.b.c"; - String eventSource = "x.y.z"; - core.registerEventListener(eventType, eventSource, new AdobeCallbackWithError() { - @Override - public void fail(AdobeError error) { - - } - - @Override - public void call(Event value) { - assertEquals("value_test", value.getEventData().get("key_test")); - latch.countDown(); - } - }); - core.registerEventListener(eventType, eventSource, new AdobeCallbackWithError() { - @Override - public void fail(AdobeError error) { - - } - - @Override - public void call(Event value) { - assertEquals("value_test", value.getEventData().get("key_test")); - latch2.countDown(); - } - }); - Map data = new HashMap<>(); - data.put("key_test", "value_test"); - core.dispatchEvent(new Event.Builder("test1", eventType, eventSource).setEventData(data).build(), null); - core.dispatchEvent(new Event.Builder("unexpected event", EventType.ANALYTICS, EventSource.REQUEST_CONTENT).build(), - null); - core.dispatchEvent(new Event.Builder("test2", eventType, eventSource).setEventData(data).build(), null); - core.dispatchEvent(new Event.Builder("test3", eventType, eventSource).setEventData(data).build(), null); - latch.await(100, TimeUnit.MICROSECONDS); - latch2.await(100, TimeUnit.MICROSECONDS); - } - - @Test - public void testResetIdentities_Happy() { - // Test - core.resetIdentities(); - - // Verify - assertTrue(eventHub.isDispatchedCalled); - assertEquals(GENERIC_IDENTITY, eventHub.dispatchedEvent.getEventType()); - assertEquals(EventSource.REQUEST_RESET, eventHub.dispatchedEvent.getEventSource()); - } -} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MobileCoreTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MobileCoreTests.java index 35aecba5b..1e7c95840 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MobileCoreTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MobileCoreTests.java @@ -1,698 +1,116 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; +///* +// Copyright 2022 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may obtain a copy +// of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under +// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +// OF ANY KIND, either express or implied. See the License for the specific language +// governing permissions and limitations under the License. +// */ +// +//package com.adobe.marketing.mobile; +// +// +//import android.app.Application; +// +//import org.junit.After; +//import org.junit.Before; +//import org.junit.Test; +//import org.junit.runner.RunWith; +//import org.mockito.Mockito; +//import org.mockito.junit.MockitoJUnitRunner; +// +//import java.util.HashMap; +//import java.util.Map; +//import java.util.concurrent.CountDownLatch; +// +//import static junit.framework.Assert.assertEquals; +//import static junit.framework.Assert.assertFalse; +//import static junit.framework.Assert.assertNotNull; +//import static junit.framework.Assert.assertNull; +//import static junit.framework.Assert.assertTrue; +//import static junit.framework.Assert.fail; +// +//@RunWith(MockitoJUnitRunner.Silent.class) +//public class MobileCoreTests { +// private Application mockApplication; +// +// @Before +// public void setup() { +// +// Log.setLoggingService(platformServices.getLoggingService()); +// } +// +// @After +// public void teardown() { +// if (mockApplication != null) { +// App.clearAppResources(); +// } +// } +// +// +// @Test +// public void testSetAndGetApplicationExpectEqual() { +// mockApplication = Mockito.mock(Application.class); +// MobileCore.setApplication(mockApplication); +// Application app = MobileCore.getApplication(); +// assertNotNull(app); +// assertEquals(mockApplication, app); +// } +// +// @Test +// public void testGetApplicationWithoutFirstSettingApplicationExpectNull() { +// Application app = MobileCore.getApplication(); +// assertNull(app); +// } +// +// @Test +// public void testDispatchEventWithResponseCallback() { +// Map data = new HashMap(); +// data.put("k", "v"); +// Event event = new Event.Builder(" event", "com.adobe.eventType.dispatchEvent", +// "com.test.MobileCoreTest").setEventData(data).build(); +// MobileCore.dispatchEventWithResponseCallback(event, new AdobeCallbackWithError() { +// @Override +// public void fail(AdobeError error) { +// +// } +// +// @Override +// public void call(Event value) { +// +// } +// }); +// assertTrue(core.dispatchEventWithResponseCallbackWithTimeoutCalled); +// } +// +// AdobeError errorInCallback; +// @Test +// public void testDispatchEventWithResponseCallback_null_event() { +// errorInCallback = null; +// MobileCore.dispatchEventWithResponseCallback(null, new AdobeCallbackWithError() { +// @Override +// public void fail(AdobeError error) { +// errorInCallback = error; +// } +// +// @Override +// public void call(Event value) { +// +// } +// }); +// assertFalse(core.dispatchEventWithResponseCallbackWithTimeoutCalled); +// assertEquals(AdobeError.UNEXPECTED_ERROR, errorInCallback); +// } +// +// @Test +// public void testDispatchEventWithResponseCallback_null_callback() { +// Map data = new HashMap(); +// data.put("k", "v"); +// Event event = new Event.Builder(" event", "com.adobe.eventType.dispatchEvent", +// "com.test.MobileCoreTest").setEventData(data).build(); +// MobileCore.dispatchEventWithResponseCallback(event, null); +// assertFalse(core.dispatchEventWithResponseCallbackWithTimeoutCalled); +// } - -import android.app.Application; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.CountDownLatch; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertNull; -import static junit.framework.Assert.assertTrue; -import static junit.framework.Assert.fail; - -@RunWith(MockitoJUnitRunner.Silent.class) -public class MobileCoreTests { - - private PlatformServices platformServices = new DummyPlatformService(); - private EventHub mockEventHub = new EventHub("", platformServices); - private MockCore core; - - private Application mockApplication; - - @Before - public void setup() { - Log.setLoggingService(platformServices.getLoggingService()); - core = new MockCore(platformServices, mockEventHub); - MobileCore.setCore(core); - } - - @After - public void teardown() { - if (mockApplication != null) { - App.clearAppResources(); - } - } - - @Test - public void testTrackState() throws Exception { - //Setup - Map c = new HashMap(); - c.put("key", "value"); - //Test - MobileCore.trackState("state", c); - //Verify - assertTrue(core.trackStateCalled); - assertEquals("state", core.trackStateParameterState); - assertEquals(c, core.trackStateParameterContextData); - } - - @Test - public void testTrackState_With_No_ContextData() throws Exception { - //Test - MobileCore.trackState("state", null); - //Verify - assertTrue(core.trackStateCalled); - assertEquals("state", core.trackStateParameterState); - assertNull(core.trackStateParameterContextData); - } - - @Test - public void testTrackAction() throws Exception { - //Setup - Map c = new HashMap(); - c.put("key", "value"); - //Test - MobileCore.trackAction("action", c); - //Verify - assertTrue(core.trackActionCalled); - assertEquals("action", core.trackActionParameterAction); - assertEquals(c, core.trackActionParameterContextData); - } - - @Test - public void testTrackAction_With_No_ContextData() throws Exception { - //Test - MobileCore.trackAction("action", null); - //Verify - assertTrue(core.trackActionCalled); - assertEquals("action", core.trackActionParameterAction); - assertNull(core.trackActionParameterContextData); - } - - @Test - public void testCollectPii() throws Exception { - //Setup - Map c = new HashMap(); - c.put("key", "value"); - //Test - MobileCore.collectPii(c); - //Verify - assertTrue(core.collectPiiCalled); - assertEquals(c, core.collectPiiParameterData); - } - - @Test - public void testCollectPii_With_No_Data() throws Exception { - //Test - MobileCore.collectPii(null); - //Verify - assertTrue(core.collectPiiCalled); - assertNull(core.collectPiiParameterData); - } - - @Test - public void testSetAdvertisingIdentifier() throws Exception { - //Test - MobileCore.setAdvertisingIdentifier("advid"); - //Verify - assertTrue(core.setAdvertisingIdentifierCalled); - assertEquals("advid", core.setAdvertisingIdentifierParameteradid); - } - - @Test - public void testSetPushIdentifier() throws Exception { - //Test - MobileCore.setPushIdentifier("pushid"); - //Verify - assertTrue(core.setPushIdentifierCalled); - assertEquals("pushid", core.setPushIdentifierParameterRegistrationID); - } - - @Test - public void testLifecycleStart() throws Exception { - //Setup - Map c = new HashMap(); - c.put("key", "value"); - //Test - MobileCore.lifecycleStart(c); - //Verify - assertTrue(core.lifecycleStartCalled); - assertEquals(c, core.lifecycleStartCalledParameterAdditionalContextData); - } - - @Test - public void testLifecyclePause() throws Exception { - //Test - MobileCore.lifecyclePause(); - //Verify - assertTrue(core.lifecyclePauseCalled); - } - - @Test - public void testSetAndGetApplicationExpectEqual() { - mockApplication = Mockito.mock(Application.class); - MobileCore.setApplication(mockApplication); - Application app = MobileCore.getApplication(); - assertNotNull(app); - assertEquals(mockApplication, app); - } - - @Test - public void testGetApplicationWithoutFirstSettingApplicationExpectNull() { - Application app = MobileCore.getApplication(); - assertNull(app); - } - - @Test - public void testSetGetLogLevel() { - - MobileCore.setLogLevel(LoggingMode.VERBOSE); - assertEquals(LoggingMode.VERBOSE, MobileCore.getLogLevel()); - - MobileCore.setLogLevel(LoggingMode.WARNING); - assertEquals(LoggingMode.WARNING, MobileCore.getLogLevel()); - - MobileCore.setLogLevel(LoggingMode.DEBUG); - assertEquals(LoggingMode.DEBUG, MobileCore.getLogLevel()); - - MobileCore.setLogLevel(LoggingMode.ERROR); - assertEquals(LoggingMode.ERROR, MobileCore.getLogLevel()); - - // check to ensure test is updated if LoggingMode is changed - assertEquals(4, LoggingMode.values().length); - } - - @Test - public void testlogVerbose() { - final String expectedTag = "LOG_TAG"; - final String expectedMessage = "Expected log message."; - final CountDownLatch latch = new CountDownLatch(1); - - MockLoggingService mockLoggingService = createMockLoggingService(); - mockLoggingService.setTraceCallback(new MockLoggingService.LoggingCallback() { - @Override - public void call(String tag, String message) { - assertEquals(expectedTag, tag); - assertEquals(expectedMessage, message); - latch.countDown(); - } - }); - Log.setLoggingService(mockLoggingService); - Log.setLogLevel(LoggingMode.VERBOSE); - - MobileCore.log(LoggingMode.VERBOSE, expectedTag, expectedMessage); - assertEquals(0, latch.getCount()); // call is synchronous so no need to wait - } - - @Test - public void testlogDebug() { - final String expectedTag = "LOG_TAG"; - final String expectedMessage = "Expected log message."; - final CountDownLatch latch = new CountDownLatch(1); - - MockLoggingService mockLoggingService = createMockLoggingService(); - mockLoggingService.setDebugCallback(new MockLoggingService.LoggingCallback() { - @Override - public void call(String tag, String message) { - assertEquals(expectedTag, tag); - assertEquals(expectedMessage, message); - latch.countDown(); - } - }); - Log.setLoggingService(mockLoggingService); - Log.setLogLevel(LoggingMode.VERBOSE); - - MobileCore.log(LoggingMode.DEBUG, expectedTag, expectedMessage); - assertEquals(0, latch.getCount()); // call is synchronous so no need to wait - } - - @Test - public void testlogWarning() { - final String expectedTag = "LOG_TAG"; - final String expectedMessage = "Expected log message."; - final CountDownLatch latch = new CountDownLatch(1); - - MockLoggingService mockLoggingService = createMockLoggingService(); - mockLoggingService.setWarningCallback(new MockLoggingService.LoggingCallback() { - @Override - public void call(String tag, String message) { - assertEquals(expectedTag, tag); - assertEquals(expectedMessage, message); - latch.countDown(); - } - }); - Log.setLoggingService(mockLoggingService); - Log.setLogLevel(LoggingMode.VERBOSE); - - MobileCore.log(LoggingMode.WARNING, expectedTag, expectedMessage); - assertEquals(0, latch.getCount()); // call is synchronous so no need to wait - } - - @Test - public void testlogError() { - final String expectedTag = "LOG_TAG"; - final String expectedMessage = "Expected log message."; - final CountDownLatch latch = new CountDownLatch(1); - - MockLoggingService mockLoggingService = createMockLoggingService(); - mockLoggingService.setErrorCallback(new MockLoggingService.LoggingCallback() { - @Override - public void call(String tag, String message) { - assertEquals(expectedTag, tag); - assertEquals(expectedMessage, message); - latch.countDown(); - } - }); - Log.setLoggingService(mockLoggingService); - Log.setLogLevel(LoggingMode.VERBOSE); - - MobileCore.log(LoggingMode.ERROR, expectedTag, expectedMessage); - assertEquals(0, latch.getCount()); // call is synchronous so no need to wait - } - - @Test - public void testVerboseLogMode() { - final String expectedTag = "LOG_TAG"; - final String expectedMessage = "Expected log message."; - final CountDownLatch latch = new CountDownLatch(4); - - MockLoggingService mockLoggingService = createMockLoggingService(); - mockLoggingService.setErrorCallback(new MockLoggingService.LoggingCallback() { - @Override - public void call(String tag, String message) { - assertEquals(expectedTag, tag); - assertEquals(expectedMessage, message); - latch.countDown(); - } - }); - mockLoggingService.setWarningCallback(new MockLoggingService.LoggingCallback() { - @Override - public void call(String tag, String message) { - assertEquals(expectedTag, tag); - assertEquals(expectedMessage, message); - latch.countDown(); - } - }); - mockLoggingService.setDebugCallback(new MockLoggingService.LoggingCallback() { - @Override - public void call(String tag, String message) { - assertEquals(expectedTag, tag); - assertEquals(expectedMessage, message); - latch.countDown(); - } - }); - mockLoggingService.setTraceCallback(new MockLoggingService.LoggingCallback() { - @Override - public void call(String tag, String message) { - assertEquals(expectedTag, tag); - assertEquals(expectedMessage, message); - latch.countDown(); - } - }); - - Log.setLoggingService(mockLoggingService); - Log.setLogLevel(LoggingMode.VERBOSE); - - MobileCore.log(LoggingMode.ERROR, expectedTag, expectedMessage); - MobileCore.log(LoggingMode.WARNING, expectedTag, expectedMessage); - MobileCore.log(LoggingMode.DEBUG, expectedTag, expectedMessage); - MobileCore.log(LoggingMode.VERBOSE, expectedTag, expectedMessage); - assertEquals(0, latch.getCount()); // call is synchronous so no need to wait - } - - @Test - public void testDebugLogMode() { - final String expectedTag = "LOG_TAG"; - final String expectedMessage = "Expected log message."; - final CountDownLatch latch = new CountDownLatch(3); - - MockLoggingService mockLoggingService = createMockLoggingService(); - mockLoggingService.setErrorCallback(new MockLoggingService.LoggingCallback() { - @Override - public void call(String tag, String message) { - assertEquals(expectedTag, tag); - assertEquals(expectedMessage, message); - latch.countDown(); - } - }); - mockLoggingService.setWarningCallback(new MockLoggingService.LoggingCallback() { - @Override - public void call(String tag, String message) { - assertEquals(expectedTag, tag); - assertEquals(expectedMessage, message); - latch.countDown(); - } - }); - mockLoggingService.setDebugCallback(new MockLoggingService.LoggingCallback() { - @Override - public void call(String tag, String message) { - assertEquals(expectedTag, tag); - assertEquals(expectedMessage, message); - latch.countDown(); - } - }); - - Log.setLoggingService(mockLoggingService); - Log.setLogLevel(LoggingMode.DEBUG); - - MobileCore.log(LoggingMode.ERROR, expectedTag, expectedMessage); - MobileCore.log(LoggingMode.WARNING, expectedTag, expectedMessage); - MobileCore.log(LoggingMode.DEBUG, expectedTag, expectedMessage); - MobileCore.log(LoggingMode.VERBOSE, expectedTag, expectedMessage); - assertEquals(0, latch.getCount()); // call is synchronous so no need to wait - } - - @Test - public void testWarningLogMode() { - final String expectedTag = "LOG_TAG"; - final String expectedMessage = "Expected log message."; - final CountDownLatch latch = new CountDownLatch(2); - - MockLoggingService mockLoggingService = createMockLoggingService(); - mockLoggingService.setErrorCallback(new MockLoggingService.LoggingCallback() { - @Override - public void call(String tag, String message) { - assertEquals(expectedTag, tag); - assertEquals(expectedMessage, message); - latch.countDown(); - } - }); - mockLoggingService.setWarningCallback(new MockLoggingService.LoggingCallback() { - @Override - public void call(String tag, String message) { - assertEquals(expectedTag, tag); - assertEquals(expectedMessage, message); - latch.countDown(); - } - }); - - Log.setLoggingService(mockLoggingService); - Log.setLogLevel(LoggingMode.WARNING); - - MobileCore.log(LoggingMode.ERROR, expectedTag, expectedMessage); - MobileCore.log(LoggingMode.WARNING, expectedTag, expectedMessage); - MobileCore.log(LoggingMode.DEBUG, expectedTag, expectedMessage); - MobileCore.log(LoggingMode.VERBOSE, expectedTag, expectedMessage); - assertEquals(0, latch.getCount()); // call is synchronous so no need to wait - } - - @Test - public void testErrorLogMode() { - final String expectedTag = "LOG_TAG"; - final String expectedMessage = "Expected log message."; - final CountDownLatch latch = new CountDownLatch(1); - - MockLoggingService mockLoggingService = createMockLoggingService(); - mockLoggingService.setErrorCallback(new MockLoggingService.LoggingCallback() { - @Override - public void call(String tag, String message) { - assertEquals(expectedTag, tag); - assertEquals(expectedMessage, message); - latch.countDown(); - } - }); - - Log.setLoggingService(mockLoggingService); - Log.setLogLevel(LoggingMode.ERROR); - - MobileCore.log(LoggingMode.ERROR, expectedTag, expectedMessage); - MobileCore.log(LoggingMode.WARNING, expectedTag, expectedMessage); - MobileCore.log(LoggingMode.DEBUG, expectedTag, expectedMessage); - MobileCore.log(LoggingMode.VERBOSE, expectedTag, expectedMessage); - assertEquals(0, latch.getCount()); // call is synchronous so no need to wait - } - - @Test - public void testlogWithNullMode_shouldNotThrow() { - final String expectedTag = "LOG_TAG"; - final String expectedMessage = "Expected log message."; - - MockLoggingService mockLoggingService = createMockLoggingService(); - Log.setLoggingService(mockLoggingService); - Log.setLogLevel(LoggingMode.VERBOSE); - - try { - MobileCore.log(null, expectedTag, expectedMessage); - } catch (Exception e) { - fail("MobileCore log should not throw for null mode"); - } - } - - @Test - public void testVersionNoWrapperType() { - assertFalse(MobileCore.extensionVersion().contains("-")); - } - - @Test - public void testVersionWrapperTypeNone() { - MobileCore.setWrapperType(WrapperType.NONE); - assertFalse(MobileCore.extensionVersion().contains("-")); - } - - @Test - public void testVersionWrapperTypeNoneReset() { - MobileCore.setWrapperType(WrapperType.NONE); - MobileCore.setWrapperType(WrapperType.REACT_NATIVE); - MobileCore.setWrapperType(WrapperType.NONE); - assertFalse(MobileCore.extensionVersion().contains("-")); - } - - @Test - public void testVersionWrapperTypeReactNative() { - MobileCore.setWrapperType(WrapperType.REACT_NATIVE); - assertTrue(MobileCore.extensionVersion().contains("-R")); - } - - @Test - public void testVersionWrapperTypeReactNativeReset() { - MobileCore.setWrapperType(WrapperType.REACT_NATIVE); - MobileCore.setWrapperType(WrapperType.NONE); - MobileCore.setWrapperType(WrapperType.REACT_NATIVE); - assertTrue(MobileCore.extensionVersion().contains("-R")); - } - - @Test - public void testVersionWrapperTypeNoneResetFlutter() { - MobileCore.setWrapperType(WrapperType.NONE); - MobileCore.setWrapperType(WrapperType.FLUTTER); - MobileCore.setWrapperType(WrapperType.NONE); - assertFalse(MobileCore.extensionVersion().contains("-")); - } - - @Test - public void testVersionWrapperTypeFlutter() { - MobileCore.setWrapperType(WrapperType.FLUTTER); - assertTrue(MobileCore.extensionVersion().contains("-F")); - } - - @Test - public void testVersionWrapperTypeFlutterReset() { - MobileCore.setWrapperType(WrapperType.FLUTTER); - MobileCore.setWrapperType(WrapperType.NONE); - MobileCore.setWrapperType(WrapperType.FLUTTER); - assertTrue(MobileCore.extensionVersion().contains("-F")); - } - - @Test - public void testVersionWrapperTypeNoneResetCordova() { - MobileCore.setWrapperType(WrapperType.NONE); - MobileCore.setWrapperType(WrapperType.CORDOVA); - MobileCore.setWrapperType(WrapperType.NONE); - assertFalse(MobileCore.extensionVersion().contains("-")); - } - - @Test - public void testVersionWrapperTypeCordova() { - MobileCore.setWrapperType(WrapperType.CORDOVA); - assertTrue(MobileCore.extensionVersion().contains("-C")); - } - - @Test - public void testVersionWrapperTypeCordovaReset() { - MobileCore.setWrapperType(WrapperType.CORDOVA); - MobileCore.setWrapperType(WrapperType.NONE); - MobileCore.setWrapperType(WrapperType.CORDOVA); - assertTrue(MobileCore.extensionVersion().contains("-C")); - } - - @Test - public void testVersionWrapperTypeNoneResetUnity() { - MobileCore.setWrapperType(WrapperType.NONE); - MobileCore.setWrapperType(WrapperType.UNITY); - MobileCore.setWrapperType(WrapperType.NONE); - assertFalse(MobileCore.extensionVersion().contains("-")); - } - - @Test - public void testVersionWrapperTypeUnity() { - MobileCore.setWrapperType(WrapperType.UNITY); - assertTrue(MobileCore.extensionVersion().contains("-U")); - } - - @Test - public void testVersionWrapperTypeUnityReset() { - MobileCore.setWrapperType(WrapperType.UNITY); - MobileCore.setWrapperType(WrapperType.NONE); - MobileCore.setWrapperType(WrapperType.UNITY); - assertTrue(MobileCore.extensionVersion().contains("-U")); - } - - @Test - public void testVersionWrapperTypeNoneResetXamarin() { - MobileCore.setWrapperType(WrapperType.NONE); - MobileCore.setWrapperType(WrapperType.XAMARIN); - MobileCore.setWrapperType(WrapperType.NONE); - assertFalse(MobileCore.extensionVersion().contains("-")); - } - - @Test - public void testVersionWrapperTypeXamarin() { - MobileCore.setWrapperType(WrapperType.XAMARIN); - assertTrue(MobileCore.extensionVersion().contains("-X")); - } - - @Test - public void testVersionWrapperTypeXamarinReset() { - MobileCore.setWrapperType(WrapperType.XAMARIN); - MobileCore.setWrapperType(WrapperType.NONE); - MobileCore.setWrapperType(WrapperType.XAMARIN); - assertTrue(MobileCore.extensionVersion().contains("-X")); - } - - /** - * Helper to create a {@link MockLoggingService} and populate each callback - * with an {@link junit.framework.Assert#fail()}; - * @return new {@code MockLoggingService} instance - */ - private MockLoggingService createMockLoggingService() { - MockLoggingService mockLoggingService = new MockLoggingService(); - mockLoggingService.setDebugCallback(new MockLoggingService.LoggingCallback() { - @Override - public void call(String tag, String message) { - fail("Call to LoggingService.debug was unexpected."); - } - }); - mockLoggingService.setTraceCallback(new MockLoggingService.LoggingCallback() { - @Override - public void call(String tag, String message) { - fail("Call to LoggingService.trace was unexpected."); - } - }); - mockLoggingService.setWarningCallback(new MockLoggingService.LoggingCallback() { - @Override - public void call(String tag, String message) { - fail("Call to LoggingService.warning was unexpected."); - } - }); - mockLoggingService.setErrorCallback(new MockLoggingService.LoggingCallback() { - @Override - public void call(String tag, String message) { - fail("Call to LoggingService.error was unexpected."); - } - }); - - return mockLoggingService; - } - - @Test - public void testCollectMessageInfo() throws Exception { - //Setup - Map c = new HashMap(); - c.put("key", "value"); - c.put("action", 1); - //Test - MobileCore.collectMessageInfo(c); - //Verify - assertTrue(core.collectDataCalled); - assertEquals(c, core.collectDataParameterMarshalledData); - } - - @Test - public void testCollectMessageInfo_With_Empty_Data() throws Exception { - //Setup - Map c = new HashMap(); - //Test - MobileCore.collectMessageInfo(c); - //Verify - assertTrue(core.collectDataCalled); - assertEquals(c, core.collectDataParameterMarshalledData); - } - - @Test - public void testCollectMessageInfo_With_No_Data() throws Exception { - //Test - MobileCore.collectMessageInfo(null); - //Verify - assertTrue(core.collectDataCalled); - assertNull(core.collectDataParameterMarshalledData); - } - - @Test - public void testDispatchEventWithResponseCallback() { - Map data = new HashMap(); - data.put("k", "v"); - Event event = new Event.Builder(" event", "com.adobe.eventType.dispatchEvent", - "com.test.MobileCoreTest").setEventData(data).build(); - MobileCore.dispatchEventWithResponseCallback(event, new AdobeCallbackWithError() { - @Override - public void fail(AdobeError error) { - - } - - @Override - public void call(Event value) { - - } - }); - assertTrue(core.dispatchEventWithResponseCallbackWithTimeoutCalled); - } - - AdobeError errorInCallback; - @Test - public void testDispatchEventWithResponseCallback_null_event() { - errorInCallback = null; - MobileCore.dispatchEventWithResponseCallback(null, new AdobeCallbackWithError() { - @Override - public void fail(AdobeError error) { - errorInCallback = error; - } - - @Override - public void call(Event value) { - - } - }); - assertFalse(core.dispatchEventWithResponseCallbackWithTimeoutCalled); - assertEquals(AdobeError.UNEXPECTED_ERROR, errorInCallback); - } - - @Test - public void testDispatchEventWithResponseCallback_null_callback() { - Map data = new HashMap(); - data.put("k", "v"); - Event event = new Event.Builder(" event", "com.adobe.eventType.dispatchEvent", - "com.test.MobileCoreTest").setEventData(data).build(); - MobileCore.dispatchEventWithResponseCallback(event, null); - assertFalse(core.dispatchEventWithResponseCallbackWithTimeoutCalled); - } - - @Test - public void testResetIdentities() throws Exception { - //Test - MobileCore.resetIdentities(); - //Verify - assertTrue(core.resetIdentitiesCalled); - } -} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MobileCoreTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MobileCoreTests.kt new file mode 100644 index 000000000..aed9cbad9 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MobileCoreTests.kt @@ -0,0 +1,732 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile + +import android.app.Application +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.any +import org.mockito.Mock +import org.mockito.Mockito +import org.powermock.api.mockito.PowerMockito +import org.powermock.core.classloader.annotations.PrepareForTest +import org.powermock.modules.junit4.PowerMockRunner +import org.powermock.reflect.Whitebox + +@RunWith(PowerMockRunner::class) +@PrepareForTest(DataMarshaller::class, MobileCore::class) +class MobileCoreTests { + + @Mock + private lateinit var eventHub: EventHub + + @Mock + private lateinit var application: Application + + @Mock + private lateinit var loggingService: LoggingService + + @Mock + private lateinit var extensionErrorCallback: ExtensionErrorCallback + + @Mock + private lateinit var adobeCallback: AdobeCallback + + @Mock + private lateinit var adobeCallbackWithError: AdobeCallbackWithError + + private lateinit var dataMarshaller: DataMarshaller + + @Before + fun setup() { + Mockito.reset(eventHub) + Mockito.reset(application) + Mockito.reset(loggingService) + Mockito.reset(extensionErrorCallback) + Mockito.reset(adobeCallback) + Mockito.reset(adobeCallbackWithError) + Whitebox.setInternalState(MobileCore::class.java, "eventHub", eventHub) + Whitebox.setInternalState(MobileCore::class.java, "startActionCalled", false) + dataMarshaller = PowerMockito.mock(DataMarshaller::class.java) + PowerMockito.mock(MobileCore::class.java) + } + + @After + fun teardown() { + } + + @Test + fun `test TrackState()`() { + MobileCore.trackState( + "state", mapOf( + "key" to "value" + ) + ) + val captor = ArgumentCaptor.forClass( + Event::class.java + ) + Mockito.verify(eventHub, Mockito.times(1)).dispatch(captor.capture()) + val dispatchedEvent = captor.value + assertNotNull(dispatchedEvent) + assertEquals("Analytics Track", dispatchedEvent.name) + assertEquals("com.adobe.eventsource.requestcontent", dispatchedEvent.source) + assertEquals("com.adobe.eventtype.generic.track", dispatchedEvent.type) + assertNull(dispatchedEvent.pairID) + assertNotNull(dispatchedEvent.responsePairID) + assertNotNull(dispatchedEvent.uniqueIdentifier) + assertNotNull(dispatchedEvent.eventNumber) + assertEquals( + mapOf( + "state" to "state", + "contextdata" to mapOf( + "key" to "value" + ) + ), dispatchedEvent.eventData + ) + } + + @Test + fun `test TrackState() with no ContextData`() { + MobileCore.trackState("state", null) + val captor = ArgumentCaptor.forClass( + Event::class.java + ) + Mockito.verify(eventHub, Mockito.times(1)).dispatch(captor.capture()) + val dispatchedEvent = captor.value + assertNotNull(dispatchedEvent) + assertEquals("Analytics Track", dispatchedEvent.name) + assertEquals("com.adobe.eventsource.requestcontent", dispatchedEvent.source) + assertEquals("com.adobe.eventtype.generic.track", dispatchedEvent.type) + assertNull(dispatchedEvent.pairID) + assertNotNull(dispatchedEvent.responsePairID) + assertNotNull(dispatchedEvent.uniqueIdentifier) + assertNotNull(dispatchedEvent.eventNumber) + assertEquals( + mapOf( + "state" to "state", + "contextdata" to emptyMap() + ), dispatchedEvent.eventData + ) + } + + @Test + fun `test trackAction()`() { + MobileCore.trackAction( + "action", mapOf( + "key" to "value" + ) + ) + val captor = ArgumentCaptor.forClass( + Event::class.java + ) + Mockito.verify(eventHub, Mockito.times(1)).dispatch(captor.capture()) + val dispatchedEvent = captor.value + assertNotNull(dispatchedEvent) + assertEquals("Analytics Track", dispatchedEvent.name) + assertEquals("com.adobe.eventsource.requestcontent", dispatchedEvent.source) + assertEquals("com.adobe.eventtype.generic.track", dispatchedEvent.type) + assertNull(dispatchedEvent.pairID) + assertNotNull(dispatchedEvent.responsePairID) + assertNotNull(dispatchedEvent.uniqueIdentifier) + assertNotNull(dispatchedEvent.eventNumber) + assertEquals( + mapOf( + "action" to "action", + "contextdata" to mapOf( + "key" to "value" + ) + ), dispatchedEvent.eventData + ) + } + + @Test + fun `test trackAction() with no ContextData`() { + MobileCore.trackAction("action", null) + val captor = ArgumentCaptor.forClass( + Event::class.java + ) + Mockito.verify(eventHub, Mockito.times(1)).dispatch(captor.capture()) + val dispatchedEvent = captor.value + assertNotNull(dispatchedEvent) + assertEquals("Analytics Track", dispatchedEvent.name) + assertEquals("com.adobe.eventsource.requestcontent", dispatchedEvent.source) + assertEquals("com.adobe.eventtype.generic.track", dispatchedEvent.type) + assertNull(dispatchedEvent.pairID) + assertNotNull(dispatchedEvent.responsePairID) + assertNotNull(dispatchedEvent.uniqueIdentifier) + assertNotNull(dispatchedEvent.eventNumber) + assertEquals( + mapOf( + "action" to "action", + "contextdata" to emptyMap() + ), dispatchedEvent.eventData + ) + } + + @Test + fun `test collectPii()`() { + MobileCore.collectPii( + mapOf( + "key" to "value" + ) + ) + val captor = ArgumentCaptor.forClass( + Event::class.java + ) + Mockito.verify(eventHub, Mockito.times(1)).dispatch(captor.capture()) + val dispatchedEvent = captor.value + assertNotNull(dispatchedEvent) + assertEquals("CollectPII", dispatchedEvent.name) + assertEquals("com.adobe.eventsource.requestcontent", dispatchedEvent.source) + assertEquals("com.adobe.eventtype.generic.pii", dispatchedEvent.type) + assertNull(dispatchedEvent.pairID) + assertNotNull(dispatchedEvent.responsePairID) + assertNotNull(dispatchedEvent.uniqueIdentifier) + assertNotNull(dispatchedEvent.eventNumber) + assertEquals( + mapOf( + "contextdata" to mapOf( + "key" to "value" + ) + ), dispatchedEvent.eventData + ) + } + + @Test + fun `test collectPii() without data`() { + MobileCore.collectPii(null) + Mockito.verify(eventHub, Mockito.times(0)).dispatch(any()) + } + + @Test + fun `test setAdvertisingIdentifier()`() { + MobileCore.setAdvertisingIdentifier("advid") + val captor = ArgumentCaptor.forClass( + Event::class.java + ) + Mockito.verify(eventHub, Mockito.times(1)).dispatch(captor.capture()) + val dispatchedEvent = captor.value + assertNotNull(dispatchedEvent) + assertEquals("SetAdvertisingIdentifier", dispatchedEvent.name) + assertEquals("com.adobe.eventsource.requestcontent", dispatchedEvent.source) + assertEquals("com.adobe.eventtype.generic.identity", dispatchedEvent.type) + assertNull(dispatchedEvent.pairID) + assertNotNull(dispatchedEvent.responsePairID) + assertNotNull(dispatchedEvent.uniqueIdentifier) + assertNotNull(dispatchedEvent.eventNumber) + assertEquals( + mapOf( + "advertisingidentifier" to "advid" + ), dispatchedEvent.eventData + ) + } + + @Test + fun `test setPushIdentifier()`() { + MobileCore.setAdvertisingIdentifier("pushid") + val captor = ArgumentCaptor.forClass( + Event::class.java + ) + Mockito.verify(eventHub, Mockito.times(1)).dispatch(captor.capture()) + val dispatchedEvent = captor.value + assertNotNull(dispatchedEvent) + assertEquals("SetAdvertisingIdentifier", dispatchedEvent.name) + assertEquals("com.adobe.eventsource.requestcontent", dispatchedEvent.source) + assertEquals("com.adobe.eventtype.generic.identity", dispatchedEvent.type) + assertNull(dispatchedEvent.pairID) + assertNotNull(dispatchedEvent.responsePairID) + assertNotNull(dispatchedEvent.uniqueIdentifier) + assertNotNull(dispatchedEvent.eventNumber) + assertEquals( + mapOf( + "advertisingidentifier" to "pushid" + ), dispatchedEvent.eventData + ) + } + + @Test + fun `test lifecycleStart()`() { + MobileCore.lifecycleStart( + mapOf( + "key" to "value" + ) + ) + val captor = ArgumentCaptor.forClass( + Event::class.java + ) + Mockito.verify(eventHub, Mockito.times(1)).dispatch(captor.capture()) + val dispatchedEvent = captor.value + assertNotNull(dispatchedEvent) + assertEquals("LifecycleResume", dispatchedEvent.name) + assertEquals("com.adobe.eventsource.requestcontent", dispatchedEvent.source) + assertEquals("com.adobe.eventtype.generic.lifecycle", dispatchedEvent.type) + assertNull(dispatchedEvent.pairID) + assertNotNull(dispatchedEvent.responsePairID) + assertNotNull(dispatchedEvent.uniqueIdentifier) + assertNotNull(dispatchedEvent.eventNumber) + assertEquals( + + mapOf( + "action" to "start", + "additionalcontextdata" to mapOf( + "key" to "value" + ) + ), dispatchedEvent.eventData + ) + } + + @Test + fun `test lifecyclePause()`() { + MobileCore.lifecyclePause() + val captor = ArgumentCaptor.forClass( + Event::class.java + ) + Mockito.verify(eventHub, Mockito.times(1)).dispatch(captor.capture()) + val dispatchedEvent = captor.value + assertNotNull(dispatchedEvent) + assertEquals("LifecyclePause", dispatchedEvent.name) + assertEquals("com.adobe.eventsource.requestcontent", dispatchedEvent.source) + assertEquals("com.adobe.eventtype.generic.lifecycle", dispatchedEvent.type) + assertNull(dispatchedEvent.pairID) + assertNotNull(dispatchedEvent.responsePairID) + assertNotNull(dispatchedEvent.uniqueIdentifier) + assertNotNull(dispatchedEvent.eventNumber) + assertEquals( + mapOf( + "action" to "pause" + ), dispatchedEvent.eventData + ) + } + + @Test + fun `test setApplication()`() { + MobileCore.setApplication(application) + // TODO: xxxx + } + + @Test + fun `test setLogLevel() & getLogLevel()`() { + MobileCore.setLogLevel(LoggingMode.ERROR) + assertEquals(LoggingMode.ERROR, MobileCore.getLogLevel()) + MobileCore.setLogLevel(LoggingMode.WARNING) + assertEquals(LoggingMode.WARNING, MobileCore.getLogLevel()) + MobileCore.setLogLevel(LoggingMode.DEBUG) + assertEquals(LoggingMode.DEBUG, MobileCore.getLogLevel()) + MobileCore.setLogLevel(LoggingMode.VERBOSE) + assertEquals(LoggingMode.VERBOSE, MobileCore.getLogLevel()) + } + + @Test + fun `test log() - VERBOSE`() { + MobileCore.setLogLevel(LoggingMode.VERBOSE) + val logTag = "log_tag" + Log.setLoggingService(loggingService) + MobileCore.log(LoggingMode.VERBOSE, logTag, "verbose logs") + val logCaptor = ArgumentCaptor.forClass( + String::class.java + ) + val tagCaptor = ArgumentCaptor.forClass( + String::class.java + ) + Mockito.verify(loggingService, Mockito.times(1)) + .trace(tagCaptor.capture(), logCaptor.capture()) + assertEquals(logTag, tagCaptor.value) + assertEquals("verbose logs", logCaptor.value) + } + + @Test + fun `test log() - DEBUG`() { + MobileCore.setLogLevel(LoggingMode.VERBOSE) + val logTag = "log_tag" + Log.setLoggingService(loggingService) + MobileCore.log(LoggingMode.DEBUG, logTag, "debug logs") + val logCaptor = ArgumentCaptor.forClass( + String::class.java + ) + val tagCaptor = ArgumentCaptor.forClass( + String::class.java + ) + Mockito.verify(loggingService, Mockito.times(1)) + .debug(tagCaptor.capture(), logCaptor.capture()) + assertEquals(logTag, tagCaptor.value) + assertEquals("debug logs", logCaptor.value) + } + + @Test + fun `test log() - WARNING`() { + MobileCore.setLogLevel(LoggingMode.VERBOSE) + val logTag = "log_tag" + Log.setLoggingService(loggingService) + MobileCore.log(LoggingMode.WARNING, logTag, "warning logs") + val logCaptor = ArgumentCaptor.forClass( + String::class.java + ) + val tagCaptor = ArgumentCaptor.forClass( + String::class.java + ) + Mockito.verify(loggingService, Mockito.times(1)) + .warning(tagCaptor.capture(), logCaptor.capture()) + assertEquals(logTag, tagCaptor.value) + assertEquals("warning logs", logCaptor.value) + } + + @Test + fun `test log() - ERROR`() { + MobileCore.setLogLevel(LoggingMode.VERBOSE) + val logTag = "log_tag" + Log.setLoggingService(loggingService) + MobileCore.log(LoggingMode.ERROR, logTag, "error logs") + val logCaptor = ArgumentCaptor.forClass( + String::class.java + ) + val tagCaptor = ArgumentCaptor.forClass( + String::class.java + ) + Mockito.verify(loggingService, Mockito.times(1)) + .error(tagCaptor.capture(), logCaptor.capture()) + assertEquals(logTag, tagCaptor.value) + assertEquals("error logs", logCaptor.value) + } + + @Test + fun `test log() with filtered out logs`() { + MobileCore.setLogLevel(LoggingMode.ERROR) + val logTag = "log_tag" + Log.setLoggingService(loggingService) + MobileCore.log(LoggingMode.VERBOSE, logTag, "verbose logs") + MobileCore.log(LoggingMode.DEBUG, logTag, "debug logs") + MobileCore.log(LoggingMode.WARNING, logTag, "verbose logs") + MobileCore.log(LoggingMode.ERROR, logTag, "error logs") + Mockito.verify(loggingService, Mockito.times(0)).trace(any(), any()) + Mockito.verify(loggingService, Mockito.times(0)).debug(any(), any()) + Mockito.verify(loggingService, Mockito.times(0)).warning(any(), any()) + val logCaptor = ArgumentCaptor.forClass( + String::class.java + ) + val tagCaptor = ArgumentCaptor.forClass( + String::class.java + ) + Mockito.verify(loggingService, Mockito.times(1)) + .error(tagCaptor.capture(), logCaptor.capture()) + assertEquals(logTag, tagCaptor.value) + assertEquals("error logs", logCaptor.value) + } + + @Test + fun `test log() without logging acceptor`() { + MobileCore.setLogLevel(LoggingMode.ERROR) + val logTag = "log_tag" + Log.setLoggingService(null) + MobileCore.log(LoggingMode.VERBOSE, logTag, "verbose logs") + MobileCore.log(LoggingMode.DEBUG, logTag, "debug logs") + MobileCore.log(LoggingMode.WARNING, logTag, "verbose logs") + MobileCore.log(LoggingMode.ERROR, logTag, "error logs") + } + + @Test + fun `test extensionVersion()`() { + MobileCore.setWrapperType(WrapperType.NONE) + MobileCore.extensionVersion() + Mockito.verify(eventHub, Mockito.times(1)).sdkVersion + } + + @Test + fun `test extensionVersion() - RN`() { + MobileCore.setWrapperType(WrapperType.REACT_NATIVE) + MobileCore.extensionVersion() + val captor = ArgumentCaptor.forClass( + WrapperType::class.java + ) + Mockito.verify(eventHub, Mockito.times(1)).wrapperType = captor.capture() + assertEquals(WrapperType.REACT_NATIVE, captor.value) + } + + @Test + fun `test collectMessageInfo()`() { + MobileCore.collectMessageInfo( + mapOf( + "key" to "value" + ) + ) + val captor = ArgumentCaptor.forClass( + Event::class.java + ) + Mockito.verify(eventHub, Mockito.times(1)).dispatch(captor.capture()) + val dispatchedEvent = captor.value + assertNotNull(dispatchedEvent) + assertEquals("CollectData", dispatchedEvent.name) + assertEquals("com.adobe.eventsource.os", dispatchedEvent.source) + assertEquals("com.adobe.eventtype.generic.data", dispatchedEvent.type) + assertNull(dispatchedEvent.pairID) + assertNotNull(dispatchedEvent.responsePairID) + assertNotNull(dispatchedEvent.uniqueIdentifier) + assertNotNull(dispatchedEvent.eventNumber) + assertEquals(mapOf("key" to "value"), dispatchedEvent.eventData) + } + + @Test + fun `test collectMessageInfo() with null data`() { + MobileCore.collectMessageInfo(null) + Mockito.verify(eventHub, Mockito.times(0)).dispatch(any()) + } + + @Test + fun `test collectMessageInfo() without empty data`() { + MobileCore.collectMessageInfo(emptyMap()) + Mockito.verify(eventHub, Mockito.times(0)).dispatch(any()) + } + + @Test + fun `test resetIdentities()`() { + MobileCore.resetIdentities() + val captor = ArgumentCaptor.forClass( + Event::class.java + ) + Mockito.verify(eventHub, Mockito.times(1)).dispatch(captor.capture()) + val dispatchedEvent = captor.value + assertNotNull(dispatchedEvent) + assertEquals("Reset Identities Request", dispatchedEvent.name) + assertEquals("com.adobe.eventsource.requestreset", dispatchedEvent.source) + assertEquals("com.adobe.eventtype.generic.identity", dispatchedEvent.type) + assertNull(dispatchedEvent.pairID) + assertNotNull(dispatchedEvent.responsePairID) + assertNotNull(dispatchedEvent.uniqueIdentifier) + assertNotNull(dispatchedEvent.eventNumber) + } + + @Test + fun `test dispatchEvent() without callback`() { + val event = Event.Builder( + "event", + "com.adobe.eventType.lifecycle", + "com.adobe.eventSource.responseContent" + ).setEventData( + mapOf( + "lifecyclecontextdata" to mapOf( + "launchevent" to "LaunchEvent" + ) + ) + ).build() + MobileCore.dispatchEvent(event, null) + val captor = ArgumentCaptor.forClass( + Event::class.java + ) + Mockito.verify(eventHub, Mockito.times(1)).dispatch(captor.capture()) + val dispatchedEvent = captor.value + assertEquals(event, dispatchedEvent) + } + + @Test + fun `test dispatchEvent() with callback`() { + MobileCore.dispatchEvent(null, extensionErrorCallback) + Mockito.verify(eventHub, Mockito.times(0)).dispatch(any()) + val captor = ArgumentCaptor.forClass( + ExtensionError::class.java + ) + Mockito.verify(extensionErrorCallback, Mockito.timeout(1000).times(1)) + .error(captor.capture()) + assertEquals(ExtensionError.EVENT_NULL, captor.value) + } + + @Test + fun `test collectLaunchInfo()`() { + val data = mapOf( + "key" to "value" + ) + + PowerMockito.whenNew(DataMarshaller::class.java).withNoArguments() + .thenReturn(dataMarshaller) + PowerMockito.`when`(dataMarshaller.data) + .thenReturn(data) + val captor = ArgumentCaptor.forClass( + Event::class.java + ) + MobileCore.collectLaunchInfo(null) + Mockito.verify(eventHub, Mockito.times(1)).dispatch(captor.capture()) + val dispatchedEvent = captor.value + assertNotNull(dispatchedEvent) + assertEquals("CollectData", dispatchedEvent.name) + assertEquals("com.adobe.eventsource.os", dispatchedEvent.source) + assertEquals("com.adobe.eventtype.generic.data", dispatchedEvent.type) + assertNull(dispatchedEvent.pairID) + assertNotNull(dispatchedEvent.responsePairID) + assertNotNull(dispatchedEvent.uniqueIdentifier) + assertNotNull(dispatchedEvent.eventNumber) + assertEquals(mapOf("key" to "value"), dispatchedEvent.eventData) + } + + @Test + fun `test collectLaunchInfo() without data in Activity`() { + PowerMockito.whenNew(DataMarshaller::class.java).withNoArguments() + .thenReturn(dataMarshaller) + PowerMockito.`when`(dataMarshaller.data) + .thenReturn(emptyMap()) + MobileCore.collectLaunchInfo(null) + Mockito.verify(eventHub, Mockito.times(0)).dispatch(any()) + } + + @Test + fun `test clearUpdatedConfiguration()`() { + val captor = ArgumentCaptor.forClass( + Event::class.java + ) + MobileCore.clearUpdatedConfiguration() + Mockito.verify(eventHub, Mockito.times(1)).dispatch(captor.capture()) + val dispatchedEvent = captor.value + assertNotNull(dispatchedEvent) + assertEquals("Clear updated configuration", dispatchedEvent.name) + assertEquals("com.adobe.eventsource.requestcontent", dispatchedEvent.source) + assertEquals("com.adobe.eventtype.configuration", dispatchedEvent.type) + assertNull(dispatchedEvent.pairID) + assertNotNull(dispatchedEvent.responsePairID) + assertNotNull(dispatchedEvent.uniqueIdentifier) + assertNotNull(dispatchedEvent.eventNumber) + assertEquals(mapOf("config.clearUpdates" to true), dispatchedEvent.eventData) + } + + @Test + fun `test configureWithAppID()`() { + val appId = "id_123" + val captor = ArgumentCaptor.forClass( + Event::class.java + ) + MobileCore.configureWithAppID(appId) + Mockito.verify(eventHub, Mockito.times(1)).dispatch(captor.capture()) + val dispatchedEvent = captor.value + assertNotNull(dispatchedEvent) + assertEquals("Configure with AppID", dispatchedEvent.name) + assertEquals("com.adobe.eventsource.requestcontent", dispatchedEvent.source) + assertEquals("com.adobe.eventtype.configuration", dispatchedEvent.type) + assertNull(dispatchedEvent.pairID) + assertNotNull(dispatchedEvent.responsePairID) + assertNotNull(dispatchedEvent.uniqueIdentifier) + assertNotNull(dispatchedEvent.eventNumber) + assertEquals(mapOf("config.appId" to appId), dispatchedEvent.eventData) + } + + @Test + fun `test configureWithFileInAssets()`() { + val path = "path/to/the/bundled/configuration" + val captor = ArgumentCaptor.forClass( + Event::class.java + ) + MobileCore.configureWithFileInAssets(path) + Mockito.verify(eventHub, Mockito.times(1)).dispatch(captor.capture()) + val dispatchedEvent = captor.value + assertNotNull(dispatchedEvent) + assertEquals("Configure with FilePath", dispatchedEvent.name) + assertEquals("com.adobe.eventsource.requestcontent", dispatchedEvent.source) + assertEquals("com.adobe.eventtype.configuration", dispatchedEvent.type) + assertNull(dispatchedEvent.pairID) + assertNotNull(dispatchedEvent.responsePairID) + assertNotNull(dispatchedEvent.uniqueIdentifier) + assertNotNull(dispatchedEvent.eventNumber) + assertEquals(mapOf("config.assetFile" to path), dispatchedEvent.eventData) + } + + @Test + fun `test configureWithFileInPath()`() { + val path = "path/to/the/bundled/configuration" + val captor = ArgumentCaptor.forClass( + Event::class.java + ) + MobileCore.configureWithFileInPath(path) + Mockito.verify(eventHub, Mockito.times(1)).dispatch(captor.capture()) + val dispatchedEvent = captor.value + assertNotNull(dispatchedEvent) + assertEquals("Configure with FilePath", dispatchedEvent.name) + assertEquals("com.adobe.eventsource.requestcontent", dispatchedEvent.source) + assertEquals("com.adobe.eventtype.configuration", dispatchedEvent.type) + assertNull(dispatchedEvent.pairID) + assertNotNull(dispatchedEvent.responsePairID) + assertNotNull(dispatchedEvent.uniqueIdentifier) + assertNotNull(dispatchedEvent.eventNumber) + assertEquals(mapOf("config.filePath" to path), dispatchedEvent.eventData) + } + + @Test + @Ignore + fun `test dispatchEventWithResponseCallback()`() { + // TODO: will test dispatchEventWithResponseCallback() after we have the redesigned OneTimeListener and EventListener APIs + } + + @Test + @Ignore + fun `test registerEventListener()`() { + // TODO: will test dispatchEventWithResponseCallback() after we have the redesigned OneTimeListener and EventListener APIs + } + + @Test + @Ignore + fun `test getPrivacyStatus()`() { + // TODO: will test dispatchEventWithResponseCallback() after we have the redesigned OneTimeListener and EventListener APIs + } + + @Test + fun `test setPrivacyStatus()`() { + val captor = ArgumentCaptor.forClass( + Event::class.java + ) + MobileCore.setPrivacyStatus(MobilePrivacyStatus.OPT_OUT) + Mockito.verify(eventHub, Mockito.times(1)).dispatch(captor.capture()) + val dispatchedEvent = captor.value + assertNotNull(dispatchedEvent) + assertEquals("Configuration Update", dispatchedEvent.name) + assertEquals("com.adobe.eventsource.requestcontent", dispatchedEvent.source) + assertEquals("com.adobe.eventtype.configuration", dispatchedEvent.type) + assertNull(dispatchedEvent.pairID) + assertNotNull(dispatchedEvent.responsePairID) + assertNotNull(dispatchedEvent.uniqueIdentifier) + assertNotNull(dispatchedEvent.eventNumber) + assertEquals( + mapOf( + "config.update" to mapOf( + "global.privacy" to MobilePrivacyStatus.OPT_OUT.value + ) + ), dispatchedEvent.eventData + ) + } + + @Test + fun `test updateConfiguration()`() { + val configurations = mapOf( + "key" to "value" + ) + val captor = ArgumentCaptor.forClass( + Event::class.java + ) + MobileCore.updateConfiguration(configurations) + Mockito.verify(eventHub, Mockito.times(1)).dispatch(captor.capture()) + val dispatchedEvent = captor.value + assertNotNull(dispatchedEvent) + assertEquals("Configuration Update", dispatchedEvent.name) + assertEquals("com.adobe.eventsource.requestcontent", dispatchedEvent.source) + assertEquals("com.adobe.eventtype.configuration", dispatchedEvent.type) + assertNull(dispatchedEvent.pairID) + assertNotNull(dispatchedEvent.responsePairID) + assertNotNull(dispatchedEvent.uniqueIdentifier) + assertNotNull(dispatchedEvent.eventNumber) + assertEquals( + mapOf( + "config.update" to mapOf( + "key" to "value" + ) + ), dispatchedEvent.eventData + ) + } +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MockCore.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MockCore.java deleted file mode 100644 index c67700db5..000000000 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MockCore.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import java.util.Map; - -public class MockCore extends Core { - - MockCore(PlatformServices platformServices) { - super(platformServices); - } - - MockCore(PlatformServices platformServices, EventHub eventHub) { - super(platformServices, eventHub); - } - - @Override - void registerExtension(Class extensionClass, - ExtensionErrorCallback errorCallback) { - super.registerExtension(extensionClass, errorCallback); - } - - @Override - boolean dispatchEvent(Event event, ExtensionErrorCallback errorCallback) { - return super.dispatchEvent(event, errorCallback); - } - - @Override - void configureWithAppID(String appId) { - super.configureWithAppID(appId); - } - - @Override - void configureWithFileInPath(String filepath) { - super.configureWithFileInPath(filepath); - } - - @Override - void updateConfiguration(Map configMap) { - super.updateConfiguration(configMap); - } - - @Override - void clearUpdatedConfiguration() { - super.clearUpdatedConfiguration(); - } - - @Override - void setPrivacyStatus(MobilePrivacyStatus privacyStatus) { - super.setPrivacyStatus(privacyStatus); - } - - @Override - void getPrivacyStatus(AdobeCallback callback) { - super.getPrivacyStatus(callback); - } - - @Override - void getSdkIdentities(AdobeCallback callback) { - super.getSdkIdentities(callback); - } - - boolean trackActionCalled; - String trackActionParameterAction; - Map trackActionParameterContextData; - @Override - void trackAction(String action, Map contextData) { - trackActionCalled = true; - trackActionParameterAction = action; - trackActionParameterContextData = contextData; - } - - boolean trackStateCalled; - String trackStateParameterState; - Map trackStateParameterContextData; - @Override - void trackState(String state, Map contextData) { - trackStateCalled = true; - trackStateParameterState = state; - trackStateParameterContextData = contextData; - } - - boolean setAdvertisingIdentifierCalled; - String setAdvertisingIdentifierParameteradid; - @Override - void setAdvertisingIdentifier(String adid) { - setAdvertisingIdentifierCalled = true; - setAdvertisingIdentifierParameteradid = adid; - } - - boolean setPushIdentifierCalled; - String setPushIdentifierParameterRegistrationID; - @Override - void setPushIdentifier(String registrationID) { - setPushIdentifierCalled = true; - setPushIdentifierParameterRegistrationID = registrationID; - } - - boolean lifecycleStartCalled; - Map lifecycleStartCalledParameterAdditionalContextData; - @Override - void lifecycleStart(Map additionalContextData) { - lifecycleStartCalled = true; - lifecycleStartCalledParameterAdditionalContextData = additionalContextData; - } - - boolean lifecyclePauseCalled; - @Override - void lifecyclePause() { - lifecyclePauseCalled = true; - } - - boolean collectPiiCalled; - Map collectPiiParameterData; - @Override - void collectPii(Map data) { - collectPiiCalled = true; - collectPiiParameterData = data; - } - - boolean collectDataCalled; - Map collectDataParameterMarshalledData; - @Override - void collectData(final Map marshalledData) { - collectDataCalled = true; - collectDataParameterMarshalledData = marshalledData; - } - - - boolean dispatchEventWithResponseCallbackWithTimeoutCalled; - @Override - void dispatchEventWithResponseCallback(Event event, AdobeCallbackWithError responseCallback) { - dispatchEventWithResponseCallbackWithTimeoutCalled = true; - } - - boolean resetIdentitiesCalled; - @Override - void resetIdentities() { - resetIdentitiesCalled = true; - } -} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/TestableCore.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/TestableCore.java deleted file mode 100644 index b0bffd85b..000000000 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/TestableCore.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -public class TestableCore extends Core { - - TestableCore(PlatformServices platformServices, EventHub eventHub) { - super(platformServices); - super.eventHub = eventHub; - } - -} From ecc06752eea3c2fa876bc1e958de8ab82a54d42c Mon Sep 17 00:00:00 2001 From: Yansong Date: Mon, 6 Jun 2022 16:36:24 -0600 Subject: [PATCH 067/476] comments out disabled functional tests --- ...idThirdPartyExtensionsFunctionalTests.java | 2905 ++++++++--------- .../mobile/ExtensionTestingHelper.java | 568 ++-- 2 files changed, 1736 insertions(+), 1737 deletions(-) diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java index fa8aa788e..45f003908 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java @@ -1,1453 +1,1452 @@ -///* -// Copyright 2022 Adobe. All rights reserved. -// This file is licensed to you under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. You may obtain a copy -// of the License at http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software distributed under -// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -// OF ANY KIND, either express or implied. See the License for the specific language -// governing permissions and limitations under the License. -// */ -// -//package com.adobe.marketing.mobile; -// -//import org.junit.After; -//import org.junit.Before; -//import org.junit.Ignore; -//import org.junit.Test; -//import org.junit.runner.RunWith; -// -//import android.support.test.runner.AndroidJUnit4; -// -//import java.util.ArrayList; -//import java.util.HashMap; -//import java.util.List; -//import java.util.Map; -// -// -//import java.util.concurrent.ConcurrentLinkedQueue; -//import java.util.concurrent.CountDownLatch; -//import java.util.concurrent.ExecutorService; -//import java.util.concurrent.Executors; -//import java.util.concurrent.TimeUnit; -// -//import static org.junit.Assert.assertNotNull; -//import static org.junit.Assert.assertEquals; -//import static org.junit.Assert.assertFalse; -//import static org.junit.Assert.assertNull; -//import static org.junit.Assert.assertTrue; -// -///** -// * Class {@link AndroidThirdPartyExtensionsFunctionalTests} that defines all the necessary test cases to test a third party extension {@link TestableExtension} that extends {@link ExtensionListener} class of the Adobe Experience Platform SDK. -// * -// * This covers all the test cases listed in the document to run as part of the CI build system. -// * -// * https://wiki.corp.adobe.com/pages/viewpage.action?spaceKey=adms&title=Mobile+SDK+v5+Extensions+Module+Test+Plan -// * -// * @author Adobe -// * @version 5.0 -// */ -// -//@RunWith(AndroidJUnit4.class) -//public class AndroidThirdPartyExtensionsFunctionalTests extends AbstractE2ETest { -// -// private static final String LOG_TAG = AndroidThirdPartyExtensionsFunctionalTests.class.getSimpleName(); -// -// private TestHelper testHelper = new TestHelper(); -// private ExtensionTestingHelper extensionTestingHelper = new ExtensionTestingHelper(); -// private AsyncHelper asyncHelper = new AsyncHelper(); -// -// private List configListenerTypes = new ArrayList(); -// private List identityListenerTypes = new ArrayList(); -// private List customListenerTypes = new ArrayList(); -// private List analyticsListenerTypes = new ArrayList(); -// private List wildcardListener = new ArrayList(); -// -// static final String CONFIGURATION_SHARED_STATE = "com.adobe.module.configuration"; -// static final String EVENT_SOURCE_SHARED_STATE = "com.adobe.eventSource.sharedstate"; -// static final String EVENT_TYPE_CONFIGURATION = "com.adobe.eventType.configuration"; -// -// private final Object executorMutex = new Object(); -// private ExecutorService executor; -// private ConcurrentLinkedQueue unprocessedEvents; -// private long noProcessedEvents; -// -// private Map eventData = new HashMap(); -// Map subEventData = new HashMap(); -// Map pairedEventData = new HashMap(); -// ExtensionError extensionError; -// Map configMap = new HashMap(); -// -// @Before -// public void setUp() { -// super.setUp(); -// testHelper.cleanCache(defaultContext); -// MobileCore.setLogLevel(LoggingMode.DEBUG); -// MobileCore.start(null); -// testableNetworkService.resetTestableNetworkService(); -// -// configListenerTypes.add(new ListenerType("com.adobe.eventType.configuration", "com.adobe.eventSource.responseContent")); -// configListenerTypes.add(new ListenerType("com.adobe.eventType.configuration", "com.adobe.eventSource.requestContent")); -// -// identityListenerTypes.add(new ListenerType("com.adobe.eventType.identity", "com.adobe.eventSource.requestIdentity")); -// identityListenerTypes.add(new ListenerType("com.adobe.eventType.identity", "com.adobe.eventSource.responseIdentity")); -// -// analyticsListenerTypes.add(new ListenerType("com.adobe.eventType.analytics", "com.adobe.eventSource.requestContent")); -// analyticsListenerTypes.add(new ListenerType("com.adobe.eventType.analytics", "com.adobe.eventSource.requestIdentity")); -// analyticsListenerTypes.add(new ListenerType("com.adobe.eventType.analytics", "com.adobe.eventSource.responseContent")); -// analyticsListenerTypes.add(new ListenerType("com.adobe.eventType.analytics", "com.adobe.eventSource.responseIdentity")); -// analyticsListenerTypes.add(new ListenerType("com.adobe.eventType.generic.track", -// "com.adobe.eventSource.requestContent")); -// -// wildcardListener.add(new ListenerType("com.adobe.eventtype._wildcard_", "com.adobe.eventsource._wildcard_")); -// -// customListenerTypes.add(new ListenerType("com.example.testable.custom", "com.example.testable.request")); -// -// subEventData.put("key0", "value0"); -// subEventData.put("key1", "value1"); -// subEventData.put("key2", "value2"); -// Map customElement = new HashMap(); -// // customElement.put("customByte", (byte)127); -// // customElement.put("customFloat", new Float(3.14)); -// // customElement.put("customShort", (short)300); -// customElement.put("customInt", new Integer(123)); -// customElement.put("customLong", (long) 300); -// customElement.put("customChar", 'c'); -// customElement.put("customString", "string1"); -// customElement.put("customNull", null); -// customElement.put("customBoolean", true); -// customElement.put("customList", customListenerTypes); -// customElement.put("customDouble", new Double(3.14)); -// customElement.put("customMap", subEventData); -// Map customData = new HashMap(); -// customData.put("customElement", customElement); -// eventData.put("customData", customData); -// -// pairedEventData.put("ResponseKey1", "ResponseData1"); -// pairedEventData.put("ResponseKey2", "ResponseData2"); -// pairedEventData.put("ResponseKey3", "ResponseData3"); -// } -// -// @After -// public void tearDown() { -// asyncHelper.waitForAppThreads(500, true); -// super.tearDown(); -// } -// -// public void updateConfiguration() { -// configMap.put("build.environment", "dev"); -// configMap.put("myExtension.server", "mydomain.dev.com"); -// configMap.put("global.privacy", "optedin"); -// configMap.put("experienceCloud.org", "972C898555E9F7BC7F000101@AdobeOrg"); -// configMap.put("analytics.server", "obumobile5.sc.omtrdc.net"); -// configMap.put("analytics.rsids", "mobile5the.v5.show"); -// configMap.put("analytics.offlineEnabled", true); -// configMap.put("analytics.referrerTimeout", 15); -// configMap.put("analytics.backdatePreviousSessionInfo", true); -// configMap.put("identity.adidEnabled", true); -// configMap.put("acquisition.server", "c00.adobe.com"); -// configMap.put("rules.url", "https://s3.amazonaws.com/ams-qe/ios-sdk-test-app-rules.zip"); -// configMap.put("target.clientCode", "yourclientcode"); -// configMap.put("target.timeout", 5); -// configMap.put("audience.server", "omniture.demdex.net"); -// configMap.put("audience.timeout", 5); -// configMap.put("analytics.aamForwardingEnabled", false); -// configMap.put("analytics.batchLimit", 0); -// configMap.put("lifecycle.sessionTimeout", 300); -// MobileCore.updateConfiguration(configMap); -// } -// -// public ExecutorService getExecutor() { -// synchronized (executorMutex) { -// if (executor == null) { -// executor = Executors.newFixedThreadPool(2); -// } -// -// return executor; -// } -// } -// -// // Test Case No : 1 -// // Register A Third Party Extension using registerExtension API -// @Test -// public void testRegisterExtension_whenValidData_returnsNoError() { -// -// // setup -// String extensionName = "ThirdPartyExtension01"; -// // test -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, -// configListenerTypes); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// } -// -// // Test Case No : 2 & 20 -// // Get the Name Of A ThirdParty Extension using getName API in Android and name in iOS -// @Test -// public void testGetName_whenExtensionRegistered_returnsName() { -// -// // setup -// String extensionName = "ThirdPartyExtension02"; -// // test -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, -// configListenerTypes); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertEquals(returnStatus.extensionName, extensionName); -// } -// -// // Test Case No : 2a -// @Test -// public void testGetExtensionInstance_whenExtensionNotRegistered_returnsNull() { -// -// // setup -// String extensionName = "ThirdPartyExtension02a"; -// // test -// Extension extension = extensionTestingHelper.getExtensionInstance(extensionName); -// // verify -// assertNull(extension); -// } -// -// // Test Case No : 3 & 20 -// // Get the Version Of A ThirdParty Extension using getVersion API in Android and version in iOS -// @Test -// public void testGetVersion_whenExtensionRegistered_returnsVersion() { -// -// // setup -// String extensionName = "ThirdPartyExtension03"; -// // test -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(returnStatus.extensionVersion.contains("1.0.0")); -// } -// -// // Test Case No : 4 -// // Unregister A Third Party Extension using unregisterExtension API -// @Test -// public void testUnRegisterExtension_whenExtensionRegistered_returnsTrue() { -// -// // setup -// String extensionName = "ThirdPartyExtension04"; -// // test -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); -// boolean unregisterStatus = extensionTestingHelper.unregisterExtension(extensionName); -// // verify -// assertTrue(unregisterStatus); -// assertFalse(extensionTestingHelper.isRegistered(extensionName)); -// } -// -// // Test Case No : 5 -// // OnUnregistered Call Of A Third Party Extension using unregisterExtension API in Android and onUnregister in IOS -// @Test -// public void testOnUnregistered_whenExtensionUnRegistered_OnUnregisteredIsCalled() { -// -// // setup -// String extensionName = "ThirdPartyExtension05"; -// // test -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); -// extensionTestingHelper.confirmExtensionUnregisteredCall = ""; -// boolean unregisterStatus = extensionTestingHelper.unregisterExtension(extensionName); -// // verify -// assertTrue(unregisterStatus); -// assertEquals(returnStatus.extensionName, extensionName); -// assertFalse(extensionTestingHelper.isRegistered(extensionName)); -// assertTrue(extensionTestingHelper.confirmExtensionUnregisteredCall.contains("ConfirmedByTestableExtension")); -// } -// -// // Test Case No : 6 -// // onUnexpectedError Call Of A Third Party Extension when trying -// // to a register Extension API for various errors mentioned in the ExtensionError Class -// @Test -// public void testRegisterExtension_whenNullName_returnsError() { -// -// // setup -// String extensionName = null; -// // test -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); -// // verify -// assertEquals(1, returnStatus.extensionUnexpectedError.getErrorCode().getErrorCode()); -// assertEquals("extension.bad_extension_name", returnStatus.extensionUnexpectedError.getErrorCode().getErrorName()); -// } -// -// // Test Case No : 6a -// @Test -// public void testRegisterExtension_whenEmptyName_returnsError() { -// -// // setup -// String extensionName = ""; -// // test -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); -// // verify -// assertEquals(1, returnStatus.extensionUnexpectedError.getErrorCode().getErrorCode()); -// assertEquals("extension.bad_extension_name", returnStatus.extensionUnexpectedError.getErrorCode().getErrorName()); -// } -// -// // Test Case No : 7 & 21 -// // Register a listener to listen an identity event using registerEventListener API for stateowner like com.adobe.module.identity -// @Test -// public void testRegisterListeners_whenEventsDispatched_eventsReceivedByRightListener() { -// -// // setup -// String extensionName = "ThirdPartyExtension07"; -// // test -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, identityListenerTypes); -// extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(0), eventData); -// extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(1), subEventData); -// Event event1 = extensionTestingHelper.getLastEventHeardByListener(extensionName, identityListenerTypes.get(0)); -// Event event2 = extensionTestingHelper.getLastEventHeardByListener(extensionName, identityListenerTypes.get(1)); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// -// assertTrue(identityListenerTypes.get(0).eventType.equalsIgnoreCase(event1.getType())); -// assertTrue(identityListenerTypes.get(0).eventSource.equalsIgnoreCase(event1.getSource())); -// assertEquals(eventData, event1.getEventData()); -// -// assertTrue(identityListenerTypes.get(1).eventType.equalsIgnoreCase(event2.getType())); -// assertTrue(identityListenerTypes.get(1).eventSource.equalsIgnoreCase(event2.getSource())); -// assertEquals(subEventData, event2.getEventData()); -// } -// -// // Test Case No : 8 -// // OnUnregistered Call in A registered listener will be called while an extension -// // gets unregistered using unregisterExtension API -// @Test -// public void testOnUnregisteredCall_whenUnregisterExtension_OnUnregisteredCallOfListenerCalled() { -// -// // setup -// String extensionName = "ThirdPartyExtension08"; -// // test -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, identityListenerTypes); -// extensionTestingHelper.confirmListenerUnregisteredCall = ""; -// extensionTestingHelper.confirmExtensionUnregisteredCall = ""; -// boolean unregisterStatus = extensionTestingHelper.unregisterExtension(extensionName); -// // verify -// assertTrue(unregisterStatus); -// assertFalse(extensionTestingHelper.isRegistered(extensionName)); -// assertTrue(extensionTestingHelper.confirmExtensionUnregisteredCall.contains("ConfirmedByTestableExtension")); -// assertTrue(extensionTestingHelper.confirmListenerUnregisteredCall.contains("ConfirmedByTestableListener")); -// } -// -// // Test Case No : 9 -// // Register A Listener to listen a custom event using registerEventListener API -// @Test -// public void testRegisterAListener_whenACustomEvent_returnsNoError() { -// -// // setup -// String extensionName = "ThirdPartyExtension07"; -// // test -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); -// EventListener eventListener = extensionTestingHelper.getListenerInstance(extensionName, customListenerTypes.get(0)); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertTrue(eventListener.getEventType().getName().equalsIgnoreCase("com.example.testable.custom")); -// assertTrue(eventListener.getEventSource().getName().equalsIgnoreCase("com.example.testable.request")); -// } -// -// // Test Case No : 10 -// // Register A Wildcard Listener to listen all the events using registerWildcardListener API -// @Test -// public void testRegisterWildcardListener_whenRegistered_returnsNoError() { -// -// // setup -// String extensionName = "ThirdPartyExtension10"; -// // test -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, wildcardListener); -// EventListener eventListener = extensionTestingHelper.getListenerInstance(extensionName, wildcardListener.get(0)); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertEquals(eventListener.getEventType().getName(), wildcardListener.get(0).eventType); -// assertEquals(eventListener.getEventSource().getName(), wildcardListener.get(0).eventSource); -// } -// -// // Test Case No : 10a -// // Dispatch events of types identity, analytics, custom, and config using ACPCore dispatchEvent API -// // And confirm that the wildcard listener is able to hear all those dispatched. -// @Test -// public void testDispatchEvent_whenWildcardListenerRegistered_hearsAllTheEventsAndreturnsNoError() { -// -// // setup -// String extensionName = "ThirdPartyExtension11a"; -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, wildcardListener); -// EventListener eventListener = extensionTestingHelper.getListenerInstance(extensionName, wildcardListener.get(0)); -// // test -// extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(0), eventData); -// extensionTestingHelper.dispatchAnEvent(analyticsListenerTypes.get(0), subEventData); -// extensionTestingHelper.dispatchAnEvent(customListenerTypes.get(0), eventData); -// extensionTestingHelper.dispatchAnEvent(configListenerTypes.get(0), subEventData); -// List eventsHearedByWildcardListener = extensionTestingHelper.getAllEventsHeardByListener(extensionName, -// wildcardListener.get(0)); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertEquals(eventListener.getEventType().getName(), wildcardListener.get(0).eventType); -// assertEquals(eventListener.getEventSource().getName(), wildcardListener.get(0).eventSource); -// assertTrue(eventsHearedByWildcardListener.get(1).getEventType().getName().equalsIgnoreCase(identityListenerTypes.get( -// 0).eventType)); -// assertTrue(eventsHearedByWildcardListener.get(1).getEventSource().getName().equalsIgnoreCase(identityListenerTypes.get( -// 0).eventSource)); -// assertTrue(eventsHearedByWildcardListener.get(1).getEventData().equals(eventData)); -// assertTrue(eventsHearedByWildcardListener.get(2).getEventType().getName().equalsIgnoreCase(analyticsListenerTypes.get( -// 0).eventType)); -// assertTrue(eventsHearedByWildcardListener.get(2).getEventSource().getName().equalsIgnoreCase(analyticsListenerTypes.get( -// 0).eventSource)); -// assertTrue(eventsHearedByWildcardListener.get(2).getEventData().equals(subEventData)); -// assertTrue(eventsHearedByWildcardListener.get(3).getEventType().getName().equalsIgnoreCase(customListenerTypes.get( -// 0).eventType)); -// assertTrue(eventsHearedByWildcardListener.get(3).getEventSource().getName().equalsIgnoreCase(customListenerTypes.get( -// 0).eventSource)); -// assertTrue(eventsHearedByWildcardListener.get(3).getEventData().equals(eventData)); -// assertTrue(eventsHearedByWildcardListener.get(4).getEventType().getName().equalsIgnoreCase(configListenerTypes.get( -// 0).eventType)); -// assertTrue(eventsHearedByWildcardListener.get(4).getEventSource().getName().equalsIgnoreCase(configListenerTypes.get( -// 0).eventSource)); -// assertEquals(eventsHearedByWildcardListener.get(4).getEventData(), subEventData); -// } -// -// // Test Case No : 11 & 12 -// // Dispatch A Custom Event using ACPCore dispatchEvent API -// @Test -// public void testDispatchEvent_whenCustomEvent_returnsTrue() { -// -// // setup -// ListenerType listenerType = customListenerTypes.get(0); -// // test -// boolean dispatchStatus = extensionTestingHelper.dispatchAnEvent(listenerType, eventData); -// // verify -// assertTrue(dispatchStatus); -// } -// -// -// // Test Case No : 13 & 14 -// // Dispatch an event using ACPCore dispatchResponseEvent API -// @Test -// public void testDispatchEventWithResponseCallback_whenPaired_returnsPairedEventAndData() throws InterruptedException { -// -// // setup -// String extensionName = "ThirdPartyExtension13"; -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); -// TestableListener eventListener = (TestableListener) extensionTestingHelper.getListenerInstance(extensionName, -// customListenerTypes.get(0)); -// eventListener.setDispatchBehavior("doDispatchResponseEvent"); -// ListenerType listenerType = customListenerTypes.get(0); -// // test -// Event responseEvent = new Event.Builder("DispatchedEvent", listenerType.eventType, -// listenerType.eventSource).setEventData(eventData).build(); -// final List result = new ArrayList(); -// final CountDownLatch latch = new CountDownLatch(1); -// AdobeCallback dispatchCallback = new AdobeCallback() { -// @Override -// public void call(Event value) { -// result.add(value); -// latch.countDown(); -// } -// }; -// boolean dispatchStatus = MobileCore.dispatchEventWithResponseCallback(responseEvent, dispatchCallback, null); -// latch.await(10, TimeUnit.SECONDS); -// // verify -// assertTrue(dispatchStatus); -// assertTrue(result.get(0).getType().equalsIgnoreCase("com.adobe.eventtype.pairedresponse")); -// assertTrue(result.get(0).getSource().equalsIgnoreCase("com.example.testable.pairedrequest")); -// assertEquals(pairedEventData, result.get(0).getEventData()); -// } -// -// // Test Case No : 15, 17 & 36 -// // Get Shared Event State Owned By A Configuration Event using getSharedEventState API -// // with the extension name as the stateowner like -// // com.adobe.module.identity, com.adobe.module.configuration -// @Test -// public void testGetSharedEventState_whenConfigEvent_returnsAppropriateSharedState() { -// -// // setup -// String extensionName = "ThirdPartyExtension15"; -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, -// configListenerTypes); -// TestableExtension testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); -// -// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// } -// }; -// // test -// updateConfiguration(); -// Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, -// configListenerTypes.get(0).eventSource).setEventData(null).setEventNumber(100).build(); -// asyncHelper.waitForAppThreads(500, true); -// Map configurationSharedState = -// testableExtension.getApi().getSharedEventState(CONFIGURATION_SHARED_STATE, event, errorCallback); -// Event eventHeard = extensionTestingHelper.getLastEventHeardByListener(extensionName, configListenerTypes.get(0)); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertEquals(configMap, configurationSharedState); -// assertEquals(configMap, eventHeard.getEventData()); -// -// } -// -// // Test Case No : 16 -// // Get Shared Event State Owned By A Custom Event using getSharedEventState API for stateowner -// // as extensionName. -// @Test -// public void testGetSharedEventState_whenItIsSet_returnsAppropriateSharedState() { -// -// // setup -// String extensionName = "Extension18"; -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, -// customListenerTypes); -// TestableExtension testableExtension = (TestableExtension) -// extensionTestingHelper.getExtensionInstance(extensionName); -// -// Event event = new Event.Builder("DispatchedEvent", customListenerTypes.get(0).eventType, -// customListenerTypes.get(0).eventSource).setEventData(eventData).build(); -// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// } -// }; -// boolean status1 = testableExtension.getApi().setSharedEventState(subEventData, event, errorCallback); -// -// Map sharedState = testableExtension.getApi().getSharedEventState( -// testableExtension.getName(), event, errorCallback); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertEquals(subEventData, sharedState); -// } -// -// // Test Case No : 18 -// // Set Shared Event State that is not tied to an event using setSharedEventState API -// @Test -// public void testSetAndGetSharedEventState_whenNullEvent_setsAndGetsAppropriateSharedState() { -// -// // setup -// String extensionName = "Extension18"; -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, -// configListenerTypes); -// TestableExtension testableExtension = (TestableExtension) -// extensionTestingHelper.getExtensionInstance(extensionName); -// -// Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, -// configListenerTypes.get(0).eventSource).setEventData(eventData).build(); -// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// } -// }; -// boolean status1 = testableExtension.getApi().setSharedEventState(subEventData, event, errorCallback); -// -// Map sharedState = testableExtension.getApi().getSharedEventState( -// testableExtension.getName(), event, errorCallback); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertEquals(subEventData, sharedState); -// } -// -// // Test Case No : 19 -// // Clear Shared Event States Of A Third Party Extension without affecting -// // the Shared Event States Of other extensions using clearSharedEventStates API -// @Test -// public void testClearSharedEventStates_whenMultipleRegisteredExtensions_doesNotAffectSharedStateOfOtherExtensions() { -// -// // setup -// CreateExtensionResponse returnStatus1 = extensionTestingHelper.registerExtension("ThirdPartyExtensionOne", -// configListenerTypes); -// TestableExtension testableExtension1 = (TestableExtension) -// extensionTestingHelper.getExtensionInstance("ThirdPartyExtensionOne"); -// CreateExtensionResponse returnStatus2 = extensionTestingHelper.registerExtension("ThirdPartyExtensionTwo", -// configListenerTypes); -// TestableExtension testableExtension2 = (TestableExtension) -// extensionTestingHelper.getExtensionInstance("ThirdPartyExtensionTwo"); -// Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, -// configListenerTypes.get(0).eventSource).setEventData(eventData).build(); -// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// } -// }; -// boolean status1 = testableExtension1.getApi().setSharedEventState(subEventData, event, errorCallback); -// boolean status2 = testableExtension2.getApi().setSharedEventState(subEventData, event, errorCallback); -// ExtensionErrorCallback errorCallback1 = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// } -// }; -// Map sharedStateBeforeClearing1 = testableExtension1.getApi().getSharedEventState( -// testableExtension1.getName(), event, errorCallback1); -// Map sharedStateBeforeClearing2 = testableExtension2.getApi().getSharedEventState( -// testableExtension2.getName(), event, errorCallback1); -// ExtensionErrorCallback errorCallback2 = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// Log.debug(LOG_TAG, String.format("An error occurred while clearing the shared states %d %s", -// extensionError.getErrorCode(), extensionError.getErrorName())); -// } -// }; -// // test -// testableExtension1.getApi().clearSharedEventStates(errorCallback2); -// Map getSharedStateAfterClearing1 = testableExtension1.getApi().getSharedEventState( -// testableExtension1.getName(), event, errorCallback1); -// Map getSharedStateAfterClearing2 = testableExtension2.getApi().getSharedEventState( -// testableExtension2.getName(), event, errorCallback1); -// // verify -// assertNull(returnStatus1.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionOne")); -// assertNull(returnStatus2.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionTwo")); -// assertEquals(subEventData, sharedStateBeforeClearing1); -// assertEquals(subEventData, sharedStateBeforeClearing2); -// assertNull(getSharedStateAfterClearing1); -// assertEquals(subEventData, getSharedStateAfterClearing2); -// } -// -// // Test Case No : 22 -// // Make a copy of the event object received using event.copy() method, -// // and the correctness of the copied event object. -// @Test -// public void testCopyEvent_whenCopied_returnsTheCopiedEvent() { -// -// // setup -// ListenerType listenerType = customListenerTypes.get(0); -// Event event = new Event.Builder("DispatchedEvent", listenerType.eventType, -// listenerType.eventSource).setEventData(eventData).build(); -// // setup -// Event copiedEvent = event.copy(); -// // verify -// assertEquals(eventData, copiedEvent.getEventData()); -// assertEquals(listenerType.eventType, copiedEvent.getType()); -// assertEquals(listenerType.eventSource, copiedEvent.getSource()); -// } -// -// // Test Case No : 23 -// // Register the same extension multiple times - should not register, the initial extension -// // should still be registered and receive events, -// @Test -// public void testCreateExtension_whenDuplicateName_returnsError() { -// -// // setup -// String extensionName = "ThirdPartyExtension23"; -// // test -// CreateExtensionResponse returnStatus1 = extensionTestingHelper.registerExtension(extensionName, -// configListenerTypes); -// CreateExtensionResponse returnStatus2 = extensionTestingHelper.registerExtension(extensionName, -// configListenerTypes); -// // verify -// assertNull(returnStatus1.extensionUnexpectedError); -// assertEquals(2, returnStatus2.extensionUnexpectedError.getErrorCode().getErrorCode()); -// assertTrue(returnStatus2.extensionUnexpectedError.getMessage().contains("Failed to register extension with name " + -// extensionName)); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// } -// -// // Test Case No : 26 -// // Register an extension with null version - should be ok, no NPE should be thrown in logs -// @Test -// public void testCreateExtension_whenNullVersion_returnsNoError() { -// -// // setup -// String extensionName = "ExtensionWithNullVersion"; -// // test -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, -// configListenerTypes); -// TestableExtension testableExtension = (TestableExtension) -// extensionTestingHelper.getExtensionInstance(extensionName); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertNull(testableExtension.getVersion()); -// } -// -// // Test Case No : 27 -// // Same as Test case No : 19, but checking with the same extension by re-registering it. -// @Test -// public void testGetSharedEventState_whenExtensionReRegistered_theSharedStateIsUnaffected() { -// -// // setup -// String extensionName = "ThirdPartyExtension27"; -// CreateExtensionResponse returnStatus1 = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); -// TestableExtension testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); -// Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, -// configListenerTypes.get(0).eventSource).setEventData(eventData).build(); -// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// } -// }; -// boolean status = testableExtension.getApi().setSharedEventState(subEventData, event, errorCallback); -// Map sharedStateBeforeUnregister = testableExtension.getApi().getSharedEventState( -// testableExtension.getName(), event, errorCallback); -// // test -// boolean unregisterStatus = extensionTestingHelper.unregisterExtension(extensionName); -// CreateExtensionResponse returnStatus2 = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); -// testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); -// Map sharedStateAfterReregister = testableExtension.getApi().getSharedEventState( -// testableExtension.getName(), event, errorCallback); -// // verify -// assertNull(returnStatus1.extensionUnexpectedError); -// assertTrue(unregisterStatus); -// assertNull(returnStatus2.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertEquals(subEventData, sharedStateBeforeUnregister); -// assertEquals(subEventData, sharedStateAfterReregister); -// } -// -// // Test Case No : 28 -// // Register a listener with null eventType - should be ok, no NPE should be thrown in logs -// @Test -// public void testRegisterListener_whenNullEventType_returnsErrorCode() { -// -// // setup -// String extensionName = "ThirdPartyExtension28a"; -// // setup -// List listenerType = new ArrayList(); -// listenerType.add(new ListenerType(null, "com.example.testable.request")); -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, -// listenerType); -// // test -// Map listenerRegistrationStatus = TestableExtension.getListenerRegistrationStatus(); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorCode(), 3); -// assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorName(), "extension.event_type_not_supported"); -// } -// -// // Test Case No : 28a -// // Register a listener with null eventSource - should be ok, no NPE should be thrown in logs -// @Test -// public void testRegisterListener_whenNullEventSource_returnsErrorCode() { -// -// // setup -// String extensionName = "ThirdPartyExtension28b"; -// // test -// List listenerType = new ArrayList(); -// listenerType.add(new ListenerType("com.example.testable.custom", null)); -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, listenerType); -// Map listenerRegistrationStatus = TestableExtension.getListenerRegistrationStatus(); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorCode(), 4); -// assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorName(), -// "extension.event_source_not_supported"); -// } -// -// // Test Case No : 28b -// // Register a listener with null for both eventType and eventSource - should be ok, no NPE should be thrown in logs -// @Test -// public void testRegisterListener_whenNullEventTypeAndSource_returnsErrorCode() { -// -// // setup -// String extensionName = "ThirdPartyExtension28c"; -// // test -// List listenerType = new ArrayList(); -// listenerType.add(new ListenerType(null, null)); -// CreateExtensionResponse returnStatus = -// extensionTestingHelper.registerExtension(extensionName, listenerType); -// Map listenerRegistrationStatus = TestableExtension.getListenerRegistrationStatus(); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorCode(), 3); -// assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorName(), "extension.event_type_not_supported"); -// } -// -// // Test Case No : 29 -// // Register listener which does busy work on the event hub thread -// // Expected : It should not block the event hub in dispatching new events -// @Test -// @Ignore -// public void testWithAListenerDoingBusyWorkOnTheHub_whenAnotherEventIsDispatched_eventHubShouldDispatchIt() throws -// InterruptedException { -// -// // setup -// final String customExtension = "CustomExtension29"; -// final String busyWorkExtension = "BusyWorkExtension29"; -// -// final List listenerTypes = new ArrayList(); -// -// final List eventsHeard = new ArrayList(); -// final List result = new ArrayList(); -// final ArrayList[] eventsHeardByListener = new ArrayList[4]; -// listenerTypes.add(new ListenerType("com.adobe.eventtype.busywork", "com.example.testable.busywork")); -// listenerTypes.add(new ListenerType("com.adobe.eventtype.customevent", "com.example.testable.customevent")); -// CreateExtensionResponse registrationStatus1 = extensionTestingHelper.registerExtension(customExtension, listenerTypes); -// CreateExtensionResponse registrationStatus2 = extensionTestingHelper.registerExtension(busyWorkExtension, -// listenerTypes); -// -// ExtensionErrorCallback dispatchCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError ec) { -// Log.debug(LOG_TAG, String.format("[dispatchAnEvent] Dispatch failed with error %s ", ec.getErrorCode())); -// } -// }; -// -// Event busyEvent = new Event.Builder("DispatchedBusyEvent", listenerTypes.get(0).eventType, -// listenerTypes.get(0).eventSource).setEventData(subEventData).build(); -// Event customEvent = new Event.Builder("DispatchedCustomEvent", listenerTypes.get(1).eventType, -// listenerTypes.get(1).eventSource).setEventData(subEventData).build(); -// -// MobileCore.dispatchEvent(customEvent, dispatchCallback); -// MobileCore.dispatchEvent(customEvent, dispatchCallback); -// MobileCore.dispatchEvent(busyEvent, dispatchCallback); -// MobileCore.dispatchEvent(customEvent, dispatchCallback); -// MobileCore.dispatchEvent(customEvent, dispatchCallback); -// asyncHelper.waitForAppThreads(500, true); -// eventsHeardByListener[0] = (ArrayList) extensionTestingHelper.getAllEventsHeardByListener(customExtension, -// listenerTypes.get(0)); -// eventsHeardByListener[1] = (ArrayList) extensionTestingHelper.getAllEventsHeardByListener(customExtension, -// listenerTypes.get(1)); -// eventsHeardByListener[2] = (ArrayList) extensionTestingHelper.getAllEventsHeardByListener(busyWorkExtension, -// listenerTypes.get(0)); -// eventsHeardByListener[3] = (ArrayList) extensionTestingHelper.getAllEventsHeardByListener(busyWorkExtension, -// listenerTypes.get(1)); -// // verify -// assertEquals(1, eventsHeardByListener[0].size()); -// assertEquals(4, eventsHeardByListener[1].size()); -// assertEquals(1, eventsHeardByListener[2].size()); -// assertEquals(4, eventsHeardByListener[3].size()); -// } -// -// -// // Test Case No : 30 -// // Build a custom event with null event data should not crash -// @Test -// public void testDispatchEvent_whenNullEventData_returnsNoError() { -// -// // setup -// String extensionName = "ThirdPartyExtension30"; -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, -// identityListenerTypes); -// TestableExtension testableExtension = (TestableExtension) -// extensionTestingHelper.getExtensionInstance(extensionName); -// ListenerType listenerType = new ListenerType("com.adobe.eventType.configuration", -// "com.adobe.eventSource.requestContent"); -// Event event = new Event.Builder("DispatchedEvent", listenerType.eventType, -// listenerType.eventSource).setEventData(null).build(); -// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// } -// }; -// boolean status = testableExtension.getApi().setSharedEventState(null, event, errorCallback); -// // test -// ExtensionErrorCallback errorCallback1 = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// } -// }; -// Map configurationSharedState = testableExtension.getApi().getSharedEventState( -// testableExtension.getName(), event, errorCallback1); -// ExtensionErrorCallback dispatchCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError ec) { -// } -// }; -// boolean dispatchStatus = MobileCore.dispatchEvent(event, dispatchCallback); -// asyncHelper.waitForAppThreads(500, true); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertNull(configurationSharedState); -// assertTrue(dispatchStatus); -// } -// -// // Test Case No : 31 Skipped due to the bug AMSDK-7597 -// // SDK v5 does not have support for Short, Float, and Byte Data types -// // Check under bourbon-core-java-core/code/shared/src/main/java/com/adobe/marketing/mobile -// // Build a custom event data with byte data type -// // dispatch it and check the returned values are the same. -// @Test -// public void testDispatchEvent_whenEventDataWithByteTypeValue_returnsNoError() { -// -// // setup -// String extensionName = "ThirdPartyExtension31a"; -// Map testEventData = new HashMap(); -// int byteData = 0b0010_0101; -// testEventData.put("customByte", byteData); -// // tes -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, -// identityListenerTypes); -// extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(0), testEventData); -// Event event = extensionTestingHelper.getLastEventHeardByListener(extensionName, -// identityListenerTypes.get(0)); -// -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertTrue(identityListenerTypes.get(0).eventType.equalsIgnoreCase(event.getType())); -// assertTrue(identityListenerTypes.get(0).eventSource.equalsIgnoreCase(event.getSource())); -// assertEquals(testEventData, event.getEventData()); -// } -// -// // Test Case No : 31a Skipped due to the bug AMSDK-7596 -// // Build a custom event data with Float data type -// // dispatch it and check the returned values are the same. -// @Ignore -// public void testDispatchEvent_whenEventDataWithFloatTypeValue_returnsNoError() { -// -// // setup -// String extensionName = "ThirdPartyExtension31b"; -// Map testEventData = new HashMap(); -// testEventData.put("customFloat", new Float(3.14)); -// // test -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, -// identityListenerTypes); -// extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(0), testEventData); -// Event event = extensionTestingHelper.getLastEventHeardByListener(extensionName, -// identityListenerTypes.get(0)); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertTrue(identityListenerTypes.get(0).eventType.equalsIgnoreCase(event.getType())); -// assertTrue(identityListenerTypes.get(0).eventSource.equalsIgnoreCase(event.getSource())); -// assertEquals(testEventData, event.getEventData()); -// } -// -// // Test Case No : 31b Skipped due to the bug AMSDK-7595 -// // Build a custom event data with Short data type -// // dispatch it and check the returned values are the same. -// @Ignore -// public void testDispatchEvent_whenEventDataWithShortTypeValue_returnsNoError() { -// -// // setup -// String extensionName = "ThirdPartyExtension31c"; -// Map testEventData = new HashMap(); -// testEventData.put("customShort", (short)300); -// // test -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, -// identityListenerTypes); -// extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(0), testEventData); -// Event event = extensionTestingHelper.getLastEventHeardByListener(extensionName, -// identityListenerTypes.get(0)); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertTrue(identityListenerTypes.get(0).eventType.equalsIgnoreCase(event.getType())); -// assertTrue(identityListenerTypes.get(0).eventSource.equalsIgnoreCase(event.getSource())); -// assertEquals(testEventData, event.getEventData()); -// } -// -// // Test Case No : 32 -// // Dispatch with null event, should return false and error in the callback -// @Test -// public void testDispatchEvent_whenNullEvent_returnsError() throws InterruptedException { -// -// // test -// ExtensionErrorCallback dispatchCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError ec) { -// extensionError = ec; -// } -// }; -// boolean dispatchStatus = MobileCore.dispatchEvent(null, dispatchCallback); -// asyncHelper.waitForAppThreads(500, true); -// // verify -// assertFalse(dispatchStatus); -// assertEquals("extension.event_null", extensionError.getErrorName()); -// assertEquals(6, extensionError.getErrorCode()); -// } -// -// // Test Case No : 33 -// // Dispatch response event with null event, should return false and error in the callback -// @Test -// public void testDispatchEventWithResponseCallback_whenNullResponseEvent_returnsError() throws InterruptedException { -// -// // setup -// String extensionName = "ThirdPartyExtension33"; -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); -// TestableListener eventListener = (TestableListener) extensionTestingHelper.getListenerInstance(extensionName, -// customListenerTypes.get(0)); -// eventListener.setDispatchBehavior("doDispatchResponseEventWithNullEvent"); -// ListenerType listenerType = customListenerTypes.get(0); -// // test -// Event responseEvent = new Event.Builder("DispatchedEvent", listenerType.eventType, -// listenerType.eventSource).setEventData(eventData).build(); -// final List result = new ArrayList(); -// final CountDownLatch latch = new CountDownLatch(1); -// AdobeCallback dispatchCallback = new AdobeCallback() { -// @Override -// public void call(Event value) { -// result.add(value); -// latch.countDown(); -// } -// }; -// boolean dispatchStatus = MobileCore.dispatchEventWithResponseCallback(responseEvent, dispatchCallback, null); -// latch.await(500, TimeUnit.MILLISECONDS); -// // verify -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertTrue(dispatchStatus); -// assertTrue(result.isEmpty()); -// assertEquals("extension.event_null", eventListener.getExtensionError().getErrorName()); -// assertEquals(6, eventListener.getExtensionError().getErrorCode()); -// } -// -// // Test Case No : 34 -// // Get the shared state for the custom extension, should be null if not set, should be valid if set before -// @Test -// public void testGetSharedEventState_whenItIsNotSet_returnsNull() { -// -// // setup -// String extensionName = "ThirdPartyExtension34"; -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); -// TestableExtension testableExtension = (TestableExtension) -// extensionTestingHelper.getExtensionInstance(extensionName); -// ListenerType listenerType = customListenerTypes.get(0); -// Event event = new Event.Builder("DispatchedEvent", listenerType.eventType, -// listenerType.eventSource).setEventData(eventData).build(); -// -// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// } -// }; -// // test -// Map sharedStateBeforeItWasSet = testableExtension.getApi().getSharedEventState( -// testableExtension.getName(), -// event, errorCallback); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertNull(sharedStateBeforeItWasSet); -// } -// -// // Test Case No : 35 -// // SetSharedState with null state, null event, should not crash -// @Test -// public void testSetAndGetSharedEventState_whenNullStateAndEvent_returnsNoError() { -// -// // setup -// String extensionName = "ThirdPartyExtension35"; -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); -// TestableExtension testableExtension = (TestableExtension) -// extensionTestingHelper.getExtensionInstance(extensionName); -// -// final ExtensionError[] extenError = new ExtensionError[1]; -// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// extenError[0] = extensionError; -// } -// }; -// boolean status = testableExtension.getApi().setSharedEventState(null, null, errorCallback); -// // test -// Map customSharedState = testableExtension.getApi().getSharedEventState(testableExtension.getName(), -// null, errorCallback); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertNull(customSharedState); -// assertNull(extenError[0]); -// } -// -// // Test Case No : 38 -// // Simulate an analytics 3rd party extension - dispatching & receiving generic events when trackAction called -// @Test -// public void testTrackAction_whenDispatched_receivedByGenericTrackEventType() { -// // setup -// String extensionName = "AnalyticsExtension38a"; -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, -// analyticsListenerTypes); -// TestableExtension testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); -// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// } -// }; -// // test -// Map additionalContextData = new HashMap(); -// additionalContextData.put("customKey", "value"); -// MobileCore.trackAction("eventDispatched", additionalContextData); -// asyncHelper.waitForAppThreads(500, true); -// Event eventHeard = extensionTestingHelper.getLastEventHeardByListener(extensionName, analyticsListenerTypes.get(4)); -// // verify -// assertEquals("eventDispatched", eventHeard.getEventData().get("action")); -// assertEquals(eventHeard.getEventData().get("contextdata"), additionalContextData); -// assertTrue(eventHeard.getEventSource().getName().equalsIgnoreCase(analyticsListenerTypes.get(4).eventSource)); -// assertTrue(eventHeard.getEventType().getName().equalsIgnoreCase(analyticsListenerTypes.get(4).eventType)); -// -// MobileCore.trackState("trackState", additionalContextData); -// } -// -// // Test Case No : 38a -// // Simulate an analytics 3rd party extension - dispatching & receiving generic events when TrackState called -// @Test -// public void testTrackState_whenDispatched_receivedByGenericTrackEventType() { -// // setup -// String extensionName = "AnalyticsExtension38a"; -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, -// analyticsListenerTypes); -// TestableExtension testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); -// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// } -// }; -// // test -// Map additionalContextData = new HashMap(); -// additionalContextData.put("customKey", "value"); -// MobileCore.trackState("trackState", additionalContextData); -// asyncHelper.waitForAppThreads(500, true); -// Event eventHeard = extensionTestingHelper.getLastEventHeardByListener(extensionName, analyticsListenerTypes.get(4)); -// // verify -// assertEquals("trackState", eventHeard.getEventData().get("state")); -// assertEquals(additionalContextData, eventHeard.getEventData().get("contextdata")); -// assertTrue(eventHeard.getEventSource().getName().equalsIgnoreCase(analyticsListenerTypes.get(4).eventSource)); -// assertTrue(eventHeard.getEventType().getName().equalsIgnoreCase(analyticsListenerTypes.get(4).eventType)); -// } -// // Test Case No : 38b -// // Dispatch different types of Analytics Events and -// // Confirm if they are all received by the appropriate listeners registered. -// @Test -// public void testAnalyticsExtension_whenAnalyticsEventsDispatched_receivesAllDispatchedEvents() { -// // setup -// String extensionName = "AnalyticsExtension38"; -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, -// analyticsListenerTypes); -// TestableExtension testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); -// -// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// } -// }; -// // test -// Event[] eventHeard = new Event[analyticsListenerTypes.size()]; -// -// for (int i = 0; i < analyticsListenerTypes.size(); i++) { -// subEventData.put("newKey" + i, "newValue" + i); -// Event event = new Event.Builder("DispatchedEvent", analyticsListenerTypes.get(i).eventType, -// analyticsListenerTypes.get(i).eventSource).setEventData(subEventData).build(); -// ExtensionErrorCallback dispatchCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError ec) { -// Log.debug(LOG_TAG, String.format("[dispatchAnEvent] Dispatch failed with error %s ", ec.getErrorCode())); -// } -// }; -// MobileCore.dispatchEvent(event, dispatchCallback); -// asyncHelper.waitForAppThreads(500, true); -// eventHeard[i] = extensionTestingHelper.getLastEventHeardByListener(extensionName, analyticsListenerTypes.get(i)); -// // verify -// assertEquals(subEventData, eventHeard[i].getEventData()); -// assertTrue(eventHeard[i].getEventSource().getName().equalsIgnoreCase(analyticsListenerTypes.get(i).eventSource)); -// assertTrue(eventHeard[i].getEventType().getName().equalsIgnoreCase(analyticsListenerTypes.get(i).eventType)); -// } -// } -// -// // Test Case No : 39 -// // Test communication between two 3rd party extensions - dispatch events and set shared states from one of them, -// // Check updates are received in the other extension -// @Test -// public void testCommunicationBetweenExtensions_whenOneDispatchesEventsAndSetsSharedState_otherExtensionReceivesThem() { -// -// // setup -// String extensionName1 = "Extension39One"; -// CreateExtensionResponse returnStatus1 = extensionTestingHelper.registerExtension(extensionName1, customListenerTypes); -// TestableExtension testableExtension1 = (TestableExtension) -// extensionTestingHelper.getExtensionInstance(extensionName1); -// -// String extensionName2 = "Extension39two"; -// CreateExtensionResponse returnStatus2 = extensionTestingHelper.registerExtension(extensionName2, customListenerTypes); -// TestableExtension testableExtension2 = (TestableExtension) -// extensionTestingHelper.getExtensionInstance(extensionName2); -// -// -// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// Log.debug(LOG_TAG, String.format("An error occurred while setting the shared state %d %s", -// extensionError.getErrorCode(), extensionError.getErrorName())); -// } -// }; -// -// // test -// Event event = new Event.Builder("DispatchedEvent", customListenerTypes.get(0).eventType, -// customListenerTypes.get(0).eventSource).setEventData(eventData).build(); -// MobileCore.dispatchEvent(event, errorCallback); -// testableExtension1.getApi().setSharedEventState(subEventData, event, errorCallback); -// asyncHelper.waitForAppThreads(500, true); -// -// Event eventHeard = extensionTestingHelper.getLastEventHeardByListener(extensionName2, customListenerTypes.get(0)); -// Map sharedState = testableExtension2.getApi().getSharedEventState( -// testableExtension1.getName(), event, errorCallback); -// -// // verify -// assertEquals(eventData, eventHeard.getEventData()); -// assertTrue(eventHeard.getEventSource().getName().equalsIgnoreCase(customListenerTypes.get(0).eventSource)); -// assertTrue(eventHeard.getEventType().getName().equalsIgnoreCase(customListenerTypes.get(0).eventType)); -// assertEquals(subEventData, sharedState); -// } -// -// // Test Case No : 40 -// // Check that paired response events are received by another extension in a wildcard listener, but not by a regular listener. -// // The response should be received in the callback by the first extension. -// @Ignore -// public void testDispatchEventWithResponseCallback_whenAnotherExtensionListens_OnlyItsWildcordListenersCanHearIt() throws -// InterruptedException { -// -// // setup -// String extensionName1 = "Extension40One"; -// CreateExtensionResponse registrationStatus1 = extensionTestingHelper.registerExtension(extensionName1, -// customListenerTypes); -// Extension extensionInstance1 = extensionTestingHelper.getExtensionInstance(extensionName1); -// TestableListener eventListener1 = (TestableListener) extensionTestingHelper.getListenerInstance(extensionName1, -// customListenerTypes.get(0)); -// eventListener1.setDispatchBehavior("doDispatchResponseEvent"); -// -// String extensionName2 = "Extension40Two"; -// List listenerTypesOfExtension2 = new ArrayList<>(); -// listenerTypesOfExtension2.add(new ListenerType("com.adobe.eventtype._wildcard_", "com.adobe.eventsource._wildcard_")); -// listenerTypesOfExtension2.add(new ListenerType("com.adobe.eventtype.pairedresponse", -// "com.example.testable.pairedrequest")); -// CreateExtensionResponse registrationStatus2 = extensionTestingHelper.registerExtension(extensionName2, -// listenerTypesOfExtension2); -// -// asyncHelper.waitForAppThreads(500, true); -// -// // test -// Event responseEvent = new Event.Builder("DispatchedEvent", customListenerTypes.get(0).eventType, -// customListenerTypes.get(0).eventSource).setEventData(subEventData).build(); -// final List result = new ArrayList(); -// final CountDownLatch latch = new CountDownLatch(1); -// AdobeCallback dispatchCallback = new AdobeCallback() { -// @Override -// public void call(Event value) { -// result.add(value); -// latch.countDown(); -// } -// }; -// boolean dispatchStatus = MobileCore.dispatchEventWithResponseCallback(responseEvent, dispatchCallback, null); -// latch.await(500, TimeUnit.MILLISECONDS); -// -// List eventsHeardByWildcardListener = extensionTestingHelper.getAllEventsHeardByListener(extensionName2, -// listenerTypesOfExtension2.get(0)); -// List eventsHeardByPairedResponseListener = extensionTestingHelper.getAllEventsHeardByListener(extensionName2, -// listenerTypesOfExtension2.get(1)); -// // verify -// assertEquals(eventsHeardByWildcardListener.size(), 2); -// assertEquals(eventsHeardByPairedResponseListener.size(), 0); -// assertTrue(eventsHeardByWildcardListener.get(0).getEventType().getName().equalsIgnoreCase(customListenerTypes.get( -// 0).eventType)); -// assertTrue(eventsHeardByWildcardListener.get(0).getEventSource().getName().equalsIgnoreCase(customListenerTypes.get( -// 0).eventSource)); -// assertEquals(subEventData, eventsHeardByWildcardListener.get(0).getEventData()); -// assertTrue(eventsHeardByWildcardListener.get( -// 1).getEventType().getName().equalsIgnoreCase("com.adobe.eventtype.pairedresponse")); -// assertTrue(eventsHeardByWildcardListener.get( -// 1).getEventSource().getName().equalsIgnoreCase("com.example.testable.pairedrequest")); -// assertEquals(pairedEventData, eventsHeardByWildcardListener.get(1).getEventData()); -// assertTrue(dispatchStatus); -// assertTrue(result.get(0).getType().equalsIgnoreCase("com.adobe.eventtype.pairedresponse")); -// assertTrue(result.get(0).getSource().equalsIgnoreCase("com.example.testable.pairedrequest")); -// assertEquals(pairedEventData, result.get(0).getEventData()); -// } -// -// // Test Case No : 41 -// // Set XDM Shared Event State that is not tied to an event using setXDMSharedEventState API -// @Test -// public void testSetAndGetXDMSharedEventState_whenDanglingEvent_setsAndGetsAppropriateXDMSharedState() { -// // setup -// String extensionName = "Extension41"; -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, -// configListenerTypes); -// TestableExtension testableExtension = (TestableExtension) -// extensionTestingHelper.getExtensionInstance(extensionName); -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertNotNull(testableExtension); -// -// Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, -// configListenerTypes.get(0).eventSource).setEventData(eventData).build(); -// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// } -// }; -// assertTrue(testableExtension.getApi().setXDMSharedEventState(subEventData, event, errorCallback)); -// -// Map sharedState = testableExtension.getApi().getXDMSharedEventState( -// testableExtension.getName(), event, errorCallback); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertEquals(subEventData, sharedState); -// } -// -// // Test Case No : 42 -// // Clear XDM Shared Event States Of A Third Party Extension without affecting -// // the XDM Shared Event States Of other extensions using clearXDMSharedEventStates API -// @Test -// public void -// testClearXDMSharedEventStates_whenMultipleRegisteredExtensions_doesNotAffectXDMSharedStateOfOtherExtensions() { -// -// // setup -// CreateExtensionResponse returnStatus1 = extensionTestingHelper.registerExtension("ThirdPartyExtensionOne", -// configListenerTypes); -// TestableExtension testableExtension1 = (TestableExtension) -// extensionTestingHelper.getExtensionInstance("ThirdPartyExtensionOne"); -// assertNull(returnStatus1.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionOne")); -// assertNotNull(testableExtension1); -// -// CreateExtensionResponse returnStatus2 = extensionTestingHelper.registerExtension("ThirdPartyExtensionTwo", -// configListenerTypes); -// TestableExtension testableExtension2 = (TestableExtension) -// extensionTestingHelper.getExtensionInstance("ThirdPartyExtensionTwo"); -// assertNull(returnStatus2.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionTwo")); -// assertNotNull(testableExtension2); -// -// Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, -// configListenerTypes.get(0).eventSource).setEventData(eventData).build(); -// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// } -// }; -// assertTrue(testableExtension1.getApi().setXDMSharedEventState(subEventData, event, errorCallback)); -// assertTrue(testableExtension2.getApi().setXDMSharedEventState(subEventData, event, errorCallback)); -// ExtensionErrorCallback errorCallback1 = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// } -// }; -// Map sharedStateBeforeClearing1 = testableExtension1.getApi().getXDMSharedEventState( -// testableExtension1.getName(), event, errorCallback1); -// Map sharedStateBeforeClearing2 = testableExtension2.getApi().getXDMSharedEventState( -// testableExtension2.getName(), event, errorCallback1); -// ExtensionErrorCallback errorCallback2 = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// Log.debug(LOG_TAG, String.format("An error occurred while clearing the XDM shared states %d %s", -// extensionError.getErrorCode(), extensionError.getErrorName())); -// } -// }; -// // test -// testableExtension1.getApi().clearXDMSharedEventStates(errorCallback2); -// Map getSharedStateAfterClearing1 = testableExtension1.getApi().getXDMSharedEventState( -// testableExtension1.getName(), event, errorCallback1); -// Map getSharedStateAfterClearing2 = testableExtension2.getApi().getXDMSharedEventState( -// testableExtension2.getName(), event, errorCallback1); -// // verify -// assertNull(returnStatus1.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionOne")); -// assertNull(returnStatus2.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionTwo")); -// assertEquals(subEventData, sharedStateBeforeClearing1); -// assertEquals(subEventData, sharedStateBeforeClearing2); -// assertNull(getSharedStateAfterClearing1); -// assertEquals(subEventData, getSharedStateAfterClearing2); -// } -// -// // Test Case No : 43 -// // Get the XDM shared state for the custom extension, should be null if not set, should be valid if set before -// @Test -// public void testGetXDMSharedEventState_whenItIsNotSet_returnsNull() { -// -// // setup -// String extensionName = "ThirdPartyExtension43"; -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); -// TestableExtension testableExtension = (TestableExtension) -// extensionTestingHelper.getExtensionInstance(extensionName); -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertNotNull(testableExtension); -// -// ListenerType listenerType = customListenerTypes.get(0); -// Event event = new Event.Builder("DispatchedEvent", listenerType.eventType, -// listenerType.eventSource).setEventData(eventData).build(); -// -// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// } -// }; -// // test -// Map sharedStateBeforeItWasSet = testableExtension.getApi().getXDMSharedEventState( -// testableExtension.getName(), -// event, errorCallback); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertNull(sharedStateBeforeItWasSet); -// } -// -// // Test Case No : 44 -// // SetXDMSharedState with valid state, null event, should not crash -// @Test -// public void testSetAndGetXDMSharedEventState_whenValidStateAndNullEvent_returnsNoError() { -// -// // setup -// String extensionName = "ThirdPartyExtension44"; -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); -// TestableExtension testableExtension = (TestableExtension) -// extensionTestingHelper.getExtensionInstance(extensionName); -// -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertNotNull(testableExtension); -// -// final ExtensionError[] extenError = new ExtensionError[1]; -// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// extenError[0] = extensionError; -// } -// }; -// Map state = new HashMap(); -// state.put("testKey", "testVal"); -// assertTrue(testableExtension.getApi().setXDMSharedEventState(state, null, errorCallback)); -// // test -// Map customSharedState = testableExtension.getApi().getXDMSharedEventState(testableExtension.getName(), -// null, errorCallback); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertEquals(state, customSharedState); -// assertNull(extenError[0]); -// } -// -// // Test Case No : 45 -// // SetXDMSharedState with null state, valid event, should not crash -// @Test -// public void testSetAndGetXDMSharedEventState_whenNullStateAndValidEvent_returnsNoError() { -// -// // setup -// String extensionName = "ThirdPartyExtension44"; -// CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); -// TestableExtension testableExtension = (TestableExtension) -// extensionTestingHelper.getExtensionInstance(extensionName); -// -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertNotNull(testableExtension); -// -// final ExtensionError[] extenError = new ExtensionError[1]; -// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError extensionError) { -// extenError[0] = extensionError; -// } -// }; -// Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, -// configListenerTypes.get(0).eventSource).setEventData(eventData).build(); -// assertTrue(testableExtension.getApi().setXDMSharedEventState(null, event, errorCallback)); -// // test -// Map customSharedState = testableExtension.getApi().getXDMSharedEventState(testableExtension.getName(), -// event, errorCallback); -// // verify -// assertNull(returnStatus.extensionUnexpectedError); -// assertTrue(extensionTestingHelper.isRegistered(extensionName)); -// assertNull(customSharedState); -// assertNull(extenError[0]); -// } -// -//} -// -// -// -// +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * Class {@link AndroidThirdPartyExtensionsFunctionalTests} that defines all the necessary test cases to test a third party extension {@link TestableExtension} that extends {@link ExtensionListener} class of the Adobe Experience Platform SDK. + *

    + * This covers all the test cases listed in the document to run as part of the CI build system. + *

    + * https://wiki.corp.adobe.com/pages/viewpage.action?spaceKey=adms&title=Mobile+SDK+v5+Extensions+Module+Test+Plan + * + * @author Adobe + * @version 5.0 + */ + +@RunWith(AndroidJUnit4.class) +public class AndroidThirdPartyExtensionsFunctionalTests extends AbstractE2ETest { + + private static final String LOG_TAG = AndroidThirdPartyExtensionsFunctionalTests.class.getSimpleName(); + + private TestHelper testHelper = new TestHelper(); + private ExtensionTestingHelper extensionTestingHelper = new ExtensionTestingHelper(); + private AsyncHelper asyncHelper = new AsyncHelper(); + + private List configListenerTypes = new ArrayList(); + private List identityListenerTypes = new ArrayList(); + private List customListenerTypes = new ArrayList(); + private List analyticsListenerTypes = new ArrayList(); + private List wildcardListener = new ArrayList(); + + static final String CONFIGURATION_SHARED_STATE = "com.adobe.module.configuration"; + static final String EVENT_SOURCE_SHARED_STATE = "com.adobe.eventSource.sharedstate"; + static final String EVENT_TYPE_CONFIGURATION = "com.adobe.eventType.configuration"; + + private final Object executorMutex = new Object(); + private ExecutorService executor; + private ConcurrentLinkedQueue unprocessedEvents; + private long noProcessedEvents; + + private Map eventData = new HashMap(); + Map subEventData = new HashMap(); + Map pairedEventData = new HashMap(); + ExtensionError extensionError; + Map configMap = new HashMap(); + + @Before + public void setUp() { + super.setUp(); + testHelper.cleanCache(defaultContext); + MobileCore.setLogLevel(LoggingMode.DEBUG); + MobileCore.start(null); + testableNetworkService.resetTestableNetworkService(); + + configListenerTypes.add(new ListenerType("com.adobe.eventType.configuration", "com.adobe.eventSource.responseContent")); + configListenerTypes.add(new ListenerType("com.adobe.eventType.configuration", "com.adobe.eventSource.requestContent")); + + identityListenerTypes.add(new ListenerType("com.adobe.eventType.identity", "com.adobe.eventSource.requestIdentity")); + identityListenerTypes.add(new ListenerType("com.adobe.eventType.identity", "com.adobe.eventSource.responseIdentity")); + + analyticsListenerTypes.add(new ListenerType("com.adobe.eventType.analytics", "com.adobe.eventSource.requestContent")); + analyticsListenerTypes.add(new ListenerType("com.adobe.eventType.analytics", "com.adobe.eventSource.requestIdentity")); + analyticsListenerTypes.add(new ListenerType("com.adobe.eventType.analytics", "com.adobe.eventSource.responseContent")); + analyticsListenerTypes.add(new ListenerType("com.adobe.eventType.analytics", "com.adobe.eventSource.responseIdentity")); + analyticsListenerTypes.add(new ListenerType("com.adobe.eventType.generic.track", + "com.adobe.eventSource.requestContent")); + + wildcardListener.add(new ListenerType("com.adobe.eventtype._wildcard_", "com.adobe.eventsource._wildcard_")); + + customListenerTypes.add(new ListenerType("com.example.testable.custom", "com.example.testable.request")); + + subEventData.put("key0", "value0"); + subEventData.put("key1", "value1"); + subEventData.put("key2", "value2"); + Map customElement = new HashMap(); + // customElement.put("customByte", (byte)127); + // customElement.put("customFloat", new Float(3.14)); + // customElement.put("customShort", (short)300); + customElement.put("customInt", new Integer(123)); + customElement.put("customLong", (long) 300); + customElement.put("customChar", 'c'); + customElement.put("customString", "string1"); + customElement.put("customNull", null); + customElement.put("customBoolean", true); + customElement.put("customList", customListenerTypes); + customElement.put("customDouble", new Double(3.14)); + customElement.put("customMap", subEventData); + Map customData = new HashMap(); + customData.put("customElement", customElement); + eventData.put("customData", customData); + + pairedEventData.put("ResponseKey1", "ResponseData1"); + pairedEventData.put("ResponseKey2", "ResponseData2"); + pairedEventData.put("ResponseKey3", "ResponseData3"); + } + + @After + public void tearDown() { + asyncHelper.waitForAppThreads(500, true); + super.tearDown(); + } + + public void updateConfiguration() { + configMap.put("build.environment", "dev"); + configMap.put("myExtension.server", "mydomain.dev.com"); + configMap.put("global.privacy", "optedin"); + configMap.put("experienceCloud.org", "972C898555E9F7BC7F000101@AdobeOrg"); + configMap.put("analytics.server", "obumobile5.sc.omtrdc.net"); + configMap.put("analytics.rsids", "mobile5the.v5.show"); + configMap.put("analytics.offlineEnabled", true); + configMap.put("analytics.referrerTimeout", 15); + configMap.put("analytics.backdatePreviousSessionInfo", true); + configMap.put("identity.adidEnabled", true); + configMap.put("acquisition.server", "c00.adobe.com"); + configMap.put("rules.url", "https://s3.amazonaws.com/ams-qe/ios-sdk-test-app-rules.zip"); + configMap.put("target.clientCode", "yourclientcode"); + configMap.put("target.timeout", 5); + configMap.put("audience.server", "omniture.demdex.net"); + configMap.put("audience.timeout", 5); + configMap.put("analytics.aamForwardingEnabled", false); + configMap.put("analytics.batchLimit", 0); + configMap.put("lifecycle.sessionTimeout", 300); + MobileCore.updateConfiguration(configMap); + } + + public ExecutorService getExecutor() { + synchronized (executorMutex) { + if (executor == null) { + executor = Executors.newFixedThreadPool(2); + } + + return executor; + } + } + + // Test Case No : 1 + // Register A Third Party Extension using registerExtension API + @Test + public void testRegisterExtension_whenValidData_returnsNoError() { + + // setup + String extensionName = "ThirdPartyExtension01"; + // test + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, + configListenerTypes); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + } + + // Test Case No : 2 & 20 + // Get the Name Of A ThirdParty Extension using getName API in Android and name in iOS + @Test + public void testGetName_whenExtensionRegistered_returnsName() { + + // setup + String extensionName = "ThirdPartyExtension02"; + // test + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, + configListenerTypes); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertEquals(returnStatus.extensionName, extensionName); + } + + // Test Case No : 2a + @Test + public void testGetExtensionInstance_whenExtensionNotRegistered_returnsNull() { + + // setup + String extensionName = "ThirdPartyExtension02a"; + // test + Extension extension = extensionTestingHelper.getExtensionInstance(extensionName); + // verify + assertNull(extension); + } + + // Test Case No : 3 & 20 + // Get the Version Of A ThirdParty Extension using getVersion API in Android and version in iOS + @Test + public void testGetVersion_whenExtensionRegistered_returnsVersion() { + + // setup + String extensionName = "ThirdPartyExtension03"; + // test + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(returnStatus.extensionVersion.contains("1.0.0")); + } + + // Test Case No : 4 + // Unregister A Third Party Extension using unregisterExtension API + @Test + public void testUnRegisterExtension_whenExtensionRegistered_returnsTrue() { + + // setup + String extensionName = "ThirdPartyExtension04"; + // test + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); + boolean unregisterStatus = extensionTestingHelper.unregisterExtension(extensionName); + // verify + assertTrue(unregisterStatus); + assertFalse(extensionTestingHelper.isRegistered(extensionName)); + } + + // Test Case No : 5 + // OnUnregistered Call Of A Third Party Extension using unregisterExtension API in Android and onUnregister in IOS + @Test + public void testOnUnregistered_whenExtensionUnRegistered_OnUnregisteredIsCalled() { + + // setup + String extensionName = "ThirdPartyExtension05"; + // test + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); + extensionTestingHelper.confirmExtensionUnregisteredCall = ""; + boolean unregisterStatus = extensionTestingHelper.unregisterExtension(extensionName); + // verify + assertTrue(unregisterStatus); + assertEquals(returnStatus.extensionName, extensionName); + assertFalse(extensionTestingHelper.isRegistered(extensionName)); + assertTrue(extensionTestingHelper.confirmExtensionUnregisteredCall.contains("ConfirmedByTestableExtension")); + } + + // Test Case No : 6 + // onUnexpectedError Call Of A Third Party Extension when trying + // to a register Extension API for various errors mentioned in the ExtensionError Class + @Test + public void testRegisterExtension_whenNullName_returnsError() { + + // setup + String extensionName = null; + // test + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); + // verify + assertEquals(1, returnStatus.extensionUnexpectedError.getErrorCode().getErrorCode()); + assertEquals("extension.bad_extension_name", returnStatus.extensionUnexpectedError.getErrorCode().getErrorName()); + } + + // Test Case No : 6a + @Test + public void testRegisterExtension_whenEmptyName_returnsError() { + + // setup + String extensionName = ""; + // test + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); + // verify + assertEquals(1, returnStatus.extensionUnexpectedError.getErrorCode().getErrorCode()); + assertEquals("extension.bad_extension_name", returnStatus.extensionUnexpectedError.getErrorCode().getErrorName()); + } + + // Test Case No : 7 & 21 + // Register a listener to listen an identity event using registerEventListener API for stateowner like com.adobe.module.identity + @Test + public void testRegisterListeners_whenEventsDispatched_eventsReceivedByRightListener() { + + // setup + String extensionName = "ThirdPartyExtension07"; + // test + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, identityListenerTypes); + extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(0), eventData); + extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(1), subEventData); + Event event1 = extensionTestingHelper.getLastEventHeardByListener(extensionName, identityListenerTypes.get(0)); + Event event2 = extensionTestingHelper.getLastEventHeardByListener(extensionName, identityListenerTypes.get(1)); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + + assertTrue(identityListenerTypes.get(0).eventType.equalsIgnoreCase(event1.getType())); + assertTrue(identityListenerTypes.get(0).eventSource.equalsIgnoreCase(event1.getSource())); + assertEquals(eventData, event1.getEventData()); + + assertTrue(identityListenerTypes.get(1).eventType.equalsIgnoreCase(event2.getType())); + assertTrue(identityListenerTypes.get(1).eventSource.equalsIgnoreCase(event2.getSource())); + assertEquals(subEventData, event2.getEventData()); + } + + // Test Case No : 8 + // OnUnregistered Call in A registered listener will be called while an extension + // gets unregistered using unregisterExtension API + @Test + public void testOnUnregisteredCall_whenUnregisterExtension_OnUnregisteredCallOfListenerCalled() { + + // setup + String extensionName = "ThirdPartyExtension08"; + // test + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, identityListenerTypes); + extensionTestingHelper.confirmListenerUnregisteredCall = ""; + extensionTestingHelper.confirmExtensionUnregisteredCall = ""; + boolean unregisterStatus = extensionTestingHelper.unregisterExtension(extensionName); + // verify + assertTrue(unregisterStatus); + assertFalse(extensionTestingHelper.isRegistered(extensionName)); + assertTrue(extensionTestingHelper.confirmExtensionUnregisteredCall.contains("ConfirmedByTestableExtension")); + assertTrue(extensionTestingHelper.confirmListenerUnregisteredCall.contains("ConfirmedByTestableListener")); + } + + // Test Case No : 9 + // Register A Listener to listen a custom event using registerEventListener API + @Test + public void testRegisterAListener_whenACustomEvent_returnsNoError() { + + // setup + String extensionName = "ThirdPartyExtension07"; + // test + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); + EventListener eventListener = extensionTestingHelper.getListenerInstance(extensionName, customListenerTypes.get(0)); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertTrue(eventListener.getEventType().getName().equalsIgnoreCase("com.example.testable.custom")); + assertTrue(eventListener.getEventSource().getName().equalsIgnoreCase("com.example.testable.request")); + } + + // Test Case No : 10 + // Register A Wildcard Listener to listen all the events using registerWildcardListener API + @Test + public void testRegisterWildcardListener_whenRegistered_returnsNoError() { + + // setup + String extensionName = "ThirdPartyExtension10"; + // test + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, wildcardListener); + EventListener eventListener = extensionTestingHelper.getListenerInstance(extensionName, wildcardListener.get(0)); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertEquals(eventListener.getEventType().getName(), wildcardListener.get(0).eventType); + assertEquals(eventListener.getEventSource().getName(), wildcardListener.get(0).eventSource); + } + + // Test Case No : 10a + // Dispatch events of types identity, analytics, custom, and config using ACPCore dispatchEvent API + // And confirm that the wildcard listener is able to hear all those dispatched. + @Test + public void testDispatchEvent_whenWildcardListenerRegistered_hearsAllTheEventsAndreturnsNoError() { + + // setup + String extensionName = "ThirdPartyExtension11a"; + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, wildcardListener); + EventListener eventListener = extensionTestingHelper.getListenerInstance(extensionName, wildcardListener.get(0)); + // test + extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(0), eventData); + extensionTestingHelper.dispatchAnEvent(analyticsListenerTypes.get(0), subEventData); + extensionTestingHelper.dispatchAnEvent(customListenerTypes.get(0), eventData); + extensionTestingHelper.dispatchAnEvent(configListenerTypes.get(0), subEventData); + List eventsHearedByWildcardListener = extensionTestingHelper.getAllEventsHeardByListener(extensionName, + wildcardListener.get(0)); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertEquals(eventListener.getEventType().getName(), wildcardListener.get(0).eventType); + assertEquals(eventListener.getEventSource().getName(), wildcardListener.get(0).eventSource); + assertTrue(eventsHearedByWildcardListener.get(1).getEventType().getName().equalsIgnoreCase(identityListenerTypes.get( + 0).eventType)); + assertTrue(eventsHearedByWildcardListener.get(1).getEventSource().getName().equalsIgnoreCase(identityListenerTypes.get( + 0).eventSource)); + assertTrue(eventsHearedByWildcardListener.get(1).getEventData().equals(eventData)); + assertTrue(eventsHearedByWildcardListener.get(2).getEventType().getName().equalsIgnoreCase(analyticsListenerTypes.get( + 0).eventType)); + assertTrue(eventsHearedByWildcardListener.get(2).getEventSource().getName().equalsIgnoreCase(analyticsListenerTypes.get( + 0).eventSource)); + assertTrue(eventsHearedByWildcardListener.get(2).getEventData().equals(subEventData)); + assertTrue(eventsHearedByWildcardListener.get(3).getEventType().getName().equalsIgnoreCase(customListenerTypes.get( + 0).eventType)); + assertTrue(eventsHearedByWildcardListener.get(3).getEventSource().getName().equalsIgnoreCase(customListenerTypes.get( + 0).eventSource)); + assertTrue(eventsHearedByWildcardListener.get(3).getEventData().equals(eventData)); + assertTrue(eventsHearedByWildcardListener.get(4).getEventType().getName().equalsIgnoreCase(configListenerTypes.get( + 0).eventType)); + assertTrue(eventsHearedByWildcardListener.get(4).getEventSource().getName().equalsIgnoreCase(configListenerTypes.get( + 0).eventSource)); + assertEquals(eventsHearedByWildcardListener.get(4).getEventData(), subEventData); + } + + // Test Case No : 11 & 12 + // Dispatch A Custom Event using ACPCore dispatchEvent API + @Test + public void testDispatchEvent_whenCustomEvent_returnsTrue() { + + // setup + ListenerType listenerType = customListenerTypes.get(0); + // test + boolean dispatchStatus = extensionTestingHelper.dispatchAnEvent(listenerType, eventData); + // verify + assertTrue(dispatchStatus); + } + + + // Test Case No : 13 & 14 + // Dispatch an event using ACPCore dispatchResponseEvent API + @Test + public void testDispatchEventWithResponseCallback_whenPaired_returnsPairedEventAndData() throws InterruptedException { + + // setup + String extensionName = "ThirdPartyExtension13"; + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); + TestableListener eventListener = (TestableListener) extensionTestingHelper.getListenerInstance(extensionName, + customListenerTypes.get(0)); + eventListener.setDispatchBehavior("doDispatchResponseEvent"); + ListenerType listenerType = customListenerTypes.get(0); + // test + Event responseEvent = new Event.Builder("DispatchedEvent", listenerType.eventType, + listenerType.eventSource).setEventData(eventData).build(); + final List result = new ArrayList(); + final CountDownLatch latch = new CountDownLatch(1); + AdobeCallback dispatchCallback = new AdobeCallback() { + @Override + public void call(Event value) { + result.add(value); + latch.countDown(); + } + }; + boolean dispatchStatus = MobileCore.dispatchEventWithResponseCallback(responseEvent, dispatchCallback, null); + latch.await(10, TimeUnit.SECONDS); + // verify + assertTrue(dispatchStatus); + assertTrue(result.get(0).getType().equalsIgnoreCase("com.adobe.eventtype.pairedresponse")); + assertTrue(result.get(0).getSource().equalsIgnoreCase("com.example.testable.pairedrequest")); + assertEquals(pairedEventData, result.get(0).getEventData()); + } + + // Test Case No : 15, 17 & 36 + // Get Shared Event State Owned By A Configuration Event using getSharedEventState API + // with the extension name as the stateowner like + // com.adobe.module.identity, com.adobe.module.configuration + @Test + public void testGetSharedEventState_whenConfigEvent_returnsAppropriateSharedState() { + + // setup + String extensionName = "ThirdPartyExtension15"; + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, + configListenerTypes); + TestableExtension testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); + + ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + } + }; + // test + updateConfiguration(); + Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, + configListenerTypes.get(0).eventSource).setEventData(null).setEventNumber(100).build(); + asyncHelper.waitForAppThreads(500, true); + Map configurationSharedState = + testableExtension.getApi().getSharedEventState(CONFIGURATION_SHARED_STATE, event, errorCallback); + Event eventHeard = extensionTestingHelper.getLastEventHeardByListener(extensionName, configListenerTypes.get(0)); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertEquals(configMap, configurationSharedState); + assertEquals(configMap, eventHeard.getEventData()); + + } + + // Test Case No : 16 + // Get Shared Event State Owned By A Custom Event using getSharedEventState API for stateowner + // as extensionName. + @Test + public void testGetSharedEventState_whenItIsSet_returnsAppropriateSharedState() { + + // setup + String extensionName = "Extension18"; + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, + customListenerTypes); + TestableExtension testableExtension = (TestableExtension) + extensionTestingHelper.getExtensionInstance(extensionName); + + Event event = new Event.Builder("DispatchedEvent", customListenerTypes.get(0).eventType, + customListenerTypes.get(0).eventSource).setEventData(eventData).build(); + ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + } + }; + boolean status1 = testableExtension.getApi().setSharedEventState(subEventData, event, errorCallback); + + Map sharedState = testableExtension.getApi().getSharedEventState( + testableExtension.getName(), event, errorCallback); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertEquals(subEventData, sharedState); + } + + // Test Case No : 18 + // Set Shared Event State that is not tied to an event using setSharedEventState API + @Test + public void testSetAndGetSharedEventState_whenNullEvent_setsAndGetsAppropriateSharedState() { + + // setup + String extensionName = "Extension18"; + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, + configListenerTypes); + TestableExtension testableExtension = (TestableExtension) + extensionTestingHelper.getExtensionInstance(extensionName); + + Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, + configListenerTypes.get(0).eventSource).setEventData(eventData).build(); + ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + } + }; + boolean status1 = testableExtension.getApi().setSharedEventState(subEventData, event, errorCallback); + + Map sharedState = testableExtension.getApi().getSharedEventState( + testableExtension.getName(), event, errorCallback); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertEquals(subEventData, sharedState); + } + + // Test Case No : 19 + // Clear Shared Event States Of A Third Party Extension without affecting + // the Shared Event States Of other extensions using clearSharedEventStates API + @Test + public void testClearSharedEventStates_whenMultipleRegisteredExtensions_doesNotAffectSharedStateOfOtherExtensions() { + + // setup + CreateExtensionResponse returnStatus1 = extensionTestingHelper.registerExtension("ThirdPartyExtensionOne", + configListenerTypes); + TestableExtension testableExtension1 = (TestableExtension) + extensionTestingHelper.getExtensionInstance("ThirdPartyExtensionOne"); + CreateExtensionResponse returnStatus2 = extensionTestingHelper.registerExtension("ThirdPartyExtensionTwo", + configListenerTypes); + TestableExtension testableExtension2 = (TestableExtension) + extensionTestingHelper.getExtensionInstance("ThirdPartyExtensionTwo"); + Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, + configListenerTypes.get(0).eventSource).setEventData(eventData).build(); + ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + } + }; + boolean status1 = testableExtension1.getApi().setSharedEventState(subEventData, event, errorCallback); + boolean status2 = testableExtension2.getApi().setSharedEventState(subEventData, event, errorCallback); + ExtensionErrorCallback errorCallback1 = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + } + }; + Map sharedStateBeforeClearing1 = testableExtension1.getApi().getSharedEventState( + testableExtension1.getName(), event, errorCallback1); + Map sharedStateBeforeClearing2 = testableExtension2.getApi().getSharedEventState( + testableExtension2.getName(), event, errorCallback1); + ExtensionErrorCallback errorCallback2 = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + Log.debug(LOG_TAG, String.format("An error occurred while clearing the shared states %d %s", + extensionError.getErrorCode(), extensionError.getErrorName())); + } + }; + // test + testableExtension1.getApi().clearSharedEventStates(errorCallback2); + Map getSharedStateAfterClearing1 = testableExtension1.getApi().getSharedEventState( + testableExtension1.getName(), event, errorCallback1); + Map getSharedStateAfterClearing2 = testableExtension2.getApi().getSharedEventState( + testableExtension2.getName(), event, errorCallback1); + // verify + assertNull(returnStatus1.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionOne")); + assertNull(returnStatus2.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionTwo")); + assertEquals(subEventData, sharedStateBeforeClearing1); + assertEquals(subEventData, sharedStateBeforeClearing2); + assertNull(getSharedStateAfterClearing1); + assertEquals(subEventData, getSharedStateAfterClearing2); + } + + // Test Case No : 22 + // Make a copy of the event object received using event.copy() method, + // and the correctness of the copied event object. + @Test + public void testCopyEvent_whenCopied_returnsTheCopiedEvent() { + + // setup + ListenerType listenerType = customListenerTypes.get(0); + Event event = new Event.Builder("DispatchedEvent", listenerType.eventType, + listenerType.eventSource).setEventData(eventData).build(); + // setup + Event copiedEvent = event.copy(); + // verify + assertEquals(eventData, copiedEvent.getEventData()); + assertEquals(listenerType.eventType, copiedEvent.getType()); + assertEquals(listenerType.eventSource, copiedEvent.getSource()); + } + + // Test Case No : 23 + // Register the same extension multiple times - should not register, the initial extension + // should still be registered and receive events, + @Test + public void testCreateExtension_whenDuplicateName_returnsError() { + + // setup + String extensionName = "ThirdPartyExtension23"; + // test + CreateExtensionResponse returnStatus1 = extensionTestingHelper.registerExtension(extensionName, + configListenerTypes); + CreateExtensionResponse returnStatus2 = extensionTestingHelper.registerExtension(extensionName, + configListenerTypes); + // verify + assertNull(returnStatus1.extensionUnexpectedError); + assertEquals(2, returnStatus2.extensionUnexpectedError.getErrorCode().getErrorCode()); + assertTrue(returnStatus2.extensionUnexpectedError.getMessage().contains("Failed to register extension with name " + + extensionName)); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + } + + // Test Case No : 26 + // Register an extension with null version - should be ok, no NPE should be thrown in logs + @Test + public void testCreateExtension_whenNullVersion_returnsNoError() { + + // setup + String extensionName = "ExtensionWithNullVersion"; + // test + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, + configListenerTypes); + TestableExtension testableExtension = (TestableExtension) + extensionTestingHelper.getExtensionInstance(extensionName); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertNull(testableExtension.getVersion()); + } + + // Test Case No : 27 + // Same as Test case No : 19, but checking with the same extension by re-registering it. + @Test + public void testGetSharedEventState_whenExtensionReRegistered_theSharedStateIsUnaffected() { + + // setup + String extensionName = "ThirdPartyExtension27"; + CreateExtensionResponse returnStatus1 = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); + TestableExtension testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); + Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, + configListenerTypes.get(0).eventSource).setEventData(eventData).build(); + ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + } + }; + boolean status = testableExtension.getApi().setSharedEventState(subEventData, event, errorCallback); + Map sharedStateBeforeUnregister = testableExtension.getApi().getSharedEventState( + testableExtension.getName(), event, errorCallback); + // test + boolean unregisterStatus = extensionTestingHelper.unregisterExtension(extensionName); + CreateExtensionResponse returnStatus2 = extensionTestingHelper.registerExtension(extensionName, configListenerTypes); + testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); + Map sharedStateAfterReregister = testableExtension.getApi().getSharedEventState( + testableExtension.getName(), event, errorCallback); + // verify + assertNull(returnStatus1.extensionUnexpectedError); + assertTrue(unregisterStatus); + assertNull(returnStatus2.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertEquals(subEventData, sharedStateBeforeUnregister); + assertEquals(subEventData, sharedStateAfterReregister); + } + + // Test Case No : 28 + // Register a listener with null eventType - should be ok, no NPE should be thrown in logs + @Test + public void testRegisterListener_whenNullEventType_returnsErrorCode() { + + // setup + String extensionName = "ThirdPartyExtension28a"; + // setup + List listenerType = new ArrayList(); + listenerType.add(new ListenerType(null, "com.example.testable.request")); + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, + listenerType); + // test + Map listenerRegistrationStatus = TestableExtension.getListenerRegistrationStatus(); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorCode(), 3); + assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorName(), "extension.event_type_not_supported"); + } + + // Test Case No : 28a + // Register a listener with null eventSource - should be ok, no NPE should be thrown in logs + @Test + public void testRegisterListener_whenNullEventSource_returnsErrorCode() { + + // setup + String extensionName = "ThirdPartyExtension28b"; + // test + List listenerType = new ArrayList(); + listenerType.add(new ListenerType("com.example.testable.custom", null)); + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, listenerType); + Map listenerRegistrationStatus = TestableExtension.getListenerRegistrationStatus(); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorCode(), 4); + assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorName(), + "extension.event_source_not_supported"); + } + + // Test Case No : 28b + // Register a listener with null for both eventType and eventSource - should be ok, no NPE should be thrown in logs + @Test + public void testRegisterListener_whenNullEventTypeAndSource_returnsErrorCode() { + + // setup + String extensionName = "ThirdPartyExtension28c"; + // test + List listenerType = new ArrayList(); + listenerType.add(new ListenerType(null, null)); + CreateExtensionResponse returnStatus = + extensionTestingHelper.registerExtension(extensionName, listenerType); + Map listenerRegistrationStatus = TestableExtension.getListenerRegistrationStatus(); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorCode(), 3); + assertEquals(listenerRegistrationStatus.get(listenerType.get(0)).getErrorName(), "extension.event_type_not_supported"); + } + + // Test Case No : 29 + // Register listener which does busy work on the event hub thread + // Expected : It should not block the event hub in dispatching new events + @Test + @Ignore + public void testWithAListenerDoingBusyWorkOnTheHub_whenAnotherEventIsDispatched_eventHubShouldDispatchIt() throws + InterruptedException { + + // setup + final String customExtension = "CustomExtension29"; + final String busyWorkExtension = "BusyWorkExtension29"; + + final List listenerTypes = new ArrayList(); + + final List eventsHeard = new ArrayList(); + final List result = new ArrayList(); + final ArrayList[] eventsHeardByListener = new ArrayList[4]; + listenerTypes.add(new ListenerType("com.adobe.eventtype.busywork", "com.example.testable.busywork")); + listenerTypes.add(new ListenerType("com.adobe.eventtype.customevent", "com.example.testable.customevent")); + CreateExtensionResponse registrationStatus1 = extensionTestingHelper.registerExtension(customExtension, listenerTypes); + CreateExtensionResponse registrationStatus2 = extensionTestingHelper.registerExtension(busyWorkExtension, + listenerTypes); + + ExtensionErrorCallback dispatchCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError ec) { + Log.debug(LOG_TAG, String.format("[dispatchAnEvent] Dispatch failed with error %s ", ec.getErrorCode())); + } + }; + + Event busyEvent = new Event.Builder("DispatchedBusyEvent", listenerTypes.get(0).eventType, + listenerTypes.get(0).eventSource).setEventData(subEventData).build(); + Event customEvent = new Event.Builder("DispatchedCustomEvent", listenerTypes.get(1).eventType, + listenerTypes.get(1).eventSource).setEventData(subEventData).build(); + + MobileCore.dispatchEvent(customEvent, dispatchCallback); + MobileCore.dispatchEvent(customEvent, dispatchCallback); + MobileCore.dispatchEvent(busyEvent, dispatchCallback); + MobileCore.dispatchEvent(customEvent, dispatchCallback); + MobileCore.dispatchEvent(customEvent, dispatchCallback); + asyncHelper.waitForAppThreads(500, true); + eventsHeardByListener[0] = (ArrayList) extensionTestingHelper.getAllEventsHeardByListener(customExtension, + listenerTypes.get(0)); + eventsHeardByListener[1] = (ArrayList) extensionTestingHelper.getAllEventsHeardByListener(customExtension, + listenerTypes.get(1)); + eventsHeardByListener[2] = (ArrayList) extensionTestingHelper.getAllEventsHeardByListener(busyWorkExtension, + listenerTypes.get(0)); + eventsHeardByListener[3] = (ArrayList) extensionTestingHelper.getAllEventsHeardByListener(busyWorkExtension, + listenerTypes.get(1)); + // verify + assertEquals(1, eventsHeardByListener[0].size()); + assertEquals(4, eventsHeardByListener[1].size()); + assertEquals(1, eventsHeardByListener[2].size()); + assertEquals(4, eventsHeardByListener[3].size()); + } + + + // Test Case No : 30 + // Build a custom event with null event data should not crash + @Test + public void testDispatchEvent_whenNullEventData_returnsNoError() { + + // setup + String extensionName = "ThirdPartyExtension30"; + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, + identityListenerTypes); + TestableExtension testableExtension = (TestableExtension) + extensionTestingHelper.getExtensionInstance(extensionName); + ListenerType listenerType = new ListenerType("com.adobe.eventType.configuration", + "com.adobe.eventSource.requestContent"); + Event event = new Event.Builder("DispatchedEvent", listenerType.eventType, + listenerType.eventSource).setEventData(null).build(); + ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + } + }; + boolean status = testableExtension.getApi().setSharedEventState(null, event, errorCallback); + // test + ExtensionErrorCallback errorCallback1 = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + } + }; + Map configurationSharedState = testableExtension.getApi().getSharedEventState( + testableExtension.getName(), event, errorCallback1); + ExtensionErrorCallback dispatchCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError ec) { + } + }; + boolean dispatchStatus = MobileCore.dispatchEvent(event, dispatchCallback); + asyncHelper.waitForAppThreads(500, true); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertNull(configurationSharedState); + assertTrue(dispatchStatus); + } + + // Test Case No : 31 Skipped due to the bug AMSDK-7597 + // SDK v5 does not have support for Short, Float, and Byte Data types + // Check under bourbon-core-java-core/code/shared/src/main/java/com/adobe/marketing/mobile + // Build a custom event data with byte data type + // dispatch it and check the returned values are the same. + @Test + public void testDispatchEvent_whenEventDataWithByteTypeValue_returnsNoError() { + + // setup + String extensionName = "ThirdPartyExtension31a"; + Map testEventData = new HashMap(); + int byteData = 0b0010_0101; + testEventData.put("customByte", byteData); + // tes + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, + identityListenerTypes); + extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(0), testEventData); + Event event = extensionTestingHelper.getLastEventHeardByListener(extensionName, + identityListenerTypes.get(0)); + + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertTrue(identityListenerTypes.get(0).eventType.equalsIgnoreCase(event.getType())); + assertTrue(identityListenerTypes.get(0).eventSource.equalsIgnoreCase(event.getSource())); + assertEquals(testEventData, event.getEventData()); + } + + // Test Case No : 31a Skipped due to the bug AMSDK-7596 + // Build a custom event data with Float data type + // dispatch it and check the returned values are the same. + @Ignore + public void testDispatchEvent_whenEventDataWithFloatTypeValue_returnsNoError() { + + // setup + String extensionName = "ThirdPartyExtension31b"; + Map testEventData = new HashMap(); + testEventData.put("customFloat", new Float(3.14)); + // test + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, + identityListenerTypes); + extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(0), testEventData); + Event event = extensionTestingHelper.getLastEventHeardByListener(extensionName, + identityListenerTypes.get(0)); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertTrue(identityListenerTypes.get(0).eventType.equalsIgnoreCase(event.getType())); + assertTrue(identityListenerTypes.get(0).eventSource.equalsIgnoreCase(event.getSource())); + assertEquals(testEventData, event.getEventData()); + } + + // Test Case No : 31b Skipped due to the bug AMSDK-7595 + // Build a custom event data with Short data type + // dispatch it and check the returned values are the same. + @Ignore + public void testDispatchEvent_whenEventDataWithShortTypeValue_returnsNoError() { + + // setup + String extensionName = "ThirdPartyExtension31c"; + Map testEventData = new HashMap(); + testEventData.put("customShort", (short) 300); + // test + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, + identityListenerTypes); + extensionTestingHelper.dispatchAnEvent(identityListenerTypes.get(0), testEventData); + Event event = extensionTestingHelper.getLastEventHeardByListener(extensionName, + identityListenerTypes.get(0)); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertTrue(identityListenerTypes.get(0).eventType.equalsIgnoreCase(event.getType())); + assertTrue(identityListenerTypes.get(0).eventSource.equalsIgnoreCase(event.getSource())); + assertEquals(testEventData, event.getEventData()); + } + + // Test Case No : 32 + // Dispatch with null event, should return false and error in the callback + @Test + public void testDispatchEvent_whenNullEvent_returnsError() throws InterruptedException { + + // test + ExtensionErrorCallback dispatchCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError ec) { + extensionError = ec; + } + }; + boolean dispatchStatus = MobileCore.dispatchEvent(null, dispatchCallback); + asyncHelper.waitForAppThreads(500, true); + // verify + assertFalse(dispatchStatus); + assertEquals("extension.event_null", extensionError.getErrorName()); + assertEquals(6, extensionError.getErrorCode()); + } + + // Test Case No : 33 + // Dispatch response event with null event, should return false and error in the callback + @Test + public void testDispatchEventWithResponseCallback_whenNullResponseEvent_returnsError() throws InterruptedException { + + // setup + String extensionName = "ThirdPartyExtension33"; + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); + TestableListener eventListener = (TestableListener) extensionTestingHelper.getListenerInstance(extensionName, + customListenerTypes.get(0)); + eventListener.setDispatchBehavior("doDispatchResponseEventWithNullEvent"); + ListenerType listenerType = customListenerTypes.get(0); + // test + Event responseEvent = new Event.Builder("DispatchedEvent", listenerType.eventType, + listenerType.eventSource).setEventData(eventData).build(); + final List result = new ArrayList(); + final CountDownLatch latch = new CountDownLatch(1); + AdobeCallback dispatchCallback = new AdobeCallback() { + @Override + public void call(Event value) { + result.add(value); + latch.countDown(); + } + }; + boolean dispatchStatus = MobileCore.dispatchEventWithResponseCallback(responseEvent, dispatchCallback, null); + latch.await(500, TimeUnit.MILLISECONDS); + // verify + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertTrue(dispatchStatus); + assertTrue(result.isEmpty()); + assertEquals("extension.event_null", eventListener.getExtensionError().getErrorName()); + assertEquals(6, eventListener.getExtensionError().getErrorCode()); + } + + // Test Case No : 34 + // Get the shared state for the custom extension, should be null if not set, should be valid if set before + @Test + public void testGetSharedEventState_whenItIsNotSet_returnsNull() { + + // setup + String extensionName = "ThirdPartyExtension34"; + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); + TestableExtension testableExtension = (TestableExtension) + extensionTestingHelper.getExtensionInstance(extensionName); + ListenerType listenerType = customListenerTypes.get(0); + Event event = new Event.Builder("DispatchedEvent", listenerType.eventType, + listenerType.eventSource).setEventData(eventData).build(); + + ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + } + }; + // test + Map sharedStateBeforeItWasSet = testableExtension.getApi().getSharedEventState( + testableExtension.getName(), + event, errorCallback); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertNull(sharedStateBeforeItWasSet); + } + + // Test Case No : 35 + // SetSharedState with null state, null event, should not crash + @Test + public void testSetAndGetSharedEventState_whenNullStateAndEvent_returnsNoError() { + + // setup + String extensionName = "ThirdPartyExtension35"; + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); + TestableExtension testableExtension = (TestableExtension) + extensionTestingHelper.getExtensionInstance(extensionName); + + final ExtensionError[] extenError = new ExtensionError[1]; + ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + extenError[0] = extensionError; + } + }; + boolean status = testableExtension.getApi().setSharedEventState(null, null, errorCallback); + // test + Map customSharedState = testableExtension.getApi().getSharedEventState(testableExtension.getName(), + null, errorCallback); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertNull(customSharedState); + assertNull(extenError[0]); + } + + // Test Case No : 38 + // Simulate an analytics 3rd party extension - dispatching & receiving generic events when trackAction called + @Test + public void testTrackAction_whenDispatched_receivedByGenericTrackEventType() { + // setup + String extensionName = "AnalyticsExtension38a"; + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, + analyticsListenerTypes); + TestableExtension testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); + ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + } + }; + // test + Map additionalContextData = new HashMap(); + additionalContextData.put("customKey", "value"); + MobileCore.trackAction("eventDispatched", additionalContextData); + asyncHelper.waitForAppThreads(500, true); + Event eventHeard = extensionTestingHelper.getLastEventHeardByListener(extensionName, analyticsListenerTypes.get(4)); + // verify + assertEquals("eventDispatched", eventHeard.getEventData().get("action")); + assertEquals(eventHeard.getEventData().get("contextdata"), additionalContextData); + assertTrue(eventHeard.getEventSource().getName().equalsIgnoreCase(analyticsListenerTypes.get(4).eventSource)); + assertTrue(eventHeard.getEventType().getName().equalsIgnoreCase(analyticsListenerTypes.get(4).eventType)); + + MobileCore.trackState("trackState", additionalContextData); + } + + // Test Case No : 38a + // Simulate an analytics 3rd party extension - dispatching & receiving generic events when TrackState called + @Test + public void testTrackState_whenDispatched_receivedByGenericTrackEventType() { + // setup + String extensionName = "AnalyticsExtension38a"; + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, + analyticsListenerTypes); + TestableExtension testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); + ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + } + }; + // test + Map additionalContextData = new HashMap(); + additionalContextData.put("customKey", "value"); + MobileCore.trackState("trackState", additionalContextData); + asyncHelper.waitForAppThreads(500, true); + Event eventHeard = extensionTestingHelper.getLastEventHeardByListener(extensionName, analyticsListenerTypes.get(4)); + // verify + assertEquals("trackState", eventHeard.getEventData().get("state")); + assertEquals(additionalContextData, eventHeard.getEventData().get("contextdata")); + assertTrue(eventHeard.getEventSource().getName().equalsIgnoreCase(analyticsListenerTypes.get(4).eventSource)); + assertTrue(eventHeard.getEventType().getName().equalsIgnoreCase(analyticsListenerTypes.get(4).eventType)); + } + + // Test Case No : 38b + // Dispatch different types of Analytics Events and + // Confirm if they are all received by the appropriate listeners registered. + @Test + public void testAnalyticsExtension_whenAnalyticsEventsDispatched_receivesAllDispatchedEvents() { + // setup + String extensionName = "AnalyticsExtension38"; + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, + analyticsListenerTypes); + TestableExtension testableExtension = (TestableExtension) extensionTestingHelper.getExtensionInstance(extensionName); + + ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + } + }; + // test + Event[] eventHeard = new Event[analyticsListenerTypes.size()]; + + for (int i = 0; i < analyticsListenerTypes.size(); i++) { + subEventData.put("newKey" + i, "newValue" + i); + Event event = new Event.Builder("DispatchedEvent", analyticsListenerTypes.get(i).eventType, + analyticsListenerTypes.get(i).eventSource).setEventData(subEventData).build(); + ExtensionErrorCallback dispatchCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError ec) { + Log.debug(LOG_TAG, String.format("[dispatchAnEvent] Dispatch failed with error %s ", ec.getErrorCode())); + } + }; + MobileCore.dispatchEvent(event, dispatchCallback); + asyncHelper.waitForAppThreads(500, true); + eventHeard[i] = extensionTestingHelper.getLastEventHeardByListener(extensionName, analyticsListenerTypes.get(i)); + // verify + assertEquals(subEventData, eventHeard[i].getEventData()); + assertTrue(eventHeard[i].getEventSource().getName().equalsIgnoreCase(analyticsListenerTypes.get(i).eventSource)); + assertTrue(eventHeard[i].getEventType().getName().equalsIgnoreCase(analyticsListenerTypes.get(i).eventType)); + } + } + + // Test Case No : 39 + // Test communication between two 3rd party extensions - dispatch events and set shared states from one of them, + // Check updates are received in the other extension + @Test + public void testCommunicationBetweenExtensions_whenOneDispatchesEventsAndSetsSharedState_otherExtensionReceivesThem() { + + // setup + String extensionName1 = "Extension39One"; + CreateExtensionResponse returnStatus1 = extensionTestingHelper.registerExtension(extensionName1, customListenerTypes); + TestableExtension testableExtension1 = (TestableExtension) + extensionTestingHelper.getExtensionInstance(extensionName1); + + String extensionName2 = "Extension39two"; + CreateExtensionResponse returnStatus2 = extensionTestingHelper.registerExtension(extensionName2, customListenerTypes); + TestableExtension testableExtension2 = (TestableExtension) + extensionTestingHelper.getExtensionInstance(extensionName2); + + + ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + Log.debug(LOG_TAG, String.format("An error occurred while setting the shared state %d %s", + extensionError.getErrorCode(), extensionError.getErrorName())); + } + }; + + // test + Event event = new Event.Builder("DispatchedEvent", customListenerTypes.get(0).eventType, + customListenerTypes.get(0).eventSource).setEventData(eventData).build(); + MobileCore.dispatchEvent(event, errorCallback); + testableExtension1.getApi().setSharedEventState(subEventData, event, errorCallback); + asyncHelper.waitForAppThreads(500, true); + + Event eventHeard = extensionTestingHelper.getLastEventHeardByListener(extensionName2, customListenerTypes.get(0)); + Map sharedState = testableExtension2.getApi().getSharedEventState( + testableExtension1.getName(), event, errorCallback); + + // verify + assertEquals(eventData, eventHeard.getEventData()); + assertTrue(eventHeard.getEventSource().getName().equalsIgnoreCase(customListenerTypes.get(0).eventSource)); + assertTrue(eventHeard.getEventType().getName().equalsIgnoreCase(customListenerTypes.get(0).eventType)); + assertEquals(subEventData, sharedState); + } + + // Test Case No : 40 + // Check that paired response events are received by another extension in a wildcard listener, but not by a regular listener. + // The response should be received in the callback by the first extension. + @Ignore + public void testDispatchEventWithResponseCallback_whenAnotherExtensionListens_OnlyItsWildcordListenersCanHearIt() throws + InterruptedException { + + // setup + String extensionName1 = "Extension40One"; + CreateExtensionResponse registrationStatus1 = extensionTestingHelper.registerExtension(extensionName1, + customListenerTypes); + Extension extensionInstance1 = extensionTestingHelper.getExtensionInstance(extensionName1); + TestableListener eventListener1 = (TestableListener) extensionTestingHelper.getListenerInstance(extensionName1, + customListenerTypes.get(0)); + eventListener1.setDispatchBehavior("doDispatchResponseEvent"); + + String extensionName2 = "Extension40Two"; + List listenerTypesOfExtension2 = new ArrayList<>(); + listenerTypesOfExtension2.add(new ListenerType("com.adobe.eventtype._wildcard_", "com.adobe.eventsource._wildcard_")); + listenerTypesOfExtension2.add(new ListenerType("com.adobe.eventtype.pairedresponse", + "com.example.testable.pairedrequest")); + CreateExtensionResponse registrationStatus2 = extensionTestingHelper.registerExtension(extensionName2, + listenerTypesOfExtension2); + + asyncHelper.waitForAppThreads(500, true); + + // test + Event responseEvent = new Event.Builder("DispatchedEvent", customListenerTypes.get(0).eventType, + customListenerTypes.get(0).eventSource).setEventData(subEventData).build(); + final List result = new ArrayList(); + final CountDownLatch latch = new CountDownLatch(1); + AdobeCallback dispatchCallback = new AdobeCallback() { + @Override + public void call(Event value) { + result.add(value); + latch.countDown(); + } + }; + boolean dispatchStatus = MobileCore.dispatchEventWithResponseCallback(responseEvent, dispatchCallback, null); + latch.await(500, TimeUnit.MILLISECONDS); + + List eventsHeardByWildcardListener = extensionTestingHelper.getAllEventsHeardByListener(extensionName2, + listenerTypesOfExtension2.get(0)); + List eventsHeardByPairedResponseListener = extensionTestingHelper.getAllEventsHeardByListener(extensionName2, + listenerTypesOfExtension2.get(1)); + // verify + assertEquals(eventsHeardByWildcardListener.size(), 2); + assertEquals(eventsHeardByPairedResponseListener.size(), 0); + assertTrue(eventsHeardByWildcardListener.get(0).getEventType().getName().equalsIgnoreCase(customListenerTypes.get( + 0).eventType)); + assertTrue(eventsHeardByWildcardListener.get(0).getEventSource().getName().equalsIgnoreCase(customListenerTypes.get( + 0).eventSource)); + assertEquals(subEventData, eventsHeardByWildcardListener.get(0).getEventData()); + assertTrue(eventsHeardByWildcardListener.get( + 1).getEventType().getName().equalsIgnoreCase("com.adobe.eventtype.pairedresponse")); + assertTrue(eventsHeardByWildcardListener.get( + 1).getEventSource().getName().equalsIgnoreCase("com.example.testable.pairedrequest")); + assertEquals(pairedEventData, eventsHeardByWildcardListener.get(1).getEventData()); + assertTrue(dispatchStatus); + assertTrue(result.get(0).getType().equalsIgnoreCase("com.adobe.eventtype.pairedresponse")); + assertTrue(result.get(0).getSource().equalsIgnoreCase("com.example.testable.pairedrequest")); + assertEquals(pairedEventData, result.get(0).getEventData()); + } + + // Test Case No : 41 + // Set XDM Shared Event State that is not tied to an event using setXDMSharedEventState API + @Test + public void testSetAndGetXDMSharedEventState_whenDanglingEvent_setsAndGetsAppropriateXDMSharedState() { + // setup + String extensionName = "Extension41"; + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, + configListenerTypes); + TestableExtension testableExtension = (TestableExtension) + extensionTestingHelper.getExtensionInstance(extensionName); + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertNotNull(testableExtension); + + Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, + configListenerTypes.get(0).eventSource).setEventData(eventData).build(); + ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + } + }; + assertTrue(testableExtension.getApi().setXDMSharedEventState(subEventData, event, errorCallback)); + + Map sharedState = testableExtension.getApi().getXDMSharedEventState( + testableExtension.getName(), event, errorCallback); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertEquals(subEventData, sharedState); + } + + // Test Case No : 42 + // Clear XDM Shared Event States Of A Third Party Extension without affecting + // the XDM Shared Event States Of other extensions using clearXDMSharedEventStates API + @Test + public void + testClearXDMSharedEventStates_whenMultipleRegisteredExtensions_doesNotAffectXDMSharedStateOfOtherExtensions() { + + // setup + CreateExtensionResponse returnStatus1 = extensionTestingHelper.registerExtension("ThirdPartyExtensionOne", + configListenerTypes); + TestableExtension testableExtension1 = (TestableExtension) + extensionTestingHelper.getExtensionInstance("ThirdPartyExtensionOne"); + assertNull(returnStatus1.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionOne")); + assertNotNull(testableExtension1); + + CreateExtensionResponse returnStatus2 = extensionTestingHelper.registerExtension("ThirdPartyExtensionTwo", + configListenerTypes); + TestableExtension testableExtension2 = (TestableExtension) + extensionTestingHelper.getExtensionInstance("ThirdPartyExtensionTwo"); + assertNull(returnStatus2.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionTwo")); + assertNotNull(testableExtension2); + + Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, + configListenerTypes.get(0).eventSource).setEventData(eventData).build(); + ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + } + }; + assertTrue(testableExtension1.getApi().setXDMSharedEventState(subEventData, event, errorCallback)); + assertTrue(testableExtension2.getApi().setXDMSharedEventState(subEventData, event, errorCallback)); + ExtensionErrorCallback errorCallback1 = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + } + }; + Map sharedStateBeforeClearing1 = testableExtension1.getApi().getXDMSharedEventState( + testableExtension1.getName(), event, errorCallback1); + Map sharedStateBeforeClearing2 = testableExtension2.getApi().getXDMSharedEventState( + testableExtension2.getName(), event, errorCallback1); + ExtensionErrorCallback errorCallback2 = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + Log.debug(LOG_TAG, String.format("An error occurred while clearing the XDM shared states %d %s", + extensionError.getErrorCode(), extensionError.getErrorName())); + } + }; + // test + testableExtension1.getApi().clearXDMSharedEventStates(errorCallback2); + Map getSharedStateAfterClearing1 = testableExtension1.getApi().getXDMSharedEventState( + testableExtension1.getName(), event, errorCallback1); + Map getSharedStateAfterClearing2 = testableExtension2.getApi().getXDMSharedEventState( + testableExtension2.getName(), event, errorCallback1); + // verify + assertNull(returnStatus1.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionOne")); + assertNull(returnStatus2.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered("ThirdPartyExtensionTwo")); + assertEquals(subEventData, sharedStateBeforeClearing1); + assertEquals(subEventData, sharedStateBeforeClearing2); + assertNull(getSharedStateAfterClearing1); + assertEquals(subEventData, getSharedStateAfterClearing2); + } + + // Test Case No : 43 + // Get the XDM shared state for the custom extension, should be null if not set, should be valid if set before + @Test + public void testGetXDMSharedEventState_whenItIsNotSet_returnsNull() { + + // setup + String extensionName = "ThirdPartyExtension43"; + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); + TestableExtension testableExtension = (TestableExtension) + extensionTestingHelper.getExtensionInstance(extensionName); + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertNotNull(testableExtension); + + ListenerType listenerType = customListenerTypes.get(0); + Event event = new Event.Builder("DispatchedEvent", listenerType.eventType, + listenerType.eventSource).setEventData(eventData).build(); + + ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + } + }; + // test + Map sharedStateBeforeItWasSet = testableExtension.getApi().getXDMSharedEventState( + testableExtension.getName(), + event, errorCallback); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertNull(sharedStateBeforeItWasSet); + } + + // Test Case No : 44 + // SetXDMSharedState with valid state, null event, should not crash + @Test + public void testSetAndGetXDMSharedEventState_whenValidStateAndNullEvent_returnsNoError() { + + // setup + String extensionName = "ThirdPartyExtension44"; + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); + TestableExtension testableExtension = (TestableExtension) + extensionTestingHelper.getExtensionInstance(extensionName); + + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertNotNull(testableExtension); + + final ExtensionError[] extenError = new ExtensionError[1]; + ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + extenError[0] = extensionError; + } + }; + Map state = new HashMap(); + state.put("testKey", "testVal"); + assertTrue(testableExtension.getApi().setXDMSharedEventState(state, null, errorCallback)); + // test + Map customSharedState = testableExtension.getApi().getXDMSharedEventState(testableExtension.getName(), + null, errorCallback); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertEquals(state, customSharedState); + assertNull(extenError[0]); + } + + // Test Case No : 45 + // SetXDMSharedState with null state, valid event, should not crash + @Test + public void testSetAndGetXDMSharedEventState_whenNullStateAndValidEvent_returnsNoError() { + + // setup + String extensionName = "ThirdPartyExtension44"; + CreateExtensionResponse returnStatus = extensionTestingHelper.registerExtension(extensionName, customListenerTypes); + TestableExtension testableExtension = (TestableExtension) + extensionTestingHelper.getExtensionInstance(extensionName); + + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertNotNull(testableExtension); + + final ExtensionError[] extenError = new ExtensionError[1]; + ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + extenError[0] = extensionError; + } + }; + Event event = new Event.Builder("DispatchedEvent", configListenerTypes.get(0).eventType, + configListenerTypes.get(0).eventSource).setEventData(eventData).build(); + assertTrue(testableExtension.getApi().setXDMSharedEventState(null, event, errorCallback)); + // test + Map customSharedState = testableExtension.getApi().getXDMSharedEventState(testableExtension.getName(), + event, errorCallback); + // verify + assertNull(returnStatus.extensionUnexpectedError); + assertTrue(extensionTestingHelper.isRegistered(extensionName)); + assertNull(customSharedState); + assertNull(extenError[0]); + } + +} + + + + diff --git a/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/ExtensionTestingHelper.java b/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/ExtensionTestingHelper.java index 3e6bea11d..464f858b8 100644 --- a/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/ExtensionTestingHelper.java +++ b/code/android-core-library/src/legacy/androidTest-common/java/com/adobe/marketing/mobile/ExtensionTestingHelper.java @@ -1,284 +1,284 @@ -///* -// Copyright 2022 Adobe. All rights reserved. -// This file is licensed to you under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. You may obtain a copy -// of the License at http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software distributed under -// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -// OF ANY KIND, either express or implied. See the License for the specific language -// governing permissions and limitations under the License. -// */ -// -//package com.adobe.marketing.mobile; -// -//import java.util.ArrayList; -//import java.util.Collection; -//import java.util.HashMap; -//import java.util.Iterator; -//import java.util.List; -//import java.util.Map; -//import java.util.concurrent.ConcurrentLinkedQueue; -// -///** -// * Class {@link ExtensionTestingHelper} that defines the necessary helper methods to work with the third party extension {@link TestableExtension} that extends {@link ExtensionListener} class of the Adobe Experience Platform SDK. -// *

    -// * This class provides the following features to write automated tests for a third party extension -// *

    -// * 1. necessary helper methods for the automated tests cases to work with an extension instance. The instance will be captured using the eventHub getActiveModules() method. -// * 2. necessary helper methods to get access to the listeners registered by the TestableExtension class. The listeners that are registered in the eventHub will be accessed with the getModuleListeners(Module) method. -// * 3. uses static countdown latch to wait for all the listeners to be registered before dispatching events -// * 4. has provisions for setting the shared state, retrieving the shared state, clearing the shared state -// * -// * @author Adobe -// * @version 5.0 -// */ -// -// -//public class ExtensionTestingHelper { -// -// private static final String LOG_TAG = ExtensionTestingHelper.class.getSimpleName(); -// static AsyncHelper asyncHelper = new AsyncHelper(); -// static boolean isDispatched = false; -// static String confirmExtensionUnregisteredCall; -// static String confirmListenerUnregisteredCall; -// -// -// /** -// * Returns an CreateExtensionResponse object -// *

    -// * This method helps registering a third party extension with a list of required listeners and -// * retuns the status of creation as an CreateExtensionResponse object -// * -// * @param tExtensionName Name of the extension to be created -// * @param listenerTypes The list of listeners as a ListenerType to be registered as part of creating the third party extension -// * @return returns an CreateExtensionResponse object as a result of creating the extension. -// */ -// -// public CreateExtensionResponse registerExtension(String tExtensionName, List listenerTypes) { -// final String extensionName = tExtensionName; -// ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError adbExtensionError) { -// Log.debug(LOG_TAG, String.format("[registerExtension] Registration failed with error %s ", -// adbExtensionError.getErrorName())); -// } -// }; -// TestableExtension.setListOfListeners(listenerTypes); -// TestableExtension.setNameCallback(new Callback() { -// public String call() { -// return extensionName; -// } -// -// ; -// }); -// MobileCore.registerExtension(TestableExtension.class, errorCallback); -// asyncHelper.waitForAppThreads(500, true); -// -// if (TestableExtension.getExtensionUnexpectedError() != null) { -// Log.debug(LOG_TAG, String.format("[registerExtension] Registration failed with error %s ", -// TestableExtension.getExtensionUnexpectedError().getMessage())); -// } -// -// return new CreateExtensionResponse(TestableExtension.getExtensionUnexpectedError(), -// TestableExtension.createdExtensionName, TestableExtension.createdExtensionVersion); -// } -// -// /** -// * Returns an Map object -// *

    -// * This method helps finding out all the third party extensions currently registered at the EventHub -// * -// * @return returns the list of Extensions registered as a Map object. -// */ -// -// public static Map getAllThirdpartyExtensions(EventHub eventhub) { -// -// Map thirdPartyExtensions = new HashMap(); -// Collection allExtensions = eventhub.getActiveModules(); -// Iterator iterator = allExtensions.iterator(); -// -// while (iterator.hasNext()) { -// Module currentModule = iterator.next(); -// -// if (currentModule instanceof Extension) { -// ExtensionApi extensionApi = (ExtensionApi) currentModule; -// Extension ext = extensionApi.getExtension(); -// thirdPartyExtensions.put(ext.getName(), ext); -// } -// } -// -// return thirdPartyExtensions; -// } -// -// /** -// * Returns a boolean value -// *

    -// * This method helps to check if an extension is currently registered at the EventHub -// * -// * @param extensionName Name of the extension to be checked -// * @return returns true if the given extension is registered otherwise false. -// */ -// -// public static boolean isRegistered(String extensionName) { -// return (getExtensionInstance(extensionName) != null); -// } -// -// /** -// * Returns an Extension Object -// *

    -// * This method helps to get the instance of an extension currently registered at the EventHub -// * -// * @param extensionName Name of the extension -// * @return returns instance of an extension if the given extension is registered otherwise a null object. -// */ -// -// public static Extension getExtensionInstance(String extensionName) { -// Map thirdPartyExtensions = getAllThirdpartyExtensions(); -// return thirdPartyExtensions.get(extensionName); -// } -// -// /** -// * Returns a boolean value -// *

    -// * This method helps to unregister an extension that's currently registered at the EventHub -// * -// * @param extensionName Name of the extension to be checked -// * @return returns true if the given extension got registered at the EventHub and successfully unregistered otherwise false. -// */ -// -// public static boolean unregisterExtension(String extensionName) { -// boolean status = false; -// TestableExtension.confirmExtensionUnregisteredCall = ""; -// TestableListener.confirmListenerUnregisteredCall = ""; -// Extension testableExtension = getExtensionInstance(extensionName); -// -// if (testableExtension != null) { -// testableExtension.getApi().unregisterExtension(); -// asyncHelper.waitForAppThreads(500, true); -// status = true; -// } -// -// confirmExtensionUnregisteredCall = TestableExtension.confirmExtensionUnregisteredCall; -// confirmListenerUnregisteredCall = TestableListener.confirmListenerUnregisteredCall; -// return status; -// } -// -// /** -// * Returns all the registered listeners of an extension as a Collection Object -// *

    -// * This method helps finding out the list of listeners that are registered as part of creating a third party extension. -// * -// * @param extensionName The name of the 3rd party extension as a String -// * @return returns all the registered listeners of an extension as a Collection Object -// */ -// public static Collection getRegisteredListeners(EventHub eventhub, String extensionName) { -// ConcurrentLinkedQueue listeners = new ConcurrentLinkedQueue(); -// Extension testableExtension = getExtensionInstance(extensionName); -// -// if (testableExtension != null) { -// listeners = eventhub.getModuleListeners(testableExtension.getApi()); -// } -// -// return listeners; -// } -// -// /** -// * Returns the instance of the EventListener from the list of listeners registered by the extension. -// *

    -// * -// * @param extensionName The name of the extension. -// * @param listenerType The listerType that contains the EventType and EventSource of the listener. -// * @return returns the instance of the EventListener from the list of listeners registered by the extension. -// * If the expected listener type is not registered this method will return null. -// */ -// -// public static EventListener getListenerInstance(String extensionName, ListenerType listenerType) { -// -// Collection registeredListeners = getRegisteredListeners(extensionName); -// Iterator listenersIterator = registeredListeners.iterator(); -// -// while (listenersIterator.hasNext()) { -// EventListener eventListener = listenersIterator.next(); -// -// if ((eventListener.getEventType().getName().equalsIgnoreCase(listenerType.eventType)) -// && (eventListener.getEventSource().getName().equalsIgnoreCase(listenerType.eventSource))) { -// return eventListener; -// } -// -// } -// -// return null; -// } -// -// -// /** -// * Returns a boolean value as the status of the dispatching of an Event specified. -// *

    -// * This method helps dispatching an event to the EventHub. -// * -// * @param listenerType The listerType that contains the EventType and EventSource of the Event to be dispatched. -// * @param data EventData to be dispatched. -// * @return returns the status of the dispatch of an Event as a boolean. -// */ -// public static boolean dispatchAnEvent(ListenerType listenerType, Map data) { -// isDispatched = true; -// Event event = new Event.Builder("DispatchedEvent", listenerType.eventType, -// listenerType.eventSource).setEventData(data).build(); -// ExtensionErrorCallback dispatchCallback = new ExtensionErrorCallback() { -// @Override -// public void error(final ExtensionError ec) { -// Log.debug(LOG_TAG, String.format("[dispatchAnEvent] Dispatch failed with error %s ", ec.getErrorCode())); -// isDispatched = false; -// } -// }; -// MobileCore.dispatchEvent(event, dispatchCallback); -// asyncHelper.waitForAppThreads(500, true); -// return isDispatched; -// } -// -// /** -// * Returns an Event Object of the Listener that's registered by a Third party extension. -// *

    -// * This method helps confirming if the Event that's dispatched was listened by the specified listener. -// * returns an Event Object of the Listener that's registered by a Third party extension. -// * -// * @param extensionName The name of the extension as a String type that owns the listener that's being checked. -// * @param listener The name of the listener as a ListenerType which is being checked to confirm whether it received the event that gets published with dispatchEvent call -// * @return returns an Event Object of the Listener that's registered by a Third party extension. -// * The caller can access the Event Type, Event Source, and Event Data with getEventType().getName(), getEventSource().getName() + getEventData() methods -// */ -// public static Event getLastEventHeardByListener(String extensionName, ListenerType listener) { -// List events = getAllEventsHeardByListener(extensionName, listener); -// return ((events.size() > 0) ? events.get(events.size() - 1) : null); -// } -// -// /** -// * Returns an Event Object of the Listener that's registered by a Third party extension. -// *

    -// * This method helps confirming if the Event that's dispatched was listened by the specified listener. -// * returns an Event Object of the Listener that's registered by a Third party extension. -// * -// * @param extensionName The name of the extension as a String type that owns the listener that's being checked. -// * @param listener The name of the listener as a ListenerType which is being checked to confirm whether it received the event that gets published with dispatchEvent call -// * @return returns an Event Object of the Listener that's registered by a Third party extension. -// * The caller can access the Event Type, Event Source, and Event Data with getEventType().getName(), getEventSource().getName() + getEventData() methods -// */ -// public static List getAllEventsHeardByListener(String extensionName, ListenerType listener) { -// Collection registeredListeners = new ConcurrentLinkedQueue(); -// registeredListeners = getRegisteredListeners(extensionName); -// Iterator listenersIterator = registeredListeners.iterator(); -// TestableListener listenerType = null; -// -// while (listenersIterator.hasNext()) { -// listenerType = (TestableListener) listenersIterator.next(); -// -// if ((listenerType != null) && -// (listenerType.getEventType().getName().equalsIgnoreCase(listener.eventType) -// && listenerType.getEventSource().getName().equalsIgnoreCase(listener.eventSource))) { -// return listenerType.getReceivedEvents(); -// } -// } -// -// return new ArrayList<>(); -// } -//} +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * Class {@link ExtensionTestingHelper} that defines the necessary helper methods to work with the third party extension {@link TestableExtension} that extends {@link ExtensionListener} class of the Adobe Experience Platform SDK. + *

    + * This class provides the following features to write automated tests for a third party extension + *

    + * 1. necessary helper methods for the automated tests cases to work with an extension instance. The instance will be captured using the eventHub getActiveModules() method. + * 2. necessary helper methods to get access to the listeners registered by the TestableExtension class. The listeners that are registered in the eventHub will be accessed with the getModuleListeners(Module) method. + * 3. uses static countdown latch to wait for all the listeners to be registered before dispatching events + * 4. has provisions for setting the shared state, retrieving the shared state, clearing the shared state + * + * @author Adobe + * @version 5.0 + */ + + +public class ExtensionTestingHelper { + + private static final String LOG_TAG = ExtensionTestingHelper.class.getSimpleName(); + static AsyncHelper asyncHelper = new AsyncHelper(); + static boolean isDispatched = false; + static String confirmExtensionUnregisteredCall; + static String confirmListenerUnregisteredCall; + + + /** + * Returns an CreateExtensionResponse object + *

    + * This method helps registering a third party extension with a list of required listeners and + * retuns the status of creation as an CreateExtensionResponse object + * + * @param tExtensionName Name of the extension to be created + * @param listenerTypes The list of listeners as a ListenerType to be registered as part of creating the third party extension + * @return returns an CreateExtensionResponse object as a result of creating the extension. + */ + + public CreateExtensionResponse registerExtension(String tExtensionName, List listenerTypes) { + final String extensionName = tExtensionName; + ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError adbExtensionError) { + Log.debug(LOG_TAG, String.format("[registerExtension] Registration failed with error %s ", + adbExtensionError.getErrorName())); + } + }; + TestableExtension.setListOfListeners(listenerTypes); + TestableExtension.setNameCallback(new Callback() { + public String call() { + return extensionName; + } + + ; + }); + MobileCore.registerExtension(TestableExtension.class, errorCallback); + asyncHelper.waitForAppThreads(500, true); + + if (TestableExtension.getExtensionUnexpectedError() != null) { + Log.debug(LOG_TAG, String.format("[registerExtension] Registration failed with error %s ", + TestableExtension.getExtensionUnexpectedError().getMessage())); + } + + return new CreateExtensionResponse(TestableExtension.getExtensionUnexpectedError(), + TestableExtension.createdExtensionName, TestableExtension.createdExtensionVersion); + } + + /** + * Returns an Map object + *

    + * This method helps finding out all the third party extensions currently registered at the EventHub + * + * @return returns the list of Extensions registered as a Map object. + */ + + public static Map getAllThirdpartyExtensions(EventHub eventhub) { + + Map thirdPartyExtensions = new HashMap(); + Collection allExtensions = eventhub.getActiveModules(); + Iterator iterator = allExtensions.iterator(); + + while (iterator.hasNext()) { + Module currentModule = iterator.next(); + + if (currentModule instanceof Extension) { + ExtensionApi extensionApi = (ExtensionApi) currentModule; + Extension ext = extensionApi.getExtension(); + thirdPartyExtensions.put(ext.getName(), ext); + } + } + + return thirdPartyExtensions; + } + + /** + * Returns a boolean value + *

    + * This method helps to check if an extension is currently registered at the EventHub + * + * @param extensionName Name of the extension to be checked + * @return returns true if the given extension is registered otherwise false. + */ + + public static boolean isRegistered(String extensionName) { + return (getExtensionInstance(extensionName) != null); + } + + /** + * Returns an Extension Object + *

    + * This method helps to get the instance of an extension currently registered at the EventHub + * + * @param extensionName Name of the extension + * @return returns instance of an extension if the given extension is registered otherwise a null object. + */ + + public static Extension getExtensionInstance(String extensionName) { + Map thirdPartyExtensions = getAllThirdpartyExtensions(); + return thirdPartyExtensions.get(extensionName); + } + + /** + * Returns a boolean value + *

    + * This method helps to unregister an extension that's currently registered at the EventHub + * + * @param extensionName Name of the extension to be checked + * @return returns true if the given extension got registered at the EventHub and successfully unregistered otherwise false. + */ + + public static boolean unregisterExtension(String extensionName) { + boolean status = false; + TestableExtension.confirmExtensionUnregisteredCall = ""; + TestableListener.confirmListenerUnregisteredCall = ""; + Extension testableExtension = getExtensionInstance(extensionName); + + if (testableExtension != null) { + testableExtension.getApi().unregisterExtension(); + asyncHelper.waitForAppThreads(500, true); + status = true; + } + + confirmExtensionUnregisteredCall = TestableExtension.confirmExtensionUnregisteredCall; + confirmListenerUnregisteredCall = TestableListener.confirmListenerUnregisteredCall; + return status; + } + + /** + * Returns all the registered listeners of an extension as a Collection Object + *

    + * This method helps finding out the list of listeners that are registered as part of creating a third party extension. + * + * @param extensionName The name of the 3rd party extension as a String + * @return returns all the registered listeners of an extension as a Collection Object + */ + public static Collection getRegisteredListeners(EventHub eventhub, String extensionName) { + ConcurrentLinkedQueue listeners = new ConcurrentLinkedQueue(); + Extension testableExtension = getExtensionInstance(extensionName); + + if (testableExtension != null) { + listeners = eventhub.getModuleListeners(testableExtension.getApi()); + } + + return listeners; + } + + /** + * Returns the instance of the EventListener from the list of listeners registered by the extension. + *

    + * + * @param extensionName The name of the extension. + * @param listenerType The listerType that contains the EventType and EventSource of the listener. + * @return returns the instance of the EventListener from the list of listeners registered by the extension. + * If the expected listener type is not registered this method will return null. + */ + + public static EventListener getListenerInstance(String extensionName, ListenerType listenerType) { + + Collection registeredListeners = getRegisteredListeners(extensionName); + Iterator listenersIterator = registeredListeners.iterator(); + + while (listenersIterator.hasNext()) { + EventListener eventListener = listenersIterator.next(); + + if ((eventListener.getEventType().getName().equalsIgnoreCase(listenerType.eventType)) + && (eventListener.getEventSource().getName().equalsIgnoreCase(listenerType.eventSource))) { + return eventListener; + } + + } + + return null; + } + + + /** + * Returns a boolean value as the status of the dispatching of an Event specified. + *

    + * This method helps dispatching an event to the EventHub. + * + * @param listenerType The listerType that contains the EventType and EventSource of the Event to be dispatched. + * @param data EventData to be dispatched. + * @return returns the status of the dispatch of an Event as a boolean. + */ + public static boolean dispatchAnEvent(ListenerType listenerType, Map data) { + isDispatched = true; + Event event = new Event.Builder("DispatchedEvent", listenerType.eventType, + listenerType.eventSource).setEventData(data).build(); + ExtensionErrorCallback dispatchCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError ec) { + Log.debug(LOG_TAG, String.format("[dispatchAnEvent] Dispatch failed with error %s ", ec.getErrorCode())); + isDispatched = false; + } + }; + MobileCore.dispatchEvent(event, dispatchCallback); + asyncHelper.waitForAppThreads(500, true); + return isDispatched; + } + + /** + * Returns an Event Object of the Listener that's registered by a Third party extension. + *

    + * This method helps confirming if the Event that's dispatched was listened by the specified listener. + * returns an Event Object of the Listener that's registered by a Third party extension. + * + * @param extensionName The name of the extension as a String type that owns the listener that's being checked. + * @param listener The name of the listener as a ListenerType which is being checked to confirm whether it received the event that gets published with dispatchEvent call + * @return returns an Event Object of the Listener that's registered by a Third party extension. + * The caller can access the Event Type, Event Source, and Event Data with getEventType().getName(), getEventSource().getName() + getEventData() methods + */ + public static Event getLastEventHeardByListener(String extensionName, ListenerType listener) { + List events = getAllEventsHeardByListener(extensionName, listener); + return ((events.size() > 0) ? events.get(events.size() - 1) : null); + } + + /** + * Returns an Event Object of the Listener that's registered by a Third party extension. + *

    + * This method helps confirming if the Event that's dispatched was listened by the specified listener. + * returns an Event Object of the Listener that's registered by a Third party extension. + * + * @param extensionName The name of the extension as a String type that owns the listener that's being checked. + * @param listener The name of the listener as a ListenerType which is being checked to confirm whether it received the event that gets published with dispatchEvent call + * @return returns an Event Object of the Listener that's registered by a Third party extension. + * The caller can access the Event Type, Event Source, and Event Data with getEventType().getName(), getEventSource().getName() + getEventData() methods + */ + public static List getAllEventsHeardByListener(String extensionName, ListenerType listener) { + Collection registeredListeners = new ConcurrentLinkedQueue(); + registeredListeners = getRegisteredListeners(extensionName); + Iterator listenersIterator = registeredListeners.iterator(); + TestableListener listenerType = null; + + while (listenersIterator.hasNext()) { + listenerType = (TestableListener) listenersIterator.next(); + + if ((listenerType != null) && + (listenerType.getEventType().getName().equalsIgnoreCase(listener.eventType) + && listenerType.getEventSource().getName().equalsIgnoreCase(listener.eventSource))) { + return listenerType.getReceivedEvents(); + } + } + + return new ArrayList<>(); + } +} From f85ceff174b4eb623c11ea20efce9689fcef10b8 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Mon, 6 Jun 2022 16:22:15 -0700 Subject: [PATCH 068/476] launch rules consequences --- .../rulesengine/LaunchRulesConsequence.kt | 307 ++++++++++++++++++ .../launch/rulesengine/LaunchRulesEngine.java | 43 ++- .../rulesengine/LaunchRulesEvaluator.kt | 282 +--------------- 3 files changed, 350 insertions(+), 282 deletions(-) create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt new file mode 100644 index 000000000..4d297ae02 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt @@ -0,0 +1,307 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.launch.rulesengine + +import com.adobe.marketing.mobile.Event +import com.adobe.marketing.mobile.ExtensionApi +import com.adobe.marketing.mobile.LoggingMode +import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.internal.utility.EventDataMerger +import com.adobe.marketing.mobile.rulesengine.DelimiterPair +import com.adobe.marketing.mobile.rulesengine.Template +import com.adobe.marketing.mobile.rulesengine.TokenFinder + +class LaunchRulesConsequence( + private val launchRulesEngine: LaunchRulesEngine, + private val extensionApi: ExtensionApi +) { + + private val logTag = "LaunchRulesConsequence" + private var dispatchChainedEventsCount = mutableMapOf() + + companion object { + // TODO: we should move the following event type/event source values to the public EventType/EventSource classes once we have those. + const val EVENT_SOURCE_RESPONSE_CONTENT = "com.adobe.eventSource.responseContent" + const val EVENT_TYPE_RULES_ENGINE = "com.adobe.eventtype.rulesengine" + const val LAUNCH_RULE_TOKEN_LEFT_DELIMITER = "{%" + const val LAUNCH_RULE_TOKEN_RIGHT_DELIMITER = "%}" + const val CONSEQUENCE_TYPE_ADD = "add" + const val CONSEQUENCE_TYPE_MOD = "mod" + const val CONSEQUENCE_TYPE_DISPATCH = "dispatch" + const val CONSEQUENCE_DETAIL_ACTION_COPY = "copy" + const val CONSEQUENCE_DETAIL_ACTION_NEW = "new" + /// Do not process Dispatch consequence if chained event count is greater than max + const val MAX_CHAINED_CONSEQUENCE_COUNT = 1 + const val CONSEQUENCE_DISPATCH_EVENT_NAME = "Dispatch Consequence Result" + const val CONSEQUENCE_EVENT_DATA_KEY_ID = "id" + const val CONSEQUENCE_EVENT_DATA_KEY_TYPE = "type" + const val CONSEQUENCE_EVENT_DATA_KEY_DETAIL = "detail" + const val CONSEQUENCE_EVENT_DATA_KEY_CONSEQUENCE = "triggeredconsequence" + const val CONSEQUENCE_EVENT_NAME = "Rules Consequence Event" + + } + + fun evaluateRules(event: Event) : Event { + val dispatchChainCount = dispatchChainedEventsCount.remove(event.uniqueIdentifier) + val launchTokenFinder = LaunchTokenFinder(event, extensionApi) + val matchedRules = launchRulesEngine.process(event) + var processedEvent: Event = event + for (rule in matchedRules) { + for (consequence in rule.consequenceList) { + val consequenceWithConcreteValue = replaceToken(consequence, launchTokenFinder) + when(consequenceWithConcreteValue.type){ + CONSEQUENCE_TYPE_ADD -> { + val attachedEventData = processAttachDataConsequence( + consequenceWithConcreteValue, + processedEvent.eventData + ) ?: continue + processedEvent = processedEvent.copyWithNewData(attachedEventData) + } + CONSEQUENCE_TYPE_MOD -> { + val modifiedEventData = processModifyDataConsequence( + consequenceWithConcreteValue, + processedEvent.eventData + ) ?: continue + processedEvent = processedEvent.copyWithNewData(modifiedEventData) + } + CONSEQUENCE_TYPE_DISPATCH -> { + if (dispatchChainCount == null || dispatchChainCount == 0 || + dispatchChainCount >= MAX_CHAINED_CONSEQUENCE_COUNT + ) { + MobileCore.log( + LoggingMode.VERBOSE, + logTag, + "Unable to process dispatch consequence, max chained " + + "dispatch consequences limit of ${MAX_CHAINED_CONSEQUENCE_COUNT}" + + "met for this event uuid ${event.uniqueIdentifier}" + ) + continue + } + val dispatchEvent = processDispatchConsequence( + consequenceWithConcreteValue, + processedEvent.eventData + ) ?: continue + MobileCore.log( + LoggingMode.VERBOSE, + logTag, + " Generating new dispatch consequence result event $dispatchEvent" + ) + MobileCore.dispatchEvent(event) { + MobileCore.log( + LoggingMode.WARNING, + logTag, + "An error occurred when dispatching dispatch consequence result event" + ) + } + } + else -> { + val consequenceEvent = generateConsequenceEvent(consequenceWithConcreteValue) + MobileCore.log( + LoggingMode.VERBOSE, + logTag, + "Generating new consequence event $consequenceEvent" + ) + MobileCore.dispatchEvent(event) { + MobileCore.log( + LoggingMode.WARNING, + logTag, + "An error occurred when dispatching consequence result event" + ) + } + } + } + } + } + return processedEvent + } + + /** + * Replace tokens inside the provided [RuleConsequence] with the right value + * + * @param consequence [RuleConsequence] instance that may contain tokens + * @param tokenFinder [TokenFinder] instance which replaces the tokens with values + * @return the [RuleConsequence] with replaced tokens + */ + private fun replaceToken(consequence: RuleConsequence, tokenFinder: TokenFinder): RuleConsequence { + val tokenReplacedMap = replaceToken(consequence.detail, tokenFinder) + return RuleConsequence(consequence.id, consequence.type, tokenReplacedMap) + } + + @Suppress("UNCHECKED_CAST") + private fun replaceToken(detail: Map?, tokenFinder: TokenFinder): Map? { + if (detail.isNullOrEmpty()) + return null + val mutableDetail = detail.toMutableMap() + for((key, value) in detail) { + when(value) { + is String -> mutableDetail[key] = replaceToken(value, tokenFinder) + is Map<*, *> -> replaceToken(mutableDetail[key] as Map, tokenFinder) + else -> break + } + } + return mutableDetail + } + + private fun replaceToken(value: String, tokenFinder: TokenFinder): String { + val template = Template(value, DelimiterPair(LAUNCH_RULE_TOKEN_LEFT_DELIMITER, LAUNCH_RULE_TOKEN_RIGHT_DELIMITER)) + return template.render(tokenFinder, LaunchRuleTransformer.createTransforming()) + } + + /** + * Process an attach data consequence event. Attaches the triggering event data from the [RuleConsequence] to the + * triggering event data without overwriting the original event data. If either the event data + * from the [RuleConsequence] or the triggering event data is null then the processing is aborted. + * + * @param consequence the [RuleConsequence] which contains the event data to attach + * @param eventData the event data of the triggering [Event] + * @return [Map] with the [RuleConsequence] data attached to the triggering event data, or + * null if the processing fails + */ + private fun processAttachDataConsequence(consequence: RuleConsequence, eventData: Map?): Map? { + val from = consequence.eventData ?: run { + MobileCore.log( + LoggingMode.ERROR, + logTag, + "Unable to process an AttachDataConsequence Event, 'eventData' is missing from 'details'" + ) + return null + } + val to = eventData ?: run { + MobileCore.log( + LoggingMode.ERROR, + logTag, + "Unable to process an AttachDataConsequence Event, 'eventData' is missing from original event" + ) + return null + } + // TODO add utility function for map pretty print + MobileCore.log( + LoggingMode.VERBOSE, + logTag, + "Attaching event data with $from" + ) + return EventDataMerger.merge(from, to, false) + } + + /** + * Process a modify data consequence event. Modifies the triggering event data by merging the + * event data from the [RuleConsequence] onto it. If either the event data + * from the [RuleConsequence] or the triggering event data is null then the processing is aborted. + * + * @param consequence the [RuleConsequence] which contains the event data to attach + * @param eventData the event data of the triggering [Event] + * @return [Map] with the Event data modified with the [RuleConsequence] data, or + * null if the processing fails + */ + private fun processModifyDataConsequence(consequence: RuleConsequence, eventData: Map?): Map? { + val from = consequence.eventData ?: run { + MobileCore.log( + LoggingMode.ERROR, + logTag, + "Unable to process a ModifyDataConsequence Event, 'eventData' is missing from 'details'" + ) + return null + } + val to = eventData ?: run { + MobileCore.log( + LoggingMode.ERROR, + logTag, + "Unable to process a ModifyDataConsequence Event, 'eventData' is missing from original event" + ) + return null + } + // TODO add utility function for map pretty print + MobileCore.log( + LoggingMode.VERBOSE, + logTag, + "Modifying event data with $from" + ) + return EventDataMerger.merge(from, to, true) + } + + /** + * Process a dispatch consequence event. Generates a new [Event] from the details contained within the [RuleConsequence] + * + * @param consequence the [RuleConsequence] which contains details on the new [Event] to generate + * @param eventData the triggering Event data + * @return a new [Event] to be dispatched to the [EventHub], or null if the processing failed. + */ + private fun processDispatchConsequence(consequence: RuleConsequence, eventData: Map?): Event? { + val type = consequence.eventType ?: run { + MobileCore.log( + LoggingMode.ERROR, + logTag, + "Unable to process a DispatchConsequence Event, 'type' is missing from 'details'" + ) + return null + } + val source = consequence.eventSource ?: run { + MobileCore.log( + LoggingMode.ERROR, + logTag, + "Unable to process a DispatchConsequence Event, 'source' is missing from 'details'" + ) + return null + } + val action = consequence.eventDataAction ?: run { + MobileCore.log( + LoggingMode.ERROR, + logTag, + "Unable to process a DispatchConsequence Event, 'eventdataaction' is missing from 'details'" + ) + return null + } + val dispatchEventData: Map? + when (action) { + CONSEQUENCE_DETAIL_ACTION_COPY -> { + dispatchEventData = eventData + } + CONSEQUENCE_DETAIL_ACTION_NEW -> { + dispatchEventData = consequence.eventData?.filterValues { it != null } + } + else -> { + MobileCore.log( + LoggingMode.ERROR, + logTag, + "Unable to process a DispatchConsequence Event, unsupported 'eventdataaction', expected values copy/new" + ) + return null + } + } + return Event.Builder(CONSEQUENCE_DISPATCH_EVENT_NAME, type, source) + .setEventData(dispatchEventData) + .build() + } + + private fun generateConsequenceEvent(consequence: RuleConsequence) : Event? { + val eventData = mutableMapOf() + eventData[CONSEQUENCE_EVENT_DATA_KEY_DETAIL] = consequence.detail + eventData[CONSEQUENCE_EVENT_DATA_KEY_ID] = consequence.id + eventData[CONSEQUENCE_EVENT_DATA_KEY_TYPE] = consequence.type + return Event.Builder( + CONSEQUENCE_EVENT_NAME, + EVENT_TYPE_RULES_ENGINE, + EVENT_SOURCE_RESPONSE_CONTENT + ) + .setEventData(mapOf(CONSEQUENCE_EVENT_DATA_KEY_CONSEQUENCE to eventData)) + .build() + } +} + +// Extend RuleConsequence with helper methods for processing Dispatch Consequence events. +val RuleConsequence.eventSource: String? + get() = detail?.get("source") as? String + +val RuleConsequence.eventType: String? + get() = detail?.get("type") as? String + +val RuleConsequence.eventDataAction: String? + get() = detail?.get("eventdataaction") as? String \ No newline at end of file diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngine.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngine.java index c94723459..bcb74f833 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngine.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngine.java @@ -11,7 +11,13 @@ package com.adobe.marketing.mobile.launch.rulesengine; import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.ExtensionApi; +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; import com.adobe.marketing.mobile.rulesengine.ConditionEvaluator; +import com.adobe.marketing.mobile.rulesengine.Log; +import com.adobe.marketing.mobile.rulesengine.LogLevel; +import com.adobe.marketing.mobile.rulesengine.Logging; import com.adobe.marketing.mobile.rulesengine.RulesEngine; import com.adobe.marketing.mobile.rulesengine.TokenFinder; @@ -19,11 +25,33 @@ public class LaunchRulesEngine { private final RulesEngine ruleRulesEngine; + private final ExtensionApi extensionApi; // TODO pass in extensionApi to the constructor @SuppressWarnings("rawtypes") - public LaunchRulesEngine() { - ruleRulesEngine = new RulesEngine<>(new ConditionEvaluator(), LaunchRuleTransformer.INSTANCE.createTransforming()); + public LaunchRulesEngine(final ExtensionApi extensionApi) { + ruleRulesEngine = new RulesEngine<>(new ConditionEvaluator(ConditionEvaluator.Option.CASE_INSENSITIVE), LaunchRuleTransformer.INSTANCE.createTransforming()); + Log.setLogging(new Logging() { + @Override + public void log(LogLevel level, String tag, String message) { + LoggingMode loggingMode; + switch (level) { + case DEBUG: + loggingMode = LoggingMode.DEBUG; + break; + case ERROR: + loggingMode = LoggingMode.ERROR; + break; + case WARNING: + loggingMode = LoggingMode.WARNING; + break; + default: + loggingMode = LoggingMode.VERBOSE; + } + MobileCore.log(loggingMode, tag, message); + } + }); + this.extensionApi = extensionApi; } /** @@ -35,11 +63,12 @@ public void replaceRules(final List rules) { } /** - * Evaluates all the current rules using the supplied {@link TokenFinder}. - * @param tokenFinder the {@link TokenFinder} used to evaluate the rules - * @return the {@link List} of {@link LaunchRule} that have been matched + * Evaluates all the current rules against the supplied {@link Event}. + * + * @param event the {@link Event} against which to evaluate the rules + * @return the matched {@link List} */ - public List process(TokenFinder tokenFinder) { - return ruleRulesEngine.evaluate(tokenFinder); + public List process(Event event) { + return ruleRulesEngine.evaluate(new LaunchTokenFinder(event, extensionApi)); } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt index 8c1af63f8..f38b7352a 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt @@ -26,124 +26,33 @@ internal class LaunchRulesEvaluator( private val name: String, private val launchRulesEngine: LaunchRulesEngine, private val extensionApi: ExtensionApi -) : EventPreprocessor { - - constructor(name: String, extensionApi: ExtensionApi) : this(name, LaunchRulesEngine(), extensionApi) + ) : EventPreprocessor { private var cachedEvents: MutableList? = mutableListOf() private val logTag = "LaunchRulesEvaluator_$name" - private val transformer: Transforming = LaunchRuleTransformer.createTransforming() - private var dispatchChainedEventsCount = mutableMapOf() + private val launchRulesConsequence: LaunchRulesConsequence = LaunchRulesConsequence(launchRulesEngine, extensionApi) + companion object { const val CACHED_EVENT_MAX = 99 - - // TODO: we should move the following event type/event source values to the public EventType/EventSource classes once we have those. const val EVENT_SOURCE_REQUEST_RESET = "com.adobe.eventsource.requestreset" - const val EVENT_SOURCE_RESPONSE_CONTENT = "com.adobe.eventSource.responseContent" const val EVENT_TYPE_RULES_ENGINE = "com.adobe.eventtype.rulesengine" - const val LAUNCH_RULE_TOKEN_LEFT_DELIMITER = "{%" - const val LAUNCH_RULE_TOKEN_RIGHT_DELIMITER = "%}" - const val CONSEQUENCE_TYPE_ADD = "add" - const val CONSEQUENCE_TYPE_MOD = "mod" - const val CONSEQUENCE_TYPE_DISPATCH = "dispatch" - const val CONSEQUENCE_DETAIL_ACTION_COPY = "copy" - const val CONSEQUENCE_DETAIL_ACTION_NEW = "new" - /// Do not process Dispatch consequence if chained event count is greater than max - const val MAX_CHAINED_CONSEQUENCE_COUNT = 1 - const val CONSEQUENCE_DISPATCH_EVENT_NAME = "Dispatch Consequence Result" - const val CONSEQUENCE_EVENT_DATA_KEY_ID = "id" - const val CONSEQUENCE_EVENT_DATA_KEY_TYPE = "type" - const val CONSEQUENCE_EVENT_DATA_KEY_DETAIL = "detail" - const val CONSEQUENCE_EVENT_DATA_KEY_CONSEQUENCE = "triggeredconsequence" - const val CONSEQUENCE_EVENT_NAME = "Rules Consequence Event" } override fun process(event: Event?): Event? { if (event == null) return null - val dispatchChainCount = dispatchChainedEventsCount.remove(event.uniqueIdentifier) - val launchTokenFinder = LaunchTokenFinder(event, extensionApi) if (event.type == EVENT_TYPE_RULES_ENGINE && event.source == EVENT_SOURCE_REQUEST_RESET) { - reprocessCachedEvents(launchTokenFinder) - return event + reprocessCachedEvents() } else { cacheEvent(event) - val matchedRules = launchRulesEngine.process(launchTokenFinder) - var processedEvent: Event = event - for (rule in matchedRules) { - for (consequence in rule.consequenceList) { - val consequenceWithConcreteValue = replaceToken(consequence, launchTokenFinder) - when(consequenceWithConcreteValue.type){ - CONSEQUENCE_TYPE_ADD -> { - val attachedEventData = processAttachDataConsequence( - consequenceWithConcreteValue, - processedEvent.eventData - ) ?: continue - processedEvent = processedEvent.copyWithNewData(attachedEventData) - } - CONSEQUENCE_TYPE_MOD -> { - val modifiedEventData = processModifyDataConsequence( - consequenceWithConcreteValue, - processedEvent.eventData - ) ?: continue - processedEvent = processedEvent.copyWithNewData(modifiedEventData) - } - CONSEQUENCE_TYPE_DISPATCH -> { - if (dispatchChainCount == null || dispatchChainCount == 0 || - dispatchChainCount >= MAX_CHAINED_CONSEQUENCE_COUNT) { - MobileCore.log( - LoggingMode.VERBOSE, - logTag, - "Unable to process dispatch consequence, max chained " + - "dispatch consequences limit of $MAX_CHAINED_CONSEQUENCE_COUNT" + - "met for this event uuid ${event.uniqueIdentifier}" - ) - continue - } - val dispatchEvent = processDispatchConsequence( - consequenceWithConcreteValue, - processedEvent.eventData - ) ?: continue - MobileCore.log( - LoggingMode.VERBOSE, - logTag, - " Generating new dispatch consequence result event $dispatchEvent" - ) - MobileCore.dispatchEvent(event) { - MobileCore.log( - LoggingMode.WARNING, - logTag, - "An error occurred when dispatching dispatch consequence result event" - ) - } - } - else -> { - val consequenceEvent = generateConsequenceEvent(consequenceWithConcreteValue) - MobileCore.log( - LoggingMode.VERBOSE, - logTag, - "Generating new consequence event $consequenceEvent" - ) - MobileCore.dispatchEvent(event) { - MobileCore.log( - LoggingMode.WARNING, - logTag, - "An error occurred when dispatching consequence result event" - ) - } - } - } - } - } - return processedEvent } + return launchRulesConsequence.evaluateRules(event) } - private fun reprocessCachedEvents(tokenFinder: TokenFinder) { + private fun reprocessCachedEvents() { cachedEvents?.forEach { event -> - launchRulesEngine.process(tokenFinder) - // TODO: handle rules consequence + launchRulesConsequence.evaluateRules(event) } clearCachedEvents() } @@ -189,182 +98,5 @@ internal class LaunchRulesEvaluator( ) } } - - /** - * Replace tokens inside the provided [RuleConsequence] with the right value - * - * @param consequence [RuleConsequence] instance that may contain tokens - * @param tokenFinder [TokenFinder] instance which replaces the tokens with values - * @return the [RuleConsequence] with replaced tokens - */ - fun replaceToken(consequence: RuleConsequence, tokenFinder: TokenFinder): RuleConsequence { - val tokenReplacedMap = replaceToken(consequence.detail, tokenFinder) - return RuleConsequence(consequence.id, consequence.type, tokenReplacedMap) - } - - @Suppress("UNCHECKED_CAST") - private fun replaceToken(detail: Map?, tokenFinder: TokenFinder): Map? { - if (detail.isNullOrEmpty()) - return null - val mutableDetail = detail.toMutableMap() - for((key, value) in detail) { - when(value) { - is String -> mutableDetail[key] = replaceToken(value, tokenFinder) - is Map<*, *> -> replaceToken(mutableDetail[key] as Map, tokenFinder) - else -> break - } - } - return mutableDetail - } - - private fun replaceToken(value: String, tokenFinder: TokenFinder): String { - val template = Template(value, DelimiterPair(LAUNCH_RULE_TOKEN_LEFT_DELIMITER, LAUNCH_RULE_TOKEN_RIGHT_DELIMITER)) - return template.render(tokenFinder, transformer) - } - - /** - * Process an attach data consequence event. Attaches the triggering event data from the [RuleConsequence] to the - * triggering event data without overwriting the original event data. If either the event data - * from the [RuleConsequence] or the triggering event data is null then the processing is aborted. - * - * @param consequence the [RuleConsequence] which contains the event data to attach - * @param eventData the event data of the triggering [Event] - * @return [Map] with the [RuleConsequence] data attached to the triggering event data, or - * null if the processing fails - */ - private fun processAttachDataConsequence(consequence: RuleConsequence, eventData: Map?): Map? { - val from = consequence.eventData ?: run { - MobileCore.log( - LoggingMode.ERROR, - logTag, - "Unable to process an AttachDataConsequence Event, 'eventData' is missing from 'details'" - ) - return null - } - val to = eventData ?: run { - MobileCore.log( - LoggingMode.ERROR, - logTag, - "Unable to process an AttachDataConsequence Event, 'eventData' is missing from original event" - ) - return null - } - // TODO add utility function for map pretty print - MobileCore.log( - LoggingMode.VERBOSE, - logTag, - "Attaching event data with $from" - ) - return EventDataMerger.merge(from, to, false) - } - - /** - * Process a modify data consequence event. Modifies the triggering event data by merging the - * event data from the [RuleConsequence] onto it. If either the event data - * from the [RuleConsequence] or the triggering event data is null then the processing is aborted. - * - * @param consequence the [RuleConsequence] which contains the event data to attach - * @param eventData the event data of the triggering [Event] - * @return [Map] with the Event data modified with the [RuleConsequence] data, or - * null if the processing fails - */ - private fun processModifyDataConsequence(consequence: RuleConsequence, eventData: Map?): Map? { - val from = consequence.eventData ?: run { - MobileCore.log( - LoggingMode.ERROR, - logTag, - "Unable to process a ModifyDataConsequence Event, 'eventData' is missing from 'details'" - ) - return null - } - val to = eventData ?: run { - MobileCore.log( - LoggingMode.ERROR, - logTag, - "Unable to process a ModifyDataConsequence Event, 'eventData' is missing from original event" - ) - return null - } - // TODO add utility function for map pretty print - MobileCore.log( - LoggingMode.VERBOSE, - logTag, - "Modifying event data with $from" - ) - return EventDataMerger.merge(from, to, true) - } - - /** - * Process a dispatch consequence event. Generates a new [Event] from the details contained within the [RuleConsequence] - * - * @param consequence the [RuleConsequence] which contains details on the new [Event] to generate - * @param eventData the triggering Event data - * @return a new [Event] to be dispatched to the [EventHub], or null if the processing failed. - */ - private fun processDispatchConsequence(consequence: RuleConsequence, eventData: Map?): Event? { - val type = consequence.eventType ?: run { - MobileCore.log( - LoggingMode.ERROR, - logTag, - "Unable to process a DispatchConsequence Event, 'type' is missing from 'details'" - ) - return null - } - val source = consequence.eventSource ?: run { - MobileCore.log( - LoggingMode.ERROR, - logTag, - "Unable to process a DispatchConsequence Event, 'source' is missing from 'details'" - ) - return null - } - val action = consequence.eventDataAction ?: run { - MobileCore.log( - LoggingMode.ERROR, - logTag, - "Unable to process a DispatchConsequence Event, 'eventdataaction' is missing from 'details'" - ) - return null - } - val dispatchEventData: Map? - when (action) { - CONSEQUENCE_DETAIL_ACTION_COPY -> { - dispatchEventData = eventData - } - CONSEQUENCE_DETAIL_ACTION_NEW -> { - dispatchEventData = consequence.eventData?.filterValues { it != null } - } - else -> { - MobileCore.log( - LoggingMode.ERROR, - logTag, - "Unable to process a DispatchConsequence Event, unsupported 'eventdataaction', expected values copy/new" - ) - return null - } - } - return Event.Builder(CONSEQUENCE_DISPATCH_EVENT_NAME, type, source) - .setEventData(dispatchEventData) - .build() - } - - private fun generateConsequenceEvent(consequence: RuleConsequence) : Event? { - val eventData = mutableMapOf() - eventData[CONSEQUENCE_EVENT_DATA_KEY_DETAIL] = consequence.detail - eventData[CONSEQUENCE_EVENT_DATA_KEY_ID] = consequence.id - eventData[CONSEQUENCE_EVENT_DATA_KEY_TYPE] = consequence.type - return Event.Builder(CONSEQUENCE_EVENT_NAME, EVENT_TYPE_RULES_ENGINE, EVENT_SOURCE_RESPONSE_CONTENT) - .setEventData(mapOf(CONSEQUENCE_EVENT_DATA_KEY_CONSEQUENCE to eventData)) - .build() - } } -// Extend RuleConsequence with helper methods for processing Dispatch Consequence events. -val RuleConsequence.eventSource: String? -get() = detail?.get("source") as? String - -val RuleConsequence.eventType: String? - get() = detail?.get("type") as? String - -val RuleConsequence.eventDataAction: String? - get() = detail?.get("eventdataaction") as? String \ No newline at end of file From 17d9f77a0d3da30ff9b589b29bffe8012cdc34da Mon Sep 17 00:00:00 2001 From: Yansong Date: Fri, 10 Jun 2022 16:47:22 -0600 Subject: [PATCH 069/476] add tests to make sure the fnv1a32 hash algorithm show the same result as the Swift side. --- .../internal/utility/MapExtensionsTests.kt | 198 +++++++++++++++++- 1 file changed, 197 insertions(+), 1 deletion(-) diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt index df30b7325..b6b5012c6 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt @@ -10,11 +10,11 @@ */ package com.adobe.marketing.mobile.internal.utility +import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNull import kotlin.test.assertTrue -import org.junit.Test class MapExtensionsTests { @@ -255,4 +255,200 @@ class MapExtensionsTests { valueUnderTest ) } + + + // test "fnv1a32" hash algorithm and make sure the result should be the same in both iOS and Android SDKs. + // tests in Swift Core => https://github.com/adobe/aepsdk-core-ios/blob/main/AEPCore/Tests/EventHubTests/HistoryTests/EventData%2BFNV1A32Tests.swift + // Validations of this class are done against an online hash calculator: https://md5calc.com/hash/fnv1a32?str= + // decimal to hex online converter: https://www.rapidtables.com/convert/number/decimal-to-hex.html + @Test + fun `test fnv1a32 - String`() { + val eventData = mapOf( + "key" to "value" + ) + val hashCode = eventData.fnv1a32() + assertEquals(4007910315, hashCode) + } + + @Test + fun `test fnv1a32 - optional String`() { + val optional: String? = "value" + val eventData = mapOf( + "key" to optional + ) + val hashCode = eventData.fnv1a32() + assertEquals(4007910315, hashCode) + } + + @Test + fun `test fnv1a32 - Char`() { + val eventData = mapOf( + "key" to 'a' + ) + val hashCode = eventData.fnv1a32() + assertEquals(135500217, hashCode) + } + + @Test + fun `test fnv1a32 - optional Char`() { + val optional: Char? = 'a' + val eventData = mapOf( + "key" to optional + ) + val hashCode = eventData.fnv1a32() + assertEquals(135500217, hashCode) + } + + @Test + fun `test fnv1a32 - Int`() { + val eventData = mapOf( + "key" to 552 + ) + val hashCode = eventData.fnv1a32() + assertEquals(874166902, hashCode) + } + + @Test + fun `test fnv1a32 - optional Int`() { + val optional: Int? = 552 + val eventData = mapOf( + "key" to optional + ) + val hashCode = eventData.fnv1a32() + assertEquals(874166902, hashCode) + } + + @Test + fun `test fnv1a32 - Long`() { + val eventData = mapOf( + "key" to 24L + ) + val hashCode = eventData.fnv1a32() + assertEquals(2995581580, hashCode) + } + + @Test + fun `test fnv1a32 - optional Long`() { + val optional: Long? = 24L + val eventData = mapOf( + "key" to optional + ) + val hashCode = eventData.fnv1a32() + assertEquals(2995581580, hashCode) + } + + @Test + fun `test fnv1a32 - Float`() { + val eventData = mapOf( + "key" to 5.52f + ) + val hashCode = eventData.fnv1a32() + assertEquals(1449854826, hashCode) + } + + @Test + fun `test fnv1a32 - optional Float`() { + val optional: Float? = 5.52f + val eventData = mapOf( + "key" to optional + ) + val hashCode = eventData.fnv1a32() + assertEquals(1449854826, hashCode) + } + + @Test + fun `test fnv1a32 - Double`() { + val eventData = mapOf( + "key" to "5.52".toDouble() + ) + val hashCode = eventData.fnv1a32() + assertEquals(1449854826, hashCode) + } + + @Test + fun `test fnv1a32 - optional Double`() { + val optional: Double? = "5.52".toDouble() + val eventData = mapOf( + "key" to optional + ) + val hashCode = eventData.fnv1a32() + assertEquals(1449854826, hashCode) + } + + @Test + fun `test fnv1a32 - Boolean`() { + val eventData = mapOf( + "key" to false + ) + val hashCode = eventData.fnv1a32() + assertEquals(138493769, hashCode) + } + + @Test + fun `test fnv1a32 - optional Boolean`() { + val optional: Boolean? = false + val eventData = mapOf( + "key" to optional + ) + val hashCode = eventData.fnv1a32() + assertEquals(138493769, hashCode) + } + + @Test + fun `test fnv1a32 - mask key is present`() { + val eventData = mapOf( + "key" to "value", + "unusedKey" to "unusedValue" + ) + val hashCode = eventData.fnv1a32(arrayOf("key")) + assertEquals(4007910315, hashCode) + } + + @Test + fun `test fnv1a32 - mask key is not present`() { + val eventData = mapOf( + "key" to "value" + ) + val hashCode = eventData.fnv1a32(arrayOf("404")) + assertEquals(0, hashCode) + } + + @Test + fun `test fnv1a32 - get keys Ascii sorted`() { + val hashCode1 = mapOf( + "key" to "value", + "number" to 1234, + "UpperCase" to "abc", + "_underscore" to "score" + ).fnv1a32() + val hashCode2 = mapOf( + "number" to 1234, + "key" to "value", + "_underscore" to "score", + "UpperCase" to "abc" + ).fnv1a32() + assertEquals(960895195, hashCode1) + assertEquals(hashCode2, hashCode1) + } + + @Test + fun `test fnv1a32 - big sort`() { + val hashCode = mapOf( + "a" to "1", + "A" to "2", + "ba" to "3", + "Ba" to "4", + "Z" to "5", + "z" to "6", + "r" to "7", + "R" to "8", + "bc" to "9", + "Bc" to "10", + "1" to 1, + "222" to 222 + ).fnv1a32() + assertEquals(2933724447, hashCode) + } + + } From 0aa75f89ea444c8bc649eac05b9ea82bcebd2031 Mon Sep 17 00:00:00 2001 From: Yansong Date: Fri, 10 Jun 2022 16:54:50 -0600 Subject: [PATCH 070/476] fix format --- .../marketing/mobile/internal/utility/MapExtensionsTests.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt index b6b5012c6..c5418e2b6 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt @@ -10,11 +10,11 @@ */ package com.adobe.marketing.mobile.internal.utility -import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNull import kotlin.test.assertTrue +import org.junit.Test class MapExtensionsTests { @@ -256,7 +256,6 @@ class MapExtensionsTests { ) } - // test "fnv1a32" hash algorithm and make sure the result should be the same in both iOS and Android SDKs. // tests in Swift Core => https://github.com/adobe/aepsdk-core-ios/blob/main/AEPCore/Tests/EventHubTests/HistoryTests/EventData%2BFNV1A32Tests.swift // Validations of this class are done against an online hash calculator: https://md5calc.com/hash/fnv1a32?str= @@ -449,6 +448,4 @@ class MapExtensionsTests { ).fnv1a32() assertEquals(2933724447, hashCode) } - - } From 28f6eda6a86cb7b418f019939c446b600688fbda Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Fri, 10 Jun 2022 12:42:01 -0700 Subject: [PATCH 071/476] [#95] Fix NPE, pending state bug & add tests for EventHub - Registered extensions are held in a concurrent hash map which does not allow null keys or queries. Fix the NPE that may result when shared state operation is an extension is triggered on an extension that is not registered - Fix EventHub.setSharedState logic to return true when a pending state is set. - ExtensionExt has extension properties that clash with protected/package private memebers of Extension. Change the names to prevent Kotlin-Java compat issues. - Add tests for EventHub --- .../mobile/internal/eventhub/EventHub.kt | 34 +- .../internal/eventhub/ExtensionContainer.kt | 10 +- .../mobile/internal/eventhub/ExtensionExt.kt | 8 +- .../mobile/internal/eventhub/EventHubTests.kt | 539 ++++++++++++++++++ 4 files changed, 575 insertions(+), 16 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index ecb5ad0af..6e8839986 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -187,7 +187,7 @@ internal class EventHub { return@Callable false } - val extensionContainer: ExtensionContainer? = registeredExtensions[getExtensionTypeName(extensionName)] + val extensionContainer: ExtensionContainer? = getExtensionContainer(extensionName) if (extensionContainer == null) { MobileCore.log(LoggingMode.ERROR, LOG_TAG, "Error setting SharedState for extension: [$extensionName]. Extension may not have been registered.") @@ -206,7 +206,8 @@ internal class EventHub { getEventNumber(event) ?: lastEventNumber.incrementAndGet() } - val wasSet: Boolean = extensionContainer.setSharedState(sharedStateType, data, version) == SharedState.Status.SET + val result: SharedState.Status = extensionContainer.setSharedState(sharedStateType, data, version) + val wasSet: Boolean = (result == SharedState.Status.SET) // Check if the new state can be dispatched as a state change event(currently implies a // non null/non pending state according to the ExtensionAPI) @@ -218,7 +219,7 @@ internal class EventHub { // dispatch a shared state notification. // TODO: dispatch() } - return@Callable wasSet + return@Callable (result == SharedState.Status.PENDING || wasSet) } return eventHubExecutor.submit(setSharedStateCallable).get() @@ -250,7 +251,7 @@ internal class EventHub { return@Callable null } - val extensionContainer: ExtensionContainer? = registeredExtensions[getExtensionTypeName(extensionName)] + val extensionContainer: ExtensionContainer? = getExtensionContainer(extensionName) if (extensionContainer == null) { MobileCore.log( @@ -295,10 +296,10 @@ internal class EventHub { MobileCore.log(LoggingMode.ERROR, LOG_TAG, "Unable to clear SharedState. State name [$extensionName] is invalid.") errorCallback?.error(ExtensionError.BAD_NAME) - return@Callable null + return@Callable false } - val extensionContainer: ExtensionContainer? = registeredExtensions[getExtensionTypeName(extensionName)] + val extensionContainer: ExtensionContainer? = getExtensionContainer(extensionName) if (extensionContainer == null) { MobileCore.log( @@ -346,7 +347,26 @@ internal class EventHub { */ private fun getEventNumber(event: Event?): Int? { val eventUUID = event?.uniqueIdentifier - return eventNumberMap[eventUUID] + return if (eventUUID == null) { + null + } else eventNumberMap[eventUUID] + } + + /** + * Retrieves a registered [ExtensionContainer] with [extensionTypeName] provided. + * + * @param [extensionName] the name of the extension for which an [ExtensionContainer] should be fetched. + * This should match [Extension.name] of an extension registered with the event hub. + * @return [ExtensionContainer] with [extensionName] provided if one was registered, + * null if no extension is registered with the [extensionName] + */ + private fun getExtensionContainer(extensionName: String): ExtensionContainer? { + val extensionTypeName = getExtensionTypeName(extensionName) + return if (extensionTypeName == null) { + null + } else { + registeredExtensions[extensionTypeName] + } } /** diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt index 3edce3ab7..52725e7d7 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt @@ -32,9 +32,9 @@ internal class ExtensionRuntime() : ExtensionApi() { var extension: Extension? = null set(value) { field = value - extensionName = value?.name - extensionFriendlyName = value?.friendlyName - extensionVersion = value?.version + extensionName = value?.extensionName + extensionFriendlyName = value?.friendlyExtensionName + extensionVersion = value?.extensionVersion } // Fetch these values on initialization @@ -199,7 +199,7 @@ internal class ExtensionContainer constructor( return@submit } - if (extension.name == null) { + if (extension.extensionName == null) { callback(EventHubError.InvalidExtensionName) return@submit } @@ -212,7 +212,7 @@ internal class ExtensionContainer constructor( fun shutdown() { taskExecutor.run { - extensionRuntime.extension?.onUnregistered() + extensionRuntime.extension?.onExtensionUnregistered() } taskExecutor.shutdown() } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt index feb6967ea..df6ecf23b 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt @@ -39,25 +39,25 @@ internal fun Class.initWith(extensionApi: ExtensionApi): Extensio /** * Property to get Extension name */ -internal val Extension.name: String? +internal val Extension.extensionName: String? get() = ExtensionHelper.getName(this) /** * Property to get Extension version */ -internal val Extension.version: String? +internal val Extension.extensionVersion: String? get() = ExtensionHelper.getVersion(this) /** * Property to get Extension friendly name */ -internal val Extension.friendlyName: String? +internal val Extension.friendlyExtensionName: String? get() = ExtensionHelper.getFriendlyName(this) /** * Function to notify that the Extension has been unregistered */ -internal fun Extension.onUnregistered() { +internal fun Extension.onExtensionUnregistered() { ExtensionHelper.onUnregistered(this) } diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt index c17fb2963..772c32f67 100644 --- a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt @@ -11,14 +11,22 @@ package com.adobe.marketing.mobile.internal.eventhub +import com.adobe.marketing.mobile.Event import com.adobe.marketing.mobile.Extension import com.adobe.marketing.mobile.ExtensionApi +import com.adobe.marketing.mobile.ExtensionError import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith +import org.powermock.modules.junit4.PowerMockRunner import java.lang.Exception import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue +import kotlin.test.fail private object MockExtensions { class MockExtensionInvalidConstructor(api: ExtensionApi, name: String?) : Extension(api) { @@ -54,9 +62,34 @@ private object MockExtensions { return MockExtensionKotlin::javaClass.name } } + + class TestExtension(api: ExtensionApi) : Extension(api) { + companion object { + const val version = "0.1" + const val extensionName = "TestExtension" + const val friendlyExtensionName = "FriendlyTestExtension" + } + + override fun getName(): String { + return TestExtension.extensionName + } + + override fun getFriendlyName(): String { + return TestExtension.friendlyExtensionName + } + + override fun getVersion(): String { + return TestExtension.version + } + } } +@RunWith(PowerMockRunner::class) internal class EventHubTests { + private val eventType = "Type" + private val eventSource = "Source" + private val event1: Event = Event.Builder("Event1", eventType, eventSource).build() + private val event2: Event = Event.Builder("Event2", eventType, eventSource).build() // Helper to register extensions fun registerExtension(extensionClass: Class): EventHubError { @@ -149,4 +182,510 @@ internal class EventHubTests { ret = registerExtension(MockExtensions.MockExtensionKotlin::class.java) assertEquals(EventHubError.None, ret) } + + @Test + fun testSetSharedState_NullOrEmptyExtensionName() { + var result: ExtensionError? = null + + registerExtension(MockExtensions.TestExtension::class.java) + EventHub.shared.dispatch(event1) // Dispatch Event1 + + val stateAtEvent1: MutableMap = mutableMapOf("One" to 1, "Yes" to true) + + // Set state at event1 with null extension name + assertFalse( + EventHub.shared.setSharedState( + SharedStateType.STANDARD, + null, stateAtEvent1, event1 + ) { + result = it + } + ) + assertEquals(result, ExtensionError.BAD_NAME) + + // Set state at event1 with empty extension name + assertFalse( + EventHub.shared.setSharedState( + SharedStateType.STANDARD, + "", stateAtEvent1, event1 + ) { + result = it + } + ) + assertEquals(result, ExtensionError.BAD_NAME) + } + + @Test + fun testSetSharedState_ExtensionNotRegistered() { + EventHub.shared.dispatch(event1) // Dispatch Event1 + + val stateAtEvent1: MutableMap = mutableMapOf("One" to 1, "Yes" to true) + + var result: ExtensionError? = null + + // Set state at event1 + assertFalse( + EventHub.shared.setSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, stateAtEvent1, event1 + ) { + result = it + } + ) + assertEquals(result, ExtensionError.UNEXPECTED_ERROR) + } + + @Test + fun testSetSharedState_PendingState() { + registerExtension(MockExtensions.TestExtension::class.java) + EventHub.shared.dispatch(event1) // Dispatch Event1 + + // Set state at event1 + assertTrue( + EventHub.shared.setSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, null, event1 + ) { + fail("State should have been set successfully ${it.errorCode} - ${it.errorName}") + } + ) + } + + @Test + fun testSetSharedState_OverwritePendingStateWithNonPendingState() { + registerExtension(MockExtensions.TestExtension::class.java) + EventHub.shared.dispatch(event1) // Dispatch Event1 + + // Set pending state at event1 + assertTrue( + EventHub.shared.setSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, null, event1 + ) { + fail("State should have been set successfully ${it.errorCode} - ${it.errorName}") + } + ) + + val stateAtEvent: MutableMap = mutableMapOf("One" to 1, "Yes" to true) + + assertTrue( + EventHub.shared.setSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, stateAtEvent, event1 + ) { + fail("State should have been set successfully ${it.errorCode} - ${it.errorName}") + } + ) + } + + @Test + fun testSetSharedState_NoPendingStateAtEvent() { + registerExtension(MockExtensions.TestExtension::class.java) + EventHub.shared.dispatch(event1) // Dispatch Event1 + + // Set non pending state at event1 + val stateAtEvent1: MutableMap = mutableMapOf("One" to 1, "Yes" to true) + assertTrue( + EventHub.shared.setSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, stateAtEvent1, event1 + ) { + fail("State should have been set successfully. ${it.errorCode} - ${it.errorName}") + } + ) + + // Verify that state at event1 cannot be overwritten + val overwriteState: MutableMap = mutableMapOf("Two" to 2, "No" to false) + + assertFalse( + EventHub.shared.setSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, overwriteState, event1 + ) { + fail("${it.errorCode} - ${it.errorName}") + } + ) + } + + @Test + fun testSetSharedState_OverwriteNonPendingStateWithPendingState() { + registerExtension(MockExtensions.TestExtension::class.java) + EventHub.shared.dispatch(event1) // Dispatch Event1 + + // Set non pending state at Event 1 + val stateAtEvent1: MutableMap = mutableMapOf("One" to 1, "Yes" to true) + assertTrue( + EventHub.shared.setSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, stateAtEvent1, event1 + ) { + fail("State should have been set successfully. ${it.errorCode} - ${it.errorName}") + } + ) + + // Verify that state at event1 cannot be overwritten with a pending state + val overwriteState: MutableMap? = null + + assertFalse( + EventHub.shared.setSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, overwriteState, event1 + ) { + fail("${it.errorCode} - ${it.errorName}") + } + ) + } + + @Test + fun testGetSharedState_NullOrEmptyExtensionName() { + var result: ExtensionError? = null + + // Get state at event1 with null extension name + EventHub.shared.getSharedState( + SharedStateType.STANDARD, + null, event1 + ) { + result = it + } + assertEquals(result, ExtensionError.BAD_NAME) + + // Get state at event1 with empty extension name + EventHub.shared.getSharedState( + SharedStateType.STANDARD, "", event1 + ) { + result = it + } + assertEquals(result, ExtensionError.BAD_NAME) + } + + @Test + fun testGetSharedState_ExtensionNotRegistered() { + var result: ExtensionError? = null + + // Set state at event1 + EventHub.shared.getSharedState( + SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, event1 + ) { + result = it + } + assertEquals(result, ExtensionError.UNEXPECTED_ERROR) + } + + @Test + fun testGetSharedState_NoStateExistsYet() { + registerExtension(MockExtensions.TestExtension::class.java) + val errorCallback: (ExtensionError) -> Unit = { + fail("Test failed ${it.errorCode} - ${it.errorName}") + } + + // Dispatch Event 1 + EventHub.shared.dispatch(event1) + + assertNull( + EventHub.shared.getSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, event1, errorCallback + ) + ) + } + + @Test + fun testGetSharedState_StateExistsAtVersion() { + registerExtension(MockExtensions.TestExtension::class.java) + val errorCallback: (ExtensionError) -> Unit = { + fail("Test failed ${it.errorCode} - ${it.errorName}") + } + + // Dispatch event1 + EventHub.shared.dispatch(event1) + + // Set non pending state at event1 + val stateAtEvent1: MutableMap = mutableMapOf("One" to 1, "Yes" to true) + assertTrue( + EventHub.shared.setSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, stateAtEvent1, event1 + ) { + fail("State should have been set successfully. ${it.errorCode} - ${it.errorName}") + } + ) + + // Dispatch event2 + EventHub.shared.dispatch(event2) + + // Set state at event2 + val stateAtEvent2: MutableMap = mutableMapOf("Two" to 1, "No" to false) + assertTrue( + EventHub.shared.setSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, stateAtEvent2, event2, errorCallback + ) + ) + + // Verify that the state at event1 and event2 + assertEquals( + stateAtEvent1, + EventHub.shared.getSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, event1, errorCallback + ) + ) + assertEquals( + stateAtEvent2, + EventHub.shared.getSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, event2, errorCallback + ) + ) + } + + @Test + fun testGetSharedState_PreviousStateDoesNotExist() { + registerExtension(MockExtensions.TestExtension::class.java) + val errorCallback: (ExtensionError) -> Unit = { + fail("Test failed ${it.errorCode} - ${it.errorName}") + } + + // Dispatch event 1 & event2 + EventHub.shared.dispatch(event1) + EventHub.shared.dispatch(event2) + + // Set state at event2 + val stateAtEvent2: MutableMap = mutableMapOf("One" to 1, "Yes" to true) + assertTrue( + EventHub.shared.setSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, stateAtEvent2, event2, errorCallback + ) + ) + + // Verify that the state at event1 is still null + assertNull( + EventHub.shared.getSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, event1, errorCallback + ) + ) + assertEquals( + stateAtEvent2, + EventHub.shared.getSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, event2, errorCallback + ) + ) + } + + @Test + fun testGetSharedState_FetchesLatestStateOnNullEvent() { + registerExtension(MockExtensions.TestExtension::class.java) + val errorCallback: (ExtensionError) -> Unit = { + fail("Test failed ${it.errorCode} - ${it.errorName}") + } + + // Dispatch event1 + EventHub.shared.dispatch(event1) + + // Set state at event1 + val state: MutableMap = mutableMapOf("One" to 1, "Yes" to true) + assertTrue( + EventHub.shared.setSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, state, event1, errorCallback + ) + ) + + assertEquals( + state, + EventHub.shared.getSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, null, errorCallback + ) + ) + } + + @Test + fun testGetSharedState_OlderStateExists() { + registerExtension(MockExtensions.TestExtension::class.java) + val errorCallback: (ExtensionError) -> Unit = { + fail("Test failed ${it.errorCode} - ${it.errorName}") + } + + // Dispatch event1 + EventHub.shared.dispatch(event1) + + // Set state at event1 + val stateAtEvent1: MutableMap = mutableMapOf("One" to 1, "Yes" to true) + assertTrue( + EventHub.shared.setSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, stateAtEvent1, event1, errorCallback + ) + ) + + // Dispatch event2 + EventHub.shared.dispatch(event2) + + // Verify that the state at event1 and event2 are the same and they equal [stateAtEvent1] + assertEquals( + stateAtEvent1, + EventHub.shared.getSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, event1, errorCallback + ) + ) + assertEquals( + stateAtEvent1, + EventHub.shared.getSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, event2, errorCallback + ) + ) + } + + @Test + fun testClearSharedState_NullOrEmptyExtensionName() { + var result: ExtensionError? = null + assertFalse( + EventHub.shared.clearSharedState( + SharedStateType.STANDARD, + null + ) { + result = it + } + ) + assertEquals(result, ExtensionError.BAD_NAME) + + assertFalse( + EventHub.shared.clearSharedState( + SharedStateType.STANDARD, "" + ) { + result = it + } + ) + assertEquals(result, ExtensionError.BAD_NAME) + } + + @Test + fun testClearSharedState_ExtensionNotRegistered() { + var result: ExtensionError? = null + assertFalse( + EventHub.shared.clearSharedState( + SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, + ) { + result = it + } + ) + + assertEquals(result, ExtensionError.UNEXPECTED_ERROR) + } + + @Test + fun testClearSharedState_NoStateYet() { + registerExtension(MockExtensions.TestExtension::class.java) + + assertTrue( + EventHub.shared.clearSharedState( + SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, + ) { + fail("State should have been cleared successfully") + } + ) + } + + @Test + fun testClearSharedState() { + registerExtension(MockExtensions.TestExtension::class.java) + val errorCallback: (ExtensionError) -> Unit = { + fail("Test failed ${it.errorCode} - ${it.errorName}") + } + EventHub.shared.dispatch(event1) + EventHub.shared.dispatch(event2) + + val stateAtEvent1: MutableMap = mutableMapOf("One" to 1, "Yes" to true) + assertTrue( + EventHub.shared.setSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, stateAtEvent1, event1, errorCallback + ) + ) + + val stateAtEvent2: MutableMap = mutableMapOf("Twi" to 2, "No" to false) + assertTrue( + EventHub.shared.setSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, stateAtEvent2, event2, errorCallback + ) + ) + + // Verify that all the states are cleared + assertTrue( + EventHub.shared.clearSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, errorCallback + ) + ) + assertNull( + EventHub.shared.getSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, event1, errorCallback + ) + ) + assertNull( + EventHub.shared.getSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, event2, errorCallback + ) + ) + } + + @Test + fun testClearSharedState_DifferentStateType() { + registerExtension(MockExtensions.TestExtension::class.java) + val errorCallback: (ExtensionError) -> Unit = { + fail("Test failed ${it.errorCode} - ${it.errorName}") + } + EventHub.shared.dispatch(event1) + + val stateAtEvent1: MutableMap = mutableMapOf("One" to 1, "Yes" to true) + val xdmStateAtEvent1: MutableMap = mutableMapOf("Two" to 1, "No" to false) + + // Set Standard shared state + assertTrue( + EventHub.shared.setSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, stateAtEvent1, event1, errorCallback + ) + ) + + // Set Standard XDM shared state + assertTrue( + EventHub.shared.setSharedState( + SharedStateType.XDM, + MockExtensions.TestExtension.extensionName, xdmStateAtEvent1, event1, errorCallback + ) + ) + + // Set Standard Standard shared state + assertTrue( + EventHub.shared.clearSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, errorCallback + ) + ) + + // Verify that only standard state is cleared. + assertNull( + EventHub.shared.getSharedState( + SharedStateType.STANDARD, + MockExtensions.TestExtension.extensionName, event1, errorCallback + ) + ) + assertEquals( + xdmStateAtEvent1, + EventHub.shared.getSharedState( + SharedStateType.XDM, + MockExtensions.TestExtension.extensionName, event1, errorCallback + ) + ) + } } From b4305eb4f8784829d79f02da2395e68ab0a49cb8 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Mon, 13 Jun 2022 09:53:37 -0700 Subject: [PATCH 072/476] added launch rules consequence test, commented out configuration test --- ...idThirdPartyExtensionsFunctionalTests.java | 5 +- .../mobile/ConfigurationModuleTest.java | 9 +- .../mobile/ConfigurationExtension.java | 3 +- .../rulesengine/LaunchRulesConsequence.kt | 49 +- .../rulesengine/LaunchRulesEvaluator.kt | 19 +- .../launch/rulesengine/RuleConsequence.kt | 2 +- ...atcherConfigurationRequestContentTest.java | 5 +- ...tcherConfigurationResponseContentTest.java | 5 +- ...cherConfigurationResponseIdentityTest.java | 5 +- .../ConfigurationListenerBootEventTest.java | 7 +- ...nfigurationListenerRequestContentTest.java | 7 +- ...figurationListenerRequestIdentityTest.java | 7 +- .../ConfigurationListenerSharedStateTest.java | 6 +- .../marketing/mobile/ConfigurationTests.java | 10 +- .../mobile/MobileIdentitiesTest.java | 5 +- .../LaunchRulesConsequenceTests.kt | 529 ++++++++++++++++++ .../rulesengine/LaunchRulesEvaluatorTests.kt | 31 +- 17 files changed, 612 insertions(+), 92 deletions(-) create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequenceTests.kt diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java index 732429473..97fb4d486 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java @@ -454,11 +454,12 @@ public void call(Event value) { assertEquals(pairedEventData, result.get(0).getEventData()); } + // TODO uncomment after Configuration refactor // Test Case No : 15, 17 & 36 // Get Shared Event State Owned By A Configuration Event using getSharedEventState API // with the extension name as the stateowner like // com.adobe.module.identity, com.adobe.module.configuration - @Test + /* @Test public void testGetSharedEventState_whenConfigEvent_returnsAppropriateSharedState() { // setup @@ -486,7 +487,7 @@ public void error(final ExtensionError extensionError) { assertEquals(configMap, configurationSharedState); assertEquals(configMap, eventHeard.getEventData()); - } + } */ // Test Case No : 16 // Get Shared Event State Owned By A Custom Event using getSharedEventState API for stateowner diff --git a/code/android-core-library/src/legacy/test-module/java/com/adobe/marketing/mobile/ConfigurationModuleTest.java b/code/android-core-library/src/legacy/test-module/java/com/adobe/marketing/mobile/ConfigurationModuleTest.java index f6c0289a1..b5e1d8e8e 100644 --- a/code/android-core-library/src/legacy/test-module/java/com/adobe/marketing/mobile/ConfigurationModuleTest.java +++ b/code/android-core-library/src/legacy/test-module/java/com/adobe/marketing/mobile/ConfigurationModuleTest.java @@ -81,7 +81,8 @@ public void evaluate() throws Throwable { } } -@RunWith(JUnit4.class) +// TODO uncomment after Configuration refactor +/*@RunWith(JUnit4.class) public class ConfigurationModuleTest extends SystemTest { // Retry failed tests up to 2 times @org.junit.Rule @@ -2026,11 +2027,11 @@ private void setupNetWorkService(String contentResourceFileName, Date resourceLa testableNetworkService.setDefaultResponse(networkResponse); } - /** + *//** * Create a Date formatter in specific format * * @return SimpleDateFormat - */ + *//* private SimpleDateFormat createRFC2822Formatter() { final String pattern = "EEE, dd MMM yyyy HH:mm:ss z"; final SimpleDateFormat rfc2822formatter = new SimpleDateFormat(pattern, Locale.US); @@ -2111,4 +2112,4 @@ private void triggerRulesDownloadWithEvent(final String rulesURL) { eventHub.dispatch(configEvent); } -} +}*/ diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ConfigurationExtension.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ConfigurationExtension.java index e6b39f676..40e1d2cc2 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ConfigurationExtension.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ConfigurationExtension.java @@ -139,7 +139,8 @@ public ConfigurationExtension(final EventHub eventHub, final PlatformServices se this.cachedEvents = Collections.synchronizedList(new ArrayList()); //TODO: need to pass an instance of ExtensionAPi to initialize LaunchRulesEngine, will do it later after we converted Configuration to a 3th party extension. LaunchRulesEngine launchRulesEngine = new LaunchRulesEngine(null); - launchRulesEvaluator = new LaunchRulesEvaluator(LAUNCH_RULES_ENGINE, launchRulesEngine); + //TODO: need to pass an instance of ExtensionAPi to initialize LaunchRulesEvaluator, will do it later after we converted Configuration to a 3th party extension. + launchRulesEvaluator = new LaunchRulesEvaluator(LAUNCH_RULES_ENGINE, launchRulesEngine, null); //TODO: enable pre-processor to utilize the new RulesEngine after the Configuration is converted to a 3th party extension. //eventHub.registerPreprocessor(launchRulesEvaluator); } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt index 4d297ae02..b1b31b5f5 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt @@ -26,7 +26,6 @@ class LaunchRulesConsequence( private val logTag = "LaunchRulesConsequence" private var dispatchChainedEventsCount = mutableMapOf() - companion object { // TODO: we should move the following event type/event source values to the public EventType/EventSource classes once we have those. const val EVENT_SOURCE_RESPONSE_CONTENT = "com.adobe.eventSource.responseContent" @@ -38,7 +37,7 @@ class LaunchRulesConsequence( const val CONSEQUENCE_TYPE_DISPATCH = "dispatch" const val CONSEQUENCE_DETAIL_ACTION_COPY = "copy" const val CONSEQUENCE_DETAIL_ACTION_NEW = "new" - /// Do not process Dispatch consequence if chained event count is greater than max + // Do not process Dispatch consequence if chained event count is greater than max const val MAX_CHAINED_CONSEQUENCE_COUNT = 1 const val CONSEQUENCE_DISPATCH_EVENT_NAME = "Dispatch Consequence Result" const val CONSEQUENCE_EVENT_DATA_KEY_ID = "id" @@ -46,10 +45,11 @@ class LaunchRulesConsequence( const val CONSEQUENCE_EVENT_DATA_KEY_DETAIL = "detail" const val CONSEQUENCE_EVENT_DATA_KEY_CONSEQUENCE = "triggeredconsequence" const val CONSEQUENCE_EVENT_NAME = "Rules Consequence Event" - } - fun evaluateRules(event: Event) : Event { + fun evaluateRulesConsequence(event: Event?): Event? { + if (event == null) return null + val dispatchChainCount = dispatchChainedEventsCount.remove(event.uniqueIdentifier) val launchTokenFinder = LaunchTokenFinder(event, extensionApi) val matchedRules = launchRulesEngine.process(event) @@ -57,7 +57,7 @@ class LaunchRulesConsequence( for (rule in matchedRules) { for (consequence in rule.consequenceList) { val consequenceWithConcreteValue = replaceToken(consequence, launchTokenFinder) - when(consequenceWithConcreteValue.type){ + when (consequenceWithConcreteValue.type) { CONSEQUENCE_TYPE_ADD -> { val attachedEventData = processAttachDataConsequence( consequenceWithConcreteValue, @@ -73,17 +73,17 @@ class LaunchRulesConsequence( processedEvent = processedEvent.copyWithNewData(modifiedEventData) } CONSEQUENCE_TYPE_DISPATCH -> { - if (dispatchChainCount == null || dispatchChainCount == 0 || - dispatchChainCount >= MAX_CHAINED_CONSEQUENCE_COUNT - ) { - MobileCore.log( - LoggingMode.VERBOSE, - logTag, - "Unable to process dispatch consequence, max chained " + - "dispatch consequences limit of ${MAX_CHAINED_CONSEQUENCE_COUNT}" + - "met for this event uuid ${event.uniqueIdentifier}" - ) - continue + if (dispatchChainCount != null) { + if (dispatchChainCount >= MAX_CHAINED_CONSEQUENCE_COUNT) { + MobileCore.log( + LoggingMode.VERBOSE, + logTag, + "Unable to process dispatch consequence, max chained " + + "dispatch consequences limit of $MAX_CHAINED_CONSEQUENCE_COUNT" + + "met for this event uuid ${event.uniqueIdentifier}" + ) + continue + } } val dispatchEvent = processDispatchConsequence( consequenceWithConcreteValue, @@ -94,13 +94,14 @@ class LaunchRulesConsequence( logTag, " Generating new dispatch consequence result event $dispatchEvent" ) - MobileCore.dispatchEvent(event) { + MobileCore.dispatchEvent(dispatchEvent) { MobileCore.log( LoggingMode.WARNING, logTag, "An error occurred when dispatching dispatch consequence result event" ) } + dispatchChainedEventsCount[dispatchEvent.uniqueIdentifier] = if (dispatchChainCount == null) 1 else dispatchChainCount + 1 } else -> { val consequenceEvent = generateConsequenceEvent(consequenceWithConcreteValue) @@ -109,7 +110,7 @@ class LaunchRulesConsequence( logTag, "Generating new consequence event $consequenceEvent" ) - MobileCore.dispatchEvent(event) { + MobileCore.dispatchEvent(consequenceEvent) { MobileCore.log( LoggingMode.WARNING, logTag, @@ -140,11 +141,11 @@ class LaunchRulesConsequence( if (detail.isNullOrEmpty()) return null val mutableDetail = detail.toMutableMap() - for((key, value) in detail) { - when(value) { + for ((key, value) in detail) { + when (value) { is String -> mutableDetail[key] = replaceToken(value, tokenFinder) - is Map<*, *> -> replaceToken(mutableDetail[key] as Map, tokenFinder) - else -> break + is Map<*, *> -> mutableDetail[key] = replaceToken(mutableDetail[key] as Map, tokenFinder) + else -> continue } } return mutableDetail @@ -281,7 +282,7 @@ class LaunchRulesConsequence( .build() } - private fun generateConsequenceEvent(consequence: RuleConsequence) : Event? { + private fun generateConsequenceEvent(consequence: RuleConsequence): Event? { val eventData = mutableMapOf() eventData[CONSEQUENCE_EVENT_DATA_KEY_DETAIL] = consequence.detail eventData[CONSEQUENCE_EVENT_DATA_KEY_ID] = consequence.id @@ -304,4 +305,4 @@ val RuleConsequence.eventType: String? get() = detail?.get("type") as? String val RuleConsequence.eventDataAction: String? - get() = detail?.get("eventdataaction") as? String \ No newline at end of file + get() = detail?.get("eventdataaction") as? String diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt index a51c9064d..b94260a64 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt @@ -13,29 +13,22 @@ package com.adobe.marketing.mobile.launch.rulesengine import com.adobe.marketing.mobile.Event import com.adobe.marketing.mobile.EventPreprocessor import com.adobe.marketing.mobile.ExtensionApi -import com.adobe.marketing.mobile.ExtensionErrorCallback import com.adobe.marketing.mobile.LoggingMode import com.adobe.marketing.mobile.MobileCore -import com.adobe.marketing.mobile.internal.utility.EventDataMerger -import com.adobe.marketing.mobile.rulesengine.DelimiterPair -import com.adobe.marketing.mobile.rulesengine.Template -import com.adobe.marketing.mobile.rulesengine.TokenFinder -import com.adobe.marketing.mobile.rulesengine.Transforming internal class LaunchRulesEvaluator( private val name: String, private val launchRulesEngine: LaunchRulesEngine, - private val extensionApi: ExtensionApi - ) : EventPreprocessor { - + extensionApi: ExtensionApi +) : EventPreprocessor { private var cachedEvents: MutableList? = mutableListOf() private val logTag = "LaunchRulesEvaluator_$name" private val launchRulesConsequence: LaunchRulesConsequence = LaunchRulesConsequence(launchRulesEngine, extensionApi) - companion object { const val CACHED_EVENT_MAX = 99 + // TODO: we should move the following event type/event source values to the public EventType/EventSource classes once we have those. const val EVENT_SOURCE_REQUEST_RESET = "com.adobe.eventsource.requestreset" const val EVENT_TYPE_RULES_ENGINE = "com.adobe.eventtype.rulesengine" } @@ -47,13 +40,14 @@ internal class LaunchRulesEvaluator( reprocessCachedEvents() } else { cacheEvent(event) + return launchRulesConsequence.evaluateRulesConsequence(event) } - return launchRulesConsequence.evaluateRules(event) + return event } private fun reprocessCachedEvents() { cachedEvents?.forEach { event -> - launchRulesConsequence.evaluateRules(event) + launchRulesConsequence.evaluateRulesConsequence(event) } clearCachedEvents() } @@ -100,4 +94,3 @@ internal class LaunchRulesEvaluator( } } } - diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence.kt index d084e840b..136ac4ea8 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence.kt @@ -27,5 +27,5 @@ data class RuleConsequence( var detail: Map? = null ) { val eventData: Map? - get() = detail?.get("eventData") as? Map? + get() = detail?.get("eventdata") as? Map? } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationRequestContentTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationRequestContentTest.java index 9b9647ac3..ae6317a93 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationRequestContentTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationRequestContentTest.java @@ -30,7 +30,8 @@ public void beforeEach() { fakePlatformServices)); } - @Test + // TODO uncomment after Configuration refactor + /* @Test public void testDispatchInternalConfigureWithAppIdEvent() throws Exception { //Test requestDispatcher.dispatchInternalConfigureWithAppIdEvent("appID"); @@ -44,7 +45,7 @@ public void testDispatchInternalConfigureWithAppIdEvent() throws Exception { assertEquals("appID", dispatchedEvent.getData().optString("config.appId", null)); assertTrue(dispatchedEvent.getData().optBoolean("config.isinternalevent", false)); assertNull(dispatchedEvent.getPairID()); - } + } */ } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationResponseContentTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationResponseContentTest.java index 3243038a9..f546df961 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationResponseContentTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationResponseContentTest.java @@ -30,7 +30,8 @@ public void beforeEach() { fakePlatformServices)); } - @Test + // TODO uncomment after Configuration refactor + /* @Test public void testDispatchConfigResponseWithEventData_Valid() { // Test EventData eventData = new EventData(); @@ -44,7 +45,7 @@ public void testDispatchConfigResponseWithEventData_Valid() { assertEquals(EventSource.RESPONSE_CONTENT, dispatchedEvent.getEventSource()); assertEquals(eventData, dispatchedEvent.getData()); assertEquals("pairID", dispatchedEvent.getPairID()); - } + } */ } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationResponseIdentityTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationResponseIdentityTest.java index 93c07cb0d..3c6ad212a 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationResponseIdentityTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationResponseIdentityTest.java @@ -30,7 +30,8 @@ public void beforeEach() { fakePlatformServices)); } - @Test + // TODO uncomment after Configuration refactor + /* @Test public void testDispatchAllIdentities() { //Test responseIdentityDispatcher.dispatchAllIdentities("jsonString", "pairID"); @@ -43,7 +44,7 @@ public void testDispatchAllIdentities() { assertEquals(EventSource.RESPONSE_IDENTITY, dispatchedEvent.getEventSource()); assertEquals("jsonString", dispatchedEvent.getData().optString("config.allIdentifiers", null)); assertEquals("pairID", dispatchedEvent.getPairID()); - } + } */ } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerBootEventTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerBootEventTest.java index 294ffd665..561fc3952 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerBootEventTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerBootEventTest.java @@ -32,9 +32,8 @@ public void setup() { EventSource.BOOTED); } - - - @Test + // TODO uncomment after Configuration refactor + /* @Test public void testListener_Constructor_With_ValidParameter() { // Verify assertNotNull("the constructor should not return Null", listener); @@ -54,5 +53,5 @@ public void testListener_when_BootEvent() throws Exception { // Verify assertTrue("Handle Boot event much br called", mockConfiguration.handleBootEventWasCalled); assertEquals("Passes the correct event", bootedEvent, mockConfiguration.handleBootEventParamEvent); - } + } */ } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerRequestContentTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerRequestContentTest.java index 9423d86ae..97bcaae46 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerRequestContentTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerRequestContentTest.java @@ -31,9 +31,8 @@ public void setup() { EventSource.REQUEST_CONTENT); } - - - @Test + // TODO uncomment after Configuration refactor + /* @Test public void testListener_Constructor_With_ValidParameter() { // Test configurationListenerRequestContent = new ConfigurationListenerRequestContent(mockConfiguration, @@ -58,7 +57,7 @@ public void testListener_when_HearCalled() throws Exception { // Verify assertTrue("Handle RequestContent event must be called", mockConfiguration.handleEventWasCalled); assertEquals("Passes the correct event", requestContentEvent, mockConfiguration.handleEventParamEvent); - } + } */ } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerRequestIdentityTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerRequestIdentityTest.java index c169cd092..fc80d07b7 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerRequestIdentityTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerRequestIdentityTest.java @@ -34,9 +34,8 @@ public void setup() { EventSource.REQUEST_IDENTITY); } - - - @Test + // TODO uncomment after Configuration refactor + /* @Test public void testListener_Constructor_With_ValidParameter() { // Test configurationListenerRequestIdentity = new ConfigurationListenerRequestIdentity(mockConfiguration, @@ -62,5 +61,5 @@ public void testListener_when_GetSDKIdentitiesEvent() throws Exception { assertTrue("Handle GetSDKIdentities must be called", mockConfiguration.handleGetSdkIdentitiesEventCalled); assertEquals("Passes the correct event", getSDKIdentitiesEvent, mockConfiguration.handleGetSdkIdentitiesEventParamEvent); - } + } */ } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerSharedStateTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerSharedStateTest.java index 9d7f80dda..a85951068 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerSharedStateTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerSharedStateTest.java @@ -34,8 +34,8 @@ public void setup() { EventSource.SHARED_STATE); } - - @Test + // TODO uncomment after Configuration refactor + /* @Test public void testListener_Constructor_With_ValidParameter() { // Test configurationListenerSharedState = new ConfigurationListenerSharedState(mockConfiguration, @@ -210,7 +210,7 @@ public void testListener_when_SharedStateOwnerIsTarget() throws Exception { // Verify assertTrue("processGetSdkIds should be called", mockConfiguration.processGetSdkIdsEventWasCalled); - } + } */ } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationTests.java index 2b5221fba..a66fe7809 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationTests.java @@ -104,7 +104,8 @@ public void afterEach() { // void handleEvent(final Event event, final boolean isFromQueue) // ================================================================================================================= - @Test + // TODO uncomment after Configuration refactor + /*@Test public void testProcessEvent_when_AppIDEvent() throws Exception { // setup beginBasic(); @@ -1030,7 +1031,7 @@ public void testProcessConfigureWithAppIdEvent_when_platformServices_notAvailabl // verify dispatchedEvent assertFalse("event should not be dispatched", responseDispatcher.dispatchConfigResponseWithEventDataWasCalled); } - +*/ // helper for testProcessConfigureWithAppIdEvent_WithNetworkOffOffOn final class FakeSystemInfoServiceForNetworkTest extends MockSystemInfoService { public List listeners = new ArrayList(); @@ -1094,7 +1095,8 @@ public boolean registerOneTimeNetworkConnectionActiveListener(final NetworkConne } } - @Test + // TODO uncomment after Configuration refactor +/* @Test public void testProcessConfigureWithAppIdEvent_WithNetworkOffOffOn() throws Exception { beginBasic(); final FakeSystemInfoServiceForNetworkTest mySystemInfoService = new FakeSystemInfoServiceForNetworkTest(); @@ -1394,7 +1396,7 @@ public void testProcessGetSdkIdsEvent_WhenAllSharedState() throws Exception { assertTrue(responseIdentityDispatcher.dispatchAllIdentitiesWasCalled); assertEquals("pairID", responseIdentityDispatcher.dispatchAllIdentitiesParameterPairId); assertNotNull(responseIdentityDispatcher.dispatchAllIdentitiesParametersSdkIdentitiesJson); - } + }*/ // ================================================================================================================= // Setup Methods diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MobileIdentitiesTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MobileIdentitiesTest.java index 8f8b5d06c..fa0a83800 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MobileIdentitiesTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MobileIdentitiesTest.java @@ -37,7 +37,8 @@ public void beforeEach() { // final Event event, final Module module) // ======================================================== - @Test + // TODO uncomment after Configuration refactor + /*@Test public void test_GetAllIdentifiers_Happy() { // Setup Event event = new Event.Builder("EventHub", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) @@ -366,7 +367,7 @@ public void test_AreAllSharedStatesReady_WhenTargetStatePending() throws Excepti // Verify assertFalse("areAllSharedStatesReady should return false", isReady); } - +*/ // ======================================================== // Helper methods diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequenceTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequenceTests.kt new file mode 100644 index 000000000..0fafb1232 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequenceTests.kt @@ -0,0 +1,529 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.launch.rulesengine + +import com.adobe.marketing.mobile.Event +import com.adobe.marketing.mobile.ExtensionApi +import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.launch.rulesengine.json.JSONRulesParser +import com.adobe.marketing.mobile.test.utility.readTestResources +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers +import org.mockito.Mockito.any +import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.powermock.api.mockito.PowerMockito +import org.powermock.core.classloader.annotations.PrepareForTest +import org.powermock.modules.junit4.PowerMockRunner + +@RunWith(PowerMockRunner::class) +@PrepareForTest(ExtensionApi::class, MobileCore::class) +class LaunchRulesConsequenceTests { + + private lateinit var extensionApi: ExtensionApi + private lateinit var launchRulesEngine: LaunchRulesEngine + private lateinit var launchRulesConsequence: LaunchRulesConsequence + private var defaultEvent = Event.Builder( + "event", + "com.adobe.eventType.lifecycle", + "com.adobe.eventSource.responseContent" + ).setEventData( + mapOf( + "lifecyclecontextdata" to mapOf( + "launchevent" to "LaunchEvent" + ) + ) + ).build() + + @Before + fun setup() { + extensionApi = PowerMockito.mock(ExtensionApi::class.java) + PowerMockito.mockStatic(MobileCore::class.java) + launchRulesEngine = LaunchRulesEngine(extensionApi) + launchRulesConsequence = LaunchRulesConsequence(launchRulesEngine, extensionApi) + } + + @Test + fun `Test Attach Data`() { + // / Given: a launch rule to attach data to event + + // ---------- attach data rule ---------- + // "eventdata": { + // "attached_data": { + // "key1": "value1", + // "launches": "{%~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches%}" + // } + // } + // -------------------------------------- + resetRulesEngine("rules_module_tests/consequence_rules_testAttachData.json") + + // / When: evaluating a launch event + + // ------------ launch event ------------ + // "eventdata": { + // "lifecyclecontextdata": { + // "launchevent": "LaunchEvent" + // } + // } + // -------------------------------------- + PowerMockito.`when`(extensionApi.getSharedEventState(ArgumentMatchers.anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "AT&T" + ) + ) + ) + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(defaultEvent) + + // / Then: no consequence event will be dispatched + PowerMockito.verifyStatic(MobileCore::class.java, never()) + MobileCore.dispatchEvent(any(), any()) + + val attachedData = processedEvent?.eventData?.get("attached_data") as Map<*, *> + + // / Then: ["key1": "value1"] should be attached to above launch event + assertEquals("value1", attachedData["key1"]) + + // / Then: should not get "launches" value from (lifecycle) shared state + assertEquals("", attachedData["launches"]) + } + + @Test + fun `Test Attach Data Invalid Json`() { + // / Given: a launch rule to attach data to event + + // ---------- attach data rule ---------- + // "eventdata_xyz": { + // "attached_data": { + // "key1": "value1", + // "launches": "{%~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches%}" + // } + // } + // -------------------------------------- + resetRulesEngine("rules_module_tests/consequence_rules_testAttachData_invalidJson.json") + + // / When: evaluating a launch event + PowerMockito.`when`(extensionApi.getSharedEventState(ArgumentMatchers.anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "AT&T" + ) + ) + ) + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(defaultEvent) + + // / Then: no consequence event will be dispatched + PowerMockito.verifyStatic(MobileCore::class.java, never()) + MobileCore.dispatchEvent(any(), any()) + + // / Then: no data should not be attached to original launch event + val attachedData = processedEvent?.eventData?.get("attached_data") + assertNull(attachedData) + } + + @Test + fun `Test Modify Data`() { + // / Given: a launch rule to modify event data + + // ---------- modify data rule ---------- + // "eventdata": { + // "lifecyclecontextdata": { + // "launches": "{%~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches%}", + // "launchevent": null + // } + // } + // -------------------------------------- + resetRulesEngine("rules_module_tests/consequence_rules_testModifyData.json") + + // / When: evaluating a launch event + + // ------------ launch event ------------ + // "eventdata": { + // "lifecyclecontextdata": { + // "launchevent": "LaunchEvent" + // } + // } + // -------------------------------------- + PowerMockito.`when`(extensionApi.getSharedEventState(ArgumentMatchers.anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "AT&T", + "launches" to 2 + ) + ) + ) + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(defaultEvent) + + // / Then: no consequence event will be dispatched + PowerMockito.verifyStatic(MobileCore::class.java, never()) + MobileCore.dispatchEvent(any(), any()) + + // / Then: "launchevent" should be removed from event data + val lifecycleContextData = processedEvent?.eventData?.get("lifecyclecontextdata") as Map<*, *> + assertNull(lifecycleContextData["launchevent"]) + + // / Then: should get "launches" value from (lifecycle) shared state + assertEquals("2", lifecycleContextData["launches"]) + } + + @Test + fun `Test Modify Data Invalid Json`() { + // / Given: a launch rule to modify event data + + // ---------- modify data rule ---------- + // "eventdata_xyz": { + // "lifecyclecontextdata": { + // "launches": "{%~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches%}", + // "launchevent": null + // } + // } + // -------------------------------------- + resetRulesEngine("rules_module_tests/consequence_rules_testModifyData_invalidJson.json") + + // / When: evaluating a launch event + PowerMockito.`when`(extensionApi.getSharedEventState(ArgumentMatchers.anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "AT&T", + "launches" to 2 + ) + ) + ) + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(defaultEvent) + + // / Then: no consequence event will be dispatched + PowerMockito.verifyStatic(MobileCore::class.java, never()) + MobileCore.dispatchEvent(any(), any()) + + // / Then: "launchevent" should not be removed from event data + val lifecycleContextData = processedEvent?.eventData?.get("lifecyclecontextdata") as Map<*, *> + assertNotNull(lifecycleContextData["launchevent"]) + assertNull(lifecycleContextData["launches"]) + } + + @Test + fun `Test Dispatch Event Copy`() { + // / Given: a launch rule to dispatch an event which copies the triggering event data + + // ---------- dispatch event rule ---------- + // "detail": { + // "type" : "com.adobe.eventType.edge", + // "source" : "com.adobe.eventSource.requestContent", + // "eventdataaction" : "copy" + // } + // -------------------------------------- + resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventCopy.json") + + val event = Event.Builder("Application Launch", + "com.adobe.eventType.lifecycle", + "com.adobe.eventSource.applicationLaunch") + .setEventData(mapOf("xdm" to "test data")) + .build() + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event) + + // / Then: One consequence event will be dispatched + val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) + PowerMockito.verifyStatic(MobileCore::class.java, times(1)) + MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + assertEquals("com.adobe.eventtype.edge", dispatchedEventCaptor.value.type) + assertEquals("com.adobe.eventsource.requestcontent", dispatchedEventCaptor.value.source) + assertEquals(event.eventData, dispatchedEventCaptor.value.eventData) + + // verify original event is unchanged + assertEquals(event, processedEvent) + } + + @Test + fun `Test Dispatch Event Copy No Event Data`() { + // / Given: a launch rule to dispatch an event which copies the triggering event data + + // ---------- dispatch event rule ---------- + // "detail": { + // "type" : "com.adobe.eventType.edge", + // "source" : "com.adobe.eventSource.requestContent", + // "eventdataaction" : "copy" + // } + // -------------------------------------- + resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventCopy.json") + val event = Event.Builder("Application Launch", + "com.adobe.eventType.lifecycle", + "com.adobe.eventSource.applicationLaunch") + .setEventData(null) + .build() + + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event) + + // / Then: One consequence event will be dispatched + val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) + PowerMockito.verifyStatic(MobileCore::class.java, times(1)) + MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + assertEquals("com.adobe.eventtype.edge", dispatchedEventCaptor.value.type) + assertEquals("com.adobe.eventsource.requestcontent", dispatchedEventCaptor.value.source) + assertEquals(mapOf(), dispatchedEventCaptor.value.eventData) + + // verify original event is unchanged + assertEquals(event, processedEvent) + } + + @Test + fun `Test Dispatch Event Copy New Event Data`() { + // / Given: a launch rule to dispatch an event which adds new event data + + // ---------- dispatch event rule ---------- + // "detail": { + // "type" : "com.adobe.eventType.edge", + // "source" : "com.adobe.eventSource.requestContent", + // "eventdataaction" : "new", + // "eventdata" : { + // "key" : "value", + // "key.subkey" : "subvalue", + // "launches": "{%~state.com.adobe.module.lifecycle/lifecyclecontextdata.launches%}", + // } + // } + // -------------------------------------- + + resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventNewData.json") + val event = Event.Builder("Application Launch", + "com.adobe.eventType.lifecycle", + "com.adobe.eventSource.applicationLaunch") + .setEventData(mapOf("xdm" to "test data")) + .build() + + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event) + + // / Then: One consequence event will be dispatched + val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) + PowerMockito.verifyStatic(MobileCore::class.java, times(1)) + MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + assertEquals("com.adobe.eventtype.edge", dispatchedEventCaptor.value.type) + assertEquals("com.adobe.eventsource.requestcontent", dispatchedEventCaptor.value.source) + assertEquals("value", dispatchedEventCaptor.value.eventData["key"]) + assertEquals("subvalue", dispatchedEventCaptor.value.eventData["key.subkey"]) + + // verify original event is unchanged + assertEquals(event, processedEvent) + } + + @Test + fun `Test Dispatch Event Copy New No Event Data`() { + // / Given: a launch rule to dispatch an event which adds new event event data, but none is configured + + // ---------- dispatch event rule ---------- + // "detail": { + // "type" : "com.adobe.eventType.edge", + // "source" : "com.adobe.eventSource.requestContent", + // "eventdataaction" : "new" + // } + // -------------------------------------- + + resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventNewNoData.json") + val event = Event.Builder("Application Launch", + "com.adobe.eventType.lifecycle", + "com.adobe.eventSource.applicationLaunch") + .setEventData(mapOf("xdm" to "test data")) + .build() + + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event) + + // / Then: One consequence event will be dispatched + val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) + PowerMockito.verifyStatic(MobileCore::class.java, times(1)) + MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + assertEquals("com.adobe.eventtype.edge", dispatchedEventCaptor.value.type) + assertEquals("com.adobe.eventsource.requestcontent", dispatchedEventCaptor.value.source) + assertEquals(mapOf(), dispatchedEventCaptor.value.eventData) + + // verify original event is unchanged + assertEquals(event, processedEvent) + } + + @Test + fun `Test Dispatch Event Invalid Action `() { + // / Given: a launch rule to dispatch an event with invalid action + + // ---------- dispatch event rule ---------- + // "detail": { + // "type" : "com.adobe.eventType.edge", + // "source" : "com.adobe.eventSource.requestContent", + // "eventdataaction" : "invalid", + // } + // -------------------------------------- + + resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventInvalidAction.json") + val event = Event.Builder("Application Launch", + "com.adobe.eventType.lifecycle", + "com.adobe.eventSource.applicationLaunch") + .setEventData(mapOf("xdm" to "test data")) + .build() + + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event) + + // / Then: No consequence event will be dispatched + val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) + PowerMockito.verifyStatic(MobileCore::class.java, never()) + MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + + // verify original event is unchanged + assertEquals(event, processedEvent) + } + + @Test + fun `Test Dispatch Event No Action `() { + // / Given: a launch rule to dispatch an event with no action specified in details + + // ---------- dispatch event rule ---------- + // "detail": { + // "type" : "com.adobe.eventType.edge", + // "source" : "com.adobe.eventSource.requestContent" + // } + // -------------------------------------- + + resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventNoAction.json") + val event = Event.Builder("Application Launch", + "com.adobe.eventType.lifecycle", + "com.adobe.eventSource.applicationLaunch") + .setEventData(mapOf("xdm" to "test data")) + .build() + + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event) + + // / Then: No consequence event will be dispatched + val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) + PowerMockito.verifyStatic(MobileCore::class.java, never()) + MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + + // verify original event is unchanged + assertEquals(event, processedEvent) + } + + @Test + fun `Test Dispatch Event No Type `() { + // / Given: a launch rule to dispatch an event with no type specified in details + + // ---------- dispatch event rule ---------- + // "detail": { + // "source" : "com.adobe.eventSource.requestContent", + // "eventdataaction" : "copy" + // } + // -------------------------------------- + resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventNoType.json") + val event = Event.Builder("Application Launch", + "com.adobe.eventType.lifecycle", + "com.adobe.eventSource.applicationLaunch") + .setEventData(mapOf("xdm" to "test data")) + .build() + + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event) + + // / Then: No consequence event will be dispatched + val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) + PowerMockito.verifyStatic(MobileCore::class.java, never()) + MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + + // verify original event is unchanged + assertEquals(event, processedEvent) + } + + @Test + fun `Test Dispatch Event No Source`() { + // / Given: a launch rule to dispatch an event with no source specified in details + + // ---------- dispatch event rule ---------- + // "detail": { + // "type" : "com.adobe.eventType.edge", + // "eventdataaction" : "copy" + // } + // -------------------------------------- + resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventNoSource.json") + val event = Event.Builder("Application Launch", + "com.adobe.eventType.lifecycle", + "com.adobe.eventSource.applicationLaunch") + .setEventData(mapOf("xdm" to "test data")) + .build() + + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event) + + // / Then: No consequence event will be dispatched + val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) + PowerMockito.verifyStatic(MobileCore::class.java, never()) + MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + + // verify original event is unchanged + assertEquals(event, processedEvent) + } + + @Test + fun `Test Chained Dispatch Events`() { + // / Given: a launch rule to dispatch an event with the same type and source which triggered the consequence + + // ---------- dispatch event rule condition ---------- + // "conditions": [ + // { + // "type": "matcher", + // "definition": { + // "key": "~type", + // "matcher": "eq", + // "values": [ + // "com.adobe.eventType.edge" + // ] + // } + // }, + // { + // "type": "matcher", + // "definition": { + // "key": "~source", + // "matcher": "eq", + // "values": [ + // "com.adobe.eventSource.requestContent" + // ] + // } + // } + // ] + // ---------- dispatch event rule consequence ---------- + // "detail": { + // "type" : "com.adobe.eventType.edge", + // "source" : "com.adobe.eventSource.requestContent", + // "eventdataaction" : "copy" + // } + // -------------------------------------- + resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventChain.json") + val event = Event.Builder("Edge Request", + "com.adobe.eventType.edge", + "com.adobe.eventSource.requestContent") + .setEventData(mapOf("xdm" to "test data")) + .build() + + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event) + + // Process original event; dispatch chain count = 0 + val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) + PowerMockito.verifyStatic(MobileCore::class.java, times(1)) + MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + + // Process dispatched event; dispatch chain count = 1 + // Expect dispatch to not be called max allowed chained events is 1 + val secondDispatchEvent = launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value) + PowerMockito.verifyStatic(MobileCore::class.java, times(1)) + MobileCore.dispatchEvent(any(), any()) + } + + private fun resetRulesEngine(rulesFileName: String) { + val json = readTestResources(rulesFileName) + val rules = json?.let { JSONRulesParser.parse(it) } + launchRulesEngine.replaceRules(rules) + } +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt index 51d0321cc..0ef092f09 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt @@ -11,6 +11,7 @@ package com.adobe.marketing.mobile.launch.rulesengine import com.adobe.marketing.mobile.Event +import com.adobe.marketing.mobile.ExtensionApi import com.adobe.marketing.mobile.MobileCore import kotlin.test.assertEquals import kotlin.test.assertNotNull @@ -31,22 +32,26 @@ import org.powermock.modules.junit4.PowerMockRunner import org.powermock.reflect.Whitebox @RunWith(PowerMockRunner::class) -@PrepareForTest(MobileCore::class) +@PrepareForTest(ExtensionApi::class, MobileCore::class) class LaunchRulesEvaluatorTests { @Mock - lateinit var launchRulesEngine: LaunchRulesEngine + private lateinit var launchRulesEngine: LaunchRulesEngine + + private lateinit var extensionApi: ExtensionApi + private lateinit var launchRulesEvaluator: LaunchRulesEvaluator + private val cachedEvents: MutableList = mutableListOf() @Before fun setup() { + extensionApi = PowerMockito.mock(ExtensionApi::class.java) PowerMockito.mockStatic(MobileCore::class.java) + launchRulesEvaluator = LaunchRulesEvaluator("", launchRulesEngine, extensionApi) + Whitebox.setInternalState(launchRulesEvaluator, "cachedEvents", cachedEvents) } @Test fun `Process a null event`() { - val launchRulesEvaluator = LaunchRulesEvaluator("", launchRulesEngine) - val cachedEvents: MutableList = mutableListOf() - Whitebox.setInternalState(launchRulesEvaluator, "cachedEvents", cachedEvents) assertNull(launchRulesEvaluator.process(null)) verify(launchRulesEngine, never()).process(any()) assertEquals(0, cachedEvents.size) @@ -54,9 +59,6 @@ class LaunchRulesEvaluatorTests { @Test fun `Cache incoming events if rules are not set`() { - val launchRulesEvaluator = LaunchRulesEvaluator("", launchRulesEngine) - val cachedEvents: MutableList = mutableListOf() - Whitebox.setInternalState(launchRulesEvaluator, "cachedEvents", cachedEvents) repeat(100) { launchRulesEvaluator.process( Event.Builder("event-$it", "type", "source").build() @@ -67,9 +69,6 @@ class LaunchRulesEvaluatorTests { @Test fun `Clear cached events if reached the limit`() { - val launchRulesEvaluator = LaunchRulesEvaluator("", launchRulesEngine) - val cachedEvents: MutableList = mutableListOf() - Whitebox.setInternalState(launchRulesEvaluator, "cachedEvents", cachedEvents) repeat(101) { launchRulesEvaluator.process( Event.Builder("event-$it", "type", "source").build() @@ -80,9 +79,6 @@ class LaunchRulesEvaluatorTests { @Test fun `Reprocess cached events when rules are ready`() { - val launchRulesEvaluator = LaunchRulesEvaluator("", launchRulesEngine) - val cachedEvents: MutableList = mutableListOf() - Whitebox.setInternalState(launchRulesEvaluator, "cachedEvents", cachedEvents) repeat(10) { launchRulesEvaluator.process( Event.Builder("event-$it", "type", "source").build() @@ -94,6 +90,7 @@ class LaunchRulesEvaluatorTests { launchRulesEvaluator.replaceRules(listOf()) assertNotNull(eventCaptor.value) assertEquals("com.adobe.eventtype.rulesengine", eventCaptor.value.type) + assertEquals("com.adobe.eventtype.rulesengine", eventCaptor.value.type) assertEquals("com.adobe.eventsource.requestreset", eventCaptor.value.source) launchRulesEvaluator.process(eventCaptor.value) assertEquals(0, cachedEvents.size) @@ -101,9 +98,6 @@ class LaunchRulesEvaluatorTests { @Test fun `Reprocess cached events in the right order`() { - val launchRulesEvaluator = LaunchRulesEvaluator("", launchRulesEngine) - val cachedEvents: MutableList = mutableListOf() - Whitebox.setInternalState(launchRulesEvaluator, "cachedEvents", cachedEvents) repeat(10) { launchRulesEvaluator.process( Event.Builder("event-$it", "type", "source").build() @@ -129,9 +123,6 @@ class LaunchRulesEvaluatorTests { @Test fun `Do nothing if set null rule`() { - val launchRulesEvaluator = LaunchRulesEvaluator("", launchRulesEngine) - val cachedEvents: MutableList = mutableListOf() - Whitebox.setInternalState(launchRulesEvaluator, "cachedEvents", cachedEvents) repeat(10) { launchRulesEvaluator.process( Event.Builder("event-$it", "type", "source").build() From eba51da44c6d8674750e07186aec4c676ca9968b Mon Sep 17 00:00:00 2001 From: Praveen Date: Mon, 13 Jun 2022 11:45:06 -0700 Subject: [PATCH 073/476] Helper classes to clone and read from event data (#49) * Helper methods to clone objects and read data * Deep clone event data as immutable map * Review comments * Ignore failing tests Ignored the tests because event data had been made immutable. These tests will be updated as part of rules engine PR. * Support cloning arrays and fix functional tests --- ...idThirdPartyExtensionsFunctionalTests.java | 2 +- .../marketing/mobile/EventCoderTests.java | 7 +- .../com/adobe/marketing/mobile/Event.java | 48 +- .../mobile/utils/CloneFailedException.java | 43 ++ .../marketing/mobile/utils/DataReader.java | 510 ++++++++++++++++++ .../mobile/utils/DataReaderException.java | 36 ++ .../mobile/utils/EventDataUtils.java | 158 ++++++ .../com/adobe/marketing/mobile/EventTest.java | 4 +- .../marketing/mobile/RulesEngineTests.java | 7 +- .../mobile/utils/DataReaderTests.java | 489 +++++++++++++++++ .../mobile/utils/EventDataUtilsTests.java | 222 ++++++++ 11 files changed, 1501 insertions(+), 25 deletions(-) create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/CloneFailedException.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/DataReader.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/DataReaderException.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/EventDataUtils.java create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/utils/DataReaderTests.java create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/utils/EventDataUtilsTests.java diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java index 732429473..d705edab0 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java @@ -116,7 +116,7 @@ public void setUp() { customElement.put("customString", "string1"); customElement.put("customNull", null); customElement.put("customBoolean", true); - customElement.put("customList", customListenerTypes); + //customElement.put("customList", customListenerTypes); customElement.put("customDouble", new Double(3.14)); customElement.put("customMap", subEventData); Map customData = new HashMap(); diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/EventCoderTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/EventCoderTests.java index a85e7efa7..605fb7909 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/EventCoderTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/EventCoderTests.java @@ -100,6 +100,7 @@ public void testEncodeDecode_When_AllTheFieldsAreValid() { @Test public void testEncodeDecode_When_AllSupportEventDataTypes() { + // Todo revisit these tests Map data = new HashMap() { { put("int", 3); @@ -112,7 +113,7 @@ public void testEncodeDecode_When_AllSupportEventDataTypes() { { put("int", 3); put("doube", 3.11d); - put("long", 3l); + put("long", Long.MAX_VALUE); put("null", null); put("String", "abcd"); put("boolean", true); @@ -123,7 +124,7 @@ public void testEncodeDecode_When_AllSupportEventDataTypes() { { put("int", 3); put("doube", 3.11d); - put("long", 3l); + put("long", Long.MAX_VALUE); put("null", null); put("String", "abcd"); put("boolean", true); @@ -133,7 +134,7 @@ public void testEncodeDecode_When_AllSupportEventDataTypes() { { put("int", 3); put("doube", 3.11d); - put("long", 3l); + put("long", Long.MAX_VALUE); put("null", null); put("String", "abcd"); put("boolean", true); diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java index b19b92ca7..7d4562e9f 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java @@ -11,6 +11,8 @@ package com.adobe.marketing.mobile; +import com.adobe.marketing.mobile.utils.EventDataUtils; + import java.util.*; import java.util.concurrent.TimeUnit; @@ -27,7 +29,7 @@ public final class Event { private EventType type; private String pairID; private String responsePairID; - private EventData data; + private Map data; private long timestamp; private int eventNumber; // Specifies the properties in the Event and its data that should be used in the hash for EventHistory storage. @@ -75,7 +77,6 @@ public static class Builder { event.uniqueIdentifier = UUID.randomUUID().toString(); event.type = type; event.source = source; - event.data = new EventData(); event.responsePairID = UUID.randomUUID().toString(); event.eventNumber = 0; event.mask = mask; @@ -134,10 +135,9 @@ public Builder setEventData(final Map data) { throwIfAlreadyBuilt(); try { - event.data = EventData.fromObjectMap(data); + event.data = EventDataUtils.immutableClone(data); } catch (final Exception e) { Log.warning("EventBuilder", "Event data couldn't be serialized, empty data was set instead %s", e); - event.data = new EventData(); } return this; @@ -157,6 +157,10 @@ public Event build() { return null; } + if (event.data == null) { + event.data = new HashMap<>(); + } + if (event.timestamp == 0) { event.timestamp = System.currentTimeMillis(); } @@ -171,9 +175,15 @@ public Event build() { * @return this Event {@link Builder} * @throws UnsupportedOperationException if this method is called after {@link Builder#build()} was called */ + @Deprecated Builder setData(final EventData data) { throwIfAlreadyBuilt(); - event.data = data; + // Todo - Remove this method once all EventData usage is removed from Core. + try { + event.data = EventDataUtils.immutableClone(data.toObjectMap()); + } catch (Exception ex) { + Log.error("Error", ex.toString()); + } return this; } @@ -320,14 +330,7 @@ public String getType() { * or if an error occurred while processing */ public Map getEventData() { - try { - return data.toObjectMap(); - } catch (final Exception e) { - Log.warning("EventBuilder", "An error occurred while retrieving the event data for %s and %s, %s", type.getName(), - source.getName(), e); - } - - return null; + return data; } /** @@ -354,8 +357,15 @@ EventType getEventType() { /** * @return event parameters */ + @Deprecated EventData getData() { - return data; + // Todo - Remove this method once all EventData usage is removed from Core. + try { + return EventData.fromObjectMap(data); + } catch (Exception ex) { + Log.warning("Event", "Error creating EventData instance %s", ex); + return new EventData(); + } } /** @@ -458,9 +468,13 @@ public String toString() { sb.append(" pairId: ").append(pairID).append(COMMA).append(NEWLINE); sb.append(" responsePairId: ").append(responsePairID).append(COMMA).append(NEWLINE); sb.append(" timestamp: ").append(timestamp).append(COMMA).append(NEWLINE); - sb.append(" data: ").append(data.prettyString(2)).append(NEWLINE); - sb.append(" mask: ").append(Arrays.toString(mask)).append(COMMA).append(NEWLINE); - sb.append(" fnv1aHash: ").append(data.toFnv1aHash(mask)).append(NEWLINE); + // Todo - Remove this once we completly remove EventData from core. + try { + EventData data = EventData.fromObjectMap(this.data); + sb.append(" data: ").append(data.prettyString(2)).append(NEWLINE); + sb.append(" mask: ").append(Arrays.toString(mask)).append(COMMA).append(NEWLINE); + sb.append(" fnv1aHash: ").append(data.toFnv1aHash(mask)).append(NEWLINE); + } catch (VariantException ex) {} sb.append("}"); return sb.toString(); diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/CloneFailedException.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/CloneFailedException.java new file mode 100644 index 000000000..a0d764732 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/CloneFailedException.java @@ -0,0 +1,43 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.utils; + +/** + * Exception thrown by {@link EventDataUtils} to indicate that an + * exception occurred during deep clone. + */ +public class CloneFailedException extends Exception { + /** + * Constructor. + */ + public CloneFailedException() { + super("Object clone error occurred."); + } + + /** + * Constructor. + * + * @param message {@code String} message for the exception + */ + public CloneFailedException(final String message) { + super(message); + } + + /** + * Constructor. + * + * @param inner {@code Exception} that caused this exception + */ + public CloneFailedException(final Exception inner) { + super(inner); + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/DataReader.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/DataReader.java new file mode 100644 index 000000000..5d1c90a47 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/DataReader.java @@ -0,0 +1,510 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.utils; + +import java.util.List; +import java.util.Map; + +/** + * Utility to read data from {@code Event} data which is represented as {@code Map} in a type safe way. + * + *

    The value of an {@code Event} data key can be obtained in multiple ways:

    + *
      + *
    • + * The {@code DataReader.optXyz(...)} methods are typically the best choice for users. These + * methods return the value as an {@code xyz}. If the value is missing or is not an + * {@code xyz}, the method will return a default value. Implicit conversions between types + * are not performed, except between numeric types. No implicit conversions even between numeric + * types are performed when reading as {@code Map} or {@code List}. + *
    • + *
    • + * The {@code DataReader.getXyz(...)} methods return the value as an {@code xyz}. If the value + * is missing or is not an {@code xyz} the method will throw. Implicit conversions between + * types are not performed, except between numeric types. No implicit conversions even between + * numeric types are performed when reading as {@code Map} or {@code List}. + *
    • + * + *
    + * + */ +public class DataReader { + + private static boolean checkOverflow(Class clazz, Number n) { + if (Double.class.equals(clazz)) { + return false; + } else if (Float.class.equals(clazz)) { + if (n instanceof Double) { + double valAsDouble = n.doubleValue(); + return valAsDouble < Float.MIN_VALUE || valAsDouble > Float.MAX_VALUE; + } + return false; + } else if (Long.class.equals(clazz)) { + if (n instanceof Double || n instanceof Float) { + double valAsDouble = n.doubleValue(); + return valAsDouble < Long.MIN_VALUE || valAsDouble > Long.MAX_VALUE; + } + return false; + } else if (Integer.class.equals(clazz)) { + if (n instanceof Double || n instanceof Float) { + double valAsDouble = n.doubleValue(); + return valAsDouble < Integer.MIN_VALUE || valAsDouble > Integer.MAX_VALUE; + } else { + long valAsLong = n.longValue(); + return valAsLong < Integer.MIN_VALUE || valAsLong > Integer.MAX_VALUE; + } + } else if (Short.class.equals(clazz)) { + if (n instanceof Double || n instanceof Float) { + double valAsDouble = n.doubleValue(); + return valAsDouble < Short.MIN_VALUE || valAsDouble > Short.MAX_VALUE; + } else { + long valAsLong = n.longValue(); + return valAsLong < Short.MIN_VALUE || valAsLong > Short.MAX_VALUE; + } + } else if (Byte.class.equals(clazz)) { + if (n instanceof Double || n instanceof Float) { + double valAsDouble = n.doubleValue(); + return valAsDouble < Byte.MIN_VALUE || valAsDouble > Byte.MAX_VALUE; + } else { + long valAsLong = n.longValue(); + return valAsLong < Byte.MIN_VALUE || valAsLong > Byte.MAX_VALUE; + } + } + + return false; + } + + private static T castObject(Class tClass, Object obj) throws DataReaderException { + if (obj == null) { + return null; + } + + try { + if (Number.class.isAssignableFrom(tClass) && obj instanceof Number) { + Number objAsNumber = (Number) obj; + + if (DataReader.checkOverflow(tClass, objAsNumber)) { + throw new DataReaderException("Value overflows type "+ tClass); + } + + if (Byte.class.equals(tClass)) { + return (T) Byte.valueOf(objAsNumber.byteValue()); + } else if (Short.class.equals(tClass)) { + return (T) Short.valueOf(objAsNumber.shortValue()); + } else if (Integer.class.equals(tClass)) { + return (T) Integer.valueOf(objAsNumber.intValue()); + } else if (Long.class.equals(tClass)) { + return (T) Long.valueOf(objAsNumber.longValue()); + } else if (Double.class.equals(tClass)) { + return (T) Double.valueOf(objAsNumber.doubleValue()); + } else if (Float.class.equals(tClass)) { + return (T) Float.valueOf(objAsNumber.floatValue()); + } + } else if (String.class.equals(tClass)) { + // Todo :- Check if we should match the behavior with Variant. It has custom logic to + // convert types to string. + return (T) obj.toString(); + } else { + return tClass.cast(obj); + } + } catch (ClassCastException ex) { + throw new DataReaderException(ex); + } + + return null; + } + + /** + * Gets the value for {@code key} from {@code map} as a custom object. + * + * @param Custom type + * @param tClass Custom class + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @return {@code T} value associated with {@code key} or null if {@code key} is not present in {@code map} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + * @throws DataReaderException if value is not gettable as a {@code T} + */ + private static T getTypedObject(Class tClass, Map map, String key) throws DataReaderException { + if (map == null || key == null) { + throw new IllegalArgumentException("Map or key is null"); + } + + Object value = map.get(key); + return castObject(tClass, value); + } + + /** + * Gets the value for {@code key} from {@code map} as a custom object or returns default value + * + * @param Custom type + * @param tClass Custom class + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @param fallback {@code T} value to return in case of failure. Can be null. + * @return {@code T} value associated with {@code key}, or {@code fallback} if value is + * not gettable as a {@code T} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + */ + private static T optTypedObject(Class tClass, Map map, String key, T fallback) { + T ret = null; + try { + ret = getTypedObject(tClass, map, key); + } catch (DataReaderException ex) {} + return ret != null ? ret : fallback; + } + + /** + * Gets the value for {@code key} from {@code map} as a {@code Map} + * + * @param Custom type + * @param tClass Custom class + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @return {@code Map} Map associated with {@code key} or null if {@code key} is not present in {@code map} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + * @throws DataReaderException if value is not gettable as a {@code Map} + */ + public static Map getTypedMap(Class tClass, Map map, String key) throws DataReaderException { + if (map == null || key == null) { + throw new IllegalArgumentException("Map or key is null"); + } + + Object value = map.get(key); + if (value == null) { + return null; + } + + if (!(value instanceof Map)) { + throw new DataReaderException("Value is not a map"); + } + + Map valueAsMap = (Map)value; + for (Map.Entry kv : valueAsMap.entrySet()) { + if (!(kv.getKey() instanceof String) || + !tClass.isInstance(kv.getValue())) { + throw new DataReaderException("Map entry is not of expected type"); + } + } + return (Map) valueAsMap; + } + + /** + * Gets the value for {@code key} from {@code map} as a {@code Map} or returns default value + * + * @param Custom type + * @param tClass Custom class + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @param fallback {@code Map} value to return in case of failure. Can be null. + * @return {@code Map} Map associated with {@code key}, or {@code fallback} if value is + * not gettable as a {@code Map} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + */ + public static Map optTypedMap(Class tClass, Map map, String key, Map fallback) { + Map ret = null; + try { + ret = getTypedMap(tClass, map, key); + } catch (DataReaderException ex) {} + return ret != null ? ret : fallback; + } + + /** + * Gets the value for {@code key} from {@code map} as a {@code List} + * + * @param Custom type + * @param tClass Custom class + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @return {@code List} List associated with {@code key} or null if {@code key} is not present in {@code map} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + * @throws DataReaderException if value is not gettable as a {@code List} + */ + public static List getTypedList(Class tClass, Map map, String key) throws DataReaderException { + if (map == null || key == null) { + throw new IllegalArgumentException("Map or key is null"); + } + + Object value = map.get(key); + if (value == null) { + return null; + } + + if (!(value instanceof List)) { + throw new DataReaderException("Value is not a list"); + } + + List valueAsList = (List) value; + for (Object obj : valueAsList) { + if (!tClass.isInstance(obj)) { + throw new DataReaderException("List entry is not of expected type"); + } + } + + return (List) valueAsList; + } + + /** + * Gets the value for {@code key} from {@code map} as a {@code List} or returns default value + * + * @param Custom type + * @param tClass Custom class + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @param fallback {@code List} value to return in case of failure. Can be null. + * @return {@code List} List associated with {@code key}, or {@code fallback} if value is + * not gettable as a {@code List} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + */ + public static List optTypedList(Class tClass, Map map, String key, List fallback) { + List ret = null; + try { + ret = getTypedList(tClass, map, key); + } catch (DataReaderException ex) {} + return ret != null ? ret : fallback; + } + + /** + * Gets the value for {@code key} from {@code map} as a {@code boolean} + * + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @return {@code boolean} value associated with {@code key} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + * @throws DataReaderException if value is not gettable as a {@code boolean} or if {@code key} is not present in {@code map} + */ + public static boolean getBoolean(Map map, String key) throws DataReaderException { + Boolean ret = getTypedObject(Boolean.class, map, key); + if (ret == null) { + throw new DataReaderException("Map contains null value for key"); + } + return ret; + } + + /** + * Gets the value for {@code key} from {@code map} as a {@code boolean} or returns default value + * + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @param fallback {@code boolean} value to return in case of failure. Can be null. + * @return {@code boolean} value associated with {@code key}, or {@code fallback} if value is + * not gettable as a {@code boolean} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + */ + public static boolean optBoolean(Map map, String key, boolean fallback) { + return optTypedObject(Boolean.class, map, key, fallback); + } + + /** + * Gets the value for {@code key} from {@code map} as an {@code int} + * + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @return {@code int} value associated with {@code key} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + * @throws DataReaderException if value is not gettable as an {@code int} or if {@code key} is not present in {@code map} + */ + public static int getInt(Map map, String key) throws DataReaderException { + Integer ret = getTypedObject(Integer.class, map, key); + if (ret == null) { + throw new DataReaderException("Map contains null value for key"); + } + return ret; + } + + /** + * Gets the value for {@code key} from {@code map} as an {@code int} or returns default value + * + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @param fallback {@code int} value to return in case of failure. Can be null. + * @return {@code int} value associated with {@code key}, or {@code fallback} if value is + * not gettable as a {@code int} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + */ + public static int optInt(Map map, String key, int fallback) { + return optTypedObject(Integer.class, map, key, fallback); + } + + /** + * Gets the value for {@code key} from {@code map} as an {@code long} + * + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @return {@code long} value associated with {@code key} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + * @throws DataReaderException if value is not gettable as an {@code long} or if {@code key} is not present in {@code map} + */ + public static long getLong(Map map, String key) throws DataReaderException { + Long ret = getTypedObject(Long.class, map, key); + if (ret == null) { + throw new DataReaderException("Map contains null value for key"); + } + return ret; + } + + /** + * Gets the value for {@code key} from {@code map} as an {@code long} or returns default value + * + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @param fallback {@code long} value to return in case of failure. Can be null. + * @return {@code long} value associated with {@code key}, or {@code fallback} if value is + * not gettable as a {@code long} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + */ + public static long optLong(Map map, String key, long fallback) { + return optTypedObject(Long.class, map, key, fallback); + } + + /** + * Gets the value for {@code key} from {@code map} as an {@code float} + * + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @return {@code float} value associated with {@code key} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + * @throws DataReaderException if value is not gettable as an {@code float} or if {@code key} is not present in {@code map} + */ + public static float getFloat(Map map, String key) throws DataReaderException { + Float ret = getTypedObject(Float.class, map, key); + if (ret == null) { + throw new DataReaderException("Map contains null value for key"); + } + return ret; + } + + /** + * Gets the value for {@code key} from {@code map} as an {@code float} or returns default value + * + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @param fallback {@code float} value to return in case of failure. Can be null. + * @return {@code float} value associated with {@code key}, or {@code fallback} if value is + * not gettable as a {@code float} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + */ + public static float optFloat(Map map, String key, float fallback) { + return optTypedObject(Float.class, map, key, fallback); + } + + /** + * Gets the value for {@code key} from {@code map} as an {@code double} + * + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @return {@code double} value associated with {@code key} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + * @throws DataReaderException if value is not gettable as an {@code double} or if {@code key} is not present in {@code map} + */ + public static double getDouble(Map map, String key) throws DataReaderException { + Double ret = getTypedObject(Double.class, map, key); + if (ret == null) { + throw new DataReaderException("Map contains null value for key"); + } + return ret; + } + + /** + * Gets the value for {@code key} from {@code map} as an {@code double} or returns default value + * + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @param fallback {@code double} value to return in case of failure. Can be null. + * @return {@code double} value associated with {@code key}, or {@code fallback} if value is + * not gettable as a {@code double} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + */ + public static double optDouble(Map map, String key, double fallback) { + return optTypedObject(Double.class, map, key, fallback); + } + + /** + * Gets the value for {@code key} from {@code map} as an {@code String} + * + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @return {@code String} value associated with {@code key} or null if {@code key} is not present in {@code map} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + * @throws DataReaderException if value is not gettable as an {@code String} + */ + public static String getString(Map map, String key) throws DataReaderException { + return getTypedObject(String.class, map, key); + } + + /** + * Gets the value for {@code key} from {@code map} as a {@code String} or returns default value + * + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @param fallback {@code String} value to return in case of failure. Can be null. + * @return {@code String} value associated with {@code key}, or {@code fallback} if value is + * not gettable as a {@code String} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + */ + public static String optString(Map map, String key, String fallback) { + return optTypedObject(String.class, map, key, fallback); + } + + /** + * Gets the value for {@code key} from {@code map} as a {@code Map} + * + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @return {@code Map} Map associated with {@code key} or null if {@code key} is not present in {@code map} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + * @throws DataReaderException if value is not gettable as a {@code Map} + */ + public static Map getStringMap(Map map, String key) throws DataReaderException { + return getTypedMap(String.class, map, key); + } + + /** + * Gets the value for {@code key} from {@code map} as a {@code Map} or returns default value + * + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @param fallback {@code Map} value to return in case of failure. Can be null. + * @return {@code Map} value associated with {@code key}, or {@code fallback} if value is + * not gettable as a {@code Map} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + */ + public static Map optStringMap(Map map, String key, Map fallback) { + return optTypedMap(String.class, map, key, fallback); + } + + /** + * Gets the value for {@code key} from {@code map} as a {@code List} + * + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @return {@code List} List associated with {@code key} or null if {@code key} is not present in {@code map} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + * @throws DataReaderException if value is not gettable as a {@code List} + */ + public static List getStringList(Map map, String key) throws DataReaderException { + return getTypedList(String.class, map, key); + } + + /** + * Gets the value for {@code key} from {@code map} as a {@code List} or returns default value + * + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch + * @param fallback {@code List} value to return in case of failure. Can be null. + * @return {@code List} List associated with {@code key}, or {@code fallback} if value is + * not gettable as a {@code List} + * @throws IllegalArgumentException if {@code map} or {@code key} is null + */ + public static List optStringList(Map map, String key, List fallback) { + return optTypedList(String.class, map, key, fallback); + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/DataReaderException.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/DataReaderException.java new file mode 100644 index 000000000..3326658d1 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/DataReaderException.java @@ -0,0 +1,36 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.utils; + +/** + * Exception thrown by {@link DataReader} to indicate an error occurred + * reading object. + */ +public class DataReaderException extends Exception { + /** + * Constructor. + * + * @param message {@code String} message for the exception + */ + public DataReaderException(final String message) { + super(message); + } + + /** + * Constructor. + * + * @param inner {@code Exception} that caused this exception + */ + public DataReaderException(final Exception inner) { + super(inner); + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/EventDataUtils.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/EventDataUtils.java new file mode 100644 index 000000000..63d6b74b2 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/EventDataUtils.java @@ -0,0 +1,158 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.utils; + +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** + * Utility to clone event data which is represented as {@code Map}. + * Currently supports cloning values which are Boolean, Byte, Short, Integer, Long, Float, Double, + * BigDecimal, BigInteger, Character, String, UUID, Maps and Collections. + */ +public class EventDataUtils { + private static final int MAX_DEPTH = 256; + + private enum CloneMode { + ImmutableContainer, + MutableContainer + } + + private static final Set> immutableClasses; + static { + immutableClasses = new HashSet<>(); + immutableClasses.add(Boolean.class); + immutableClasses.add(Byte.class); + immutableClasses.add(Short.class); + immutableClasses.add(Integer.class); + immutableClasses.add(Long.class); + immutableClasses.add(Float.class); + immutableClasses.add(Double.class); + immutableClasses.add(BigDecimal.class); + immutableClasses.add(BigInteger.class); + immutableClasses.add(Character.class); + immutableClasses.add(String.class); + immutableClasses.add(UUID.class); + } + + private EventDataUtils(){} + + private static Object cloneObject(Object obj, CloneMode mode, int depth) throws CloneFailedException { + if (obj == null) { + return null; + } + + if (depth > MAX_DEPTH) { + throw new CloneFailedException("Max depth reached"); + } + + if (immutableClasses.contains(obj.getClass())) { + return obj; + } + + if (obj instanceof Map) { + return cloneMap((Map) obj, mode ,depth); + } else if (obj instanceof Collection) { + return cloneCollection((Collection) obj, mode, depth); + } else if (obj.getClass().isArray()) { + return cloneArray(obj, mode, depth); + } + else { + throw new CloneFailedException("Object is of unsupported type"); + } + } + + private static Map cloneMap(Map map, CloneMode mode, int depth) throws CloneFailedException { + if (map == null) return null; + + Map ret = new HashMap<>(); + for (Map.Entry kv : map.entrySet()) { + + Object key = kv.getKey(); + if (key != null && key instanceof String) { + Object clonedValue = cloneObject(kv.getValue(), mode, depth + 1); + ret.put(key.toString(), clonedValue); + } + } + return mode == CloneMode.ImmutableContainer ? Collections.unmodifiableMap(ret): ret; + } + + private static Collection cloneCollection(Collection collection, CloneMode mode, int depth) throws CloneFailedException { + if (collection == null) return null; + + List ret = new ArrayList<>(); + for (Object element : collection) { + Object clonedElement = cloneObject(element, mode, depth + 1); + ret.add(clonedElement); + } + return mode == CloneMode.ImmutableContainer ? Collections.unmodifiableList(ret): ret; + } + + private static Collection cloneArray(Object array, CloneMode mode, int depth) throws CloneFailedException { + if (array == null) return null; + + List ret = new ArrayList<>(); + + int length = Array.getLength(array); + for (int i = 0; i < length; ++i) { + ret.add(cloneObject(Array.get(array, i), mode, depth + 1)); + } + + return mode == CloneMode.ImmutableContainer ? Collections.unmodifiableList(ret): ret; + } + + /** + * Deep clones the provided map. Support cloning values which are basic types, maps and collections. + *
    + * Values which are {@code Map} are cloned as {@code HashMap}. + *
      + *
    • Entry with null key is dropped.
    • + *
    • Entry with non {@code String} key is converted to entry with {@code String} key by calling {@link Object#toString()} method.
    • + *
    + * Values which are {@code Collection} are cloned as {@code ArrayList}. + * + * @param map map to be cloned + * @return Cloned map + * @throws CloneFailedException if object depth exceeds {@value EventDataUtils#MAX_DEPTH} or contains unsupported type. + */ + public static Map clone(Map map) throws CloneFailedException { + return cloneMap(map, CloneMode.MutableContainer, 0); + } + + /** + * Deep clones the provided map. Support cloning values which are basic types, maps and collections. + *
    + * Values which are {@code Map} are cloned as unmodifiable {@code HashMap}. + *
      + *
    • Entry with null key is dropped.
    • + *
    • Entry with non {@code String} key is converted to entry with {@code String} key by calling {@link Object#toString()} method.
    • + *
    + * Values which are {@code Collection} are cloned as unmodifiable {@code ArrayList}. + * + * @param map map to be cloned + * @return Cloned immutable map + * @throws CloneFailedException if object depth exceeds {@value EventDataUtils#MAX_DEPTH} or contains unsupported type. + */ + public static Map immutableClone(Map map) throws CloneFailedException { + return cloneMap(map, CloneMode.ImmutableContainer, 0); + } +} \ No newline at end of file diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/EventTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/EventTest.java index 034ad60e3..c7e70e822 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/EventTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/EventTest.java @@ -202,9 +202,9 @@ public void Event_getEventDataMap_returnedMapIsImmutable() throws Exception { Map resultData = event.getEventData(); assertEquals(1, resultData.size()); - resultData.put("new", "key"); + assertThrows(UnsupportedOperationException.class, () -> {resultData.put("new", "key");}); assertEquals(1, event.getEventData().size()); - resultData.clear(); + assertThrows(UnsupportedOperationException.class, () -> {resultData.clear();}); assertEquals(1, event.getEventData().size()); } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RulesEngineTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RulesEngineTests.java index 82e8852fd..54f81ce6f 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RulesEngineTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/RulesEngineTests.java @@ -13,6 +13,7 @@ import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import java.util.ArrayList; @@ -471,6 +472,7 @@ public void addRule_When_ModuleAlreadyInModuleMap_Then_RuleIsAddedToExistingModu // protected List evaluateRules(final Event triggerEvent) // ================================================================================================================= @Test + @Ignore public void evaluateRules_When_Happy_Then_ShouldProperlyHandleVariousRuleConsequenceTypes() throws Exception { // setup final Event triggeringEvent = GetTriggeringEvent(); @@ -823,8 +825,9 @@ public void evaluateEventWithRules_When_MultipleConsequencesIncludingDispatch_Sh // ================================================================================================================= // protected void processAttachDataConsequence(final Map consequenceMap, final Event triggeringEvent) - // ================================================================================================================= + // =================================================================================================================evaluateRules_When_Happy_Then_ShouldProperlyHandleVariousRuleConsequenceTypes @Test + @Ignore public void processAttachDataConsequence_When_Happy_Then_ShouldAttachDataButNotOverwriteExistingData() throws Exception { // setup @@ -996,6 +999,7 @@ public void processAttachDataConsequence_When_ConsequenceMapHasNoDetail_Then_Sho // protected void processModifyDataConsequence(final Map consequenceMap, final Event triggeringEvent) // ================================================================================================================= @Test + @Ignore public void processModifyDataConsequence_When_Happy_Then_ShouldAttachDataButNotOverwriteExistingData() throws Exception { // setup @@ -1026,7 +1030,6 @@ public void processModifyDataConsequence_When_Happy_Then_ShouldAttachDataButNotO assertFalse(newData.containsKey("aList")); } - @Test public void processModifyDataConsequence_With_ListOfObjects_ShouldAttachDataButNotOverwriteExistingData() throws Exception { // setup diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/utils/DataReaderTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/utils/DataReaderTests.java new file mode 100644 index 000000000..861791a1c --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/utils/DataReaderTests.java @@ -0,0 +1,489 @@ +package com.adobe.marketing.mobile.utils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +interface CheckedBiConsumer { + void accept(Map map, String data) throws Exception; +} + +interface CheckedConsumer { + void accept(String data) throws Exception; +} + + +public class DataReaderTests { + final String NULL_KEY_ERROR_MSG = "Map or key is null"; + final String NULL_VALUE_ERROR_MSG = "Map contains null value for key"; + final String OVERFLOW_ERROR_MSG = "Value overflows type "; + final String MAP_ERROR_MSG = "Value is not a map"; + final String MAP_ENTRY_ERROR_MSG = "Map entry is not of expected type"; + final String LIST_ERROR_MSG = "Value is not a list"; + final String LIST_ENTRY_ERROR_MSG = "List entry is not of expected type"; + + final double DELTA = 0.000001d; + + Map data = new HashMap<>();{ + data.put("BOOL_TRUE", Boolean.TRUE); + data.put("BOOL_FALSE", Boolean.FALSE); + + data.put("BYTE_MAX", Byte.MAX_VALUE); + data.put("BYTE", (byte) 1); + data.put("SHORT_MAX", Short.MAX_VALUE); + data.put("SHORT", (short) 2); + data.put("INT_MAX", Integer.MAX_VALUE); + data.put("INT", 3); + data.put("LONG_MAX", Long.MAX_VALUE); + data.put("LONG", 4L); + data.put("FLOAT_MAX", Float.MAX_VALUE); + data.put("FLOAT", 5.5F); + data.put("DOUBLE_MAX", Double.MAX_VALUE); + data.put("DOUBLE", 6.6); + + data.put("STRING", "STRING"); + data.put("NULL", null); + data.put("EMPTY_MAP", new HashMap<>()); + data.put("EMPTY_LIST", new ArrayList<>()); + + data.put("INT_MAP", new HashMap() {{ + put("a", 1); + put("b", 2); + }}); + data.put("INT_LIST", Arrays.asList(1, 2)); + + data.put("STRING_MAP", new HashMap() {{ + put("a", "a"); + put("b", "b"); + }}); + data.put("STRING_LIST", Arrays.asList("a", "b")); + } + + void checkIllegalArgumentException(CheckedBiConsumer fn) { + Exception ex; + ex = assertThrows(IllegalArgumentException.class, () -> fn.accept(null, "key")); + assertEquals(NULL_KEY_ERROR_MSG, ex.getMessage()); + + ex = assertThrows(IllegalArgumentException.class, () -> fn.accept(data, null)); + assertEquals(NULL_KEY_ERROR_MSG, ex.getMessage()); + } + + void checkCastException(CheckedConsumer fn, List keys) { + keys.forEach((key) -> { + Exception ex = assertThrows(DataReaderException.class, () -> fn.accept(key)); + if (ex != null && ex.getCause() != null) { + assertEquals(ClassCastException.class, ex.getCause().getClass()); + } else { + fail(); + } + }); + } + + void checkExceptionMessage(CheckedConsumer fn, List keys, String errorMessage) { + keys.forEach((key) -> { + Exception ex = assertThrows(DataReaderException.class, () -> fn.accept(key)); + assertEquals(ex.getMessage(), errorMessage); + }); + } + + @Test + public void testReadBoolean() throws Exception { + assertTrue(DataReader.getBoolean(data, "BOOL_TRUE")); + assertFalse(DataReader.getBoolean(data, "BOOL_FALSE")); + + checkIllegalArgumentException(DataReader::getBoolean); + checkExceptionMessage((key) -> DataReader.getBoolean(data, key), Arrays.asList("NULL", "INVALID"), NULL_VALUE_ERROR_MSG); + checkCastException((key) -> DataReader.getBoolean(data, key), Arrays.asList("FLOAT","BYTE", "INT", "DOUBLE", "LONG", "STRING")); + } + + @Test + public void testOptBoolean() { + assertTrue(DataReader.optBoolean(data, "BOOL_TRUE", false)); + assertFalse(DataReader.optBoolean(data, "BOOL_FALSE", true)); + + checkIllegalArgumentException((m, k) -> DataReader.optBoolean(m, k, true)); + assertTrue(DataReader.optBoolean(data, "FLOAT", true)); + assertTrue(DataReader.optBoolean(data, "LONG", true)); + assertTrue(DataReader.optBoolean(data, "STRING", true)); + assertTrue(DataReader.optBoolean(data, "NULL", true)); + assertTrue(DataReader.optBoolean(data, "INVALID", true)); + } + + @Test + public void testReadInt() throws Exception { + assertEquals(Integer.MAX_VALUE, DataReader.getInt(data, "INT_MAX")); + assertEquals(Short.MAX_VALUE, DataReader.getInt(data, "SHORT_MAX")); + assertEquals(Byte.MAX_VALUE, DataReader.getInt(data, "BYTE_MAX")); + + assertEquals(1, DataReader.getInt(data, "BYTE")); + assertEquals(2, DataReader.getInt(data, "SHORT")); + assertEquals(3, DataReader.getInt(data, "INT")); + assertEquals(4, DataReader.getInt(data, "LONG")); + assertEquals(5, DataReader.getInt(data, "FLOAT")); + assertEquals(6, DataReader.getInt(data, "DOUBLE")); + + checkIllegalArgumentException(DataReader::getInt); + checkExceptionMessage((key) -> DataReader.getInt(data, key), Arrays.asList("NULL", "INVALID"), NULL_VALUE_ERROR_MSG); + + String errorMessage = OVERFLOW_ERROR_MSG + Integer.class; + checkExceptionMessage((key) -> DataReader.getInt(data, key), Arrays.asList("LONG_MAX", "FLOAT_MAX", "DOUBLE_MAX"), errorMessage); + + checkCastException((key) -> DataReader.getInt(data, key), Arrays.asList("STRING", "STRING_MAP", "STRING_LIST")); + } + + @Test + public void testOptInt() { + assertEquals(Integer.MAX_VALUE, DataReader.optInt(data, "INT_MAX", 123)); + assertEquals(Short.MAX_VALUE, DataReader.optInt(data, "SHORT_MAX", 123)); + assertEquals(Byte.MAX_VALUE, DataReader.optInt(data, "BYTE_MAX", 123)); + + assertEquals(1, DataReader.optInt(data, "BYTE", 123)); + assertEquals(2, DataReader.optInt(data, "SHORT", 123)); + assertEquals(3, DataReader.optInt(data, "INT", 123)); + assertEquals(4, DataReader.optInt(data, "LONG", 123)); + assertEquals(5, DataReader.optInt(data, "FLOAT", 123)); + assertEquals(6, DataReader.optInt(data, "DOUBLE", 123)); + + assertEquals(123, DataReader.optInt(data, "LONG_MAX", 123)); + assertEquals(123, DataReader.optInt(data, "FLOAT_MAX", 123)); + assertEquals(123, DataReader.optInt(data, "DOUBLE_MAX", 123)); + assertEquals(123, DataReader.optInt(data, "STRING", 123)); + assertEquals(123, DataReader.optInt(data, "NULL", 123)); + assertEquals(123, DataReader.optInt(data, "INVALID", 123)); + + checkIllegalArgumentException((m, k) -> DataReader.optInt(m, k, 123)); + } + + @Test + public void testReadLong() throws Exception { + assertEquals(Long.MAX_VALUE, DataReader.getLong(data, "LONG_MAX")); + assertEquals(Integer.MAX_VALUE, DataReader.getLong(data, "INT_MAX")); + assertEquals(Short.MAX_VALUE, DataReader.getLong(data, "SHORT_MAX")); + assertEquals(Byte.MAX_VALUE, DataReader.getLong(data, "BYTE_MAX")); + + assertEquals(1, DataReader.getLong(data, "BYTE")); + assertEquals(2, DataReader.getLong(data, "SHORT")); + assertEquals(3, DataReader.getLong(data, "INT")); + assertEquals(4, DataReader.getLong(data, "LONG")); + assertEquals(5, DataReader.getLong(data, "FLOAT")); + assertEquals(6, DataReader.getLong(data, "DOUBLE")); + + checkIllegalArgumentException(DataReader::getLong); + checkExceptionMessage((key) -> DataReader.getLong(data, key), Arrays.asList("NULL", "INVALID"), NULL_VALUE_ERROR_MSG); + + String errorMessage = OVERFLOW_ERROR_MSG + Long.class; + checkExceptionMessage((key) -> DataReader.getLong(data, key), Arrays.asList("FLOAT_MAX", "DOUBLE_MAX"), errorMessage); + + checkCastException((key) -> DataReader.getLong(data, key), Arrays.asList("STRING","STRING_MAP", "STRING_LIST")); + } + + @Test + public void testOptLong() { + assertEquals(Long.MAX_VALUE, DataReader.optLong(data, "LONG_MAX", 123)); + assertEquals(Integer.MAX_VALUE, DataReader.optLong(data, "INT_MAX", 123)); + assertEquals(Short.MAX_VALUE, DataReader.optLong(data, "SHORT_MAX", 123)); + assertEquals(Byte.MAX_VALUE, DataReader.optLong(data, "BYTE_MAX", 123)); + + assertEquals(1, DataReader.optLong(data, "BYTE", 123)); + assertEquals(2, DataReader.optLong(data, "SHORT", 123)); + assertEquals(3, DataReader.optLong(data, "INT", 123)); + assertEquals(4, DataReader.optLong(data, "LONG", 123)); + assertEquals(5, DataReader.optLong(data, "FLOAT", 123)); + assertEquals(6, DataReader.optLong(data, "DOUBLE", 123)); + + assertEquals(123, DataReader.optLong(data, "FLOAT_MAX", 123)); + assertEquals(123, DataReader.optLong(data, "DOUBLE_MAX", 123)); + assertEquals(123, DataReader.optLong(data, "STRING", 123)); + assertEquals(123, DataReader.optLong(data, "NULL", 123)); + assertEquals(123, DataReader.optLong(data, "INVALID", 123)); + + checkIllegalArgumentException((m, k) -> DataReader.optLong(m, k, 123)); + } + + @Test + public void testReadFloat() throws Exception { + assertEquals(Float.MAX_VALUE, DataReader.getFloat(data, "FLOAT_MAX"), DELTA); + assertEquals((float) Long.MAX_VALUE, DataReader.getFloat(data, "LONG_MAX"), DELTA); + assertEquals((float) Integer.MAX_VALUE, DataReader.getFloat(data, "INT_MAX"), DELTA); + assertEquals((float) Short.MAX_VALUE, DataReader.getFloat(data, "SHORT_MAX"), DELTA); + assertEquals((float) Byte.MAX_VALUE, DataReader.getFloat(data, "BYTE_MAX"), DELTA); + + assertEquals(1, DataReader.getFloat(data, "BYTE"), DELTA); + assertEquals(2, DataReader.getFloat(data, "SHORT"), DELTA); + assertEquals(3, DataReader.getFloat(data, "INT"), DELTA); + assertEquals(4, DataReader.getFloat(data, "LONG"), DELTA); + assertEquals(5.5, DataReader.getFloat(data, "FLOAT"), DELTA); + assertEquals(6.6, DataReader.getFloat(data, "DOUBLE"), DELTA); + + checkIllegalArgumentException(DataReader::getFloat); + checkExceptionMessage((key) -> DataReader.getFloat(data, key), Arrays.asList("NULL", "INVALID"), NULL_VALUE_ERROR_MSG); + + String errorMessage = OVERFLOW_ERROR_MSG + Float.class; + checkExceptionMessage((key) -> DataReader.getFloat(data, key), Arrays.asList("DOUBLE_MAX"), errorMessage); + + checkCastException((key) -> DataReader.getFloat(data, key), Arrays.asList("STRING","STRING_MAP", "STRING_LIST")); + } + + @Test + public void testOptFloat() { + assertEquals(Float.MAX_VALUE, DataReader.optFloat(data, "FLOAT_MAX", 3.3F), DELTA); + assertEquals((float) Long.MAX_VALUE, DataReader.optFloat(data, "LONG_MAX", 3.3F), DELTA); + assertEquals((float) Integer.MAX_VALUE, DataReader.optFloat(data, "INT_MAX", 3.3F), DELTA); + assertEquals((float) Short.MAX_VALUE, DataReader.optFloat(data, "SHORT_MAX", 3.3F), DELTA); + assertEquals((float) Byte.MAX_VALUE, DataReader.optFloat(data, "BYTE_MAX", 3.3F), DELTA); + + assertEquals(1, DataReader.optFloat(data, "BYTE",3.3F), DELTA); + assertEquals(2, DataReader.optFloat(data, "SHORT",3.3F), DELTA); + assertEquals(3, DataReader.optFloat(data, "INT",3.3F), DELTA); + assertEquals(4, DataReader.optFloat(data, "LONG",3.3F), DELTA); + assertEquals(5.5, DataReader.optFloat(data, "FLOAT",3.3F), DELTA); + assertEquals(6.6, DataReader.optFloat(data, "DOUBLE",3.3F), DELTA); + + assertEquals(3.3, DataReader.optFloat(data, "DOUBLE_MAX",3.3F), DELTA); + assertEquals(3.3, DataReader.optFloat(data, "STRING",3.3F), DELTA); + assertEquals(3.3, DataReader.optFloat(data, "NULL",3.3F), DELTA); + assertEquals(3.3, DataReader.optFloat(data, "INVALID",3.3F), DELTA); + + checkIllegalArgumentException((m, k) -> DataReader.optFloat(m, k, 3.3F)); + } + + @Test + public void testReadDouble() throws Exception { + assertEquals(Double.MAX_VALUE, DataReader.getDouble(data, "DOUBLE_MAX"), DELTA); + assertEquals(Float.MAX_VALUE, DataReader.getDouble(data, "FLOAT_MAX"), DELTA); + assertEquals(Long.MAX_VALUE, DataReader.getDouble(data, "LONG_MAX"), DELTA); + assertEquals(Integer.MAX_VALUE, DataReader.getDouble(data, "INT_MAX"), DELTA); + assertEquals(Short.MAX_VALUE, DataReader.getDouble(data, "SHORT_MAX"), DELTA); + assertEquals(Byte.MAX_VALUE, DataReader.getDouble(data, "BYTE_MAX"), DELTA); + + assertEquals(1, DataReader.getDouble(data, "BYTE"), DELTA); + assertEquals(2, DataReader.getDouble(data, "SHORT"), DELTA); + assertEquals(3, DataReader.getDouble(data, "INT"), DELTA); + assertEquals(4, DataReader.getDouble(data, "LONG"), DELTA); + assertEquals(5.5, DataReader.getDouble(data, "FLOAT"), DELTA); + assertEquals(6.6, DataReader.getDouble(data, "DOUBLE"), DELTA); + + checkIllegalArgumentException(DataReader::getDouble); + checkExceptionMessage((key) -> DataReader.getDouble(data, key), Arrays.asList("NULL", "INVALID"), NULL_VALUE_ERROR_MSG); + checkCastException((key) -> DataReader.getDouble(data, key), Arrays.asList("STRING")); + } + + @Test + public void testOptDouble() { + assertEquals(Double.MAX_VALUE, DataReader.optDouble(data, "DOUBLE_MAX", 3.3), DELTA); + assertEquals(Float.MAX_VALUE, DataReader.optDouble(data, "FLOAT_MAX", 3.3), DELTA); + assertEquals(Long.MAX_VALUE, DataReader.optDouble(data, "LONG_MAX", 3.3), DELTA); + assertEquals(Integer.MAX_VALUE, DataReader.optDouble(data, "INT_MAX", 3.3), DELTA); + assertEquals(Short.MAX_VALUE, DataReader.optDouble(data, "SHORT_MAX", 3.3), DELTA); + assertEquals(Byte.MAX_VALUE, DataReader.optDouble(data, "BYTE_MAX", 3.3), DELTA); + + assertEquals(1, DataReader.optDouble(data, "BYTE", 3.3), DELTA); + assertEquals(2, DataReader.optDouble(data, "SHORT", 3.3), DELTA); + assertEquals(3, DataReader.optDouble(data, "INT", 3.3), DELTA); + assertEquals(4, DataReader.optDouble(data, "LONG", 3.3), DELTA); + assertEquals(5.5, DataReader.optDouble(data, "FLOAT", 3.3), DELTA); + assertEquals(6.6, DataReader.optDouble(data, "DOUBLE", 3.3), DELTA); + + assertEquals(3.3, DataReader.optDouble(data, "STRING", 3.3), DELTA); + assertEquals(3.3, DataReader.optDouble(data, "NULL", 3.3), DELTA); + assertEquals(3.3, DataReader.optDouble(data, "INVALID", 3.3), DELTA); + + checkIllegalArgumentException((m, k) -> DataReader.optDouble(m, k, 123)); + } + + @Test + public void testReadString() throws Exception { + assertEquals(Long.toString(Long.MAX_VALUE), DataReader.getString(data, "LONG_MAX")); + assertEquals(Integer.toString(Integer.MAX_VALUE), DataReader.getString(data, "INT_MAX")); + assertEquals(Short.toString(Short.MAX_VALUE), DataReader.getString(data, "SHORT_MAX")); + assertEquals(Byte.toString(Byte.MAX_VALUE), DataReader.getString(data, "BYTE_MAX")); + assertEquals(Float.toString(Float.MAX_VALUE), DataReader.getString(data, "FLOAT_MAX")); + assertEquals(Double.toString(Double.MAX_VALUE), DataReader.getString(data, "DOUBLE_MAX")); + assertEquals("STRING", DataReader.getString(data, "STRING")); + + assertEquals("1", DataReader.getString(data, "BYTE")); + assertEquals("2", DataReader.getString(data, "SHORT")); + assertEquals("3", DataReader.getString(data, "INT")); + assertEquals("4", DataReader.getString(data, "LONG")); + assertEquals("5.5", DataReader.getString(data, "FLOAT")); + assertEquals("6.6", DataReader.getString(data, "DOUBLE")); + assertNull(DataReader.getString(data, "NULL")); + assertNull(DataReader.getString(data, "INVALID")); + + checkIllegalArgumentException(DataReader::getString); + } + + @Test + public void testOptString() { + assertEquals("1", DataReader.optString(data, "BYTE", "DEFAULT")); + assertEquals("2", DataReader.optString(data, "SHORT", "DEFAULT")); + assertEquals("3", DataReader.optString(data, "INT", "DEFAULT")); + assertEquals("4", DataReader.optString(data, "LONG", "DEFAULT")); + assertEquals("5.5", DataReader.optString(data, "FLOAT", "DEFAULT")); + assertEquals("6.6", DataReader.optString(data, "DOUBLE", "DEFAULT")); + assertEquals("DEFAULT", DataReader.optString(data, "NULL", "DEFAULT")); + assertEquals("DEFAULT",DataReader.optString(data, "INVALID", "DEFAULT")); + + checkIllegalArgumentException((m, k) -> DataReader.optString(m, k, "DEFAULT")); + } + + @Test + public void testGetStringMap() throws Exception { + Map map; + map = DataReader.getStringMap(data, "EMPTY_MAP"); + assertEquals(map, new HashMap<>()); + + map = DataReader.getStringMap(data, "STRING_MAP"); + assertEquals(map, new HashMap() {{ + put("a", "a"); + put("b", "b"); + }}); + + checkExceptionMessage((key) -> DataReader.getStringMap(data, key), Arrays.asList("STRING", "INT", "STRING_LIST"), MAP_ERROR_MSG); + checkExceptionMessage((key) -> DataReader.getStringMap(data, key), Arrays.asList("INT_MAP"), MAP_ENTRY_ERROR_MSG); + checkIllegalArgumentException(DataReader::getStringMap); + } + + @Test + public void testOptStringMap() { + Map defaultMap = new HashMap() {{ + put("c", "c"); + put("d", "d"); + }}; + + Map map; + map = DataReader.optStringMap(data, "EMPTY_MAP", defaultMap); + assertEquals(map, new HashMap<>()); + + map = DataReader.optStringMap(data, "STRING_MAP", defaultMap); + assertEquals(map, new HashMap() {{ + put("a", "a"); + put("b", "b"); + }}); + + map = DataReader.optStringMap(data, "INT_MAP", defaultMap); + assertEquals(map, defaultMap); + + map = DataReader.optStringMap(data, "STRING", defaultMap); + assertEquals(map, defaultMap); + map = DataReader.optStringMap(data, "STRING_LIST", defaultMap); + assertEquals(map, defaultMap); + checkIllegalArgumentException((m, k) -> DataReader.optStringMap(m, k, defaultMap)); + } + + @Test + public void testGetStringList() throws Exception { + List list; + list = DataReader.getStringList(data, "EMPTY_LIST"); + assertEquals(list, new ArrayList<>()); + + list = DataReader.getStringList(data, "STRING_LIST"); + assertEquals(list, Arrays.asList("a", "b")); + + checkExceptionMessage((key) -> DataReader.getStringList(data, key), Arrays.asList("STRING", "INT", "STRING_MAP"), LIST_ERROR_MSG); + checkExceptionMessage((key) -> DataReader.getStringList(data, key), Arrays.asList("INT_LIST"), LIST_ENTRY_ERROR_MSG); + checkIllegalArgumentException(DataReader::getStringList); + } + + @Test + public void testOptStringList() { + List defaultList = Arrays.asList("c", "d"); + List list; + list = DataReader.optStringList(data, "EMPTY_LIST", defaultList); + assertEquals(list, new ArrayList<>()); + + list = DataReader.optStringList(data, "STRING_LIST", defaultList); + assertEquals(list, Arrays.asList("a", "b")); + + list = DataReader.optStringList(data, "INT_LIST", defaultList); + assertEquals(list, defaultList); + list = DataReader.optStringList(data, "STRING", defaultList); + assertEquals(list, defaultList); + list = DataReader.optStringList(data, "STRING_MAP", defaultList); + assertEquals(list, defaultList); + + checkIllegalArgumentException((m, k) -> DataReader.optStringList(m, k, defaultList)); + } + + @Test + public void testGetTypedMap() throws Exception { + Map map = DataReader.getTypedMap(Integer.class, data, "INT_MAP"); + assertEquals(map, new HashMap() {{ + put("a", 1); + put("b", 2); + }}); + + checkExceptionMessage((key) -> DataReader.getTypedMap(Integer.class, data, key), Arrays.asList("STRING", "INT", "STRING_LIST"), MAP_ERROR_MSG); + checkExceptionMessage((key) -> DataReader.getTypedMap(Integer.class, data, key), Arrays.asList("STRING_MAP"), MAP_ENTRY_ERROR_MSG); + checkIllegalArgumentException((m, k) -> DataReader.getTypedMap(Integer.class, m, k)); + } + + @Test + public void testOptTypedMap() { + Map defaultMap = new HashMap() {{ + put("c", 3); + put("d", 4); + }}; + + Map map = DataReader.optTypedMap(Integer.class, data, "INT_MAP", defaultMap); + assertEquals(map, new HashMap() {{ + put("a", 1); + put("b", 2); + }}); + + map = DataReader.optTypedMap(Integer.class, data, "STRING", defaultMap); + assertEquals(map, defaultMap); + map = DataReader.optTypedMap(Integer.class, data, "STRING_MAP", defaultMap); + assertEquals(map, defaultMap); + + checkIllegalArgumentException((m, k) -> DataReader.getTypedMap(Integer.class, m, k)); + } + + @Test + public void testGetTypedList() throws Exception { + List list = DataReader.getTypedList(Integer.class, data, "INT_LIST"); + assertEquals(list, Arrays.asList(1, 2)); + + checkExceptionMessage((key) -> DataReader.getTypedList(Integer.class, data, key), Arrays.asList("STRING", "INT", "STRING_MAP"), LIST_ERROR_MSG); + checkExceptionMessage((key) -> DataReader.getTypedList(Integer.class, data, key), Arrays.asList("STRING_LIST"), LIST_ENTRY_ERROR_MSG); + checkIllegalArgumentException((m, k) -> DataReader.getTypedList(Integer.class, m, k)); + } + + @Test + public void testOptTypedList() { + List defaultList = Arrays.asList(3, 4); + + List list; + list = DataReader.optTypedList(Integer.class, data, "INT_LIST", defaultList); + assertEquals(list, Arrays.asList(1, 2)); + + list = DataReader.optTypedList(Integer.class, data, "STRING", defaultList); + assertEquals(list, defaultList); + list = DataReader.optTypedList(Integer.class, data, "STRING_LIST", defaultList); + assertEquals(list, defaultList); + checkIllegalArgumentException((m, k) -> DataReader.optTypedList(Integer.class, m, k, defaultList)); + } + + @Test + public void testReadNestedValue() throws Exception { + Map clonedData = EventDataUtils.immutableClone(data); + + Map map = DataReader.getTypedMap(Integer.class, clonedData, "INT_MAP"); + int value = DataReader.getInt(map, "a"); + assertEquals(value, 1); + + List stringList = DataReader.getStringList(clonedData, "STRING_LIST"); + assertEquals("a", stringList.get(0) ); + } +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/utils/EventDataUtilsTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/utils/EventDataUtilsTests.java new file mode 100644 index 000000000..a3914e089 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/utils/EventDataUtilsTests.java @@ -0,0 +1,222 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.utils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import com.adobe.marketing.mobile.Event; + +import org.junit.Test; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.UUID; + +public class EventDataUtilsTests { + + @Test + public void testClone_SimpleObjects() throws CloneFailedException { + Map values = new HashMap<>(); + values.put("boolean", true); + + byte b = 100; + values.put("byte", b); + + short s = 1000; + values.put("short", s); + + int i = 10000; + values.put("int", i); + + long l = 100000L; + values.put("long", l); + + float f = 1.1F; + values.put("float", f); + + double d = 1.1e10D; + values.put("double", d); + + BigDecimal bd = BigDecimal.TEN; + values.put("bigdecimal", bd); + + BigInteger bi = BigInteger.TEN; + values.put("biginteger", bi); + + char c = 'a'; + values.put("char", c); + + String str = "hello"; + values.put("string", str); + + UUID uuid = UUID.randomUUID(); + values.put("uuid", uuid); + + + Map clonedValues = EventDataUtils.clone(values); + assertEquals(values, clonedValues); + } + + @Test + public void testClone_NestedObjects() throws CloneFailedException { + Map nestedMap = new TreeMap<>(); + nestedMap.put("integer", 1); + nestedMap.put("float", 1f); + nestedMap.put("double", 1d); + nestedMap.put("string", "hello"); + + List nestedList = new LinkedList<>(); + nestedList.add(1); + nestedList.add(1f); + nestedList.add(1d); + nestedList.add("hello"); + + Map data = new HashMap<>(); + data.put("map", nestedList); + data.put("list", nestedList); + data.put("string", "top level"); + data.put("uuid", UUID.randomUUID()); + data.put("null", null); + + Map clonedData = EventDataUtils.clone(data); + assertEquals(data, clonedData); + } + + @Test + public void testClone_MapWithNonStringKeys() throws CloneFailedException { + Map nestedMap = new HashMap<>(); + nestedMap.put(null, 1); + nestedMap.put(new Integer(1), 1); + nestedMap.put(new Double(1.1), 1d); + nestedMap.put("string", "hello"); + + Map data = new HashMap<>(); + data.put("map", nestedMap); + + Map expectedNestedMap = new HashMap<>(); + expectedNestedMap.put("string", "hello"); + Map expectedData = new HashMap<>(); + expectedData.put("map", expectedNestedMap); + + Map clonedData = EventDataUtils.clone(data); + assertEquals(expectedData, clonedData); + } + + @Test + public void testClone_Array() throws CloneFailedException { + String[] stringArray = new String[]{ "string1", "string2"}; + + Map data = new HashMap<>(); + data.put("stringArray", stringArray); + + Map[] mapArray = new Map[] { + new HashMap(), + new HashMap() {{ + put("k1" , "v1"); + put("k2" , "v2"); + }}, + new HashMap() {{ + put("integer", 1); + put("float", 1f); + put("double", 1d); + put("string", "hello"); + }} + }; + data.put("mapArray", mapArray); + + Map clonedData = EventDataUtils.clone(data); + + assertEquals(clonedData.get("stringArray"), Arrays.asList(stringArray)); + assertEquals(clonedData.get("mapArray"), Arrays.asList(mapArray)); + } + + @Test + public void testClone_FailUnsupportedTypes() { + class Data {} + + Map map = new HashMap<>(); + map.put("data", new Data()); + + Exception ex = assertThrows( + CloneFailedException.class, + () -> EventDataUtils.clone(map)); + + assertEquals(ex.getMessage(), "Object is of unsupported type"); + } + + @Test + public void testCloneFailCircularReference() { + Map map = new HashMap<>(); + List list = new ArrayList<>(); + list.add(map); + + map.put("list", list); + + Exception ex = assertThrows( + CloneFailedException.class, + () -> EventDataUtils.clone(map)); + + assertEquals(ex.getMessage(), "Max depth reached"); + } + + @Test + public void testImmutableClone() throws CloneFailedException { + Map nestedMap = new TreeMap<>(); + nestedMap.put("integer", 1); + nestedMap.put("float", 1f); + nestedMap.put("double", 1d); + nestedMap.put("string", "hello"); + + List nestedList = new LinkedList<>(); + nestedList.add(1); + nestedList.add(1f); + nestedList.add(1d); + nestedList.add("hello"); + + Map data = new HashMap<>(); + data.put("map", nestedMap); + data.put("list", nestedList); + + Map clonedData = EventDataUtils.immutableClone(data); + + assertThrows( + UnsupportedOperationException.class, + () -> clonedData.put("newKey", "newValue")); + + assertThrows( + UnsupportedOperationException.class, + () -> { + Map clonedNestedMap = (Map)clonedData.get("map"); + clonedNestedMap.put("newKey", "newValue"); + }); + + assertThrows( + UnsupportedOperationException.class, + () -> { + List clonedNestedList = (List)clonedData.get("list"); + clonedNestedList.add("value"); + }); + + } +} From f3950e6958229bf47abd0f18136e169f4547dceb Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Mon, 13 Jun 2022 14:56:35 -0700 Subject: [PATCH 074/476] fix launch token finder tests --- .../mobile/internal/utility/MapExtensions.kt | 2 +- .../launch/rulesengine/LaunchTokenFinder.kt | 16 +-- .../rulesengine}/LaunchTokenFinderTest.kt | 103 ++++++++++-------- 3 files changed, 61 insertions(+), 60 deletions(-) rename code/android-core-library/src/test/java/com/adobe/marketing/mobile/{ => launch/rulesengine}/LaunchTokenFinderTest.kt (85%) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt index 9f3f96c13..04e947ad4 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt @@ -122,7 +122,7 @@ internal fun Map.serializeToQueryString(): String { } } - return builder.substring(1).toString() + return if (builder.isNotEmpty()) builder.substring(1).toString() else builder.toString() } private fun Set<*>.isAllString(): Boolean { diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt index f2280e06a..07febe002 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt @@ -110,20 +110,9 @@ internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionAp // private getter methods // ======================================================== - /** - * Returns the value for shared state key specified by the [key]. - * - * - * The [key] is provided in the format ~state.valid_shared_state_name/key - * For example: ~state.com.adobe.marketing.mobile.Identity/mid - * - * @param key [String] containing the key to search for in `EventHub#moduleSharedStates` - * - * @return [Any] containing the value for the shared state key if valid, null otherwise - */ private fun getValueFromSharedState(key: String): Any? { val sharedStateKeyString = key.substring(KEY_SHARED_STATE.length) - if (StringUtils.isNullOrEmpty(sharedStateKeyString)) { + if (sharedStateKeyString.isBlank()) { return null } if (!sharedStateKeyString.contains(SHARED_STATE_KEY_DELIMITER)) { @@ -137,7 +126,7 @@ internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionAp String.format("Unable to replace the token %s, token not found in shared state for the event", key) ) }?.flattening() - if (sharedStateMap == null || sharedStateMap.isEmpty() || StringUtils.isNullOrEmpty(dataKeyName) || !sharedStateMap.containsKey(dataKeyName)) { + if (sharedStateMap.isNullOrEmpty() || dataKeyName.isBlank() || !sharedStateMap.containsKey(dataKeyName)) { return null } return sharedStateMap[dataKeyName] @@ -162,6 +151,5 @@ internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionAp // TODO uncomment once map flattening logic is finalized val eventDataMap = event.eventData.flattening() return eventDataMap[key] - return EMPTY_STRING } } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/LaunchTokenFinderTest.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt similarity index 85% rename from code/android-core-library/src/test/java/com/adobe/marketing/mobile/LaunchTokenFinderTest.kt rename to code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt index dc2897eaa..afe5f111b 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/LaunchTokenFinderTest.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt @@ -9,24 +9,32 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile +package com.adobe.marketing.mobile.launch.rulesengine +import com.adobe.marketing.mobile.BaseTest +import com.adobe.marketing.mobile.Event +import com.adobe.marketing.mobile.ExtensionApi +import com.adobe.marketing.mobile.MobileCore import com.adobe.marketing.mobile.internal.utility.TimeUtil -import com.adobe.marketing.mobile.launch.rulesengine.LaunchTokenFinder import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith +import org.powermock.api.mockito.PowerMockito +import org.powermock.core.classloader.annotations.PrepareForTest +import org.powermock.modules.junit4.PowerMockRunner +@RunWith(PowerMockRunner::class) +@PrepareForTest(ExtensionApi::class) class LaunchTokenFinderTest : BaseTest() { private lateinit var extensionApi: ExtensionApi @Before fun setup() { - super.beforeEach() - extensionApi = ExtensionApi(eventHub) + extensionApi = PowerMockito.mock(ExtensionApi::class.java) } @Test @@ -122,10 +130,11 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return all string variables on valid event encoded in url format`() { // setup - val testEventData = EventData() - testEventData.putString("key1", "value 1") - testEventData.putNull("key8") - val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val testEventData = mapOf( + "key1" to "value 1", + "key8" to null + ) + val testEvent = getDefaultEvent(testEventData) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("~all_url") @@ -136,10 +145,11 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return all numeric variables on valid event encoded in url format`() { // setup - val testEventData = EventData() - testEventData.putInteger("key3", 123) - testEventData.putLong("key4", -456L) - val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val testEventData = mapOf( + "key3" to 123, + "key4" to -456L + ) + val testEvent = getDefaultEvent(testEventData) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("~all_url") @@ -150,10 +160,11 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return all boolean variables on valid event encoded in url format`() { // setup - val testEventData = EventData() - testEventData.putBoolean("key2", true) - testEventData.putDouble("key5", -123.456) - val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val testEventData = mapOf( + "key2" to true, + "key5" to -123.456 + ) + val testEvent = getDefaultEvent(testEventData) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("~all_url") @@ -164,12 +175,10 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return all list variables on valid event encoded in url format`() { // setup - val testEventData = EventData() - val stringList: MutableList = ArrayList() - stringList.add("String1") - stringList.add("String2") - testEventData.putStringList("key6", stringList) - val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val testEventData = mapOf( + "key6" to listOf("String1", "String2") + ) + val testEvent = getDefaultEvent(testEventData) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("~all_url") @@ -180,12 +189,13 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return all map variables on valid event encoded in url format`() { // setup - val testEventData = EventData() - val stringMap: MutableMap = HashMap() - stringMap["innerKey1"] = "inner val1" - stringMap["innerKey2"] = "innerVal2" - testEventData.putStringMap("key7", stringMap) - val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val testEventData = mapOf( + "key7" to mapOf( + "innerKey1" to "inner val1", + "innerKey2" to "innerVal2" + ) + ) + val testEvent = getDefaultEvent(testEventData) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("~all_url") @@ -196,7 +206,7 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return empty string on event with no event data for url`() { // setup - val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) + val testEvent = getDefaultEvent(null) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("~all_url") @@ -236,12 +246,12 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return empty string on event with no event data for json`() { // setup - val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) + val testEvent = getDefaultEvent(null) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("~all_json") // verify - assertEquals("", result) + assertEquals("{}", result) } // TODO uncomment when map flattening logic is finalized @@ -297,7 +307,7 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return null when key does not have shared state name`() { // setup - val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) + val testEvent = getDefaultEvent(null) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("~state.") @@ -308,7 +318,7 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return null when key does not have shared state key name`() { // setup - val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) + val testEvent = getDefaultEvent(null) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("~state.com.adobe.marketing.mobile.Analytics/") @@ -319,7 +329,7 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return null when key does not have valid format`() { // setup - val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) + val testEvent = getDefaultEvent(null) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("~state.com.adobe/.marketing.mobile.Analytics/analytics.contextData.akey") @@ -330,8 +340,8 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return null when key does not exist in shared state`() { // setup - val testEventData = EventData() - val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val testEventData = mapOf() + val testEvent = getDefaultEvent(testEventData) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("~state.com.adobe.marketing.mobile.Analytics/analytics.contextData.akey") @@ -351,15 +361,16 @@ class LaunchTokenFinderTest : BaseTest() { assertEquals("value1", result) } */ + // TODO change if we decide to keep event data as null instead of empty map by default @Test - fun `get should return empty string when event data is null on valid event`() { + fun `get should return null when event data is null on valid event`() { // setup - val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) + val testEvent = getDefaultEvent(null) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("key1") // verify - assertEquals("", result) + assertEquals(null, result) } // TODO uncomment when map flattening logic is finalized @@ -448,14 +459,16 @@ class LaunchTokenFinderTest : BaseTest() { assertEquals("inner val1", result) } */ - private fun getEvent(type: EventType?, source: EventSource?, eventData: EventData?): Event { - return Event.Builder("TEST", type, source) - .setData(eventData).build() + private fun getDefaultEvent(eventData: Map?): Event { + return Event.Builder( + "TEST", + "com.adobe.eventType.analytics", + "com.adobe.eventSource.requestContent" + ).setEventData(eventData).build() } private fun getDefaultEvent(): Event { - val testEventData = EventData() - testEventData.putString("key1", "value1") - return getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val testEventData = mapOf("key1" to "value1") + return getDefaultEvent(testEventData) } } From a36bc5471144b0f96e3746a33f58c52f3e6ce284 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Mon, 13 Jun 2022 14:59:56 -0700 Subject: [PATCH 075/476] lint fixes --- .../marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt index 07febe002..24e4dd063 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt @@ -15,7 +15,6 @@ import com.adobe.marketing.mobile.Event import com.adobe.marketing.mobile.ExtensionApi import com.adobe.marketing.mobile.LoggingMode import com.adobe.marketing.mobile.MobileCore -import com.adobe.marketing.mobile.internal.utility.StringUtils import com.adobe.marketing.mobile.internal.utility.TimeUtil import com.adobe.marketing.mobile.internal.utility.flattening import com.adobe.marketing.mobile.internal.utility.serializeToQueryString From c9b44b46214eb3127bfa06c7502056f8f9dc4070 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Tue, 14 Jun 2022 10:13:34 -0700 Subject: [PATCH 076/476] added more tests, used event data changes --- .../com/adobe/marketing/mobile/Event.java | 1 + .../rulesengine/LaunchRulesConsequence.kt | 3 +- .../LaunchRulesConsequenceTests.kt | 428 +++++++++++++++++- 3 files changed, 417 insertions(+), 15 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java index 6ef92332c..32234b388 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java @@ -301,6 +301,7 @@ public Event copyWithNewData(final Map newData) { newEvent.timestamp = this.timestamp; newEvent.pairID = this.pairID; newEvent.responsePairID = this.responsePairID; + newEvent.eventNumber = this.eventNumber; return newEvent; } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt index b1b31b5f5..b0cde561b 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt @@ -290,8 +290,7 @@ class LaunchRulesConsequence( return Event.Builder( CONSEQUENCE_EVENT_NAME, EVENT_TYPE_RULES_ENGINE, - EVENT_SOURCE_RESPONSE_CONTENT - ) + EVENT_SOURCE_RESPONSE_CONTENT) .setEventData(mapOf(CONSEQUENCE_EVENT_DATA_KEY_CONSEQUENCE to eventData)) .build() } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequenceTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequenceTests.kt index 0fafb1232..54b8f7f49 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequenceTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequenceTests.kt @@ -228,7 +228,8 @@ class LaunchRulesConsequenceTests { // -------------------------------------- resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventCopy.json") - val event = Event.Builder("Application Launch", + val event = Event.Builder( + "Application Launch", "com.adobe.eventType.lifecycle", "com.adobe.eventSource.applicationLaunch") .setEventData(mapOf("xdm" to "test data")) @@ -259,7 +260,8 @@ class LaunchRulesConsequenceTests { // } // -------------------------------------- resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventCopy.json") - val event = Event.Builder("Application Launch", + val event = Event.Builder( + "Application Launch", "com.adobe.eventType.lifecycle", "com.adobe.eventSource.applicationLaunch") .setEventData(null) @@ -297,7 +299,8 @@ class LaunchRulesConsequenceTests { // -------------------------------------- resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventNewData.json") - val event = Event.Builder("Application Launch", + val event = Event.Builder( + "Application Launch", "com.adobe.eventType.lifecycle", "com.adobe.eventSource.applicationLaunch") .setEventData(mapOf("xdm" to "test data")) @@ -331,7 +334,8 @@ class LaunchRulesConsequenceTests { // -------------------------------------- resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventNewNoData.json") - val event = Event.Builder("Application Launch", + val event = Event.Builder( + "Application Launch", "com.adobe.eventType.lifecycle", "com.adobe.eventSource.applicationLaunch") .setEventData(mapOf("xdm" to "test data")) @@ -364,7 +368,8 @@ class LaunchRulesConsequenceTests { // -------------------------------------- resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventInvalidAction.json") - val event = Event.Builder("Application Launch", + val event = Event.Builder( + "Application Launch", "com.adobe.eventType.lifecycle", "com.adobe.eventSource.applicationLaunch") .setEventData(mapOf("xdm" to "test data")) @@ -393,7 +398,8 @@ class LaunchRulesConsequenceTests { // -------------------------------------- resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventNoAction.json") - val event = Event.Builder("Application Launch", + val event = Event.Builder( + "Application Launch", "com.adobe.eventType.lifecycle", "com.adobe.eventSource.applicationLaunch") .setEventData(mapOf("xdm" to "test data")) @@ -421,7 +427,8 @@ class LaunchRulesConsequenceTests { // } // -------------------------------------- resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventNoType.json") - val event = Event.Builder("Application Launch", + val event = Event.Builder( + "Application Launch", "com.adobe.eventType.lifecycle", "com.adobe.eventSource.applicationLaunch") .setEventData(mapOf("xdm" to "test data")) @@ -449,7 +456,8 @@ class LaunchRulesConsequenceTests { // } // -------------------------------------- resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventNoSource.json") - val event = Event.Builder("Application Launch", + val event = Event.Builder( + "Application Launch", "com.adobe.eventType.lifecycle", "com.adobe.eventSource.applicationLaunch") .setEventData(mapOf("xdm" to "test data")) @@ -467,7 +475,7 @@ class LaunchRulesConsequenceTests { } @Test - fun `Test Chained Dispatch Events`() { + fun `Test Dispatch Event chained Dispatch Events`() { // / Given: a launch rule to dispatch an event with the same type and source which triggered the consequence // ---------- dispatch event rule condition ---------- @@ -501,24 +509,418 @@ class LaunchRulesConsequenceTests { // } // -------------------------------------- resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventChain.json") - val event = Event.Builder("Edge Request", + val event = Event.Builder( + "Edge Request", "com.adobe.eventType.edge", "com.adobe.eventSource.requestContent") .setEventData(mapOf("xdm" to "test data")) .build() - val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event) - // Process original event; dispatch chain count = 0 + launchRulesConsequence.evaluateRulesConsequence(event) val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) PowerMockito.verifyStatic(MobileCore::class.java, times(1)) MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) // Process dispatched event; dispatch chain count = 1 // Expect dispatch to not be called max allowed chained events is 1 - val secondDispatchEvent = launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value) + launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value) + PowerMockito.verifyStatic(MobileCore::class.java, times(1)) + MobileCore.dispatchEvent(any(), any()) + } + + @Test + fun `Test Dispatch Event multiple Processing of Same Original Event`() { + // Given: a launch rule to dispatch an event with the same type and source which triggered the consequence + + // ---------- dispatch event rule condition ---------- + // "conditions": [ + // { + // "type": "matcher", + // "definition": { + // "key": "~type", + // "matcher": "eq", + // "values": [ + // "com.adobe.eventType.edge" + // ] + // } + // }, + // { + // "type": "matcher", + // "definition": { + // "key": "~source", + // "matcher": "eq", + // "values": [ + // "com.adobe.eventSource.requestContent" + // ] + // } + // } + // ] + // ---------- dispatch event rule consequence ---------- + // "detail": { + // "type" : "com.adobe.eventType.edge", + // "source" : "com.adobe.eventSource.requestContent", + // "eventdataaction" : "copy" + // } + // -------------------------------------- + resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventChain.json") + val event = Event.Builder( + "Edge Request", + "com.adobe.eventType.edge", + "com.adobe.eventSource.requestContent") + .setEventData(mapOf("xdm" to "test data")) + .build() + + // Process original event; dispatch chain count = 0 + launchRulesConsequence.evaluateRulesConsequence(event) + val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) + PowerMockito.verifyStatic(MobileCore::class.java, times(1)) + MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + + // Process dispatched event; dispatch chain count = 1 + // Expect dispatch to fail as max allowed chained events is 1 + launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value) + PowerMockito.verifyStatic(MobileCore::class.java, times(1)) + MobileCore.dispatchEvent(any(), any()) + + // Process dispatched event; dispatch chain count = 1 + // Expect event to be processed as if first time + launchRulesConsequence.evaluateRulesConsequence(event) + PowerMockito.verifyStatic(MobileCore::class.java, times(2)) + MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + + // Process dispatched event; dispatch chain count = 1 + // Expect dispatch to fail as max allowed chained events is 1 + launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value) + PowerMockito.verifyStatic(MobileCore::class.java, times(2)) + MobileCore.dispatchEvent(any(), any()) + } + + @Test + fun `Test Dispatch Event multiple Processing of Same Dispatched Event`() { + // Given: a launch rule to dispatch an event with the same type and source which triggered the consequence + + // ---------- dispatch event rule condition ---------- + // "conditions": [ + // { + // "type": "matcher", + // "definition": { + // "key": "~type", + // "matcher": "eq", + // "values": [ + // "com.adobe.eventType.edge" + // ] + // } + // }, + // { + // "type": "matcher", + // "definition": { + // "key": "~source", + // "matcher": "eq", + // "values": [ + // "com.adobe.eventSource.requestContent" + // ] + // } + // } + // ] + // ---------- dispatch event rule consequence ---------- + // "detail": { + // "type" : "com.adobe.eventType.edge", + // "source" : "com.adobe.eventSource.requestContent", + // "eventdataaction" : "copy" + // } + // -------------------------------------- + resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventChain.json") + val event = Event.Builder( + "Edge Request", + "com.adobe.eventType.edge", + "com.adobe.eventSource.requestContent") + .setEventData(mapOf("xdm" to "test data")) + .build() + + // Process original event; dispatch chain count = 0 + launchRulesConsequence.evaluateRulesConsequence(event) + val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) + PowerMockito.verifyStatic(MobileCore::class.java, times(1)) + MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + + // Process dispatched event; dispatch chain count = 1 + // Expect dispatch to fail as max allowed chained events is 1 + launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value) + PowerMockito.verifyStatic(MobileCore::class.java, times(1)) + MobileCore.dispatchEvent(any(), any()) + + // Process dispatched event; dispatch chain count = 1 + // Expect event to be processed as if first time + launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value) + PowerMockito.verifyStatic(MobileCore::class.java, times(2)) + MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + + // Process dispatched event; dispatch chain count = 1 + // Expect dispatch to fail as max allowed chained events is 1 + launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value) + PowerMockito.verifyStatic(MobileCore::class.java, times(2)) + MobileCore.dispatchEvent(any(), any()) + } + + @Test + fun `Test Dispatch Event interleaved Chained Dispatched Event`() { + // Given: two launch rules with the same consequence but different event triggers + + // ---------- dispatch event rule 1 condition ---------- + // "conditions": [ + // { + // "type": "matcher", + // "definition": { + // "key": "~type", + // "matcher": "eq", + // "values": [ + // "com.adobe.eventType.edge" + // ] + // } + // }, + // { + // "type": "matcher", + // "definition": { + // "key": "~source", + // "matcher": "eq", + // "values": [ + // "com.adobe.eventSource.requestContent" + // ] + // } + // } + // ] + // ---------- dispatch event rule 1 consequence ---------- + // "detail": { + // "type" : "com.adobe.eventType.edge", + // "source" : "com.adobe.eventSource.requestContent", + // "eventdataaction" : "copy" + // } + // -------------------------------------- + + // ---------- dispatch event rule 2 condition ---------- + // "conditions": [ + // { + // "type": "matcher", + // "definition": { + // "key": "~type", + // "matcher": "eq", + // "values": [ + // "com.adobe.eventType.lifecycle" + // ] + // } + // }, + // { + // "type": "matcher", + // "definition": { + // "key": "~source", + // "matcher": "eq", + // "values": [ + // "com.adobe.eventSource.applicationLaunch" + // ] + // } + // } + // ] + // ---------- dispatch event rule 2 consequence ---------- + // "detail": { + // "type" : "com.adobe.eventType.edge", + // "source" : "com.adobe.eventSource.requestContent", + // "eventdataaction" : "copy" + // } + // -------------------------------------- + resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventChain.json") + + // Then: dispatch event to trigger rule 1 + val eventEdgeRequest = Event.Builder( + "Edge Request", + "com.adobe.eventType.edge", + "com.adobe.eventSource.requestContent") + .setEventData(mapOf("xdm" to "test data")) + .build() + + // Then: dispatch event to trigger rule 2 + val eventLaunch = Event.Builder( + "Application Launch", + "com.adobe.eventType.lifecycle", + "com.adobe.eventSource.applicationLaunch") + .setEventData(mapOf("xdm" to "test data")) + .build() + + // Process original event; dispatch chain count = 0 + launchRulesConsequence.evaluateRulesConsequence(eventEdgeRequest) + val dispatchedEventCaptor1: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) PowerMockito.verifyStatic(MobileCore::class.java, times(1)) + MobileCore.dispatchEvent(dispatchedEventCaptor1.capture(), any()) + + // Process launch event + launchRulesConsequence.evaluateRulesConsequence(eventLaunch) + val dispatchedEventCaptor2: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) + PowerMockito.verifyStatic(MobileCore::class.java, times(2)) + MobileCore.dispatchEvent(dispatchedEventCaptor2.capture(), any()) + + // Process first dispatched event; dispatch chain count = 1 + // Expect dispatch to fail as max allowed chained events is 1 + launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor1.value) + PowerMockito.verifyStatic(MobileCore::class.java, times(2)) + MobileCore.dispatchEvent(any(), any()) + + // Process second dispatched event; dispatch chain count = 1 + // Expect dispatch to fail as max allowed chained events is 1 + launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor2.value) + PowerMockito.verifyStatic(MobileCore::class.java, times(2)) + MobileCore.dispatchEvent(any(), any()) + } + + @Test + fun `Test Dispatch Event processed Event matches multiple Dispatch Consequences`() { + // Given: two launch rules with the same consequence but different conditions + + // ---------- dispatch event rule 1 condition ---------- + // "conditions": [ + // { + // "type": "matcher", + // "definition": { + // "key": "~type", + // "matcher": "eq", + // "values": [ + // "com.adobe.eventType.edge" + // ] + // } + // }, + // { + // "type": "matcher", + // "definition": { + // "key": "~source", + // "matcher": "eq", + // "values": [ + // "com.adobe.eventSource.requestContent" + // ] + // } + // } + // ] + // ---------- dispatch event rule 1 consequence ---------- + // "detail": { + // "type" : "com.adobe.eventType.edge", + // "source" : "com.adobe.eventSource.requestContent", + // "eventdataaction" : "copy" + // } + // -------------------------------------- + + // ---------- dispatch event rule 2 condition ---------- + // "conditions": [ + // { + // "type": "matcher", + // "definition": { + // "key": "dispatch", + // "matcher": "eq", + // "values": [ + // "yes" + // ] + // } + // } + // ] + // ---------- dispatch event rule 2 consequence ---------- + // "detail": { + // "type" : "com.adobe.eventType.edge", + // "source" : "com.adobe.eventSource.requestContent", + // "eventdataaction" : "copy" + // } + // -------------------------------------- + resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventChain.json") + + // Then: dispatch event which will trigger two launch rules + val event = Event.Builder( + "Edge Request", + "com.adobe.eventType.edge", + "com.adobe.eventSource.requestContent") + .setEventData(mapOf("dispatch" to "yes")) + .build() + + // Process original event, expect 2 dispatched events + launchRulesConsequence.evaluateRulesConsequence(event) + val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) + PowerMockito.verifyStatic(MobileCore::class.java, times(2)) + MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + + // Process dispatched event 1, expect 0 dispatch events + // chain count = 1, which is max chained events + launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.allValues[0]) + PowerMockito.verifyStatic(MobileCore::class.java, times(2)) MobileCore.dispatchEvent(any(), any()) + + // Process dispatched event 2, expect 0 dispatch events + // chain count = 1, which is max chained events + launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.allValues[1]) + PowerMockito.verifyStatic(MobileCore::class.java, times(2)) + MobileCore.dispatchEvent(any(), any()) + } + + @Test + fun `Test Url Encode`() { + // Given: { + // "id": "RC48ef3f5e83c84405a3da6cc5128c090c", + // "type": "url", + // "detail": { + // "url": "http://www.adobe.com/a={%urlenc(~state.com.adobe.module.lifecycle/lifecyclecontextdata.carriername)%}" + // } + // } + + resetRulesEngine("rules_module_tests/consequence_rules_testUrlenc.json") + + PowerMockito.`when`(extensionApi.getSharedEventState(ArgumentMatchers.anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "x y" + ) + ) + ) + + launchRulesConsequence.evaluateRulesConsequence(defaultEvent) + + val consequenceEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) + PowerMockito.verifyStatic(MobileCore::class.java, times(1)) + MobileCore.dispatchEvent(consequenceEventCaptor.capture(), any()) + + assertEquals("com.adobe.eventtype.rulesengine", consequenceEventCaptor.value.type) + assertEquals("com.adobe.eventsource.responsecontent", consequenceEventCaptor.value.source) + val data = consequenceEventCaptor.value.eventData?.get("triggeredconsequence") as Map<*, *>? + val detail = data?.get("detail") as Map<*, *>? + assertEquals("url", data?.get("type")) + assertEquals("http://www.adobe.com/a=x%20y", detail?.get("url")) + } + + @Test + fun `Test Url Encode Invalid Fn Name`() { + // Given: + // { + // "id": "RC48ef3f5e83c84405a3da6cc5128c090c", + // "type": "url", + // "detail": { + // "url": "http://www.adobe.com/a={%urlenc1(~state.com.adobe.module.lifecycle/lifecyclecontextdata.carriername)%}" + // } + // } + resetRulesEngine("rules_module_tests/consequence_rules_testUrlenc_invalidFnName.json") + + PowerMockito.`when`(extensionApi.getSharedEventState(ArgumentMatchers.anyString(), any(), any())).thenReturn( + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "x y" + ) + ) + ) + + launchRulesConsequence.evaluateRulesConsequence(defaultEvent) + + val consequenceEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) + PowerMockito.verifyStatic(MobileCore::class.java, times(1)) + MobileCore.dispatchEvent(consequenceEventCaptor.capture(), any()) + + assertEquals("com.adobe.eventtype.rulesengine", consequenceEventCaptor.value.type) + assertEquals("com.adobe.eventsource.responsecontent", consequenceEventCaptor.value.source) + val data = consequenceEventCaptor.value.eventData?.get("triggeredconsequence") as Map<*, *>? + val detail = data?.get("detail") as Map<*, *>? + assertEquals("url", data?.get("type")) + assertEquals("http://www.adobe.com/a=x y", detail?.get("url")) } private fun resetRulesEngine(rulesFileName: String) { From 7c88d084b6a362e4565fff1fbeec309424705894 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Tue, 14 Jun 2022 12:51:39 -0700 Subject: [PATCH 077/476] add map pretty print extension function --- .../mobile/internal/utility/MapExtensions.kt | 16 ++++++++++++++++ .../launch/rulesengine/LaunchRulesConsequence.kt | 12 ++++++++---- .../internal/utility/MapExtensionsTests.kt | 16 ++++++++++++++++ .../rulesengine/LaunchRulesEvaluatorTests.kt | 1 - .../launch/rulesengine/LaunchTokenFinderTest.kt | 16 ++++++++-------- 5 files changed, 48 insertions(+), 13 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt index 04e947ad4..51c8c1d1d 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt @@ -12,6 +12,7 @@ package com.adobe.marketing.mobile.internal.utility import com.adobe.marketing.mobile.internal.utility.UrlUtilities.urlEncode +import org.json.JSONObject /** * Convert map to a decimal FNV1a 32-bit hash. If a mask is provided, only use keys in the provided mask and alphabetize their order. @@ -125,6 +126,21 @@ internal fun Map.serializeToQueryString(): String { return if (builder.isNotEmpty()) builder.substring(1).toString() else builder.toString() } +/** + * Converts a map to a prettified JSON string + * + * @return map as json string + */ + +internal fun Map?.prettify(): String { + if (this == null) return "" + return try { + JSONObject(this).toString(4) + } catch (e: Exception) { + return this.toString() + } +} + private fun Set<*>.isAllString(): Boolean { this.forEach { if (it !is String) return false diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt index b0cde561b..4f804393c 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt @@ -15,6 +15,7 @@ import com.adobe.marketing.mobile.ExtensionApi import com.adobe.marketing.mobile.LoggingMode import com.adobe.marketing.mobile.MobileCore import com.adobe.marketing.mobile.internal.utility.EventDataMerger +import com.adobe.marketing.mobile.internal.utility.prettify import com.adobe.marketing.mobile.rulesengine.DelimiterPair import com.adobe.marketing.mobile.rulesengine.Template import com.adobe.marketing.mobile.rulesengine.TokenFinder @@ -183,11 +184,10 @@ class LaunchRulesConsequence( ) return null } - // TODO add utility function for map pretty print MobileCore.log( LoggingMode.VERBOSE, logTag, - "Attaching event data with $from" + "Attaching event data with ${from.prettify()}" ) return EventDataMerger.merge(from, to, false) } @@ -219,11 +219,10 @@ class LaunchRulesConsequence( ) return null } - // TODO add utility function for map pretty print MobileCore.log( LoggingMode.VERBOSE, logTag, - "Modifying event data with $from" + "Modifying event data with ${from.prettify()}" ) return EventDataMerger.merge(from, to, true) } @@ -282,6 +281,11 @@ class LaunchRulesConsequence( .build() } + /** + * Generate a consequence event with provided consequence data + * @param consequence [RuleConsequence] of the rule + * @return a consequence [Event] + */ private fun generateConsequenceEvent(consequence: RuleConsequence): Event? { val eventData = mutableMapOf() eventData[CONSEQUENCE_EVENT_DATA_KEY_DETAIL] = consequence.detail diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt index c5418e2b6..683d9fe4c 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt @@ -448,4 +448,20 @@ class MapExtensionsTests { ).fnv1a32() assertEquals(2933724447, hashCode) } + + @Test + fun `test prettify map`() { + val data = mapOf( + "a" to "13435454", + "b" to mapOf( + "b1" to 1235566, + "b2" to null + ) + ) + val expected = """{ + "a": "13435454", + "b": {"b1": 1235566} +}""" + assertEquals(expected, data.prettify()) + } } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt index 0ef092f09..803e7ccaf 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt @@ -90,7 +90,6 @@ class LaunchRulesEvaluatorTests { launchRulesEvaluator.replaceRules(listOf()) assertNotNull(eventCaptor.value) assertEquals("com.adobe.eventtype.rulesengine", eventCaptor.value.type) - assertEquals("com.adobe.eventtype.rulesengine", eventCaptor.value.type) assertEquals("com.adobe.eventsource.requestreset", eventCaptor.value.source) launchRulesEvaluator.process(eventCaptor.value) assertEquals(0, cachedEvents.size) diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt index afe5f111b..3c065b475 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt @@ -130,7 +130,7 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return all string variables on valid event encoded in url format`() { // setup - val testEventData = mapOf( + val testEventData = mapOf( "key1" to "value 1", "key8" to null ) @@ -145,7 +145,7 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return all numeric variables on valid event encoded in url format`() { // setup - val testEventData = mapOf( + val testEventData = mapOf( "key3" to 123, "key4" to -456L ) @@ -160,7 +160,7 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return all boolean variables on valid event encoded in url format`() { // setup - val testEventData = mapOf( + val testEventData = mapOf( "key2" to true, "key5" to -123.456 ) @@ -175,8 +175,8 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return all list variables on valid event encoded in url format`() { // setup - val testEventData = mapOf( - "key6" to listOf("String1", "String2") + val testEventData = mapOf( + "key6" to listOf("String1", "String2") ) val testEvent = getDefaultEvent(testEventData) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) @@ -189,8 +189,8 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return all map variables on valid event encoded in url format`() { // setup - val testEventData = mapOf( - "key7" to mapOf( + val testEventData = mapOf( + "key7" to mapOf( "innerKey1" to "inner val1", "innerKey2" to "innerVal2" ) @@ -468,7 +468,7 @@ class LaunchTokenFinderTest : BaseTest() { } private fun getDefaultEvent(): Event { - val testEventData = mapOf("key1" to "value1") + val testEventData = mapOf("key1" to "value1") return getDefaultEvent(testEventData) } } From f5a4954e05a4e8d8895e436a181005f3d93bd540 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Tue, 14 Jun 2022 14:25:57 -0700 Subject: [PATCH 078/476] ignore configuration tests --- ...idThirdPartyExtensionsFunctionalTests.java | 5 +- .../mobile/ConfigurationModuleTest.java | 48 +++++++++---- ...atcherConfigurationRequestContentTest.java | 6 +- ...tcherConfigurationResponseContentTest.java | 6 +- ...cherConfigurationResponseIdentityTest.java | 6 +- .../ConfigurationListenerBootEventTest.java | 7 +- ...nfigurationListenerRequestContentTest.java | 9 ++- ...figurationListenerRequestIdentityTest.java | 7 +- .../ConfigurationListenerSharedStateTest.java | 20 ++++-- .../marketing/mobile/ConfigurationTests.java | 71 ++++++++++++++++--- .../mobile/MobileIdentitiesTest.java | 23 ++++-- 11 files changed, 160 insertions(+), 48 deletions(-) diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java index 662e089d4..477477171 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/AndroidThirdPartyExtensionsFunctionalTests.java @@ -459,7 +459,8 @@ public void call(Event value) { // Get Shared Event State Owned By A Configuration Event using getSharedEventState API // with the extension name as the stateowner like // com.adobe.module.identity, com.adobe.module.configuration - /* @Test + @Ignore + @Test public void testGetSharedEventState_whenConfigEvent_returnsAppropriateSharedState() { // setup @@ -487,7 +488,7 @@ public void error(final ExtensionError extensionError) { assertEquals(configMap, configurationSharedState); assertEquals(configMap, eventHeard.getEventData()); - } */ + } // Test Case No : 16 // Get Shared Event State Owned By A Custom Event using getSharedEventState API for stateowner diff --git a/code/android-core-library/src/legacy/test-module/java/com/adobe/marketing/mobile/ConfigurationModuleTest.java b/code/android-core-library/src/legacy/test-module/java/com/adobe/marketing/mobile/ConfigurationModuleTest.java index b5e1d8e8e..973071dc9 100644 --- a/code/android-core-library/src/legacy/test-module/java/com/adobe/marketing/mobile/ConfigurationModuleTest.java +++ b/code/android-core-library/src/legacy/test-module/java/com/adobe/marketing/mobile/ConfigurationModuleTest.java @@ -81,8 +81,8 @@ public void evaluate() throws Throwable { } } -// TODO uncomment after Configuration refactor -/*@RunWith(JUnit4.class) +// TODO fix after Configuration refactor +@RunWith(JUnit4.class) public class ConfigurationModuleTest extends SystemTest { // Retry failed tests up to 2 times @org.junit.Rule @@ -166,6 +166,7 @@ public void beforeEach() throws Exception { // First Launch Tests // =============================================================== + @Ignore @Test public void test_FirstLaunch_with_BundledConfiguration() { eventHub.ignoreAllStateChangeEvents(); @@ -187,7 +188,7 @@ public void test_FirstLaunch_with_BundledConfiguration() { assertEquals(BUNDLED_SERVER, sharedStateData.optString(ANALYTICS_SERVER_KEY, null)); } - + @Ignore @Test public void test_FirstLaunch_ConfigureWithFilePath() { eventHub.ignoreAllStateChangeEvents(); @@ -212,8 +213,7 @@ public void test_FirstLaunch_ConfigureWithFilePath() { deleteSampleConfigBundledFile(); } - - + @Ignore @Test public void test_FirstLaunch_with_AppIDInManifest() { // setup network @@ -250,6 +250,7 @@ public void test_FirstLaunch_with_AppIDInManifest() { assertTrue(checkIfManifestAppIdCacheFileExists()); } + @Ignore @Test public void test_FirstLaunch_AndThen_ConfigureWithAppId_should_TriggerNetworkCall() { setUpBasic(); @@ -276,6 +277,7 @@ public void test_FirstLaunch_AndThen_ConfigureWithAppId_should_TriggerNetworkCal assertEquals(TestableNetworkService.NetworkRequestType.SYNC, testableNetworkService.getItem(0).type); } + @Ignore @Test public void test_FirstLaunch_AndThen_ConfigureWithAppId_should_SaveCachedFile_And_CreateResponseEvent() { setUpBasic(); @@ -316,6 +318,7 @@ public void test_FirstLaunch_AndThen_ConfigureWithAppId_should_SaveCachedFile_An // ==================================================================================== // Subsequent Launch Tests // =====================================================================================\ + @Ignore @Test public void test_subsequentLaunch_When_AppIdAlreadySet_should_TriggerNetworkCall() { @@ -352,6 +355,7 @@ public void test_SetInternalAppID_withOldId_WillNot_RefreshConfiguration() { assertEquals(0, testableNetworkService.waitAndGetCount()); } + @Ignore @Test public void test_SetInternalAppID_withSameID_Will_RefreshConfiguration() { @@ -371,6 +375,7 @@ public void test_SetInternalAppID_withSameID_Will_RefreshConfiguration() { assertEquals(1, testableNetworkService.waitAndGetCount()); } + @Ignore @Test public void test_subsequentLaunch_When_AppIdAlreadySet_and_networkError_should_ReturnCachedFile() { // setup network @@ -413,6 +418,7 @@ public void test_subsequentLaunch_When_AppIdAlreadySet_and_networkError_should_R assertTrue(checkIfTheCacheFileExists()); } + @Ignore @Test public void test_subsequentLaunch_When_AppIdAlreadySet_and_newContentFromNetwork_should_DispatchNewContent() { @@ -454,6 +460,7 @@ public void test_subsequentLaunch_When_AppIdAlreadySet_and_newContentFromNetwork // Subsequent Launch tests for Bundled configuration and AppIdInManifest are same as its first launch test + @Ignore @Test public void test_subsequentLaunch_when_overriddenConfigInPersistence_should_configureWithOverriddenConfig() { @@ -489,6 +496,7 @@ public void test_subsequentLaunch_when_overriddenConfigInPersistence_should_conf // Config Order of Preference Tests // =====================================================================================\ + @Ignore @Test public void test_preferences_When_AppIdAlreadySet_and_ProgrammaticConfigCreated_should_overridePrimaryConfigFromAppId() { @@ -528,6 +536,7 @@ public void test_subsequentLaunch_when_overriddenConfigInPersistence_should_conf assertTrue(checkIfTheCacheFileExists()); } + @Ignore @Test public void test_preferences_When_BundledConfiguration_and_ProgrammaticConfigCreated_should_overridePrimaryConfigFromBundle() { @@ -557,6 +566,7 @@ public void test_subsequentLaunch_when_overriddenConfigInPersistence_should_conf assertFalse(checkIfTheCacheFileExists()); } + @Ignore @Test public void test_preferences_When_everythingSetup_AppIdTakesPreference_overridden_by_programmedConfig() { @@ -589,7 +599,7 @@ public void test_preferences_When_everythingSetup_AppIdTakesPreference_overridde assertTrue(checkIfTheCacheFileExists()); } - + @Ignore @Test public void test_preferences_When_AppIdAlreadyInPersistence_and_ConfigureWithAppIdCalled_AndThen_ConfigFilePathIsCalled() { @@ -640,6 +650,7 @@ public void test_preferences_When_everythingSetup_AppIdTakesPreference_overridde deleteSampleConfigBundledFile(); } + @Ignore @Test public void test_preferences_When_AppIdAlreadyInPersistence_and_ConfigFilePathIsCalled_AndThen_ConfigureWithAppIdCalled() { @@ -691,6 +702,7 @@ public void test_preferences_When_everythingSetup_AppIdTakesPreference_overridde deleteSampleConfigBundledFile(); } + @Ignore @Test public void test_preferences_OverriddenConfig_and_UpdateConfiguration_AndThen_ClearUpdatedConfigCalled() { // setup @@ -731,6 +743,7 @@ public void test_preferences_OverriddenConfig_and_UpdateConfiguration_AndThen_Cl assertFalse(checkIfTheCacheFileExists()); } + @Ignore @Test public void test_preferences_When_AppIdAlreadyInPersistence_and_UpdateConfiguration_AndThen_ClearUpdatedConfigCalled() { @@ -779,6 +792,7 @@ public void test_preferences_OverriddenConfig_and_UpdateConfiguration_AndThen_Cl assertTrue(checkIfTheCacheFileExists()); } + @Ignore @Test public void test_preferences_When_AppIdAlreadyInPersistence_and_overridden_by_programmedConfig_and_UpdateConfiguration_AndThen_ClearUpdatedConfigCalled() { @@ -829,6 +843,7 @@ public void test_preferences_OverriddenConfig_and_UpdateConfiguration_AndThen_Cl assertTrue(checkIfTheCacheFileExists()); } + @Ignore @Test public void test_preferences_When_BundledConfiguration_and_UpdatedConfiguration_AndThen_ClearUpdatedConfigCalled() { // setup @@ -869,6 +884,7 @@ public void test_preferences_When_BundledConfiguration_and_UpdatedConfiguration_ assertFalse(checkIfTheCacheFileExists()); } + @Ignore @Test public void test_preferences_When_BundledConfiguration_and_overridden_by_programmedConfig_and_UpdatedConfiguration_AndThen_ClearUpdatedConfigCalled() { @@ -912,6 +928,7 @@ public void test_preferences_When_BundledConfiguration_and_UpdatedConfiguration_ assertFalse(checkIfTheCacheFileExists()); } + @Ignore @Test public void test_preferences_When_everythingSetup_and_overridden_by_programmedConfig_AndThen_ClearUpdatedConfigCalled() { @@ -965,6 +982,7 @@ public void test_preferences_When_BundledConfiguration_and_UpdatedConfiguration_ assertTrue(checkIfTheCacheFileExists()); } + @Ignore @Test public void test_preferences_When_UpdateConfiguration_AndThen_ClearUpdatedConfig_AndThen_UpdateConfigurationCalled() { @@ -1034,6 +1052,7 @@ public void test_preferences_When_UpdateConfiguration_AndThen_ClearUpdatedConfig // Delayed Response Tests // =====================================================================================\ + @Ignore @Test public void test_delayedResponse_FirstLaunch_BundledConfig_ConfigureWithAppId_and_UpdateConfig() { @@ -1072,6 +1091,7 @@ public void test_delayedResponse_FirstLaunch_BundledConfig_ConfigureWithAppId_an assertEquals(CUSTOMMODULE_CONFIG_VALUE, sharedStateData.optString(CUSTOMMODULE_CONFIG_KEY, null)); } + @Ignore @Test public void test_delayedResponse_When_AppIdAlreadySet_ConfigureWithAppId_and_UpdateConfig() { @@ -1111,8 +1131,7 @@ public void test_delayedResponse_When_AppIdAlreadySet_ConfigureWithAppId_and_Upd assertEquals(CUSTOMMODULE_CONFIG_VALUE, sharedStateData.optString(CUSTOMMODULE_CONFIG_KEY, null)); } - - + @Ignore @Test public void test_getPrivacyStatus_returnsValueInCallback() throws Exception { // setup event hub @@ -1188,6 +1207,7 @@ public void configEvent_WithNoRulesUrl_ShouldNotTriggerNetworkConnect() { assertEquals(0, testableNetworkService.waitAndGetCount()); } + @Ignore @Test public void configEvent_WithValidRulesUrl_ShouldTriggerNetworkRequest() throws Exception { setUpBasic(); @@ -1378,6 +1398,7 @@ public void analyticsRequestEvent_WithRulesUrlDownloadReturning404() throws Exce assertEquals("Should not dispatch any event, since rules data was not downloaded!", 0, events.size()); } + @Ignore @Test public void rules_with_same_url_not_download_within_time_sec() throws Exception { setupNetWorkService("RulesEngineTest_Rules_ModuleTest1.zip", new Date(), 1, HttpURLConnection.HTTP_OK); @@ -1395,8 +1416,9 @@ public void rules_with_same_url_not_download_within_time_sec() throws Exception count = testableNetworkService.waitAndGetCount(); assertEquals(0, count); } - @Test + @Ignore + @Test public void rules_with_same_url_will_download_when_timeout() throws Exception { setupNetWorkService("RulesEngineTest_Rules_ModuleTest1.zip", new Date(), 1, HttpURLConnection.HTTP_OK); setupSystemInfoService(temporaryFolder.newFolder()); @@ -1536,6 +1558,7 @@ public void configEvent_shouldNotClearRulesData_WhenRemoteDownloadReturnsUnknown } + @Ignore @Test public void reprocessEvent_When_FirstAppLaunch() throws Exception { eventHub.registerModule(ConfigurationExtension.class); @@ -2027,11 +2050,12 @@ private void setupNetWorkService(String contentResourceFileName, Date resourceLa testableNetworkService.setDefaultResponse(networkResponse); } - *//** + /** * Create a Date formatter in specific format * * @return SimpleDateFormat - *//* + */ + private SimpleDateFormat createRFC2822Formatter() { final String pattern = "EEE, dd MMM yyyy HH:mm:ss z"; final SimpleDateFormat rfc2822formatter = new SimpleDateFormat(pattern, Locale.US); @@ -2112,4 +2136,4 @@ private void triggerRulesDownloadWithEvent(final String rulesURL) { eventHub.dispatch(configEvent); } -}*/ +} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationRequestContentTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationRequestContentTest.java index ae6317a93..526c46dc8 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationRequestContentTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationRequestContentTest.java @@ -12,6 +12,7 @@ package com.adobe.marketing.mobile; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.*; @@ -31,7 +32,8 @@ public void beforeEach() { } // TODO uncomment after Configuration refactor - /* @Test + @Ignore + @Test public void testDispatchInternalConfigureWithAppIdEvent() throws Exception { //Test requestDispatcher.dispatchInternalConfigureWithAppIdEvent("appID"); @@ -45,7 +47,7 @@ public void testDispatchInternalConfigureWithAppIdEvent() throws Exception { assertEquals("appID", dispatchedEvent.getData().optString("config.appId", null)); assertTrue(dispatchedEvent.getData().optBoolean("config.isinternalevent", false)); assertNull(dispatchedEvent.getPairID()); - } */ + } } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationResponseContentTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationResponseContentTest.java index f546df961..5729de9a8 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationResponseContentTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationResponseContentTest.java @@ -12,6 +12,7 @@ package com.adobe.marketing.mobile; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.*; @@ -31,7 +32,8 @@ public void beforeEach() { } // TODO uncomment after Configuration refactor - /* @Test + @Ignore + @Test public void testDispatchConfigResponseWithEventData_Valid() { // Test EventData eventData = new EventData(); @@ -45,7 +47,7 @@ public void testDispatchConfigResponseWithEventData_Valid() { assertEquals(EventSource.RESPONSE_CONTENT, dispatchedEvent.getEventSource()); assertEquals(eventData, dispatchedEvent.getData()); assertEquals("pairID", dispatchedEvent.getPairID()); - } */ + } } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationResponseIdentityTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationResponseIdentityTest.java index 3c6ad212a..ffe166e9d 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationResponseIdentityTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationDispatcherConfigurationResponseIdentityTest.java @@ -12,6 +12,7 @@ package com.adobe.marketing.mobile; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.*; @@ -31,7 +32,8 @@ public void beforeEach() { } // TODO uncomment after Configuration refactor - /* @Test + @Ignore + @Test public void testDispatchAllIdentities() { //Test responseIdentityDispatcher.dispatchAllIdentities("jsonString", "pairID"); @@ -44,7 +46,7 @@ public void testDispatchAllIdentities() { assertEquals(EventSource.RESPONSE_IDENTITY, dispatchedEvent.getEventSource()); assertEquals("jsonString", dispatchedEvent.getData().optString("config.allIdentifiers", null)); assertEquals("pairID", dispatchedEvent.getPairID()); - } */ + } } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerBootEventTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerBootEventTest.java index 561fc3952..a2cadbe89 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerBootEventTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerBootEventTest.java @@ -12,6 +12,7 @@ package com.adobe.marketing.mobile; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.*; @@ -33,7 +34,8 @@ public void setup() { } // TODO uncomment after Configuration refactor - /* @Test + @Ignore + @Test public void testListener_Constructor_With_ValidParameter() { // Verify assertNotNull("the constructor should not return Null", listener); @@ -42,6 +44,7 @@ public void testListener_Constructor_With_ValidParameter() { ConfigurationListenerBootEvent.class); } + @Ignore @Test public void testListener_when_BootEvent() throws Exception { // Setup @@ -53,5 +56,5 @@ public void testListener_when_BootEvent() throws Exception { // Verify assertTrue("Handle Boot event much br called", mockConfiguration.handleBootEventWasCalled); assertEquals("Passes the correct event", bootedEvent, mockConfiguration.handleBootEventParamEvent); - } */ + } } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerRequestContentTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerRequestContentTest.java index 97bcaae46..c7857b5a6 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerRequestContentTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerRequestContentTest.java @@ -11,6 +11,7 @@ package com.adobe.marketing.mobile; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.*; @@ -31,8 +32,9 @@ public void setup() { EventSource.REQUEST_CONTENT); } - // TODO uncomment after Configuration refactor - /* @Test + // TODO fix after Configuration refactor + @Ignore + @Test public void testListener_Constructor_With_ValidParameter() { // Test configurationListenerRequestContent = new ConfigurationListenerRequestContent(mockConfiguration, @@ -45,6 +47,7 @@ public void testListener_Constructor_With_ValidParameter() { ConfigurationListenerRequestContent.class); } + @Ignore @Test public void testListener_when_HearCalled() throws Exception { // Setup @@ -57,7 +60,7 @@ public void testListener_when_HearCalled() throws Exception { // Verify assertTrue("Handle RequestContent event must be called", mockConfiguration.handleEventWasCalled); assertEquals("Passes the correct event", requestContentEvent, mockConfiguration.handleEventParamEvent); - } */ + } } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerRequestIdentityTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerRequestIdentityTest.java index fc80d07b7..3e3af495c 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerRequestIdentityTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerRequestIdentityTest.java @@ -12,6 +12,7 @@ package com.adobe.marketing.mobile; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -35,7 +36,8 @@ public void setup() { } // TODO uncomment after Configuration refactor - /* @Test + @Ignore + @Test public void testListener_Constructor_With_ValidParameter() { // Test configurationListenerRequestIdentity = new ConfigurationListenerRequestIdentity(mockConfiguration, @@ -48,6 +50,7 @@ public void testListener_Constructor_With_ValidParameter() { ConfigurationListenerRequestIdentity.class); } + @Ignore @Test public void testListener_when_GetSDKIdentitiesEvent() throws Exception { // Setup @@ -61,5 +64,5 @@ public void testListener_when_GetSDKIdentitiesEvent() throws Exception { assertTrue("Handle GetSDKIdentities must be called", mockConfiguration.handleGetSdkIdentitiesEventCalled); assertEquals("Passes the correct event", getSDKIdentitiesEvent, mockConfiguration.handleGetSdkIdentitiesEventParamEvent); - } */ + } } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerSharedStateTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerSharedStateTest.java index a85951068..46fda8ae0 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerSharedStateTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationListenerSharedStateTest.java @@ -12,6 +12,7 @@ package com.adobe.marketing.mobile; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -34,8 +35,9 @@ public void setup() { EventSource.SHARED_STATE); } - // TODO uncomment after Configuration refactor - /* @Test + // TODO fix after Configuration refactor + @Ignore + @Test public void testListener_Constructor_With_ValidParameter() { // Test configurationListenerSharedState = new ConfigurationListenerSharedState(mockConfiguration, @@ -48,6 +50,7 @@ public void testListener_Constructor_With_ValidParameter() { ConfigurationListenerSharedState.class); } + @Ignore @Test public void testListener_when_NullEventData() throws Exception { // Setup @@ -61,6 +64,7 @@ public void testListener_when_NullEventData() throws Exception { assertFalse("processGetSdkIds should not be called", mockConfiguration.processGetSdkIdsEventWasCalled); } + @Ignore @Test public void testListener_when_EmptyEventData() throws Exception { // Setup @@ -74,7 +78,7 @@ public void testListener_when_EmptyEventData() throws Exception { assertFalse("processGetSdkIds should not be called", mockConfiguration.processGetSdkIdsEventWasCalled); } - + @Ignore @Test public void testListener_when_SharedStateOwnerNull() throws Exception { // Setup @@ -92,6 +96,7 @@ public void testListener_when_SharedStateOwnerNull() throws Exception { assertFalse("processGetSdkIds should not be called", mockConfiguration.processGetSdkIdsEventWasCalled); } + @Ignore @Test public void testListener_when_SharedStateOwnerInvalid() throws Exception { // Setup @@ -109,6 +114,7 @@ public void testListener_when_SharedStateOwnerInvalid() throws Exception { assertFalse("processGetSdkIds should not be called", mockConfiguration.processGetSdkIdsEventWasCalled); } + @Ignore @Test public void testListener_when_SharedStateOwnerIsNotRequiredModule() throws Exception { // Setup @@ -126,7 +132,7 @@ public void testListener_when_SharedStateOwnerIsNotRequiredModule() throws Excep assertFalse("processGetSdkIds should not be called", mockConfiguration.processGetSdkIdsEventWasCalled); } - + @Ignore @Test public void testListener_when_SharedStateOwnerIsConfiguration() throws Exception { // Setup @@ -144,6 +150,7 @@ public void testListener_when_SharedStateOwnerIsConfiguration() throws Exception assertTrue("processGetSdkIds should be called", mockConfiguration.processGetSdkIdsEventWasCalled); } + @Ignore @Test public void testListener_when_SharedStateOwnerIsAudience() throws Exception { // Setup @@ -161,6 +168,7 @@ public void testListener_when_SharedStateOwnerIsAudience() throws Exception { assertTrue("processGetSdkIds should be called", mockConfiguration.processGetSdkIdsEventWasCalled); } + @Ignore @Test public void testListener_when_SharedStateOwnerIsAnalytics() throws Exception { // Setup @@ -178,6 +186,7 @@ public void testListener_when_SharedStateOwnerIsAnalytics() throws Exception { assertTrue("processGetSdkIds should be called", mockConfiguration.processGetSdkIdsEventWasCalled); } + @Ignore @Test public void testListener_when_SharedStateOwnerIsIdentity() throws Exception { // Setup @@ -195,6 +204,7 @@ public void testListener_when_SharedStateOwnerIsIdentity() throws Exception { assertTrue("processGetSdkIds should be called", mockConfiguration.processGetSdkIdsEventWasCalled); } + @Ignore @Test public void testListener_when_SharedStateOwnerIsTarget() throws Exception { // Setup @@ -210,7 +220,7 @@ public void testListener_when_SharedStateOwnerIsTarget() throws Exception { // Verify assertTrue("processGetSdkIds should be called", mockConfiguration.processGetSdkIdsEventWasCalled); - } */ + } } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationTests.java index a66fe7809..aff941139 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/ConfigurationTests.java @@ -12,6 +12,7 @@ import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import java.io.File; @@ -105,7 +106,8 @@ public void afterEach() { // ================================================================================================================= // TODO uncomment after Configuration refactor - /*@Test + @Ignore + @Test public void testProcessEvent_when_AppIDEvent() throws Exception { // setup beginBasic(); @@ -118,6 +120,7 @@ public void testProcessEvent_when_AppIDEvent() throws Exception { assertEquals(configuration.processConfigureWithAppIDEventParameterNewAppId, "appID"); } + @Ignore @Test public void testProcessEvent_when_updateConfigEvent() throws Exception { // setup @@ -130,6 +133,7 @@ public void testProcessEvent_when_updateConfigEvent() throws Exception { assertEquals(configuration.processUpdateConfigEventParameterEvent, event); } + @Ignore @Test public void testProcessEvent_when_clearUpdatedConfigEvent() throws Exception { // setup @@ -142,6 +146,7 @@ public void testProcessEvent_when_clearUpdatedConfigEvent() throws Exception { assertEquals(configuration.processClearUpdatedConfigEventParameterEvent, event); } + @Ignore @Test public void testProcessEvent_when_configWithFilePathEvent() throws Exception { // setup @@ -155,6 +160,7 @@ public void testProcessEvent_when_configWithFilePathEvent() throws Exception { assertEquals(configuration.processConfigWithFilePathEventParametersFilePath, "FilePath"); } + @Ignore @Test public void testProcessEvent_when_publishConfigEvent() throws Exception { // setup @@ -172,6 +178,7 @@ public void testProcessEvent_when_publishConfigEvent() throws Exception { // void processConfigWithFilePathEvent(final String filePath, final Event event, final boolean isUpdate) // ================================================================================================================= + @Ignore @Test public void testProcessConfigWithFilePathEvent_NullFilePath() throws Exception { // setup @@ -188,6 +195,7 @@ public void testProcessConfigWithFilePathEvent_NullFilePath() throws Exception } + @Ignore @Test public void testProcessConfigWithFilePathEvent_EmptyFilePath() throws Exception { // setup @@ -205,6 +213,7 @@ public void testProcessConfigWithFilePathEvent_EmptyFilePath() throws Exception } + @Ignore @Test public void testProcessConfigWithFilePathEvent_When_NoFileIsPresent() throws Exception { // setup @@ -231,6 +240,7 @@ public void testProcessConfigWithFilePathEvent_When_NoFileIsPresent() throws Exc deleteTempAppDirectory(); } + @Ignore @Test public void testProcessConfigWithFilePathEvent_When_FileReturnsEmptyString() throws Exception { // setup @@ -256,6 +266,7 @@ public void testProcessConfigWithFilePathEvent_When_FileReturnsEmptyString() thr deleteTempAppDirectory(); } + @Ignore @Test public void testProcessConfigWithFilePathEvent_When_FileReturnsValidString() throws Exception { // setup @@ -287,6 +298,7 @@ public void testProcessConfigWithFilePathEvent_When_FileReturnsValidString() thr // test loadBundledConfig // ================================================================================================================= + @Ignore @Test public void testProcessConfigWithAssetFileEvent_NullFilePath() throws Exception { // setup @@ -304,6 +316,7 @@ public void testProcessConfigWithAssetFileEvent_NullFilePath() throws Exception } + @Ignore @Test public void testProcessConfigWithAssetFileEvent_EmptyFilePath() throws Exception { // setup @@ -322,6 +335,7 @@ public void testProcessConfigWithAssetFileEvent_EmptyFilePath() throws Exception } + @Ignore @Test public void testProcessConfigWithAssetFileEvent_When_NoFileIsPresent() throws Exception { // setup @@ -348,6 +362,7 @@ public void testProcessConfigWithAssetFileEvent_When_NoFileIsPresent() throws Ex deleteTempAppDirectory(); } + @Ignore @Test public void testProcessConfigWithAssetFileEvent_When_FileReturnsEmptyString() throws Exception { // setup @@ -374,6 +389,7 @@ public void testProcessConfigWithAssetFileEvent_When_FileReturnsEmptyString() th deleteTempAppDirectory(); } + @Ignore @Test public void testProcessConfigWithAssetFileEvent_When_FileReturnsValidString() throws Exception { // setup @@ -407,6 +423,7 @@ public void testProcessConfigWithAssetFileEvent_When_FileReturnsValidString() th // void processUpdateConfigEvent(final Event event, final boolean isUpdate) // ================================================================================================================= + @Ignore @Test public void testProcessConfigUpdateEvent_when_Null() throws Exception { beginBasic(); @@ -420,6 +437,7 @@ public void testProcessConfigUpdateEvent_when_Null() throws Exception { assertNull(configEventData); } + @Ignore @Test public void testProcessConfigUpdateEvent_when_EmptyConfig() throws Exception { beginBasic(); @@ -434,6 +452,7 @@ public void testProcessConfigUpdateEvent_when_EmptyConfig() throws Exception { assertNull(configEventData); } + @Ignore @Test public void testProcessConfigUpdateEvent_with_ValidConfig() throws Exception { beginBasic(); @@ -457,6 +476,7 @@ public void testProcessConfigUpdateEvent_with_ValidConfig() throws Exception { assertNull("event should not have pairId", responseDispatcher.dispatchConfigResponseEventParameterPairID); } + @Ignore @Test public void testProcessConfigUpdateEvent_with_ValidConfig_and_PrimaryConfigurationOccurred() throws Exception { // setup @@ -486,6 +506,7 @@ public void testProcessConfigUpdateEvent_with_ValidConfig_and_PrimaryConfigurati assertNull("event should not have pairId", responseDispatcher.dispatchConfigResponseEventParameterPairID); } + @Ignore @Test public void testProcessConfigUpdateEvent_when_localStorageService_not_initialized_shouldNotCrash() throws Exception { platformServices.fakeLocalStorageService = null; @@ -514,6 +535,7 @@ public void testProcessConfigUpdateEvent_when_localStorageService_not_initialize // void processClearUpdatedConfigEvent(final Event event) // ================================================================================================================= + @Ignore @Test public void testProcessClearUpdatedConfigEvent_when_PrimaryConfigurationNull() throws Exception { beginBasic(); @@ -529,6 +551,7 @@ public void testProcessClearUpdatedConfigEvent_when_PrimaryConfigurationNull() t } + @Ignore @Test public void testProcessClearUpdatedConfigEvent_with_ValidPrimaryConfiguration() throws Exception { // setup @@ -556,6 +579,7 @@ public void testProcessClearUpdatedConfigEvent_with_ValidPrimaryConfiguration() } + @Ignore @Test public void testProcessClearUpdatedConfigEvent_with_ValidPrimaryConfiguration_OverriddenByProgrammaticConfig() throws Exception { @@ -588,6 +612,7 @@ public void testProcessClearUpdatedConfigEvent_with_ValidPrimaryConfiguration_Ov // void handleBootEvent(final Event event) // ================================================================================================================= + @Ignore @Test public void testHandleBootEvent_when_NoAppIdInManifest_AppIDPersisted_then_CachedFileIsLoaded() throws Exception { @@ -617,6 +642,7 @@ public void testHandleBootEvent_when_NoAppIdInManifest_AppIDPersisted_then_Cache assertEquals("mockAppID", requestDispatcher.dispatchInternalConfigureWithAppIdEventParameterAppID); } + @Ignore @Test public void testHandleBootEvent_when_AppIdInManifest_then_CachedFileIsLoaded() throws Exception { // setup @@ -646,6 +672,7 @@ public void testHandleBootEvent_when_AppIdInManifest_then_CachedFileIsLoaded() t assertEquals("manifestAppID", requestDispatcher.dispatchInternalConfigureWithAppIdEventParameterAppID); } + @Ignore @Test public void testHandleBootEvent_when_AppIdInManifest_and_AppIDInPersistence_then_CachedFileIsLoaded_WithPersistedAppID() @@ -678,6 +705,7 @@ public void testHandleBootEvent_when_AppIdInManifest_then_CachedFileIsLoaded() t assertEquals("mockAppID", requestDispatcher.dispatchInternalConfigureWithAppIdEventParameterAppID); } + @Ignore @Test public void testHandleBootEvent_when_AppIDInPersistence_and_NoCachedFile_then_OverriddenConfiguration_isLoaded() @@ -712,6 +740,7 @@ public void testHandleBootEvent_when_AppIdInManifest_then_CachedFileIsLoaded() t assertEquals("mockAppID", requestDispatcher.dispatchInternalConfigureWithAppIdEventParameterAppID); } + @Ignore @Test public void testHandleBootEvent_when_AppIDAbsent_BundledConfigPresent_thenConfigureWithBundledContent() throws Exception { @@ -728,6 +757,7 @@ public void testHandleBootEvent_when_AppIDAbsent_BundledConfigPresent_thenConfig requestDispatcher.dispatchInternalConfigureWithAppIdEventWasCalled); } + @Ignore @Test public void testHandleBootEvent_when_AppIDAbsent_BundledConfigAbsent_WithOverriddenConfig_thenShouldConfigure() throws Exception { @@ -755,6 +785,7 @@ public void testHandleBootEvent_when_AppIDAbsent_BundledConfigAbsent_WithOverrid requestDispatcher.dispatchInternalConfigureWithAppIdEventWasCalled); } + @Ignore @Test public void testHandleBootEvent_when_AppIDAbsent_BundledConfigAbsent_NoOverriddenConfig_thenShouldNotConfigure() throws @@ -768,7 +799,7 @@ public void testHandleBootEvent_when_AppIDAbsent_BundledConfigAbsent_WithOverrid assertFalse(configuration.configureWithJsonStringWasCalled); } - + @Ignore @Test public void testHandleBootEvent_when_SystemInfoService_and_LocalStorageService_unavailable_then_ShouldNotConfigure() throws @@ -785,6 +816,7 @@ public void testHandleBootEvent_when_AppIDAbsent_BundledConfigAbsent_WithOverrid assertFalse(configuration.configureWithJsonStringWasCalled); } + @Ignore @Test public void testHandleBootEvent_when_AppID_InPersistence_NoCachedFile_WithOverriddenConfig_thenShouldConfigure() throws Exception { @@ -816,7 +848,7 @@ public void testHandleBootEvent_when_AppID_InPersistence_NoCachedFile_WithOverri // void processConfigureWithAppIDEvent(final String newAppId, final Event event, final boolean isUpdate) // ================================================================================================================= - + @Ignore @Test public void testProcessConfigureWithAppIdEvent_when_EmptyEventData() throws Exception { beginBasic(); @@ -833,6 +865,7 @@ public void testProcessConfigureWithAppIdEvent_when_EmptyEventData() throws Exce assertFalse("event should not be dispatched", responseDispatcher.dispatchConfigResponseWithEventDataWasCalled); } + @Ignore @Test public void testProcessConfigureWithAppIdInternalEvent_when_DifferentAppIdInPersistence() throws Exception { beginWithAppIDInPersistence(); @@ -847,6 +880,7 @@ public void testProcessConfigureWithAppIdInternalEvent_when_DifferentAppIdInPers assertFalse("event should not be dispatched", responseDispatcher.dispatchConfigResponseWithEventDataWasCalled); } + @Ignore @Test public void testProcessConfigureWithAppIdInternalEvent_when_SameAppIdInPersistence() throws Exception { beginWithAppIDInPersistence(); @@ -877,6 +911,7 @@ public void testProcessConfigureWithAppIdInternalEvent_when_SameAppIdInPersisten responseDispatcher.dispatchConfigResponseEventParameterEventData.getString(ANALYTICS_SERVER_KEY)); } + @Ignore @Test public void testProcessConfigureWithAppIdEvent_when_nullAppID_should_RemoveAppIdFromPersistence() throws Exception { beginWithAppIDInPersistence(); @@ -899,6 +934,7 @@ public void testProcessConfigureWithAppIdEvent_when_nullAppID_should_RemoveAppId } + @Ignore @Test public void testProcessConfigureWithAppIdEvent_when_emptyAppID_should_RemoveAppIdFromPersistence() throws Exception { beginWithAppIDInPersistence(); @@ -921,7 +957,7 @@ public void testProcessConfigureWithAppIdEvent_when_emptyAppID_should_RemoveAppI } - + @Ignore @Test public void testProcessConfigureWithAppIdEvent_OnRemoteFetchSuccess() throws Exception { beginBasic(); @@ -954,6 +990,7 @@ public void testProcessConfigureWithAppIdEvent_OnRemoteFetchSuccess() throws Exc } + @Ignore @Test public void testProcessConfigureWithAppIdEvent_OnRemoteFetchFailure() throws Exception { beginBasic(); @@ -978,7 +1015,7 @@ public void testProcessConfigureWithAppIdEvent_OnRemoteFetchFailure() throws Exc assertFalse("event should not be dispatched", responseDispatcher.dispatchConfigResponseWithEventDataWasCalled); } - + @Ignore @Test public void testProcessConfigureWithAppIdEvent_when_OverriddenConfig_persisted_then_OnRemoteFetchFailure_should_loadOnlyOverriddenConfig() @@ -1010,8 +1047,7 @@ public void testProcessConfigureWithAppIdEvent_OnRemoteFetchFailure() throws Exc assertFalse("event should not be dispatched", responseDispatcher.dispatchConfigResponseWithEventDataWasCalled); } - - + @Ignore @Test public void testProcessConfigureWithAppIdEvent_when_platformServices_notAvailable_shouldNotCrash() throws Exception { platformServices.fakeLocalStorageService = null; @@ -1031,7 +1067,7 @@ public void testProcessConfigureWithAppIdEvent_when_platformServices_notAvailabl // verify dispatchedEvent assertFalse("event should not be dispatched", responseDispatcher.dispatchConfigResponseWithEventDataWasCalled); } -*/ + // helper for testProcessConfigureWithAppIdEvent_WithNetworkOffOffOn final class FakeSystemInfoServiceForNetworkTest extends MockSystemInfoService { public List listeners = new ArrayList(); @@ -1095,8 +1131,8 @@ public boolean registerOneTimeNetworkConnectionActiveListener(final NetworkConne } } - // TODO uncomment after Configuration refactor -/* @Test + @Ignore + @Test public void testProcessConfigureWithAppIdEvent_WithNetworkOffOffOn() throws Exception { beginBasic(); final FakeSystemInfoServiceForNetworkTest mySystemInfoService = new FakeSystemInfoServiceForNetworkTest(); @@ -1135,6 +1171,7 @@ public void testProcessConfigureWithAppIdEvent_WithNetworkOffOffOn() throws Exce } + @Ignore @Test public void testProcessConfigureWithAppIdEvent_WithNetworkOffAndCachedConfig() throws Exception { beginBasic(); @@ -1176,6 +1213,7 @@ public void testProcessConfigureWithAppIdEvent_WithNetworkOffAndCachedConfig() t // void configureWithJsonString(final String jsonConfigString, final Event event, final boolean isUpdate) // ================================================================================================================= + @Ignore @Test public void testConfigureWithJsonString_when_NullJsonString() throws Exception { beginBasic(); @@ -1189,6 +1227,7 @@ public void testConfigureWithJsonString_when_NullJsonString() throws Exception assertFalse("event should not be dispatched", responseDispatcher.dispatchConfigResponseWithEventDataWasCalled); } + @Ignore @Test public void testConfigureWithJsonString_when_EmptyJsonString() throws Exception { beginBasic(); @@ -1203,6 +1242,7 @@ public void testConfigureWithJsonString_when_EmptyJsonString() throws Exception assertFalse("event should not be dispatched", responseDispatcher.dispatchConfigResponseWithEventDataWasCalled); } + @Ignore @Test public void testConfigureWithJsonString_when_ValidJsonString() throws Exception { beginBasic(); @@ -1216,6 +1256,7 @@ public void testConfigureWithJsonString_when_ValidJsonString() throws Exception responseDispatcher.dispatchConfigResponseEventParameterEventData.optString(ANALYTICS_SERVER_KEY, null)); } + @Ignore @Test public void testConfigureWithJsonString_when_ValidJsonString_and_overriddenConfigInPersistence_isUpdateIsFalse() throws Exception { @@ -1243,6 +1284,7 @@ public void testConfigureWithJsonString_when_ValidJsonString_and_overriddenConfi } + @Ignore @Test public void testConfigureWithJsonString_when_ValidJsonString_and_overriddenConfigInPersistence_isUpdateIsTrue() throws Exception { @@ -1264,6 +1306,7 @@ public void testConfigureWithJsonString_when_ValidJsonString_and_overriddenConfi // ================================================================================================================= // ConfigurationDownloader getConfigurationDownloader(final String url, final String pairId) // ================================================================================================================= + @Ignore @Test public void testGetConfigurationDownloader_happyPath() throws Exception { // test @@ -1276,6 +1319,7 @@ public void testGetConfigurationDownloader_happyPath() throws Exception { assertEquals("downloader should of type ConfigurationDownloader", ConfigurationDownloader.class, downloader.getClass()); } + @Ignore @Test public void testGetConfigurationDownloader_when_NetworkService_ISNull() throws Exception { // test @@ -1289,6 +1333,7 @@ public void testGetConfigurationDownloader_when_NetworkService_ISNull() throws E // ================================================================================================================= // void handleGetSdkIdentitiesEvent(final Event event) // ================================================================================================================= + @Ignore @Test public void testHandleGetSdkIdentitiesEvent() throws Exception { // setup @@ -1312,6 +1357,7 @@ public void testHandleGetSdkIdentitiesEvent() throws Exception { // ================================================================================================================= // void processGetSdkIdsEvent() // ================================================================================================================= + @Ignore @Test public void testProcessGetSdkIdsEvent_WhenNoEventQueued() throws Exception { // setup @@ -1325,6 +1371,7 @@ public void testProcessGetSdkIdsEvent_WhenNoEventQueued() throws Exception { assertFalse(responseIdentityDispatcher.dispatchAllIdentitiesWasCalled); } + @Ignore @Test public void testProcessGetSdkIdsEvent_when_JSONUtilityServiceNull() throws Exception { // setup @@ -1346,6 +1393,7 @@ public void testProcessGetSdkIdsEvent_when_JSONUtilityServiceNull() throws Excep assertEquals("{}", responseIdentityDispatcher.dispatchAllIdentitiesParametersSdkIdentitiesJson); } + @Ignore @Test public void testProcessGetSdkIdsEvent_WhenSharedStateNotReady() throws Exception { // setup @@ -1371,6 +1419,7 @@ public void testProcessGetSdkIdsEvent_WhenSharedStateNotReady() throws Exception assertFalse(responseIdentityDispatcher.dispatchAllIdentitiesWasCalled); } + @Ignore @Test public void testProcessGetSdkIdsEvent_WhenAllSharedState() throws Exception { // setup @@ -1396,7 +1445,7 @@ public void testProcessGetSdkIdsEvent_WhenAllSharedState() throws Exception { assertTrue(responseIdentityDispatcher.dispatchAllIdentitiesWasCalled); assertEquals("pairID", responseIdentityDispatcher.dispatchAllIdentitiesParameterPairId); assertNotNull(responseIdentityDispatcher.dispatchAllIdentitiesParametersSdkIdentitiesJson); - }*/ + } // ================================================================================================================= // Setup Methods diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MobileIdentitiesTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MobileIdentitiesTest.java index fa0a83800..a742bdffd 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MobileIdentitiesTest.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MobileIdentitiesTest.java @@ -12,6 +12,7 @@ package com.adobe.marketing.mobile; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import java.util.ArrayList; @@ -38,7 +39,8 @@ public void beforeEach() { // ======================================================== // TODO uncomment after Configuration refactor - /*@Test + @Ignore + @Test public void test_GetAllIdentifiers_Happy() { // Setup Event event = new Event.Builder("EventHub", EventType.CONFIGURATION, EventSource.REQUEST_CONTENT) @@ -67,6 +69,7 @@ public void test_GetAllIdentifiers_Happy() { "{\"namespace\":\"20919\",\"type\":\"integrationCode\",\"value\":\"test_pushId\"}]}]}", allIdentifiers); } + @Ignore @Test public void test_GetAllIdentifiers_WhenTargetStateNotAvailable() { // Setup @@ -95,6 +98,7 @@ public void test_GetAllIdentifiers_WhenTargetStateNotAvailable() { "{\"namespace\":\"20919\",\"type\":\"integrationCode\",\"value\":\"test_pushId\"}]}]}", allIdentifiers); } + @Ignore @Test public void test_GetAllIdentifiers_WhenAudienceStateNotAvailable() { // Setup @@ -123,7 +127,7 @@ public void test_GetAllIdentifiers_WhenAudienceStateNotAvailable() { "{\"namespace\":\"20919\",\"type\":\"integrationCode\",\"value\":\"test_pushId\"}]}]}", allIdentifiers); } - + @Ignore @Test public void test_GetAllIdentifiers_WhenConfigurationStateNotAvailable() { // Setup @@ -152,6 +156,7 @@ public void test_GetAllIdentifiers_WhenConfigurationStateNotAvailable() { "{\"namespace\":\"20919\",\"type\":\"integrationCode\",\"value\":\"test_pushId\"}]}]}", allIdentifiers); } + @Ignore @Test public void test_GetAllIdentifiers_WhenAnalyticsStateNotAvailable() { // Setup @@ -180,6 +185,7 @@ public void test_GetAllIdentifiers_WhenAnalyticsStateNotAvailable() { "{\"namespace\":\"20919\",\"type\":\"integrationCode\",\"value\":\"test_pushId\"}]}]}", allIdentifiers); } + @Ignore @Test public void test_GetAllIdentifiers_WhenIdentityStateNotAvailable() { // Setup @@ -205,6 +211,7 @@ public void test_GetAllIdentifiers_WhenIdentityStateNotAvailable() { "{\"namespace\":\"0\",\"type\":\"namespaceId\",\"value\":\"test_uuid\"}]}]}", allIdentifiers); } + @Ignore @Test public void test_GetAllIdentifiers_WhenAllSharedStateAreInvalid() { // Setup @@ -220,6 +227,7 @@ public void test_GetAllIdentifiers_WhenAllSharedStateAreInvalid() { assertEquals("{}", allIdentifiers); } + @Ignore @Test public void test_GetAllIdentifiers_InvalidVisitorIDList_ShouldNotCrash() { // Setup @@ -242,6 +250,7 @@ public void test_GetAllIdentifiers_InvalidVisitorIDList_ShouldNotCrash() { // static boolean areAllSharedStatesReady(final Event event, final Module module) // ======================================================== + @Ignore @Test public void test_AreAllSharedStatesReady_WhenNoSharedStateSet() throws Exception { // Setup @@ -256,6 +265,7 @@ public void test_AreAllSharedStatesReady_WhenNoSharedStateSet() throws Exception assertTrue("areAllSharedStatesReady should return true", isReady); } + @Ignore @Test public void test_AreAllSharedStatesReady_WhenALlSharedStateSetArePending() throws Exception { // Setup @@ -271,7 +281,7 @@ public void test_AreAllSharedStatesReady_WhenALlSharedStateSetArePending() throw assertFalse("areAllSharedStatesReady should return false", isReady); } - + @Ignore @Test public void test_AreAllSharedStatesReady_WhenAllSharedStateSet() throws Exception { // Setup @@ -287,6 +297,7 @@ public void test_AreAllSharedStatesReady_WhenAllSharedStateSet() throws Exceptio assertTrue("areAllSharedStatesReady should return true", isReady); } + @Ignore @Test public void test_AreAllSharedStatesReady_WhenAudienceStatePending() throws Exception { // Setup @@ -303,6 +314,7 @@ public void test_AreAllSharedStatesReady_WhenAudienceStatePending() throws Excep assertFalse("areAllSharedStatesReady should return false", isReady); } + @Ignore @Test public void test_AreAllSharedStatesReady_WhenAnalyticsStatePending() throws Exception { // Setup @@ -319,6 +331,7 @@ public void test_AreAllSharedStatesReady_WhenAnalyticsStatePending() throws Exce assertFalse("areAllSharedStatesReady should return false", isReady); } + @Ignore @Test public void test_AreAllSharedStatesReady_WhenConfigurationStatePending() throws Exception { // Setup @@ -335,6 +348,7 @@ public void test_AreAllSharedStatesReady_WhenConfigurationStatePending() throws assertFalse("areAllSharedStatesReady should return false", isReady); } + @Ignore @Test public void test_AreAllSharedStatesReady_WhenIdentityStatePending() throws Exception { // Setup @@ -351,7 +365,7 @@ public void test_AreAllSharedStatesReady_WhenIdentityStatePending() throws Excep assertFalse("areAllSharedStatesReady should return false", isReady); } - + @Ignore @Test public void test_AreAllSharedStatesReady_WhenTargetStatePending() throws Exception { // Setup @@ -367,7 +381,6 @@ public void test_AreAllSharedStatesReady_WhenTargetStatePending() throws Excepti // Verify assertFalse("areAllSharedStatesReady should return false", isReady); } -*/ // ======================================================== // Helper methods From 318e9eb7c63e9797f85d74d20a512afde939a41d Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Wed, 15 Jun 2022 09:22:04 -0700 Subject: [PATCH 079/476] code review changes --- .../mobile/services/DataQueueService.java | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java index 6cea88fd5..0ef230c82 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java @@ -13,6 +13,8 @@ import android.content.Context; +import androidx.annotation.NonNull; + import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; import com.adobe.marketing.mobile.internal.utility.StringUtils; @@ -36,9 +38,8 @@ class DataQueueService implements DataQueuing { dataQueueCache = new HashMap<>(); } - //TODO add @NonNUll annotation after merging with dev-v2.0.0 branch @Override - public DataQueue getDataQueue(final String databaseName) { + public DataQueue getDataQueue(@NonNull final String databaseName) { DataQueue dataQueue = dataQueueCache.get(databaseName); if (dataQueue == null) { @@ -46,16 +47,7 @@ public DataQueue getDataQueue(final String databaseName) { dataQueue = dataQueueCache.get(databaseName); if (dataQueue == null) { - Context appContext = ServiceProvider.getInstance().getApplicationContext(); - - if(appContext == null) { - MobileCore.log(LoggingMode.WARNING, - LOG_TAG, - String.format("Failed to create DataQueue for database (%s), the ApplicationContext is null", databaseName)); - return null; - } - - final File databaseDirDataQueue = migrateDataQueueService(databaseName); + final File databaseDirDataQueue = openOrMigrateExistingDataQueue(databaseName); if(databaseDirDataQueue == null){ return null; @@ -69,7 +61,16 @@ public DataQueue getDataQueue(final String databaseName) { return dataQueue; } - private File migrateDataQueueService(String databaseName) { + /** + * Returns the database if it exists in the path returned by {@link Context#getDatabasePath(String)} + * Else copies the existing database from {@link Context#getCacheDir()} to {@code Context#getDatabasePath(String)} + * Database is migrated from cache directory because of Android 12 app hibernation changes + * which can clear app's cache folder when user doesn't interact with app for few months. + * + * @param databaseName name of the database to be migrated or opened + * @return {@code File} representing the database in {@code Context#getDatabasePath(String)} + */ + private File openOrMigrateExistingDataQueue(String databaseName) { final String cleanedDatabaseName = FileUtil.removeRelativePath(databaseName); if(StringUtils.isNullOrEmpty(databaseName)) { @@ -79,7 +80,16 @@ private File migrateDataQueueService(String databaseName) { return null; } - final File databaseDirDataQueue = ServiceProvider.getInstance().getApplicationContext().getDatabasePath(cleanedDatabaseName); + Context appContext = ServiceProvider.getInstance().getApplicationContext(); + + if(appContext == null) { + MobileCore.log(LoggingMode.WARNING, + LOG_TAG, + String.format("Failed to create DataQueue for database (%s), the ApplicationContext is null", databaseName)); + return null; + } + + final File databaseDirDataQueue = appContext.getDatabasePath(cleanedDatabaseName); final File cacheDir = ServiceProvider.getInstance().getDeviceInfoService().getApplicationCacheDir(); if (!databaseDirDataQueue.exists() && cacheDir != null) { @@ -91,6 +101,11 @@ private File migrateDataQueueService(String databaseName) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Successfully moved DataQueue for database (%s) from cache directory to database directory", databaseName)); + if(cacheDirDataQueue.delete()) { + MobileCore.log(LoggingMode.DEBUG, + LOG_TAG, + String.format("Successfully delete DataQueue for database (%s) from cache directory", databaseName)); + } } } catch (Exception e) { MobileCore.log(LoggingMode.WARNING, From 6742f43ded985c8f9e99546d23a1243c11d87a3b Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Wed, 15 Jun 2022 10:05:37 -0700 Subject: [PATCH 080/476] remove unused functions in file and db utilities --- .../com/adobe/marketing/mobile/TestUtils.java | 17 ------------- .../com/adobe/marketing/mobile/FileUtil.java | 1 - .../marketing/mobile/FileTestHelper.java | 24 ------------------- .../adobe/marketing/mobile/FileUtilTests.java | 3 --- 4 files changed, 45 deletions(-) diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/TestUtils.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/TestUtils.java index b9655d3e5..f27a4ab09 100644 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/TestUtils.java +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/TestUtils.java @@ -33,15 +33,6 @@ public static void deleteAllFilesInCacheDir(Context appContext) { } } - public static void deleteDatabaseInDatabaseDir(Context appContext, String databaseName) { - if (appContext == null) { - return; - } - - File databasePath = appContext.getDatabasePath(databaseName); - databasePath.delete(); - } - public static String getCacheDir(Context appContext) { if (appContext == null) { return null; @@ -50,14 +41,6 @@ public static String getCacheDir(Context appContext) { return appContext.getCacheDir().getPath(); } - public static String getDatabasePathInDatabaseDir(Context appContext, String databaseName) { - if (appContext == null) { - return null; - } - - return appContext.getDatabasePath(databaseName).getPath(); - } - public static boolean almostEqual(long actual, long expected, long tolerance) { return Math.abs(actual - expected) < tolerance; } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/FileUtil.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/FileUtil.java index 8fb581be2..b0e8173cf 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/FileUtil.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/FileUtil.java @@ -81,5 +81,4 @@ static String readStringFromFile(final File file) { static boolean isValidDirectory(final File directory) { return directory != null && directory.isDirectory() && directory.canWrite(); } - } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileTestHelper.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileTestHelper.java index 997dc3f45..1ea96be5c 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileTestHelper.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileTestHelper.java @@ -128,30 +128,6 @@ File placeSampleCacheDirectory(final String dirName, final String fileName) { return cacheDirectory; } - File createEmptyFile(final String dirName, final String fileName) { - File fileDirectory = new File(getCacheDirectory(dirName) + File.separator); - fileDirectory.mkdir(); - File dest = new File(getCacheDirectory(dirName) + File.separator + fileName); - try { - dest.createNewFile(); - } catch (IOException ex) { - fail("Could not create test directory and files " + ex); - } - - return dest; - } - - void writeToFile(final File file, final String content) { - try { - FileWriter fileWriter = new FileWriter(file); - fileWriter.write(content); - fileWriter.flush(); - fileWriter.close(); - } catch (IOException ex) { - fail("Could not write to file " + ex); - } - } - private File createFile(final String fileName) { return createFile(null, fileName); } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileUtilTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileUtilTests.java index 368d73a8b..90edea51d 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileUtilTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/FileUtilTests.java @@ -16,9 +16,7 @@ import org.junit.Test; import java.io.File; -import java.io.IOException; -import static com.adobe.marketing.mobile.FileTestHelper.FILE_DIRECTORY; import static com.adobe.marketing.mobile.FileTestHelper.MOCK_CONFIG_JSON; import static com.adobe.marketing.mobile.FileTestHelper.MOCK_FILE_NAME; import static org.junit.Assert.*; @@ -97,5 +95,4 @@ public void testReadJsonStringFromFile_When_DirectoryInsteadOfFile_Then_ReturnsN "testFileName")); assertNull(content); } - } \ No newline at end of file From 89009595e21a271fd85196e75aef51e5ed885349 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Wed, 15 Jun 2022 13:30:53 -0700 Subject: [PATCH 081/476] code review comments --- .../com/adobe/marketing/mobile/Event.java | 7 +- .../mobile/internal/utility/MapExtensions.kt | 3 +- .../rulesengine/LaunchRulesConsequence.kt | 16 ++-- .../rulesengine/LaunchRulesEvaluator.kt | 8 +- .../launch/rulesengine/RuleConsequence.kt | 6 +- .../LaunchRulesConsequenceTests.kt | 94 ++++++++++++------- 6 files changed, 84 insertions(+), 50 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java index 32234b388..192454d1a 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java @@ -293,7 +293,12 @@ public Event copy() { return this; } - public Event copyWithNewData(final Map newData) { + /** + * Clones the current {@link Event} with updated data + * @param newData data associated with the new {@code Event} + * @return new cloned {@code Event} with provided data + */ + public Event cloneWithEventData(final Map newData) { Event newEvent = new Event.Builder(this.name, this.type, this.source, this.mask) .setEventData(newData) .build(); diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt index 51c8c1d1d..e39cd2462 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt @@ -132,8 +132,7 @@ internal fun Map.serializeToQueryString(): String { * @return map as json string */ -internal fun Map?.prettify(): String { - if (this == null) return "" +internal fun Map.prettify(): String { return try { JSONObject(this).toString(4) } catch (e: Exception) { diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt index 4f804393c..8964054e8 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt @@ -21,7 +21,6 @@ import com.adobe.marketing.mobile.rulesengine.Template import com.adobe.marketing.mobile.rulesengine.TokenFinder class LaunchRulesConsequence( - private val launchRulesEngine: LaunchRulesEngine, private val extensionApi: ExtensionApi ) { @@ -48,12 +47,11 @@ class LaunchRulesConsequence( const val CONSEQUENCE_EVENT_NAME = "Rules Consequence Event" } - fun evaluateRulesConsequence(event: Event?): Event? { + fun evaluateRulesConsequence(event: Event?, matchedRules: List): Event? { if (event == null) return null val dispatchChainCount = dispatchChainedEventsCount.remove(event.uniqueIdentifier) val launchTokenFinder = LaunchTokenFinder(event, extensionApi) - val matchedRules = launchRulesEngine.process(event) var processedEvent: Event = event for (rule in matchedRules) { for (consequence in rule.consequenceList) { @@ -64,14 +62,14 @@ class LaunchRulesConsequence( consequenceWithConcreteValue, processedEvent.eventData ) ?: continue - processedEvent = processedEvent.copyWithNewData(attachedEventData) + processedEvent = processedEvent.cloneWithEventData(attachedEventData) } CONSEQUENCE_TYPE_MOD -> { val modifiedEventData = processModifyDataConsequence( consequenceWithConcreteValue, processedEvent.eventData ) ?: continue - processedEvent = processedEvent.copyWithNewData(modifiedEventData) + processedEvent = processedEvent.cloneWithEventData(modifiedEventData) } CONSEQUENCE_TYPE_DISPATCH -> { if (dispatchChainCount != null) { @@ -145,7 +143,7 @@ class LaunchRulesConsequence( for ((key, value) in detail) { when (value) { is String -> mutableDetail[key] = replaceToken(value, tokenFinder) - is Map<*, *> -> mutableDetail[key] = replaceToken(mutableDetail[key] as Map, tokenFinder) + is Map<*, *> -> mutableDetail[key] = replaceToken(mutableDetail[key] as? Map?, tokenFinder) else -> continue } } @@ -300,7 +298,11 @@ class LaunchRulesConsequence( } } -// Extend RuleConsequence with helper methods for processing Dispatch Consequence events. +// Extend RuleConsequence with helper methods for processing consequence events. +@Suppress("UNCHECKED_CAST") +val RuleConsequence.eventData: Map? + get() = detail?.get("eventdata") as? Map + val RuleConsequence.eventSource: String? get() = detail?.get("source") as? String diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt index b94260a64..c977eda72 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt @@ -24,7 +24,7 @@ internal class LaunchRulesEvaluator( private var cachedEvents: MutableList? = mutableListOf() private val logTag = "LaunchRulesEvaluator_$name" - private val launchRulesConsequence: LaunchRulesConsequence = LaunchRulesConsequence(launchRulesEngine, extensionApi) + private val launchRulesConsequence: LaunchRulesConsequence = LaunchRulesConsequence(extensionApi) companion object { const val CACHED_EVENT_MAX = 99 @@ -40,14 +40,16 @@ internal class LaunchRulesEvaluator( reprocessCachedEvents() } else { cacheEvent(event) - return launchRulesConsequence.evaluateRulesConsequence(event) + val matchedRules = launchRulesEngine.process(event) + return launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) } return event } private fun reprocessCachedEvents() { cachedEvents?.forEach { event -> - launchRulesConsequence.evaluateRulesConsequence(event) + val matchedRules = launchRulesEngine.process(event) + launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) } clearCachedEvents() } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence.kt index 136ac4ea8..a65532867 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/RuleConsequence.kt @@ -20,12 +20,8 @@ package com.adobe.marketing.mobile.launch.rulesengine * @constructor Constructs a new [RuleConsequence] */ -@Suppress("UNCHECKED_CAST") data class RuleConsequence( val id: String, val type: String, var detail: Map? = null -) { - val eventData: Map? - get() = detail?.get("eventdata") as? Map? -} +) diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequenceTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequenceTests.kt index 54b8f7f49..b17d507b7 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequenceTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequenceTests.kt @@ -54,7 +54,7 @@ class LaunchRulesConsequenceTests { extensionApi = PowerMockito.mock(ExtensionApi::class.java) PowerMockito.mockStatic(MobileCore::class.java) launchRulesEngine = LaunchRulesEngine(extensionApi) - launchRulesConsequence = LaunchRulesConsequence(launchRulesEngine, extensionApi) + launchRulesConsequence = LaunchRulesConsequence(extensionApi) } @Test @@ -87,7 +87,8 @@ class LaunchRulesConsequenceTests { ) ) ) - val processedEvent = launchRulesConsequence.evaluateRulesConsequence(defaultEvent) + val matchedRules = launchRulesEngine.process(defaultEvent) + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(defaultEvent, matchedRules) // / Then: no consequence event will be dispatched PowerMockito.verifyStatic(MobileCore::class.java, never()) @@ -124,7 +125,8 @@ class LaunchRulesConsequenceTests { ) ) ) - val processedEvent = launchRulesConsequence.evaluateRulesConsequence(defaultEvent) + val matchedRules = launchRulesEngine.process(defaultEvent) + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(defaultEvent, matchedRules) // / Then: no consequence event will be dispatched PowerMockito.verifyStatic(MobileCore::class.java, never()) @@ -166,7 +168,8 @@ class LaunchRulesConsequenceTests { ) ) ) - val processedEvent = launchRulesConsequence.evaluateRulesConsequence(defaultEvent) + val matchedRules = launchRulesEngine.process(defaultEvent) + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(defaultEvent, matchedRules) // / Then: no consequence event will be dispatched PowerMockito.verifyStatic(MobileCore::class.java, never()) @@ -203,7 +206,8 @@ class LaunchRulesConsequenceTests { ) ) ) - val processedEvent = launchRulesConsequence.evaluateRulesConsequence(defaultEvent) + val matchedRules = launchRulesEngine.process(defaultEvent) + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(defaultEvent, matchedRules) // / Then: no consequence event will be dispatched PowerMockito.verifyStatic(MobileCore::class.java, never()) @@ -234,7 +238,9 @@ class LaunchRulesConsequenceTests { "com.adobe.eventSource.applicationLaunch") .setEventData(mapOf("xdm" to "test data")) .build() - val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event) + + val matchedRules = launchRulesEngine.process(event) + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) // / Then: One consequence event will be dispatched val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) @@ -267,7 +273,8 @@ class LaunchRulesConsequenceTests { .setEventData(null) .build() - val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event) + val matchedRules = launchRulesEngine.process(event) + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) // / Then: One consequence event will be dispatched val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) @@ -306,7 +313,8 @@ class LaunchRulesConsequenceTests { .setEventData(mapOf("xdm" to "test data")) .build() - val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event) + val matchedRules = launchRulesEngine.process(event) + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) // / Then: One consequence event will be dispatched val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) @@ -341,7 +349,8 @@ class LaunchRulesConsequenceTests { .setEventData(mapOf("xdm" to "test data")) .build() - val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event) + val matchedRules = launchRulesEngine.process(event) + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) // / Then: One consequence event will be dispatched val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) @@ -375,7 +384,8 @@ class LaunchRulesConsequenceTests { .setEventData(mapOf("xdm" to "test data")) .build() - val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event) + val matchedRules = launchRulesEngine.process(event) + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) // / Then: No consequence event will be dispatched val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) @@ -405,7 +415,8 @@ class LaunchRulesConsequenceTests { .setEventData(mapOf("xdm" to "test data")) .build() - val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event) + val matchedRules = launchRulesEngine.process(event) + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) // / Then: No consequence event will be dispatched val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) @@ -434,7 +445,8 @@ class LaunchRulesConsequenceTests { .setEventData(mapOf("xdm" to "test data")) .build() - val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event) + val matchedRules = launchRulesEngine.process(event) + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) // / Then: No consequence event will be dispatched val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) @@ -463,7 +475,8 @@ class LaunchRulesConsequenceTests { .setEventData(mapOf("xdm" to "test data")) .build() - val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event) + val matchedRules = launchRulesEngine.process(event) + val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) // / Then: No consequence event will be dispatched val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) @@ -517,14 +530,16 @@ class LaunchRulesConsequenceTests { .build() // Process original event; dispatch chain count = 0 - launchRulesConsequence.evaluateRulesConsequence(event) + val matchedRules = launchRulesEngine.process(event) + launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) PowerMockito.verifyStatic(MobileCore::class.java, times(1)) MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) // Process dispatched event; dispatch chain count = 1 // Expect dispatch to not be called max allowed chained events is 1 - launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value) + val matchedRulesDDispatchedEvent = launchRulesEngine.process(dispatchedEventCaptor.value) + launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value, matchedRulesDDispatchedEvent) PowerMockito.verifyStatic(MobileCore::class.java, times(1)) MobileCore.dispatchEvent(any(), any()) } @@ -572,26 +587,29 @@ class LaunchRulesConsequenceTests { .build() // Process original event; dispatch chain count = 0 - launchRulesConsequence.evaluateRulesConsequence(event) + val matchedRules = launchRulesEngine.process(event) + launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) PowerMockito.verifyStatic(MobileCore::class.java, times(1)) MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) // Process dispatched event; dispatch chain count = 1 // Expect dispatch to fail as max allowed chained events is 1 - launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value) + val matchedRulesDispatchedEvent = launchRulesEngine.process(dispatchedEventCaptor.value) + launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value, matchedRulesDispatchedEvent) PowerMockito.verifyStatic(MobileCore::class.java, times(1)) MobileCore.dispatchEvent(any(), any()) // Process dispatched event; dispatch chain count = 1 // Expect event to be processed as if first time - launchRulesConsequence.evaluateRulesConsequence(event) + launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) PowerMockito.verifyStatic(MobileCore::class.java, times(2)) MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) // Process dispatched event; dispatch chain count = 1 // Expect dispatch to fail as max allowed chained events is 1 - launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value) + val matchedRulesDispatchedEvent2 = launchRulesEngine.process(dispatchedEventCaptor.value) + launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value, matchedRulesDispatchedEvent2) PowerMockito.verifyStatic(MobileCore::class.java, times(2)) MobileCore.dispatchEvent(any(), any()) } @@ -639,26 +657,29 @@ class LaunchRulesConsequenceTests { .build() // Process original event; dispatch chain count = 0 - launchRulesConsequence.evaluateRulesConsequence(event) + val matchedRules = launchRulesEngine.process(event) + launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) PowerMockito.verifyStatic(MobileCore::class.java, times(1)) MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) // Process dispatched event; dispatch chain count = 1 // Expect dispatch to fail as max allowed chained events is 1 - launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value) + val matchedRulesDispatchedEvent = launchRulesEngine.process(dispatchedEventCaptor.value) + launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value, matchedRulesDispatchedEvent) PowerMockito.verifyStatic(MobileCore::class.java, times(1)) MobileCore.dispatchEvent(any(), any()) // Process dispatched event; dispatch chain count = 1 // Expect event to be processed as if first time - launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value) + launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value, matchedRulesDispatchedEvent) PowerMockito.verifyStatic(MobileCore::class.java, times(2)) MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) // Process dispatched event; dispatch chain count = 1 // Expect dispatch to fail as max allowed chained events is 1 - launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value) + val matchedRulesDispatchedEvent2 = launchRulesEngine.process(dispatchedEventCaptor.value) + launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value, matchedRulesDispatchedEvent2) PowerMockito.verifyStatic(MobileCore::class.java, times(2)) MobileCore.dispatchEvent(any(), any()) } @@ -747,26 +768,30 @@ class LaunchRulesConsequenceTests { .build() // Process original event; dispatch chain count = 0 - launchRulesConsequence.evaluateRulesConsequence(eventEdgeRequest) + val matchedRulesEdgeRequestEvent = launchRulesEngine.process(eventEdgeRequest) + launchRulesConsequence.evaluateRulesConsequence(eventEdgeRequest, matchedRulesEdgeRequestEvent) val dispatchedEventCaptor1: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) PowerMockito.verifyStatic(MobileCore::class.java, times(1)) MobileCore.dispatchEvent(dispatchedEventCaptor1.capture(), any()) // Process launch event - launchRulesConsequence.evaluateRulesConsequence(eventLaunch) + val matchedRulesLaunchEvent = launchRulesEngine.process(eventLaunch) + launchRulesConsequence.evaluateRulesConsequence(eventLaunch, matchedRulesLaunchEvent) val dispatchedEventCaptor2: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) PowerMockito.verifyStatic(MobileCore::class.java, times(2)) MobileCore.dispatchEvent(dispatchedEventCaptor2.capture(), any()) // Process first dispatched event; dispatch chain count = 1 // Expect dispatch to fail as max allowed chained events is 1 - launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor1.value) + val matchedRulesDispatchEvent1 = launchRulesEngine.process(dispatchedEventCaptor1.value) + launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor1.value, matchedRulesDispatchEvent1) PowerMockito.verifyStatic(MobileCore::class.java, times(2)) MobileCore.dispatchEvent(any(), any()) // Process second dispatched event; dispatch chain count = 1 // Expect dispatch to fail as max allowed chained events is 1 - launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor2.value) + val matchedRulesDispatchEvent2 = launchRulesEngine.process(dispatchedEventCaptor2.value) + launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor2.value, matchedRulesDispatchEvent2) PowerMockito.verifyStatic(MobileCore::class.java, times(2)) MobileCore.dispatchEvent(any(), any()) } @@ -837,20 +862,23 @@ class LaunchRulesConsequenceTests { .build() // Process original event, expect 2 dispatched events - launchRulesConsequence.evaluateRulesConsequence(event) + val matchedRules = launchRulesEngine.process(event) + launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) PowerMockito.verifyStatic(MobileCore::class.java, times(2)) MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) // Process dispatched event 1, expect 0 dispatch events // chain count = 1, which is max chained events - launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.allValues[0]) + val matchedRulesDispatchEvent1 = launchRulesEngine.process(dispatchedEventCaptor.allValues[0]) + launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.allValues[0], matchedRulesDispatchEvent1) PowerMockito.verifyStatic(MobileCore::class.java, times(2)) MobileCore.dispatchEvent(any(), any()) // Process dispatched event 2, expect 0 dispatch events // chain count = 1, which is max chained events - launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.allValues[1]) + val matchedRulesDispatchEvent2 = launchRulesEngine.process(dispatchedEventCaptor.allValues[1]) + launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.allValues[1], matchedRulesDispatchEvent2) PowerMockito.verifyStatic(MobileCore::class.java, times(2)) MobileCore.dispatchEvent(any(), any()) } @@ -875,7 +903,8 @@ class LaunchRulesConsequenceTests { ) ) - launchRulesConsequence.evaluateRulesConsequence(defaultEvent) + val matchedRules = launchRulesEngine.process(defaultEvent) + launchRulesConsequence.evaluateRulesConsequence(defaultEvent, matchedRules) val consequenceEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) PowerMockito.verifyStatic(MobileCore::class.java, times(1)) @@ -909,7 +938,8 @@ class LaunchRulesConsequenceTests { ) ) - launchRulesConsequence.evaluateRulesConsequence(defaultEvent) + val matchedRules = launchRulesEngine.process(defaultEvent) + launchRulesConsequence.evaluateRulesConsequence(defaultEvent, matchedRules) val consequenceEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) PowerMockito.verifyStatic(MobileCore::class.java, times(1)) From 988cc6e87328c38b9bfb5768e40473353567a643 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Wed, 15 Jun 2022 15:23:16 -0700 Subject: [PATCH 082/476] [#95] Omit type for trivial declarations & rename friendlyName --- .../adobe/marketing/mobile/internal/eventhub/EventHub.kt | 6 +++--- .../mobile/internal/eventhub/ExtensionContainer.kt | 2 +- .../marketing/mobile/internal/eventhub/ExtensionExt.kt | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index 6e8839986..3e5373f7a 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -207,11 +207,11 @@ internal class EventHub { } val result: SharedState.Status = extensionContainer.setSharedState(sharedStateType, data, version) - val wasSet: Boolean = (result == SharedState.Status.SET) + val wasSet = (result == SharedState.Status.SET) // Check if the new state can be dispatched as a state change event(currently implies a // non null/non pending state according to the ExtensionAPI) - val shouldDispatch: Boolean = (data == null) + val shouldDispatch = (data == null) if (shouldDispatch && wasSet) { // If the new state can be dispatched and was successfully @@ -219,7 +219,7 @@ internal class EventHub { // dispatch a shared state notification. // TODO: dispatch() } - return@Callable (result == SharedState.Status.PENDING || wasSet) + return@Callable (wasSet || result == SharedState.Status.PENDING) } return eventHubExecutor.submit(setSharedStateCallable).get() diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt index 52725e7d7..72d5da218 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt @@ -33,7 +33,7 @@ internal class ExtensionRuntime() : ExtensionApi() { set(value) { field = value extensionName = value?.extensionName - extensionFriendlyName = value?.friendlyExtensionName + extensionFriendlyName = value?.extensionFriendlyName extensionVersion = value?.extensionVersion } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt index df6ecf23b..b4652ef1b 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt @@ -51,7 +51,7 @@ internal val Extension.extensionVersion: String? /** * Property to get Extension friendly name */ -internal val Extension.friendlyExtensionName: String? +internal val Extension.extensionFriendlyName: String? get() = ExtensionHelper.getFriendlyName(this) /** From 7aa60c04df04aafca9ec203832c3d036becee5e0 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Wed, 15 Jun 2022 19:01:49 -0700 Subject: [PATCH 083/476] added utility function to convert generic map to Map --- .../rulesengine/LaunchRulesConsequence.kt | 13 +- .../mobile/utils/EventDataUtils.java | 23 ++++ .../mobile/utils/EventDataUtilsTests.java | 111 ++++++++++++++++++ 3 files changed, 141 insertions(+), 6 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt index 8964054e8..abedc51ff 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt @@ -19,6 +19,7 @@ import com.adobe.marketing.mobile.internal.utility.prettify import com.adobe.marketing.mobile.rulesengine.DelimiterPair import com.adobe.marketing.mobile.rulesengine.Template import com.adobe.marketing.mobile.rulesengine.TokenFinder +import com.adobe.marketing.mobile.utils.EventDataUtils class LaunchRulesConsequence( private val extensionApi: ExtensionApi @@ -166,7 +167,7 @@ class LaunchRulesConsequence( * null if the processing fails */ private fun processAttachDataConsequence(consequence: RuleConsequence, eventData: Map?): Map? { - val from = consequence.eventData ?: run { + val from = EventDataUtils.castFromGenericType(consequence.eventData) ?: run { MobileCore.log( LoggingMode.ERROR, logTag, @@ -201,7 +202,7 @@ class LaunchRulesConsequence( * null if the processing fails */ private fun processModifyDataConsequence(consequence: RuleConsequence, eventData: Map?): Map? { - val from = consequence.eventData ?: run { + val from = EventDataUtils.castFromGenericType(consequence.eventData) ?: run { MobileCore.log( LoggingMode.ERROR, logTag, @@ -263,7 +264,8 @@ class LaunchRulesConsequence( dispatchEventData = eventData } CONSEQUENCE_DETAIL_ACTION_NEW -> { - dispatchEventData = consequence.eventData?.filterValues { it != null } + val data = EventDataUtils.castFromGenericType(consequence.eventData) + dispatchEventData = data?.filterValues { it != null } } else -> { MobileCore.log( @@ -299,9 +301,8 @@ class LaunchRulesConsequence( } // Extend RuleConsequence with helper methods for processing consequence events. -@Suppress("UNCHECKED_CAST") -val RuleConsequence.eventData: Map? - get() = detail?.get("eventdata") as? Map +val RuleConsequence.eventData: Map<*, *>? + get() = detail?.get("eventdata") as? Map<*, *> val RuleConsequence.eventSource: String? get() = detail?.get("source") as? String diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/EventDataUtils.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/EventDataUtils.java index 63d6b74b2..a74ed7659 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/EventDataUtils.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/EventDataUtils.java @@ -155,4 +155,27 @@ public static Map clone(Map map) throws CloneFailedEx public static Map immutableClone(Map map) throws CloneFailedException { return cloneMap(map, CloneMode.ImmutableContainer, 0); } + + /** + * Casts generic map {@code Map} to {@code HashMap} + *
      + *
    • Entry with null and non {@code String} key is dropped.
    • + *
    • Entry withnon {@code String} key is dropped
    • + *
    + * @param map map to be cast + * @return map cast to type {@code Map} + * + */ + public static Map castFromGenericType(Map map) { + if (map == null) return null; + + Map result = new HashMap<>(); + for(Map.Entry entry : map.entrySet()) { + Object key = entry.getKey(); + if (key instanceof String) { + result.put((String) key, entry.getValue()); + } + } + return result; + } } \ No newline at end of file diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/utils/EventDataUtilsTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/utils/EventDataUtilsTests.java index a3914e089..ad5d15311 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/utils/EventDataUtilsTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/utils/EventDataUtilsTests.java @@ -219,4 +219,115 @@ public void testImmutableClone() throws CloneFailedException { }); } + + @Test + public void testCastFromGenericType_SimpleObjects() { + Map values = new HashMap<>(); + values.put("boolean", true); + + byte b = 100; + values.put("byte", b); + + short s = 1000; + values.put("short", s); + + int i = 10000; + values.put("int", i); + + long l = 100000L; + values.put("long", l); + + float f = 1.1F; + values.put("float", f); + + double d = 1.1e10D; + values.put("double", d); + + BigDecimal bd = BigDecimal.TEN; + values.put("bigdecimal", bd); + + BigInteger bi = BigInteger.TEN; + values.put("biginteger", bi); + + char c = 'a'; + values.put("char", c); + + String str = "hello"; + values.put("string", str); + + UUID uuid = UUID.randomUUID(); + values.put("uuid", uuid); + + + Map castMap = EventDataUtils.castFromGenericType(values); + assertEquals(values, castMap); + } + + @Test + public void testCastGenericType_NestedObjects() { + Map nestedMap = new TreeMap<>(); + nestedMap.put("integer", 1); + nestedMap.put("float", 1f); + nestedMap.put("double", 1d); + nestedMap.put("string", "hello"); + + List nestedList = new LinkedList<>(); + nestedList.add(1); + nestedList.add(1f); + nestedList.add(1d); + nestedList.add("hello"); + + Map data = new HashMap<>(); + data.put("map", nestedList); + data.put("list", nestedList); + data.put("string", "top level"); + data.put("uuid", UUID.randomUUID()); + data.put("null", null); + + Map castMap = EventDataUtils.castFromGenericType(data); + assertEquals(data, castMap); + } + + @Test + public void testCastGenericType_MapWithNonStringKeys() { + Map data = new HashMap<>(); + data.put(null, 1); + data.put(new Integer(1), 1); + data.put(new Double(1.1), 1d); + data.put("string", "hello"); + + Map expectedData = new HashMap<>(); + expectedData.put("string", "hello"); + + Map castMap = EventDataUtils.castFromGenericType(data); + assertEquals(expectedData, castMap); + } + + @Test + public void testCastGenericType_Array() { + String[] stringArray = new String[]{ "string1", "string2"}; + + Map data = new HashMap<>(); + data.put("stringArray", stringArray); + + Map[] mapArray = new Map[] { + new HashMap(), + new HashMap() {{ + put("k1" , "v1"); + put("k2" , "v2"); + }}, + new HashMap() {{ + put("integer", 1); + put("float", 1f); + put("double", 1d); + put("string", "hello"); + }} + }; + data.put("mapArray", mapArray); + + Map castMap = EventDataUtils.castFromGenericType(data); + + assertEquals(castMap.get("stringArray"), stringArray); + assertEquals(castMap.get("mapArray"), mapArray); + } } From c3c27c1cbbbb377c1127e1511a0df94ff2f9fcd3 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Wed, 15 Jun 2022 19:08:46 -0700 Subject: [PATCH 084/476] code review comments --- .../adobe/marketing/mobile/services/DataQueueService.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java index 0ef230c82..e773316b9 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java @@ -39,7 +39,13 @@ class DataQueueService implements DataQueuing { } @Override - public DataQueue getDataQueue(@NonNull final String databaseName) { + public DataQueue getDataQueue(final String databaseName) { + if(StringUtils.isNullOrEmpty(databaseName)) { + MobileCore.log(LoggingMode.WARNING, + LOG_TAG, + "Failed to create DataQueue, database name is null"); + return null; + } DataQueue dataQueue = dataQueueCache.get(databaseName); if (dataQueue == null) { From c29837a1e40ab82b0115f8ac1bd6bc4806ea12f3 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Thu, 16 Jun 2022 17:28:06 -0700 Subject: [PATCH 085/476] code review comments --- .../rulesengine/LaunchRulesConsequence.kt | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt index abedc51ff..316986fe9 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt @@ -51,7 +51,7 @@ class LaunchRulesConsequence( fun evaluateRulesConsequence(event: Event?, matchedRules: List): Event? { if (event == null) return null - val dispatchChainCount = dispatchChainedEventsCount.remove(event.uniqueIdentifier) + val dispatchChainCount = dispatchChainedEventsCount.remove(event.uniqueIdentifier) ?: 0 val launchTokenFinder = LaunchTokenFinder(event, extensionApi) var processedEvent: Event = event for (rule in matchedRules) { @@ -73,17 +73,15 @@ class LaunchRulesConsequence( processedEvent = processedEvent.cloneWithEventData(modifiedEventData) } CONSEQUENCE_TYPE_DISPATCH -> { - if (dispatchChainCount != null) { - if (dispatchChainCount >= MAX_CHAINED_CONSEQUENCE_COUNT) { - MobileCore.log( - LoggingMode.VERBOSE, - logTag, - "Unable to process dispatch consequence, max chained " + - "dispatch consequences limit of $MAX_CHAINED_CONSEQUENCE_COUNT" + - "met for this event uuid ${event.uniqueIdentifier}" - ) - continue - } + if (dispatchChainCount >= MAX_CHAINED_CONSEQUENCE_COUNT) { + MobileCore.log( + LoggingMode.VERBOSE, + logTag, + "Unable to process dispatch consequence, max chained " + + "dispatch consequences limit of $MAX_CHAINED_CONSEQUENCE_COUNT" + + "met for this event uuid ${event.uniqueIdentifier}" + ) + continue } val dispatchEvent = processDispatchConsequence( consequenceWithConcreteValue, @@ -101,7 +99,7 @@ class LaunchRulesConsequence( "An error occurred when dispatching dispatch consequence result event" ) } - dispatchChainedEventsCount[dispatchEvent.uniqueIdentifier] = if (dispatchChainCount == null) 1 else dispatchChainCount + 1 + dispatchChainedEventsCount[dispatchEvent.uniqueIdentifier] = dispatchChainCount + 1 } else -> { val consequenceEvent = generateConsequenceEvent(consequenceWithConcreteValue) @@ -144,7 +142,9 @@ class LaunchRulesConsequence( for ((key, value) in detail) { when (value) { is String -> mutableDetail[key] = replaceToken(value, tokenFinder) - is Map<*, *> -> mutableDetail[key] = replaceToken(mutableDetail[key] as? Map?, tokenFinder) + is Map<*, *> -> mutableDetail[key] = replaceToken( + EventDataUtils.castFromGenericType(value), + tokenFinder) else -> continue } } From 65708e6e129ed7eaeeb969d9cb96dc2a39483087 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Wed, 22 Jun 2022 08:25:33 -0700 Subject: [PATCH 086/476] casting event data from Map to Map --- .../rulesengine/LaunchRulesEvaluator.kt | 29 +++++++------------ .../mobile/utils/EventDataUtils.java | 9 +++--- .../rulesengine/LaunchRulesEvaluatorTests.kt | 19 ++++-------- .../mobile/utils/EventDataUtilsTests.java | 6 ++-- 4 files changed, 21 insertions(+), 42 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt index c977eda72..6cc9fd15d 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt @@ -27,7 +27,6 @@ internal class LaunchRulesEvaluator( private val launchRulesConsequence: LaunchRulesConsequence = LaunchRulesConsequence(extensionApi) companion object { - const val CACHED_EVENT_MAX = 99 // TODO: we should move the following event type/event source values to the public EventType/EventSource classes once we have those. const val EVENT_SOURCE_REQUEST_RESET = "com.adobe.eventsource.requestreset" const val EVENT_TYPE_RULES_ENGINE = "com.adobe.eventtype.rulesengine" @@ -36,14 +35,20 @@ internal class LaunchRulesEvaluator( override fun process(event: Event?): Event? { if (event == null) return null + // if cachedEvents is null, we know rules are set and can skip to evaluation + val matchedRules = launchRulesEngine.process(event) + if (cachedEvents == null) { + return launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) + } + + // check if this is an event to start processing of cachedEvents + // otherwise, add the event to cachedEvents till rules are set if (event.type == EVENT_TYPE_RULES_ENGINE && event.source == EVENT_SOURCE_REQUEST_RESET) { reprocessCachedEvents() } else { - cacheEvent(event) - val matchedRules = launchRulesEngine.process(event) - return launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) + cachedEvents?.add(event) } - return event + return launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) } private fun reprocessCachedEvents() { @@ -59,20 +64,6 @@ internal class LaunchRulesEvaluator( cachedEvents = null } - private fun cacheEvent(event: Event) { - cachedEvents?.let { - if ((cachedEvents?.size ?: -1) > CACHED_EVENT_MAX) { - clearCachedEvents() - MobileCore.log( - LoggingMode.WARNING, - logTag, - "Will not to reprocess cached events as the cached events have reached the limit: $CACHED_EVENT_MAX" - ) - } - cachedEvents?.add(event) - } - } - /** * Reset the current [LaunchRule]s. A RulesEngine Reset [Event] will be dispatched to reprocess the cached events. * diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/EventDataUtils.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/EventDataUtils.java index a74ed7659..acf153139 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/EventDataUtils.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/EventDataUtils.java @@ -166,16 +166,15 @@ public static Map immutableClone(Map map) throws Clon * @return map cast to type {@code Map} * */ + @SuppressWarnings("unchecked") public static Map castFromGenericType(Map map) { if (map == null) return null; - Map result = new HashMap<>(); for(Map.Entry entry : map.entrySet()) { - Object key = entry.getKey(); - if (key instanceof String) { - result.put((String) key, entry.getValue()); + if (!(entry.getKey() instanceof String)) { + return null; } } - return result; + return (Map) map; } } \ No newline at end of file diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt index 803e7ccaf..f53492638 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt @@ -67,16 +67,6 @@ class LaunchRulesEvaluatorTests { assertEquals(100, cachedEvents.size) } - @Test - fun `Clear cached events if reached the limit`() { - repeat(101) { - launchRulesEvaluator.process( - Event.Builder("event-$it", "type", "source").build() - ) - } - assertEquals(0, cachedEvents.size) - } - @Test fun `Reprocess cached events when rules are ready`() { repeat(10) { @@ -112,10 +102,11 @@ class LaunchRulesEvaluatorTests { Mockito.reset(launchRulesEngine) launchRulesEvaluator.process(eventCaptor.value) val cachedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) - verify(launchRulesEngine, Mockito.times(10)).process(cachedEventCaptor.capture()) - assertEquals(10, cachedEventCaptor.allValues.size) - cachedEventCaptor.allValues.forEachIndexed { index, element -> - assertEquals("event-$index", element.name) + verify(launchRulesEngine, Mockito.times(11)).process(cachedEventCaptor.capture()) + assertEquals(11, cachedEventCaptor.allValues.size) + // 0th event will be current processed event + for (index in 1 until cachedEventCaptor.allValues.size) { + assertEquals("event-${index - 1}", cachedEventCaptor.allValues[index].name) } assertEquals(0, cachedEvents.size) } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/utils/EventDataUtilsTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/utils/EventDataUtilsTests.java index ad5d15311..3b4473de5 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/utils/EventDataUtilsTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/utils/EventDataUtilsTests.java @@ -13,6 +13,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -296,11 +297,8 @@ public void testCastGenericType_MapWithNonStringKeys() { data.put(new Double(1.1), 1d); data.put("string", "hello"); - Map expectedData = new HashMap<>(); - expectedData.put("string", "hello"); - Map castMap = EventDataUtils.castFromGenericType(data); - assertEquals(expectedData, castMap); + assertNull(castMap); } @Test From 93d22bd222248dfe5d43c9decc174230ea726284 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Thu, 23 Jun 2022 11:47:19 -0700 Subject: [PATCH 087/476] adding platform services tests --- .../services/DataQueueServiceTests.java | 107 ++++++++++++++++ .../services/DeviceInfoServiceTests.java | 50 ++++++++ .../mobile/services/NetworkServiceTests.java | 117 ++++++++++++++++++ .../mobile/services/DataQueueService.java | 30 ++--- 4 files changed, 290 insertions(+), 14 deletions(-) create mode 100644 code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/DataQueueServiceTests.java create mode 100644 code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/DeviceInfoServiceTests.java create mode 100644 code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/NetworkServiceTests.java diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/DataQueueServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/DataQueueServiceTests.java new file mode 100644 index 000000000..75a8fd571 --- /dev/null +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/DataQueueServiceTests.java @@ -0,0 +1,107 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.services; + +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertNotNull; +import static junit.framework.TestCase.assertNull; +import static junit.framework.TestCase.assertTrue; + +import android.content.Context; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.IOException; + +@RunWith(AndroidJUnit4.class) +public class DataQueueServiceTests { + + Context context; + private static final String TEST_DATABASE_NAME = "test.sqlite"; + + @Before + public void beforeEach() { + context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + ServiceProvider.getInstance().setContext(context); + } + + @After + public void afterEach() { + context.getDatabasePath(TEST_DATABASE_NAME).delete(); + new File(context.getCacheDir(), TEST_DATABASE_NAME).delete(); + } + + @Test + public void testGetDataQueue_DatabaseNameIsNullOrEmpty() { + DataQueue dataQueue = new DataQueueService().getDataQueue(null); + assertNull(dataQueue); + dataQueue = new DataQueueService().getDataQueue(""); + assertNull(dataQueue); + } + + @Test + public void testGetDataQueue_ApplicationContextIsNotSet() { + ServiceProvider.getInstance().setContext(null); + DataQueue dataQueue = new DataQueueService().getDataQueue(null); + assertNull(dataQueue); + } + + @Test + public void testGetDataQueue_DataQueueDoesNotExist() { + assertFalse(context.getDatabasePath(TEST_DATABASE_NAME).exists()); + DataQueue dataQueue = new DataQueueService().getDataQueue(TEST_DATABASE_NAME); + assertNotNull(dataQueue); + assertTrue(context.getDatabasePath(TEST_DATABASE_NAME).exists()); + } + + @Test + public void testGetDataQueue_DataQueueMigrationFromCacheDirectory() { + assertFalse(context.getDatabasePath(TEST_DATABASE_NAME).exists()); + File cacheDatabaseFile = new File(context.getCacheDir(), TEST_DATABASE_NAME); + try { + cacheDatabaseFile.createNewFile(); + DataQueue dataQueue = new SQLiteDataQueue(cacheDatabaseFile.getPath()); + dataQueue.add(new DataEntity("test_data_1")); + DataQueue dataQueueExisting = new DataQueueService().getDataQueue(TEST_DATABASE_NAME); + Assert.assertEquals("test_data_1", dataQueueExisting.peek().getData()); + assertFalse(cacheDatabaseFile.exists()); + } catch (IOException e) { } + } + + @Test + public void testGetDataQueue_DataQueueExistsInDatabaseDirectory() { + File databaseFile = context.getDatabasePath(TEST_DATABASE_NAME); + try { + databaseFile.createNewFile(); + DataQueue dataQueue = new SQLiteDataQueue(databaseFile.getPath()); + dataQueue.add(new DataEntity("test_data_1")); + DataQueue dataQueueExisting = new DataQueueService().getDataQueue(TEST_DATABASE_NAME); + Assert.assertEquals("test_data_1", dataQueueExisting.peek().getData()); + } catch (IOException e) { } + } + + @Test + public void testGetDataQueue_DataQueueExistsInDataQueueCache() { + DataQueueService dataQueueService = new DataQueueService(); + DataQueue dataQueue = dataQueueService.getDataQueue(TEST_DATABASE_NAME); + dataQueue.add(new DataEntity("test_data_1")); + DataQueue dataQueueExisting = dataQueueService.getDataQueue(TEST_DATABASE_NAME); + Assert.assertEquals("test_data_1", dataQueueExisting.peek().getData()); + } +} diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/DeviceInfoServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/DeviceInfoServiceTests.java new file mode 100644 index 000000000..5c83e4d96 --- /dev/null +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/DeviceInfoServiceTests.java @@ -0,0 +1,50 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.services; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertNull; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class DeviceInfoServiceTests { + + private DeviceInfoService deviceInfoService; + + @Before + public void setup() { + deviceInfoService = new DeviceInfoService(); + } + + @Test + public void testWhenNullContext() { + ServiceProvider.getInstance().setContext(null); + assertNull(deviceInfoService.getActiveLocale()); + assertNull(deviceInfoService.getDisplayInformation()); + assertEquals(DeviceInforming.DeviceType.UNKNOWN, deviceInfoService.getDeviceType()); + assertNull(deviceInfoService.getMobileCarrierName()); + assertEquals(DeviceInforming.ConnectionStatus.UNKNOWN,deviceInfoService.getNetworkConnectionStatus()); + assertNull(deviceInfoService.getApplicationCacheDir()); + assertNull(deviceInfoService.getAsset("testFileName")); + assertNull(deviceInfoService.getPropertyFromManifest("key")); + assertNull(deviceInfoService.getApplicationName()); + assertNull(deviceInfoService.getApplicationPackageName()); + assertNull(deviceInfoService.getApplicationVersion()); + assertNull(deviceInfoService.getApplicationVersionCode()); + assertNull(deviceInfoService.getApplicationBaseDir()); + assertEquals("en-US", deviceInfoService.getLocaleString()); + } +} diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/NetworkServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/NetworkServiceTests.java new file mode 100644 index 000000000..9fd6fdd5c --- /dev/null +++ b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/NetworkServiceTests.java @@ -0,0 +1,117 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.services; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertNotNull; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import junit.framework.TestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.HashMap; +import java.util.Map; + +@RunWith(AndroidJUnit4.class) +public class NetworkServiceTests { + + private NetworkService networkService; + + @Before + public void setup() { + networkService = new NetworkService(); + } + + @Test + public void testConnectAsync_NullUrl() { + networkService.connectAsync( + new NetworkRequest(null, null, null, null, 0, 0), + TestCase::assertNull); + } + + @Test + public void testConnectAsync_NonHttpsUrl() { + networkService.connectAsync( + new NetworkRequest("http://www.adobe.com", null, null, null, 0, 0), + TestCase::assertNull); + } + + @Test + public void testConnectAsync_MalformedUrl() + { + networkService.connectAsync( + new NetworkRequest("://www.adobehttps.com", null, null, null, 0, 0), + TestCase::assertNull); + } + + @Test + public void testConnectAsync_UnsupportedUrlProtocol() + { + networkService.connectAsync( + new NetworkRequest("http://www.adobehttps.com", null, null, null, 0, 0), + TestCase::assertNull); + + networkService.connectAsync( + new NetworkRequest("ssh://www.adobehttps.com", null, null, null, 0, 0), + TestCase::assertNull); + } + + @Test + public void testConnectAsync_GetMethod() + { + networkService.connectAsync( + new NetworkRequest("https://www.adobe.com", HttpMethod.GET, null, null, 10, 10), + connection -> { + assertNotNull(connection); + assertEquals(200, connection.getResponseCode()); + }); + } + + @Test + public void testConnectAsync_PostMethod() + { + networkService.connectAsync( + new NetworkRequest("https://www.adobe.com", HttpMethod.GET, null, null, 10, 10), + connection -> { + assertNotNull(connection); + assertEquals(200, connection.getResponseCode()); + }); + } + + @Test + public void testConnectAsync_WithBody() + { + networkService.connectAsync( + new NetworkRequest("https://www.adobe.com", HttpMethod.GET, new byte[] {}, null, 10, 10), + connection -> { + assertNotNull(connection); + assertEquals(200, connection.getResponseCode()); + }); + } + + @Test + public void testConnectAsync_WithHeader() + { + Map properties = new HashMap(); + properties.put("testing", "header"); + networkService.connectAsync( + new NetworkRequest("https://www.adobe.com", HttpMethod.GET, null, properties, 10, 10), + connection -> { + assertNotNull(connection); + assertEquals(200, connection.getResponseCode()); + }); + } + +} diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java index e773316b9..631426d75 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/DataQueueService.java @@ -32,7 +32,7 @@ class DataQueueService implements DataQueuing { private static final String LOG_TAG = "DataQueueService"; - private Map dataQueueCache; + private final Map dataQueueCache; DataQueueService() { dataQueueCache = new HashMap<>(); @@ -97,20 +97,23 @@ private File openOrMigrateExistingDataQueue(String databaseName) { final File databaseDirDataQueue = appContext.getDatabasePath(cleanedDatabaseName); - final File cacheDir = ServiceProvider.getInstance().getDeviceInfoService().getApplicationCacheDir(); - if (!databaseDirDataQueue.exists() && cacheDir != null) { - final File cacheDirDataQueue = new File(cacheDir, cleanedDatabaseName); - if (cacheDirDataQueue.exists()) { + if (!databaseDirDataQueue.exists()) { try { if(databaseDirDataQueue.createNewFile()) { - FileUtil.copyFile(cacheDirDataQueue, databaseDirDataQueue); - MobileCore.log(LoggingMode.DEBUG, - LOG_TAG, - String.format("Successfully moved DataQueue for database (%s) from cache directory to database directory", databaseName)); - if(cacheDirDataQueue.delete()) { - MobileCore.log(LoggingMode.DEBUG, - LOG_TAG, - String.format("Successfully delete DataQueue for database (%s) from cache directory", databaseName)); + final File cacheDir = ServiceProvider.getInstance().getDeviceInfoService().getApplicationCacheDir(); + if(cacheDir != null) { + final File cacheDirDataQueue = new File(cacheDir, cleanedDatabaseName); + if (cacheDirDataQueue.exists()) { + FileUtil.copyFile(cacheDirDataQueue, databaseDirDataQueue); + MobileCore.log(LoggingMode.DEBUG, + LOG_TAG, + String.format("Successfully moved DataQueue for database (%s) from cache directory to database directory", databaseName)); + if (cacheDirDataQueue.delete()) { + MobileCore.log(LoggingMode.DEBUG, + LOG_TAG, + String.format("Successfully delete DataQueue for database (%s) from cache directory", databaseName)); + } + } } } } catch (Exception e) { @@ -119,7 +122,6 @@ private File openOrMigrateExistingDataQueue(String databaseName) { String.format("Failed to move DataQueue for database (%s), could not create new file in database directory", databaseName)); return null; } - } } return databaseDirDataQueue; } From 3df1bcfff2abdadf6c1ca50fcc3d5f5b3e23b0bd Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Sat, 25 Jun 2022 16:09:14 -0700 Subject: [PATCH 088/476] final code review changes --- .../rulesengine/LaunchRulesConsequence.kt | 37 +++++++++---------- .../rulesengine/LaunchRulesEvaluator.kt | 8 ++-- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt index 316986fe9..2355b1141 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt @@ -29,28 +29,26 @@ class LaunchRulesConsequence( private var dispatchChainedEventsCount = mutableMapOf() companion object { // TODO: we should move the following event type/event source values to the public EventType/EventSource classes once we have those. - const val EVENT_SOURCE_RESPONSE_CONTENT = "com.adobe.eventSource.responseContent" - const val EVENT_TYPE_RULES_ENGINE = "com.adobe.eventtype.rulesengine" - const val LAUNCH_RULE_TOKEN_LEFT_DELIMITER = "{%" - const val LAUNCH_RULE_TOKEN_RIGHT_DELIMITER = "%}" - const val CONSEQUENCE_TYPE_ADD = "add" - const val CONSEQUENCE_TYPE_MOD = "mod" - const val CONSEQUENCE_TYPE_DISPATCH = "dispatch" - const val CONSEQUENCE_DETAIL_ACTION_COPY = "copy" - const val CONSEQUENCE_DETAIL_ACTION_NEW = "new" + private const val EVENT_SOURCE_RESPONSE_CONTENT = "com.adobe.eventSource.responseContent" + private const val EVENT_TYPE_RULES_ENGINE = "com.adobe.eventtype.rulesengine" + private const val LAUNCH_RULE_TOKEN_LEFT_DELIMITER = "{%" + private const val LAUNCH_RULE_TOKEN_RIGHT_DELIMITER = "%}" + private const val CONSEQUENCE_TYPE_ADD = "add" + private const val CONSEQUENCE_TYPE_MOD = "mod" + private const val CONSEQUENCE_TYPE_DISPATCH = "dispatch" + private const val CONSEQUENCE_DETAIL_ACTION_COPY = "copy" + private const val CONSEQUENCE_DETAIL_ACTION_NEW = "new" // Do not process Dispatch consequence if chained event count is greater than max - const val MAX_CHAINED_CONSEQUENCE_COUNT = 1 - const val CONSEQUENCE_DISPATCH_EVENT_NAME = "Dispatch Consequence Result" - const val CONSEQUENCE_EVENT_DATA_KEY_ID = "id" - const val CONSEQUENCE_EVENT_DATA_KEY_TYPE = "type" - const val CONSEQUENCE_EVENT_DATA_KEY_DETAIL = "detail" - const val CONSEQUENCE_EVENT_DATA_KEY_CONSEQUENCE = "triggeredconsequence" - const val CONSEQUENCE_EVENT_NAME = "Rules Consequence Event" + private const val MAX_CHAINED_CONSEQUENCE_COUNT = 1 + private const val CONSEQUENCE_DISPATCH_EVENT_NAME = "Dispatch Consequence Result" + private const val CONSEQUENCE_EVENT_DATA_KEY_ID = "id" + private const val CONSEQUENCE_EVENT_DATA_KEY_TYPE = "type" + private const val CONSEQUENCE_EVENT_DATA_KEY_DETAIL = "detail" + private const val CONSEQUENCE_EVENT_DATA_KEY_CONSEQUENCE = "triggeredconsequence" + private const val CONSEQUENCE_EVENT_NAME = "Rules Consequence Event" } - fun evaluateRulesConsequence(event: Event?, matchedRules: List): Event? { - if (event == null) return null - + fun evaluateRulesConsequence(event: Event, matchedRules: List): Event? { val dispatchChainCount = dispatchChainedEventsCount.remove(event.uniqueIdentifier) ?: 0 val launchTokenFinder = LaunchTokenFinder(event, extensionApi) var processedEvent: Event = event @@ -134,7 +132,6 @@ class LaunchRulesConsequence( return RuleConsequence(consequence.id, consequence.type, tokenReplacedMap) } - @Suppress("UNCHECKED_CAST") private fun replaceToken(detail: Map?, tokenFinder: TokenFinder): Map? { if (detail.isNullOrEmpty()) return null diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt index 6cc9fd15d..506efc4e8 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt @@ -36,14 +36,12 @@ internal class LaunchRulesEvaluator( if (event == null) return null // if cachedEvents is null, we know rules are set and can skip to evaluation + // else check if this is an event to start processing of cachedEvents + // otherwise, add the event to cachedEvents till rules are set val matchedRules = launchRulesEngine.process(event) if (cachedEvents == null) { return launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) - } - - // check if this is an event to start processing of cachedEvents - // otherwise, add the event to cachedEvents till rules are set - if (event.type == EVENT_TYPE_RULES_ENGINE && event.source == EVENT_SOURCE_REQUEST_RESET) { + } else if (event.type == EVENT_TYPE_RULES_ENGINE && event.source == EVENT_SOURCE_REQUEST_RESET) { reprocessCachedEvents() } else { cachedEvents?.add(event) From 40bd8ab9170387b287df6e0d2ca0703b153dbb80 Mon Sep 17 00:00:00 2001 From: Yansong Date: Fri, 1 Jul 2022 10:06:29 -0600 Subject: [PATCH 089/476] address review comments --- .../phone/java/com/adobe/marketing/mobile/MobileCore.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java index bed13d32e..ae4e5721a 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java @@ -670,17 +670,13 @@ public static void getSdkIdentities(final AdobeCallback callback) { if (eventHub == null) { Log.debug(LOG_TAG, "Failed to retrieve the all SDK identities (%s)", NULL_CONTEXT_MESSAGE); - if (callback != null & callback instanceof AdobeCallbackWithError) { + if (callback instanceof AdobeCallbackWithError) { ((AdobeCallbackWithError) callback).fail(AdobeError.EXTENSION_NOT_INITIALIZED); } return; } - if (callback == null) { - return; - } - final AdobeCallbackWithError adobeCallbackWithError = callback instanceof AdobeCallbackWithError ? (AdobeCallbackWithError) callback : null; From e117732b83d9693e6f1acca2b43c36b041be1dd9 Mon Sep 17 00:00:00 2001 From: Yansong Date: Fri, 1 Jul 2022 10:39:44 -0600 Subject: [PATCH 090/476] better wording for logs --- .../src/phone/java/com/adobe/marketing/mobile/MobileCore.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java index ae4e5721a..43e49bd65 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java @@ -530,7 +530,7 @@ public static void configureWithFileInPath(final String filepath) { } if (StringUtils.isNullOrEmpty(filepath)) { - Log.warning("Configuration", "Unable to configure with null or empty remoteURL"); + Log.warning("Configuration", "Unable to configure with null or empty file path"); return; } From 69971fe47bb4dcb3cfa7d2fa07027afdee1b8c4e Mon Sep 17 00:00:00 2001 From: Yansong Yang Date: Mon, 11 Jul 2022 13:47:17 -0600 Subject: [PATCH 091/476] bug fix (#103) --- .../marketing/mobile/utils/DataReader.java | 116 +++++++++--------- .../mobile/utils/DataReaderTests.java | 71 +++++++---- 2 files changed, 106 insertions(+), 81 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/DataReader.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/DataReader.java index 5d1c90a47..22c84764a 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/DataReader.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/utils/DataReader.java @@ -34,7 +34,6 @@ * * * - * */ public class DataReader { @@ -92,7 +91,7 @@ private static T castObject(Class tClass, Object obj) throws DataReaderEx Number objAsNumber = (Number) obj; if (DataReader.checkOverflow(tClass, objAsNumber)) { - throw new DataReaderException("Value overflows type "+ tClass); + throw new DataReaderException("Value overflows type " + tClass); } if (Byte.class.equals(tClass)) { @@ -125,13 +124,13 @@ private static T castObject(Class tClass, Object obj) throws DataReaderEx /** * Gets the value for {@code key} from {@code map} as a custom object. * - * @param Custom type + * @param Custom type * @param tClass Custom class - * @param map {@code Map} map to fetch data - * @param key {@code String} key to fetch + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch * @return {@code T} value associated with {@code key} or null if {@code key} is not present in {@code map} * @throws IllegalArgumentException if {@code map} or {@code key} is null - * @throws DataReaderException if value is not gettable as a {@code T} + * @throws DataReaderException if value is not gettable as a {@code T} */ private static T getTypedObject(Class tClass, Map map, String key) throws DataReaderException { if (map == null || key == null) { @@ -145,10 +144,10 @@ private static T getTypedObject(Class tClass, Map map, String /** * Gets the value for {@code key} from {@code map} as a custom object or returns default value * - * @param Custom type - * @param tClass Custom class - * @param map {@code Map} map to fetch data - * @param key {@code String} key to fetch + * @param Custom type + * @param tClass Custom class + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch * @param fallback {@code T} value to return in case of failure. Can be null. * @return {@code T} value associated with {@code key}, or {@code fallback} if value is * not gettable as a {@code T} @@ -158,20 +157,21 @@ private static T optTypedObject(Class tClass, Map map, String T ret = null; try { ret = getTypedObject(tClass, map, key); - } catch (DataReaderException ex) {} + } catch (DataReaderException ex) { + } return ret != null ? ret : fallback; } /** * Gets the value for {@code key} from {@code map} as a {@code Map} * - * @param Custom type + * @param Custom type * @param tClass Custom class - * @param map {@code Map} map to fetch data - * @param key {@code String} key to fetch + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch * @return {@code Map} Map associated with {@code key} or null if {@code key} is not present in {@code map} * @throws IllegalArgumentException if {@code map} or {@code key} is null - * @throws DataReaderException if value is not gettable as a {@code Map} + * @throws DataReaderException if value is not gettable as a {@code Map} */ public static Map getTypedMap(Class tClass, Map map, String key) throws DataReaderException { if (map == null || key == null) { @@ -187,10 +187,12 @@ public static Map getTypedMap(Class tClass, Map map throw new DataReaderException("Value is not a map"); } - Map valueAsMap = (Map)value; + Map valueAsMap = (Map) value; for (Map.Entry kv : valueAsMap.entrySet()) { - if (!(kv.getKey() instanceof String) || - !tClass.isInstance(kv.getValue())) { + if (!(kv.getKey() instanceof String)) { + throw new DataReaderException("Map entry is not of expected type"); + } + if (kv.getValue() != null && !tClass.isInstance(kv.getValue())) { throw new DataReaderException("Map entry is not of expected type"); } } @@ -200,10 +202,10 @@ public static Map getTypedMap(Class tClass, Map map /** * Gets the value for {@code key} from {@code map} as a {@code Map} or returns default value * - * @param Custom type - * @param tClass Custom class - * @param map {@code Map} map to fetch data - * @param key {@code String} key to fetch + * @param Custom type + * @param tClass Custom class + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch * @param fallback {@code Map} value to return in case of failure. Can be null. * @return {@code Map} Map associated with {@code key}, or {@code fallback} if value is * not gettable as a {@code Map} @@ -213,20 +215,21 @@ public static Map optTypedMap(Class tClass, Map map Map ret = null; try { ret = getTypedMap(tClass, map, key); - } catch (DataReaderException ex) {} + } catch (DataReaderException ex) { + } return ret != null ? ret : fallback; } /** * Gets the value for {@code key} from {@code map} as a {@code List} * - * @param Custom type + * @param Custom type * @param tClass Custom class - * @param map {@code Map} map to fetch data - * @param key {@code String} key to fetch + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch * @return {@code List} List associated with {@code key} or null if {@code key} is not present in {@code map} * @throws IllegalArgumentException if {@code map} or {@code key} is null - * @throws DataReaderException if value is not gettable as a {@code List} + * @throws DataReaderException if value is not gettable as a {@code List} */ public static List getTypedList(Class tClass, Map map, String key) throws DataReaderException { if (map == null || key == null) { @@ -255,10 +258,10 @@ public static List getTypedList(Class tClass, Map map, Stri /** * Gets the value for {@code key} from {@code map} as a {@code List} or returns default value * - * @param Custom type - * @param tClass Custom class - * @param map {@code Map} map to fetch data - * @param key {@code String} key to fetch + * @param Custom type + * @param tClass Custom class + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch * @param fallback {@code List} value to return in case of failure. Can be null. * @return {@code List} List associated with {@code key}, or {@code fallback} if value is * not gettable as a {@code List} @@ -268,7 +271,8 @@ public static List optTypedList(Class tClass, Map map, Stri List ret = null; try { ret = getTypedList(tClass, map, key); - } catch (DataReaderException ex) {} + } catch (DataReaderException ex) { + } return ret != null ? ret : fallback; } @@ -279,7 +283,7 @@ public static List optTypedList(Class tClass, Map map, Stri * @param key {@code String} key to fetch * @return {@code boolean} value associated with {@code key} * @throws IllegalArgumentException if {@code map} or {@code key} is null - * @throws DataReaderException if value is not gettable as a {@code boolean} or if {@code key} is not present in {@code map} + * @throws DataReaderException if value is not gettable as a {@code boolean} or if {@code key} is not present in {@code map} */ public static boolean getBoolean(Map map, String key) throws DataReaderException { Boolean ret = getTypedObject(Boolean.class, map, key); @@ -292,8 +296,8 @@ public static boolean getBoolean(Map map, String key) throws DataRead /** * Gets the value for {@code key} from {@code map} as a {@code boolean} or returns default value * - * @param map {@code Map} map to fetch data - * @param key {@code String} key to fetch + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch * @param fallback {@code boolean} value to return in case of failure. Can be null. * @return {@code boolean} value associated with {@code key}, or {@code fallback} if value is * not gettable as a {@code boolean} @@ -310,7 +314,7 @@ public static boolean optBoolean(Map map, String key, boolean fallbac * @param key {@code String} key to fetch * @return {@code int} value associated with {@code key} * @throws IllegalArgumentException if {@code map} or {@code key} is null - * @throws DataReaderException if value is not gettable as an {@code int} or if {@code key} is not present in {@code map} + * @throws DataReaderException if value is not gettable as an {@code int} or if {@code key} is not present in {@code map} */ public static int getInt(Map map, String key) throws DataReaderException { Integer ret = getTypedObject(Integer.class, map, key); @@ -323,8 +327,8 @@ public static int getInt(Map map, String key) throws DataReaderExcept /** * Gets the value for {@code key} from {@code map} as an {@code int} or returns default value * - * @param map {@code Map} map to fetch data - * @param key {@code String} key to fetch + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch * @param fallback {@code int} value to return in case of failure. Can be null. * @return {@code int} value associated with {@code key}, or {@code fallback} if value is * not gettable as a {@code int} @@ -341,7 +345,7 @@ public static int optInt(Map map, String key, int fallback) { * @param key {@code String} key to fetch * @return {@code long} value associated with {@code key} * @throws IllegalArgumentException if {@code map} or {@code key} is null - * @throws DataReaderException if value is not gettable as an {@code long} or if {@code key} is not present in {@code map} + * @throws DataReaderException if value is not gettable as an {@code long} or if {@code key} is not present in {@code map} */ public static long getLong(Map map, String key) throws DataReaderException { Long ret = getTypedObject(Long.class, map, key); @@ -354,8 +358,8 @@ public static long getLong(Map map, String key) throws DataReaderExce /** * Gets the value for {@code key} from {@code map} as an {@code long} or returns default value * - * @param map {@code Map} map to fetch data - * @param key {@code String} key to fetch + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch * @param fallback {@code long} value to return in case of failure. Can be null. * @return {@code long} value associated with {@code key}, or {@code fallback} if value is * not gettable as a {@code long} @@ -372,7 +376,7 @@ public static long optLong(Map map, String key, long fallback) { * @param key {@code String} key to fetch * @return {@code float} value associated with {@code key} * @throws IllegalArgumentException if {@code map} or {@code key} is null - * @throws DataReaderException if value is not gettable as an {@code float} or if {@code key} is not present in {@code map} + * @throws DataReaderException if value is not gettable as an {@code float} or if {@code key} is not present in {@code map} */ public static float getFloat(Map map, String key) throws DataReaderException { Float ret = getTypedObject(Float.class, map, key); @@ -385,8 +389,8 @@ public static float getFloat(Map map, String key) throws DataReaderEx /** * Gets the value for {@code key} from {@code map} as an {@code float} or returns default value * - * @param map {@code Map} map to fetch data - * @param key {@code String} key to fetch + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch * @param fallback {@code float} value to return in case of failure. Can be null. * @return {@code float} value associated with {@code key}, or {@code fallback} if value is * not gettable as a {@code float} @@ -403,7 +407,7 @@ public static float optFloat(Map map, String key, float fallback) { * @param key {@code String} key to fetch * @return {@code double} value associated with {@code key} * @throws IllegalArgumentException if {@code map} or {@code key} is null - * @throws DataReaderException if value is not gettable as an {@code double} or if {@code key} is not present in {@code map} + * @throws DataReaderException if value is not gettable as an {@code double} or if {@code key} is not present in {@code map} */ public static double getDouble(Map map, String key) throws DataReaderException { Double ret = getTypedObject(Double.class, map, key); @@ -416,8 +420,8 @@ public static double getDouble(Map map, String key) throws DataReader /** * Gets the value for {@code key} from {@code map} as an {@code double} or returns default value * - * @param map {@code Map} map to fetch data - * @param key {@code String} key to fetch + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch * @param fallback {@code double} value to return in case of failure. Can be null. * @return {@code double} value associated with {@code key}, or {@code fallback} if value is * not gettable as a {@code double} @@ -434,7 +438,7 @@ public static double optDouble(Map map, String key, double fallback) * @param key {@code String} key to fetch * @return {@code String} value associated with {@code key} or null if {@code key} is not present in {@code map} * @throws IllegalArgumentException if {@code map} or {@code key} is null - * @throws DataReaderException if value is not gettable as an {@code String} + * @throws DataReaderException if value is not gettable as an {@code String} */ public static String getString(Map map, String key) throws DataReaderException { return getTypedObject(String.class, map, key); @@ -443,8 +447,8 @@ public static String getString(Map map, String key) throws DataReader /** * Gets the value for {@code key} from {@code map} as a {@code String} or returns default value * - * @param map {@code Map} map to fetch data - * @param key {@code String} key to fetch + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch * @param fallback {@code String} value to return in case of failure. Can be null. * @return {@code String} value associated with {@code key}, or {@code fallback} if value is * not gettable as a {@code String} @@ -461,7 +465,7 @@ public static String optString(Map map, String key, String fallback) * @param key {@code String} key to fetch * @return {@code Map} Map associated with {@code key} or null if {@code key} is not present in {@code map} * @throws IllegalArgumentException if {@code map} or {@code key} is null - * @throws DataReaderException if value is not gettable as a {@code Map} + * @throws DataReaderException if value is not gettable as a {@code Map} */ public static Map getStringMap(Map map, String key) throws DataReaderException { return getTypedMap(String.class, map, key); @@ -470,8 +474,8 @@ public static Map getStringMap(Map map, String key) t /** * Gets the value for {@code key} from {@code map} as a {@code Map} or returns default value * - * @param map {@code Map} map to fetch data - * @param key {@code String} key to fetch + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch * @param fallback {@code Map} value to return in case of failure. Can be null. * @return {@code Map} value associated with {@code key}, or {@code fallback} if value is * not gettable as a {@code Map} @@ -488,7 +492,7 @@ public static Map optStringMap(Map map, String key, M * @param key {@code String} key to fetch * @return {@code List} List associated with {@code key} or null if {@code key} is not present in {@code map} * @throws IllegalArgumentException if {@code map} or {@code key} is null - * @throws DataReaderException if value is not gettable as a {@code List} + * @throws DataReaderException if value is not gettable as a {@code List} */ public static List getStringList(Map map, String key) throws DataReaderException { return getTypedList(String.class, map, key); @@ -497,8 +501,8 @@ public static List getStringList(Map map, String key) throws /** * Gets the value for {@code key} from {@code map} as a {@code List} or returns default value * - * @param map {@code Map} map to fetch data - * @param key {@code String} key to fetch + * @param map {@code Map} map to fetch data + * @param key {@code String} key to fetch * @param fallback {@code List} value to return in case of failure. Can be null. * @return {@code List} List associated with {@code key}, or {@code fallback} if value is * not gettable as a {@code List} diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/utils/DataReaderTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/utils/DataReaderTests.java index 861791a1c..cf081925b 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/utils/DataReaderTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/utils/DataReaderTests.java @@ -1,10 +1,10 @@ package com.adobe.marketing.mobile.utils; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; import org.junit.Test; @@ -35,7 +35,9 @@ public class DataReaderTests { final double DELTA = 0.000001d; - Map data = new HashMap<>();{ + Map data = new HashMap<>(); + + { data.put("BOOL_TRUE", Boolean.TRUE); data.put("BOOL_FALSE", Boolean.FALSE); @@ -104,7 +106,7 @@ public void testReadBoolean() throws Exception { checkIllegalArgumentException(DataReader::getBoolean); checkExceptionMessage((key) -> DataReader.getBoolean(data, key), Arrays.asList("NULL", "INVALID"), NULL_VALUE_ERROR_MSG); - checkCastException((key) -> DataReader.getBoolean(data, key), Arrays.asList("FLOAT","BYTE", "INT", "DOUBLE", "LONG", "STRING")); + checkCastException((key) -> DataReader.getBoolean(data, key), Arrays.asList("FLOAT", "BYTE", "INT", "DOUBLE", "LONG", "STRING")); } @Test @@ -185,7 +187,7 @@ public void testReadLong() throws Exception { String errorMessage = OVERFLOW_ERROR_MSG + Long.class; checkExceptionMessage((key) -> DataReader.getLong(data, key), Arrays.asList("FLOAT_MAX", "DOUBLE_MAX"), errorMessage); - checkCastException((key) -> DataReader.getLong(data, key), Arrays.asList("STRING","STRING_MAP", "STRING_LIST")); + checkCastException((key) -> DataReader.getLong(data, key), Arrays.asList("STRING", "STRING_MAP", "STRING_LIST")); } @Test @@ -232,7 +234,7 @@ public void testReadFloat() throws Exception { String errorMessage = OVERFLOW_ERROR_MSG + Float.class; checkExceptionMessage((key) -> DataReader.getFloat(data, key), Arrays.asList("DOUBLE_MAX"), errorMessage); - checkCastException((key) -> DataReader.getFloat(data, key), Arrays.asList("STRING","STRING_MAP", "STRING_LIST")); + checkCastException((key) -> DataReader.getFloat(data, key), Arrays.asList("STRING", "STRING_MAP", "STRING_LIST")); } @Test @@ -243,17 +245,17 @@ public void testOptFloat() { assertEquals((float) Short.MAX_VALUE, DataReader.optFloat(data, "SHORT_MAX", 3.3F), DELTA); assertEquals((float) Byte.MAX_VALUE, DataReader.optFloat(data, "BYTE_MAX", 3.3F), DELTA); - assertEquals(1, DataReader.optFloat(data, "BYTE",3.3F), DELTA); - assertEquals(2, DataReader.optFloat(data, "SHORT",3.3F), DELTA); - assertEquals(3, DataReader.optFloat(data, "INT",3.3F), DELTA); - assertEquals(4, DataReader.optFloat(data, "LONG",3.3F), DELTA); - assertEquals(5.5, DataReader.optFloat(data, "FLOAT",3.3F), DELTA); - assertEquals(6.6, DataReader.optFloat(data, "DOUBLE",3.3F), DELTA); + assertEquals(1, DataReader.optFloat(data, "BYTE", 3.3F), DELTA); + assertEquals(2, DataReader.optFloat(data, "SHORT", 3.3F), DELTA); + assertEquals(3, DataReader.optFloat(data, "INT", 3.3F), DELTA); + assertEquals(4, DataReader.optFloat(data, "LONG", 3.3F), DELTA); + assertEquals(5.5, DataReader.optFloat(data, "FLOAT", 3.3F), DELTA); + assertEquals(6.6, DataReader.optFloat(data, "DOUBLE", 3.3F), DELTA); - assertEquals(3.3, DataReader.optFloat(data, "DOUBLE_MAX",3.3F), DELTA); - assertEquals(3.3, DataReader.optFloat(data, "STRING",3.3F), DELTA); - assertEquals(3.3, DataReader.optFloat(data, "NULL",3.3F), DELTA); - assertEquals(3.3, DataReader.optFloat(data, "INVALID",3.3F), DELTA); + assertEquals(3.3, DataReader.optFloat(data, "DOUBLE_MAX", 3.3F), DELTA); + assertEquals(3.3, DataReader.optFloat(data, "STRING", 3.3F), DELTA); + assertEquals(3.3, DataReader.optFloat(data, "NULL", 3.3F), DELTA); + assertEquals(3.3, DataReader.optFloat(data, "INVALID", 3.3F), DELTA); checkIllegalArgumentException((m, k) -> DataReader.optFloat(m, k, 3.3F)); } @@ -333,7 +335,7 @@ public void testOptString() { assertEquals("5.5", DataReader.optString(data, "FLOAT", "DEFAULT")); assertEquals("6.6", DataReader.optString(data, "DOUBLE", "DEFAULT")); assertEquals("DEFAULT", DataReader.optString(data, "NULL", "DEFAULT")); - assertEquals("DEFAULT",DataReader.optString(data, "INVALID", "DEFAULT")); + assertEquals("DEFAULT", DataReader.optString(data, "INVALID", "DEFAULT")); checkIllegalArgumentException((m, k) -> DataReader.optString(m, k, "DEFAULT")); } @@ -345,7 +347,7 @@ public void testGetStringMap() throws Exception { assertEquals(map, new HashMap<>()); map = DataReader.getStringMap(data, "STRING_MAP"); - assertEquals(map, new HashMap() {{ + assertEquals(map, new HashMap() {{ put("a", "a"); put("b", "b"); }}); @@ -367,7 +369,7 @@ public void testOptStringMap() { assertEquals(map, new HashMap<>()); map = DataReader.optStringMap(data, "STRING_MAP", defaultMap); - assertEquals(map, new HashMap() {{ + assertEquals(map, new HashMap() {{ put("a", "a"); put("b", "b"); }}); @@ -419,7 +421,7 @@ public void testOptStringList() { @Test public void testGetTypedMap() throws Exception { Map map = DataReader.getTypedMap(Integer.class, data, "INT_MAP"); - assertEquals(map, new HashMap() {{ + assertEquals(map, new HashMap() {{ put("a", 1); put("b", 2); }}); @@ -429,6 +431,25 @@ public void testGetTypedMap() throws Exception { checkIllegalArgumentException((m, k) -> DataReader.getTypedMap(Integer.class, m, k)); } + @Test + public void testGetTypedMapWithNullValue() throws Exception { + Map data = new HashMap() { + { + put("INT_MAP", new HashMap() { + { + put("a", 1); + put("b", null); + } + }); + } + }; + Map map = DataReader.getTypedMap(Integer.class, data, "INT_MAP"); + assertEquals(map, new HashMap() {{ + put("a", 1); + put("b", null); + }}); + } + @Test public void testOptTypedMap() { Map defaultMap = new HashMap() {{ @@ -437,7 +458,7 @@ public void testOptTypedMap() { }}; Map map = DataReader.optTypedMap(Integer.class, data, "INT_MAP", defaultMap); - assertEquals(map, new HashMap() {{ + assertEquals(map, new HashMap() {{ put("a", 1); put("b", 2); }}); @@ -453,7 +474,7 @@ public void testOptTypedMap() { @Test public void testGetTypedList() throws Exception { List list = DataReader.getTypedList(Integer.class, data, "INT_LIST"); - assertEquals(list, Arrays.asList(1, 2)); + assertEquals(list, Arrays.asList(1, 2)); checkExceptionMessage((key) -> DataReader.getTypedList(Integer.class, data, key), Arrays.asList("STRING", "INT", "STRING_MAP"), LIST_ERROR_MSG); checkExceptionMessage((key) -> DataReader.getTypedList(Integer.class, data, key), Arrays.asList("STRING_LIST"), LIST_ENTRY_ERROR_MSG); @@ -466,12 +487,12 @@ public void testOptTypedList() { List list; list = DataReader.optTypedList(Integer.class, data, "INT_LIST", defaultList); - assertEquals(list, Arrays.asList(1, 2)); + assertEquals(list, Arrays.asList(1, 2)); list = DataReader.optTypedList(Integer.class, data, "STRING", defaultList); - assertEquals(list, defaultList); + assertEquals(list, defaultList); list = DataReader.optTypedList(Integer.class, data, "STRING_LIST", defaultList); - assertEquals(list, defaultList); + assertEquals(list, defaultList); checkIllegalArgumentException((m, k) -> DataReader.optTypedList(Integer.class, m, k, defaultList)); } @@ -484,6 +505,6 @@ public void testReadNestedValue() throws Exception { assertEquals(value, 1); List stringList = DataReader.getStringList(clonedData, "STRING_LIST"); - assertEquals("a", stringList.get(0) ); + assertEquals("a", stringList.get(0)); } } From 64a94b079aed784c44b02f113c778b2231cdc1d1 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Mon, 11 Jul 2022 13:08:20 -0700 Subject: [PATCH 092/476] mock HttpConnectionHandler for NetworkServiceTests instead of actual network call --- .../mobile/services/NetworkServiceTests.java | 117 -------- .../mobile/services/NetworkService.java | 7 + .../mobile/services/NetworkServiceTests.java | 257 ++++++++++++++++++ 3 files changed, 264 insertions(+), 117 deletions(-) delete mode 100644 code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/NetworkServiceTests.java create mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/NetworkServiceTests.java diff --git a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/NetworkServiceTests.java b/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/NetworkServiceTests.java deleted file mode 100644 index 9fd6fdd5c..000000000 --- a/code/android-core-library/src/androidTest/java/com/adobe/marketing/mobile/services/NetworkServiceTests.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ -package com.adobe.marketing.mobile.services; - -import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertNotNull; - -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import junit.framework.TestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.HashMap; -import java.util.Map; - -@RunWith(AndroidJUnit4.class) -public class NetworkServiceTests { - - private NetworkService networkService; - - @Before - public void setup() { - networkService = new NetworkService(); - } - - @Test - public void testConnectAsync_NullUrl() { - networkService.connectAsync( - new NetworkRequest(null, null, null, null, 0, 0), - TestCase::assertNull); - } - - @Test - public void testConnectAsync_NonHttpsUrl() { - networkService.connectAsync( - new NetworkRequest("http://www.adobe.com", null, null, null, 0, 0), - TestCase::assertNull); - } - - @Test - public void testConnectAsync_MalformedUrl() - { - networkService.connectAsync( - new NetworkRequest("://www.adobehttps.com", null, null, null, 0, 0), - TestCase::assertNull); - } - - @Test - public void testConnectAsync_UnsupportedUrlProtocol() - { - networkService.connectAsync( - new NetworkRequest("http://www.adobehttps.com", null, null, null, 0, 0), - TestCase::assertNull); - - networkService.connectAsync( - new NetworkRequest("ssh://www.adobehttps.com", null, null, null, 0, 0), - TestCase::assertNull); - } - - @Test - public void testConnectAsync_GetMethod() - { - networkService.connectAsync( - new NetworkRequest("https://www.adobe.com", HttpMethod.GET, null, null, 10, 10), - connection -> { - assertNotNull(connection); - assertEquals(200, connection.getResponseCode()); - }); - } - - @Test - public void testConnectAsync_PostMethod() - { - networkService.connectAsync( - new NetworkRequest("https://www.adobe.com", HttpMethod.GET, null, null, 10, 10), - connection -> { - assertNotNull(connection); - assertEquals(200, connection.getResponseCode()); - }); - } - - @Test - public void testConnectAsync_WithBody() - { - networkService.connectAsync( - new NetworkRequest("https://www.adobe.com", HttpMethod.GET, new byte[] {}, null, 10, 10), - connection -> { - assertNotNull(connection); - assertEquals(200, connection.getResponseCode()); - }); - } - - @Test - public void testConnectAsync_WithHeader() - { - Map properties = new HashMap(); - properties.put("testing", "header"); - networkService.connectAsync( - new NetworkRequest("https://www.adobe.com", HttpMethod.GET, null, properties, 10, 10), - connection -> { - assertNotNull(connection); - assertEquals(200, connection.getResponseCode()); - }); - } - -} diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/NetworkService.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/NetworkService.java index 08f5c024a..f4a46429a 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/NetworkService.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/services/NetworkService.java @@ -11,6 +11,8 @@ package com.adobe.marketing.mobile.services; +import androidx.annotation.VisibleForTesting; + import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; @@ -45,6 +47,11 @@ class NetworkService implements Networking { new SynchronousQueue()); } + @VisibleForTesting + NetworkService(ExecutorService executorService) { + this.executorService = executorService; + } + @Override public void connectAsync(final NetworkRequest request, diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/NetworkServiceTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/NetworkServiceTests.java new file mode 100644 index 000000000..fc7c144a7 --- /dev/null +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/NetworkServiceTests.java @@ -0,0 +1,257 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile.services; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertNull; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +@RunWith(PowerMockRunner.class) +@PrepareForTest(NetworkService.class) +public class NetworkServiceTests { + + private NetworkService networkService; + + @Mock + private HttpConnectionHandler httpConnectionHandler; + + @Before + public void setup() throws Exception { + networkService = new NetworkService(currentThreadExecutorService()); + PowerMockito.whenNew(HttpConnectionHandler.class).withAnyArguments().thenReturn(httpConnectionHandler); + Mockito.when(httpConnectionHandler.setCommand(Mockito.any(HttpMethod.class))).thenReturn(true); + } + + @Test + public void testConnectAsync_NullUrl() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + networkService.connectAsync( + new NetworkRequest(null, null, null, null, 0, 0), + connection -> { + assertNull(connection); + latch.countDown(); + }); + latch.await(); + Mockito.verifyZeroInteractions(httpConnectionHandler); + } + + @Test + public void testConnectAsync_NonHttpsUrl() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + networkService.connectAsync( + new NetworkRequest("http://www.adobe.com", null, null, null, 0, 0), + connection -> { + assertNull(connection); + latch.countDown(); + }); + latch.await(); + Mockito.verifyZeroInteractions(httpConnectionHandler); + } + + @Test + public void testConnectAsync_MalformedUrl() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + networkService.connectAsync( + new NetworkRequest("://www.adobehttps.com", null, null, null, 0, 0), + connection -> { + assertNull(connection); + latch.countDown(); + }); + latch.await(); + Mockito.verifyZeroInteractions(httpConnectionHandler); + } + + @Test + public void testConnectAsync_UnsupportedUrlProtocol() throws InterruptedException { + final CountDownLatch latch1 = new CountDownLatch(1); + networkService.connectAsync( + new NetworkRequest("http://www.adobehttps.com", null, null, null, 0, 0), + connection -> { + assertNull(connection); + latch1.countDown(); + }); + latch1.await(); + Mockito.verifyZeroInteractions(httpConnectionHandler); + + final CountDownLatch latch2 = new CountDownLatch(1); + networkService.connectAsync( + new NetworkRequest("ssh://www.adobehttps.com", null, null, null, 0, 0), + connection -> { + assertNull(connection); + latch2.countDown(); + }); + latch2.await(); + Mockito.verifyZeroInteractions(httpConnectionHandler); + } + + @Test + public void testConnectAsync_DefaultHeaders() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + networkService.connectAsync( + new NetworkRequest("https://www.adobe.com", HttpMethod.GET, null, null, 10, 10), + connection -> { + latch.countDown(); + + }); + latch.await(); + Mockito.verify(httpConnectionHandler, Mockito.times(1)).connect(Mockito.any()); + + final ArgumentCaptor> captor = ArgumentCaptor.forClass(Map.class); + Mockito.verify(httpConnectionHandler).setRequestProperty(captor.capture()); + assertEquals("Mozilla/5.0 (Linux; U; Android null; en-US; unknown Build/unknown)", captor.getValue().get("User-Agent")); + assertEquals("en-US", captor.getValue().get("Accept-Language")); + } + + @Test + public void testConnectAsync_WithHeader() throws InterruptedException { + Map properties = new HashMap<>(); + properties.put("testing", "header"); + + final CountDownLatch latch = new CountDownLatch(1); + networkService.connectAsync( + new NetworkRequest("https://www.adobe.com", HttpMethod.GET, null, properties, 10, 10), + connection -> { + latch.countDown(); + + }); + latch.await(); + Mockito.verify(httpConnectionHandler, Mockito.times(1)).connect(Mockito.any()); + + final ArgumentCaptor> captor = ArgumentCaptor.forClass(Map.class); + Mockito.verify(httpConnectionHandler).setRequestProperty(captor.capture()); + assertEquals("Mozilla/5.0 (Linux; U; Android null; en-US; unknown Build/unknown)", captor.getValue().get("User-Agent")); + assertEquals("en-US", captor.getValue().get("Accept-Language")); + assertEquals("header", captor.getValue().get("testing")); + } + + @Test + public void testConnectAsync_GetMethod() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + networkService.connectAsync( + new NetworkRequest("https://www.adobe.com", HttpMethod.GET, null, null, 10, 5), + connection -> { + latch.countDown(); + + }); + latch.await(); + Mockito.verify(httpConnectionHandler, Mockito.times(1)).connect(Mockito.any()); + + final ArgumentCaptor httpMethodCaptor = ArgumentCaptor.forClass(HttpMethod.class); + Mockito.verify(httpConnectionHandler).setCommand(httpMethodCaptor.capture()); + assertEquals(HttpMethod.GET, httpMethodCaptor.getValue()); + } + + @Test + public void testConnectAsync_PostMethod() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + networkService.connectAsync( + new NetworkRequest("https://www.adobe.com", HttpMethod.POST, null, null, 10, 5), + connection -> { + latch.countDown(); + + }); + latch.await(); + Mockito.verify(httpConnectionHandler, Mockito.times(1)).connect(Mockito.any()); + + final ArgumentCaptor httpMethodCaptor = ArgumentCaptor.forClass(HttpMethod.class); + Mockito.verify(httpConnectionHandler).setCommand(httpMethodCaptor.capture()); + assertEquals(HttpMethod.POST, httpMethodCaptor.getValue()); + } + + @Test + public void testConnectAsync_ConnectAndReadTimeout() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + networkService.connectAsync( + new NetworkRequest("https://www.adobe.com", HttpMethod.GET, null, null, 10, 5), + connection -> { + latch.countDown(); + }); + latch.await(); + + final ArgumentCaptor connectTimeoutCaptor = ArgumentCaptor.forClass(Integer.class); + Mockito.verify(httpConnectionHandler).setConnectTimeout(connectTimeoutCaptor.capture()); + assertEquals(10*1000, (int)connectTimeoutCaptor.getValue()); + + final ArgumentCaptor readTimeoutCaptor = ArgumentCaptor.forClass(Integer.class); + Mockito.verify(httpConnectionHandler).setReadTimeout(readTimeoutCaptor.capture()); + assertEquals(5*1000, (int)readTimeoutCaptor.getValue()); + } + + @Test + public void testConnectAsync_WithEmptyBody() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + networkService.connectAsync( + new NetworkRequest("https://www.adobe.com", HttpMethod.GET, new byte[] {}, null, 10, 10), + connection -> { + latch.countDown(); + + }); + latch.await(); + Mockito.verify(httpConnectionHandler, Mockito.times(1)).connect(Mockito.any()); + + final ArgumentCaptor bodyCaptor = ArgumentCaptor.forClass(Object.class); + Mockito.verify(httpConnectionHandler).connect((byte[]) bodyCaptor.capture()); + assertEquals(0, ((byte[]) bodyCaptor.getValue()).length); + } + + @Test + public void testConnectAsync_WithBody() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + networkService.connectAsync( + new NetworkRequest("https://www.adobe.com", HttpMethod.GET, new byte[] {12, 34}, null, 10, 10), + connection -> { + latch.countDown(); + + }); + latch.await(); + Mockito.verify(httpConnectionHandler, Mockito.times(1)).connect(Mockito.any()); + + final ArgumentCaptor bodyCaptor = ArgumentCaptor.forClass(Object.class); + Mockito.verify(httpConnectionHandler).connect((byte[]) bodyCaptor.capture()); + assertEquals(2, ((byte[]) bodyCaptor.getValue()).length); + assertEquals(12, ((byte[]) bodyCaptor.getValue())[0]); + assertEquals(34, ((byte[]) bodyCaptor.getValue())[1]); + } + + private static ExecutorService currentThreadExecutorService() { + + // Handler for tasks that runs the task directly in the calling thread of the execute method + ThreadPoolExecutor.CallerRunsPolicy callerRunsPolicy = + new ThreadPoolExecutor.CallerRunsPolicy(); + + return new ThreadPoolExecutor(0, 1, 0L, TimeUnit.SECONDS, + new SynchronousQueue(), callerRunsPolicy) { + @Override + public void execute(Runnable command) { + // Executes task in the caller's thread + callerRunsPolicy.rejectedExecution(command, this); + } + }; + } + +} From 5e79c0a167c0c7d5f11c45633949b765951a6998 Mon Sep 17 00:00:00 2001 From: praveek Date: Mon, 11 Jul 2022 17:35:19 -0700 Subject: [PATCH 093/476] Fix build issues --- code/android-core-library/build.gradle | 1 + .../mobile/EventHubSharedStateTest.java | 247 ----------------- .../java/com/adobe/marketing/mobile/Core.java | 11 +- .../com/adobe/marketing/mobile/EventHub.java | 227 ++++++++-------- .../mobile/internal/eventhub/EventHub.kt | 2 +- .../mobile/util/SerialWorkDispatcher.kt | 2 +- .../marketing/mobile/EventListenerTest.java | 256 ------------------ 7 files changed, 117 insertions(+), 629 deletions(-) delete mode 100644 code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/EventHubSharedStateTest.java delete mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/EventListenerTest.java diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index f25ea3c58..fb230827d 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -112,6 +112,7 @@ apply from: 'release.gradle' dependencies { //noinspection GradleCompatible implementation 'androidx.appcompat:appcompat:1.0.0' + implementation 'androidx.annotation:annotation:1.3.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" // unit tests testImplementation "junit:junit:4.13.2" diff --git a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/EventHubSharedStateTest.java b/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/EventHubSharedStateTest.java deleted file mode 100644 index 9cb16ccb8..000000000 --- a/code/android-core-library/src/legacy/test-common/java/com/adobe/marketing/mobile/EventHubSharedStateTest.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import org.junit.Assert; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -public class EventHubSharedStateTest { - private static PlatformServices services = new FakePlatformServices(); - private static EventData readState = null; - private static final Semaphore moduleReadingStateSemaphore = new Semaphore(0, true); - private static CountDownLatch eventHubLatch = new CountDownLatch(1); - private static int EVENTHUB_WAIT_MS = 50; - - // simple read/write test - public static class ModuleSharingState extends InternalModule { - public ModuleSharingState(final EventHub hub, final PlatformServices services) { - super("SHARER", hub, services); - this.registerListener(EventType.CUSTOM, EventSource.NONE, CreateStateListener.class); - } - - public static class CreateStateListener extends ModuleEventListener { - public CreateStateListener(final ModuleSharingState module, final EventType type, final EventSource source) { - super(module, type, source); - } - - public void hear(final Event e) { - parentModule.createSharedState(e.getEventNumber(), new EventData().putString("myString", "myValue")); - } - } - } - - public static class ModuleReadingState extends InternalModule { - public ModuleReadingState(final EventHub hub, final PlatformServices services) { - super("ModuleReadingState", hub, services); - this.registerListener(EventType.ANALYTICS, EventSource.NONE, ReadStateListener.class); - this.registerListener(EventType.TARGET, EventSource.NONE, EndListener.class); - } - - public static class ReadStateListener extends ModuleEventListener { - public ReadStateListener(final ModuleReadingState module, final EventType type, final EventSource source) { - super(module, type, source); - } - - public void hear(final Event e) { - readState = parentModule.getSharedEventState("SHARER", e); - } - } - - public static class EndListener extends ModuleEventListener { - public EndListener(final ModuleReadingState module, final EventType type, final EventSource source) { - super(module, type, source); - } - - public void hear(final Event e) { - moduleReadingStateSemaphore.release(); - } - } - } - - @Test(timeout = 1000) - public void readStateTest() throws Exception { - final EventHub hub = new EventHub("Shared State Test Hub", services); - hub.registerModule(ModuleSharingState.class); - hub.registerModule(ModuleReadingState.class); - - final CountDownLatch bootLatch = new CountDownLatch(1); - hub.finishModulesRegistration(new AdobeCallback() { - @Override - public void call(Void value) { - bootLatch.countDown(); - } - }); - bootLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - hub.dispatch(new Event.Builder("Create Shared State", EventType.CUSTOM, EventSource.NONE).build()); - hub.dispatch(new Event.Builder("Read Shared State", EventType.ANALYTICS, EventSource.NONE).build()); - hub.dispatch(new Event.Builder("Stop Waiting", EventType.TARGET, EventSource.NONE).build()); - - moduleReadingStateSemaphore.tryAcquire(1000, TimeUnit.MILLISECONDS); - - Assert.assertEquals("shared state was not set", "myValue", readState.getString("myString")); - } - - // time that the async update should "take" to run - private static final int ASYNC_TIMEOUT = 1; - - // deferred read/write test (with an update) - public static class ModuleSharedStateAsyncUpdate extends InternalModule { - - private final static AtomicInteger updateCount = new AtomicInteger(0); - - public ModuleSharedStateAsyncUpdate(final EventHub hub, final PlatformServices services) { - super("SHARED", hub, services); - this.registerListener(EventType.CUSTOM, EventSource.NONE, CreateStateListener.class); - } - - public static class CreateStateListener extends ModuleEventListener { - public CreateStateListener(final ModuleSharedStateAsyncUpdate module, final EventType type, final EventSource source) { - super(module, type, source); - } - - public void hear(final Event e) { - // create the initial null state so we can update it after background task completes. - final int createdVersion = e.getEventNumber(); - parentModule.createSharedState(createdVersion, null); - // fire background task - new Thread(new AsyncBackgroundTask(createdVersion, parentModule)).run(); - } - } - - // emulates a network request that responds in 1 second - private static class AsyncBackgroundTask implements Runnable { - private final int updateVersion; - private final ModuleSharedStateAsyncUpdate module; - private static final Semaphore waitingSemaphore = new Semaphore(0, true); - public AsyncBackgroundTask(final int updateVersion, final ModuleSharedStateAsyncUpdate module) { - this.updateVersion = updateVersion; - this.module = module; - } - - public void run() { - try { - waitingSemaphore.tryAcquire(ASYNC_TIMEOUT, TimeUnit.MILLISECONDS); - } catch (Exception e) {} - - this.module.updateSharedState(updateVersion, new EventData().putString("state", - "updated" + updateCount.incrementAndGet())); - } - } - } - - private static ArrayList moduleReadingAsyncStateProcessedEvents = new ArrayList(); - private static final Semaphore moduleReadingAsyncStateSemaphore = new Semaphore(0, true); - - public static class ModuleReadingAsyncState extends InternalModule { - private final ConcurrentLinkedQueue waitingEvents; - - public ModuleReadingAsyncState(final EventHub hub, final PlatformServices services) { - super("ModuleReadingAsyncState", hub, services); - waitingEvents = new ConcurrentLinkedQueue(); - this.registerListener(EventType.HUB, EventSource.SHARED_STATE, StateChangeListener.class); - this.registerListener(EventType.ANALYTICS, EventSource.NONE, EventListener.class); - } - - private void processEvent(final Event e) { - if (e != null) { - waitingEvents.add(e); - } - - while (!waitingEvents.isEmpty()) { - Event curEvent = waitingEvents.peek(); - EventData state = getSharedEventState("SHARED", curEvent); - - if (state == null) { - break; - } - - moduleReadingAsyncStateProcessedEvents.add(state.getString("state")); - - if (moduleReadingAsyncStateProcessedEvents.size() == 5) { - moduleReadingAsyncStateSemaphore.release(); - } - - waitingEvents.poll(); - } - } - - // standard event listener - public static class EventListener extends ModuleEventListener { - public EventListener(final ModuleReadingAsyncState module, final EventType type, final EventSource source) { - super(module, type, source); - } - - public void hear(final Event e) { - parentModule.processEvent(e); - } - } - - // listens for shared state changes - public static class StateChangeListener extends ModuleEventListener { - public StateChangeListener(final ModuleReadingAsyncState module, final EventType type, final EventSource source) { - super(module, type, source); - } - - public void hear(final Event e) { - // make sure we try and process events after the state change. - parentModule.processEvent(null); - } - } - - } - - @Test(timeout = 3000) - public void readAsyncStateTest() throws Exception { - final EventHub hub = new EventHub("Shared State Async Hub", services); - hub.registerModule(ModuleSharedStateAsyncUpdate.class); - hub.registerModule(ModuleReadingAsyncState.class); - - final CountDownLatch bootLatch = new CountDownLatch(1); - hub.finishModulesRegistration(new AdobeCallback() { - @Override - public void call(Void value) { - bootLatch.countDown(); - } - }); - bootLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - - hub.dispatch(new Event.Builder("Create New Shared State", EventType.CUSTOM, EventSource.NONE).build()); - hub.dispatch(new Event.Builder("Event That Needs Shared State", EventType.ANALYTICS, EventSource.NONE).build()); - hub.dispatch(new Event.Builder("Event That Needs Shared State", EventType.ANALYTICS, EventSource.NONE).build()); - hub.dispatch(new Event.Builder("Create New Shared State", EventType.CUSTOM, EventSource.NONE).build()); - hub.dispatch(new Event.Builder("Event That Needs Shared State", EventType.ANALYTICS, EventSource.NONE).build()); - hub.dispatch(new Event.Builder("Event That Needs Shared State", EventType.ANALYTICS, EventSource.NONE).build()); - hub.dispatch(new Event.Builder("Event That Needs Shared State", EventType.ANALYTICS, EventSource.NONE).build()); - hub.dispatch(new Event.Builder("Event That Needs Shared State", EventType.ANALYTICS, EventSource.NONE).build()); - hub.dispatch(new Event.Builder("Event That Needs Shared State", EventType.ANALYTICS, EventSource.NONE).build()); - hub.dispatch(new Event.Builder("Event That Needs Shared State", EventType.ANALYTICS, EventSource.NONE).build()); - - // wait for events to be processed, each update to shared state above will require ASYNC_TIMEOUT to run - moduleReadingAsyncStateSemaphore.tryAcquire(ASYNC_TIMEOUT * 2 + 500, TimeUnit.MILLISECONDS); - - Assert.assertEquals("event 1 did not receive state", "updated1", moduleReadingAsyncStateProcessedEvents.get(0)); - Assert.assertEquals("event 2 did not receive state", "updated1", moduleReadingAsyncStateProcessedEvents.get(1)); - Assert.assertEquals("event 3 did not receive state", "updated2", moduleReadingAsyncStateProcessedEvents.get(2)); - Assert.assertEquals("event 4 did not receive state", "updated2", moduleReadingAsyncStateProcessedEvents.get(3)); - Assert.assertEquals("event 5 did not receive state", "updated2", moduleReadingAsyncStateProcessedEvents.get(4)); - } - - -} - diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Core.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Core.java index 68dcd6b38..a7049d7c1 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Core.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Core.java @@ -73,16 +73,7 @@ EventHistory getEventHistory() { */ void registerExtension(final Class extensionClass, final ExtensionErrorCallback errorCallback) { - try { - eventHub.registerExtension(extensionClass); - } catch (InvalidModuleException e) { - Log.debug(LOG_TAG, "Core.registerExtension - Failed to register extension class %s (%s)", - extensionClass.getSimpleName(), e); - - if (errorCallback != null) { - errorCallback.error(ExtensionError.UNEXPECTED_ERROR); - } - } + eventHub.registerExtensionWithCallback(extensionClass, errorCallback); } /** diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java index 4c02f1b57..1e31aa855 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java @@ -80,7 +80,6 @@ class EventHub { private final ConcurrentHashMap> moduleXdmSharedStates; private final ConcurrentHashMap sharedStateCircularCheck; private final LinkedList preBootEvents; // locked on bootMutex - private final RulesEngine rulesEngine; private final AtomicInteger currentEventNumber; private final ExecutorService threadPool; private final ExecutorService eventHubThreadService; @@ -137,7 +136,7 @@ public EventHub(final String name, final PlatformServices services, final String new LinkedBlockingQueue()); this.eventHubSharedState = getInitialEventHubSharedState(); this.isBooted = false; - this.rulesEngine = new RulesEngine(this); + //this.rulesEngine = new RulesEngine(this); this.eventBus = new EventBus(); } @@ -198,20 +197,20 @@ void dispatch(final Event e) { } else { this.eventHubThreadService.submit(new EventRunnable(e)); } - - final EventHistory eventHistory = EventHistoryProvider.getEventHistory(); - - // record the event in the event history database if the event has a mask - if (eventHistory != null && e.getMask() != null) { - final EventHistoryResultHandler handler = new EventHistoryResultHandler() { - @Override - public void call(final Boolean value) { - Log.trace(logPrefix, value ? "Successfully inserted an Event into EventHistory database" : - "Failed to insert an Event into EventHistory database"); - } - }; - eventHistory.recordEvent(e, handler); - } +// Todo +// final EventHistory eventHistory = EventHistoryProvider.getEventHistory(); +// +// // record the event in the event history database if the event has a mask +// if (eventHistory != null && e.getMask() != null) { +// final EventHistoryResultHandler handler = new EventHistoryResultHandler() { +// @Override +// public void call(final Boolean value) { +// Log.trace(logPrefix, value ? "Successfully inserted an Event into EventHistory database" : +// "Failed to insert an Event into EventHistory database"); +// } +// }; +// eventHistory.recordEvent(e, handler); +// } } } @@ -281,9 +280,9 @@ protected interface RegisterModuleCallback { * * @return all loaded rules */ - protected ConcurrentHashMap> getModuleRuleAssociation() { - return this.rulesEngine.getModuleRuleAssociation(); - } +// protected ConcurrentHashMap> getModuleRuleAssociation() { +// return this.rulesEngine.getModuleRuleAssociation(); +// } /** * Registers a module with the event hub. Modules must extend {@code Module} @@ -365,31 +364,31 @@ public void run() { * @param rule {@code Rule} to register * @throws InvalidModuleException if module is null */ - final void registerModuleRule(final Module module, final Rule rule) throws InvalidModuleException { - if (module == null) { - throw new InvalidModuleException(LOG_PROVIDED_MODULE_WAS_NULL); - } - - if (rule == null) { - throw new IllegalArgumentException("Cannot register a null rule"); - } - - - rulesEngine.addRule(module, rule); - } - - final void replaceModuleRules(final Module module, final List rules) throws InvalidModuleException { - if (module == null) { - throw new InvalidModuleException(LOG_PROVIDED_MODULE_WAS_NULL); - } - - if (rules == null) { - throw new IllegalArgumentException("Cannot register a null rule"); - } - - - rulesEngine.replaceRules(module, rules); - } +// final void registerModuleRule(final Module module, final Rule rule) throws InvalidModuleException { +// if (module == null) { +// throw new InvalidModuleException(LOG_PROVIDED_MODULE_WAS_NULL); +// } +// +// if (rule == null) { +// throw new IllegalArgumentException("Cannot register a null rule"); +// } +// +// +// rulesEngine.addRule(module, rule); +// } +// +// final void replaceModuleRules(final Module module, final List rules) throws InvalidModuleException { +// if (module == null) { +// throw new InvalidModuleException(LOG_PROVIDED_MODULE_WAS_NULL); +// } +// +// if (rules == null) { +// throw new IllegalArgumentException("Cannot register a null rule"); +// } +// +// +// rulesEngine.replaceRules(module, rules); +// } /** @@ -399,21 +398,21 @@ final void replaceModuleRules(final Module module, final List rules) throw * @param rules {@code Rule} to register * @param reprocessEventsHandler handler to return custom events */ - protected void replaceRulesAndEvaluateEvents(final Module module, final List rules, - final ReprocessEventsHandler reprocessEventsHandler) { - - if (reprocessEventsHandler == null) { - Log.debug(logPrefix, "failed to reprocess events as is null "); - return; - } - - if (rules == null) { - Log.debug(logPrefix, "failed to reprocess events as is null "); - return; - } - - this.eventHubThreadService.submit(new ReprocessEventsWithRules(reprocessEventsHandler, rules, module)); - } +// protected void replaceRulesAndEvaluateEvents(final Module module, final List rules, +// final ReprocessEventsHandler reprocessEventsHandler) { +// +// if (reprocessEventsHandler == null) { +// Log.debug(logPrefix, "failed to reprocess events as is null "); +// return; +// } +// +// if (rules == null) { +// Log.debug(logPrefix, "failed to reprocess events as is null "); +// return; +// } +// +// this.eventHubThreadService.submit(new ReprocessEventsWithRules(reprocessEventsHandler, rules, module)); +// } /** * Unregisters all rules registered by the given {@code Module} @@ -426,7 +425,7 @@ final void unregisterModuleRules(final Module module) throws InvalidModuleExcept throw new InvalidModuleException(LOG_PROVIDED_MODULE_WAS_NULL); } - rulesEngine.unregisterAllRules(module); + //rulesEngine.unregisterAllRules(module); } @@ -1454,49 +1453,49 @@ private HashMap getWrapperInfo() { return wrapperInfo; } - private final class ReprocessEventsWithRules implements Runnable { - - final ReprocessEventsHandler reprocessEventsHandler; - final List rules; - final List consequenceEvents; - final Module module; - - ReprocessEventsWithRules(final ReprocessEventsHandler reprocessEventsHandler, final List rules, - final Module module) { - this.reprocessEventsHandler = reprocessEventsHandler; - this.rules = rules; - this.module = module; - this.consequenceEvents = new ArrayList(); - } - - @Override - public void run() { - try { - List events = reprocessEventsHandler.getEvents(); - - if (events.size() > REPROCESS_EVENTS_AMOUNT_LIMIT) { - Log.debug(logPrefix, "Failed to reprocess cached events, since the amount of events (%s) reach the limits (%s)", - events.size(), - REPROCESS_EVENTS_AMOUNT_LIMIT); - } else { - for (final Event e : events) { - List resultEvents = rulesEngine.evaluateEventWithRules(e, rules); - consequenceEvents.addAll(resultEvents); - } - } - - reprocessEventsHandler.onEventReprocessingComplete(); - - replaceModuleRules(module, rules); - - for (final Event e : consequenceEvents) { - dispatch(e); - } - } catch (Exception e) { - Log.debug(logPrefix, "Failed to reprocess cached events (%s)", e); - } - } - } +// private final class ReprocessEventsWithRules implements Runnable { +// +// final ReprocessEventsHandler reprocessEventsHandler; +// final List rules; +// final List consequenceEvents; +// final Module module; +// +// ReprocessEventsWithRules(final ReprocessEventsHandler reprocessEventsHandler, final List rules, +// final Module module) { +// this.reprocessEventsHandler = reprocessEventsHandler; +// this.rules = rules; +// this.module = module; +// this.consequenceEvents = new ArrayList(); +// } +// +// @Override +// public void run() { +// try { +// List events = reprocessEventsHandler.getEvents(); +// +// if (events.size() > REPROCESS_EVENTS_AMOUNT_LIMIT) { +// Log.debug(logPrefix, "Failed to reprocess cached events, since the amount of events (%s) reach the limits (%s)", +// events.size(), +// REPROCESS_EVENTS_AMOUNT_LIMIT); +// } else { +// for (final Event e : events) { +// List resultEvents = rulesEngine.evaluateEventWithRules(e, rules); +// consequenceEvents.addAll(resultEvents); +// } +// } +// +// reprocessEventsHandler.onEventReprocessingComplete(); +// +// replaceModuleRules(module, rules); +// +// for (final Event e : consequenceEvents) { +// dispatch(e); +// } +// } catch (Exception e) { +// Log.debug(logPrefix, "Failed to reprocess cached events (%s)", e); +// } +// } +// } /** * Class implementing the Runnable for event @@ -1511,17 +1510,17 @@ private final class EventRunnable implements Runnable { @Override public void run() { // run rules - final long preRulesTime = System.currentTimeMillis(); - final List resultEvents = rulesEngine.evaluateRules(event); - - for (final Event e : resultEvents) { - dispatch(e); - } - - final long totalRulesTime = System.currentTimeMillis() - preRulesTime; - Log.trace(logPrefix, "Event (%s) #%d (%s) resulted in %d consequence events. Time in rules was %d milliseconds.", - event.getUniqueIdentifier(), event.getEventNumber(), event.getName(), resultEvents.size(), totalRulesTime); - eventBus.dispatch(event); +// final long preRulesTime = System.currentTimeMillis(); +// final List resultEvents = rulesEngine.evaluateRules(event); +// +// for (final Event e : resultEvents) { +// dispatch(e); +// } +// +// final long totalRulesTime = System.currentTimeMillis() - preRulesTime; +// Log.trace(logPrefix, "Event (%s) #%d (%s) resulted in %d consequence events. Time in rules was %d milliseconds.", +// event.getUniqueIdentifier(), event.getEventNumber(), event.getName(), resultEvents.size(), totalRulesTime); +// eventBus.dispatch(event); } } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index 3e5373f7a..1d07a4794 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -11,7 +11,7 @@ package com.adobe.marketing.mobile.internal.eventhub -import android.support.annotation.NonNull +import androidx.annotation.NonNull import com.adobe.marketing.mobile.Event import com.adobe.marketing.mobile.Extension import com.adobe.marketing.mobile.ExtensionError diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/util/SerialWorkDispatcher.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/util/SerialWorkDispatcher.kt index 6c9070402..271561c4e 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/util/SerialWorkDispatcher.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/util/SerialWorkDispatcher.kt @@ -11,7 +11,7 @@ package com.adobe.marketing.mobile.util -import android.support.annotation.VisibleForTesting +import androidx.annotation.VisibleForTesting import com.adobe.marketing.mobile.LoggingMode import com.adobe.marketing.mobile.MobileCore import java.lang.Exception diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/EventListenerTest.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/EventListenerTest.java deleted file mode 100644 index 97341df6e..000000000 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/EventListenerTest.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - Copyright 2022 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. - */ - -package com.adobe.marketing.mobile; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -public class EventListenerTest { - final static String TESTPAIRID = "THISISATESTLOL"; - - private static PlatformServices services = new FakePlatformServices(); - private EventHub hub; - - private static CountDownLatch latch = new CountDownLatch(1); - private static CountDownLatch countingEventLatch; - private static int countingEventCount = 0; - private static int wildcardListenerEventCount = 0; - private static int EVENTHUB_WAIT_MS = 50; - - @Before - public void perTest() { - countingEventLatch = new CountDownLatch(1); - countingEventCount = 0; - wildcardListenerEventCount = 0; - - try { - hub = new EventHub("Listener Test Hub", services); - hub.registerModule(CountingTestModule.class); - - final CountDownLatch hubBootWaitLatch = new CountDownLatch(1); - hub.finishModulesRegistration(new AdobeCallback() { - @Override - public void call(Void value) { - hubBootWaitLatch.countDown(); - } - }); - hubBootWaitLatch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - } catch (Exception e) { - e.printStackTrace(); - Assert.fail("Failed to register module!"); - } - } - - public static class CountingTestModule extends Module { - - public CountingTestModule(final EventHub hub) { - super("CountingTestModule", hub); - registerListener(EventType.CUSTOM, EventSource.NONE, CountingListener.class); - registerListener(EventType.ANALYTICS, EventSource.NONE, EndListener.class); - registerListener(EventType.PII, EventSource.RESPONSE_CONTENT, SelfRemovingListener.class); - registerWildcardListener(WildcardListener.class); - - // for reregister test, we should only end up with one of these. - for (int i = 0; i < 100; i++) { - registerListener(EventType.AUDIENCEMANAGER, EventSource.REQUEST_PROFILE, CountingListener.class); - } - - // for oneTimeListenerTest - hub.registerOneTimeListener(null, new Module.OneTimeListenerBlock() { - public void call(final Event e) { - ++countingEventCount; - } - }); - - // for pairIDTest - hub.registerOneTimeListener(TESTPAIRID, new Module.OneTimeListenerBlock() { - public void call(final Event e) { - ++countingEventCount; - } - }); - } - - protected static class SelfRemovingListener extends ModuleEventListener { - public SelfRemovingListener(final CountingTestModule m, final EventType t, final EventSource s) { - super(m, t, s); - } - - public void hear(final Event e) { - ++countingEventCount; - parentModule.unregisterListener(EventType.PII, EventSource.RESPONSE_CONTENT); - // This does not actually happen immediately now -- so some events may be already in the queue - } - } - - protected static class CountingListener extends ModuleEventListener { - public CountingListener(final CountingTestModule m, final EventType t, final EventSource s) { - super(m, t, s); - } - - public void hear(final Event e) { - ++countingEventCount; - } - } - - protected static class WildcardListener extends ModuleEventListener { - public WildcardListener(final CountingTestModule m, final EventType t, final EventSource s) { - super(m, t, s); - } - - public void hear(final Event e) { - ++wildcardListenerEventCount; - } - } - - public static class EndListener extends ModuleEventListener { - public EndListener(final CountingTestModule m, final EventType t, final EventSource s) { - super(m, t, s); - } - - public void hear(final Event e) { - countingEventLatch.countDown(); - } - } - } - - private void waitForEventDone(final EventHub hub) throws Exception { - hub.dispatch(new Event.Builder("ending event", EventType.ANALYTICS, EventSource.NONE).build()); - - countingEventLatch.await(300, TimeUnit.MILLISECONDS); - } - - @Test(timeout = 1000) - public void multiListenerTest() throws Exception { - final EventData eventData = new EventData() - .putString("initialkey", "initialvalue"); - final Event newEvent = new Event.Builder("Test Event", EventType.CUSTOM, EventSource.NONE).setData(eventData) - .build(); - - for (int i = 0; i < 100; i++) { - hub.dispatch(newEvent); - } - - waitForEventDone(hub); - Assert.assertEquals(100, countingEventCount); - } - - @Test(timeout = 1000) - public void oneTimeListenerTest() throws Exception { - final EventData eventData = new EventData() - .putString("initialkey", "initialvalue"); - final Event newEvent = new Event.Builder("Test Event", EventType.CUSTOM, - EventSource.RESPONSE_CONTENT).setData(eventData).setPairID(TESTPAIRID).build(); - - for (int i = 0; i < 100; i++) { - hub.dispatch(newEvent); - } - - waitForEventDone(hub); - Assert.assertEquals(1, countingEventCount); - } - - @Test(timeout = 1000) - public void wildCardListenerTest() throws Exception { - final EventData eventData = new EventData() - .putString("initialkey", "initialvalue"); - final Event newEvent = new Event.Builder("Test Event", EventType.CUSTOM, - EventSource.RESPONSE_CONTENT).setData(eventData).build(); - - for (int i = 0; i < 100; i++) { - hub.dispatch(newEvent); - } - - waitForEventDone(hub); - //There are 3 extra events counted: - // - One extra one heard by the wild card listener because a event was dispatched by waitForEventDone() too. - // - One extra for Hub Booted event from module registration - // - One extra for eventhub shared state - // The other 100 times event dispatched by the test were heard by the wild card listener - Assert.assertEquals(103, wildcardListenerEventCount); - } - - @Test(timeout = 1000) - public void wildCardListenerTest_ThirdPartyEvent() throws Exception { - final EventData eventData = new EventData() - .putString("initialkey", "initialvalue"); - - final Event newEvent = new Event.Builder("Test Event", EventType.get("thirdPartyType"), - EventSource.get("thirdPartySource")).setData(eventData).build(); - - for (int i = 0; i < 100; i++) { - hub.dispatch(newEvent); - } - - waitForEventDone(hub); - //There are 3 extra events counted: - // - One extra one heard by the wild card listener because a event was dispatched by waitForEventDone() too. - // - One extra for Hub Booted event from module registration - // - One extra for Hub shared state - // The other 100 times event dispatched by the test were heard by the wild card listener - Assert.assertEquals(103, wildcardListenerEventCount); - } - - - @Test(timeout = 1000) - public void pairIDTest() throws Exception { - final EventData eventData = new EventData() - .putString("initialkey", "initialvalue"); - final Event newEvent = new Event.Builder("Test Event", EventType.CUSTOM, - EventSource.RESPONSE_PROFILE).setData(eventData).setPairID(TESTPAIRID).build(); - - for (int i = 0; i < 100; i++) { - hub.dispatch(newEvent); - } - - waitForEventDone(hub); - Assert.assertEquals(1, countingEventCount); - } - - @Test(timeout = 1000) - public void unregisterTest() throws Exception { - final EventData eventData = new EventData() - .putString("initialkey", "initialvalue"); - final Event newEvent = new Event.Builder("Test Event", EventType.PII, - EventSource.RESPONSE_CONTENT).setData(eventData).build(); - hub.dispatch(newEvent); - - latch.await(EVENTHUB_WAIT_MS, TimeUnit.MILLISECONDS); - - for (int i = 0; i < 100; i++) { - hub.dispatch(newEvent); - } - - waitForEventDone(hub); - - Log.debug("unregisterTest", "countingEventCount=%d", countingEventCount); - Assert.assertEquals(1, countingEventCount); - } - - @Test(timeout = 1000) - public void reregisterTest() throws Exception { - final EventData eventData = new EventData().putString("initialkey", "initialvalue"); - final Event newEvent = new Event.Builder("Test Event", EventType.AUDIENCEMANAGER, - EventSource.REQUEST_PROFILE).setData(eventData).build(); - // we registered the listener 100 times, but we should only have one living listener on this hub - // a single dispatch should result in a single count. - hub.dispatch(newEvent); - - waitForEventDone(hub); - - Log.debug("unregisterTest", "countingEventCount=%d", countingEventCount); - Assert.assertEquals(1, countingEventCount); - } -} From 40853b8c846a23b49ca05ee13da90c402fa44e58 Mon Sep 17 00:00:00 2001 From: praveek Date: Mon, 11 Jul 2022 17:41:58 -0700 Subject: [PATCH 094/476] Fix lint issues --- .../internal/utility/EventDataMerger.kt | 25 ++++--- .../mobile/internal/utility/UrlUtilities.kt | 71 ++++++++++--------- .../rulesengine/LaunchRulesConsequence.kt | 10 +-- .../launch/rulesengine/LaunchTokenFinder.kt | 13 ++-- .../rulesengine/json/HistoricalCondition.kt | 19 ++--- .../internal/utility/JSONExtensionsTests.kt | 22 +++--- .../internal/utility/MapExtensionsTests.kt | 2 +- .../rulesengine/LaunchRuleTransformerTests.kt | 4 +- .../LaunchRulesConsequenceTests.kt | 48 ++++++++----- .../LaunchRulesEngineModuleTests.kt | 4 +- .../rulesengine/LaunchRulesEvaluatorTests.kt | 6 +- .../rulesengine/json/JSONConditionTests.kt | 2 +- .../rulesengine/json/JSONConsequenceTests.kt | 2 +- .../rulesengine/json/JSONRuleRootTests.kt | 2 +- .../launch/rulesengine/json/JSONRuleTests.kt | 4 +- .../rulesengine/json/JSONRulesParserTests.kt | 2 +- .../internal/utility/EventDataMergerTests.kt | 2 +- 17 files changed, 135 insertions(+), 103 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/EventDataMerger.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/EventDataMerger.kt index 2d81b2dbf..7e472fe6a 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/EventDataMerger.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/EventDataMerger.kt @@ -28,18 +28,21 @@ internal object EventDataMerger { to: Map?, overwrite: Boolean ): Map { - return innerMerge(from, to, overwrite, fun(fromValue, toValue): Any? { - if (fromValue is Map<*, *> && toValue is Map<*, *>) { - return mergeWildcardMaps(fromValue, toValue, overwrite) - } - if (!overwrite) { - return toValue - } - if (fromValue is Collection<*> && toValue is Collection<*>) { - return mergeCollection(fromValue, toValue) + return innerMerge( + from, to, overwrite, + fun(fromValue, toValue): Any? { + if (fromValue is Map<*, *> && toValue is Map<*, *>) { + return mergeWildcardMaps(fromValue, toValue, overwrite) + } + if (!overwrite) { + return toValue + } + if (fromValue is Collection<*> && toValue is Collection<*>) { + return mergeCollection(fromValue, toValue) + } + return fromValue } - return fromValue - }) + ) } private fun mergeCollection(from: Collection<*>?, to: Collection<*>?): Collection { diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/UrlUtilities.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/UrlUtilities.kt index 66f92c3bf..e5598b111 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/UrlUtilities.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/UrlUtilities.kt @@ -22,42 +22,42 @@ internal object UrlUtilities { // lookup tables used by urlEncode private val encodedChars = arrayOf( - "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", "%08", "%09", "%0A", "%0B", "%0C", "%0D", "%0E", "%0F", - "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17", "%18", "%19", "%1A", "%1B", "%1C", "%1D", "%1E", "%1F", - "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27", "%28", "%29", "%2A", "%2B", "%2C", "-", ".", "%2F", - "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "%3A", "%3B", "%3C", "%3D", "%3E", "%3F", - "%40", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", - "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "%5B", "%5C", "%5D", "%5E", "_", - "%60", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", - "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "%7B", "%7C", "%7D", "~", "%7F", - "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87", "%88", "%89", "%8A", "%8B", "%8C", "%8D", "%8E", "%8F", - "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97", "%98", "%99", "%9A", "%9B", "%9C", "%9D", "%9E", "%9F", - "%A0", "%A1", "%A2", "%A3", "%A4", "%A5", "%A6", "%A7", "%A8", "%A9", "%AA", "%AB", "%AC", "%AD", "%AE", "%AF", - "%B0", "%B1", "%B2", "%B3", "%B4", "%B5", "%B6", "%B7", "%B8", "%B9", "%BA", "%BB", "%BC", "%BD", "%BE", "%BF", - "%C0", "%C1", "%C2", "%C3", "%C4", "%C5", "%C6", "%C7", "%C8", "%C9", "%CA", "%CB", "%CC", "%CD", "%CE", "%CF", - "%D0", "%D1", "%D2", "%D3", "%D4", "%D5", "%D6", "%D7", "%D8", "%D9", "%DA", "%DB", "%DC", "%DD", "%DE", "%DF", - "%E0", "%E1", "%E2", "%E3", "%E4", "%E5", "%E6", "%E7", "%E8", "%E9", "%EA", "%EB", "%EC", "%ED", "%EE", "%EF", - "%F0", "%F1", "%F2", "%F3", "%F4", "%F5", "%F6", "%F7", "%F8", "%F9", "%FA", "%FB", "%FC", "%FD", "%FE", "%FF" + "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", "%08", "%09", "%0A", "%0B", "%0C", "%0D", "%0E", "%0F", + "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17", "%18", "%19", "%1A", "%1B", "%1C", "%1D", "%1E", "%1F", + "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27", "%28", "%29", "%2A", "%2B", "%2C", "-", ".", "%2F", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "%3A", "%3B", "%3C", "%3D", "%3E", "%3F", + "%40", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", + "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "%5B", "%5C", "%5D", "%5E", "_", + "%60", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", + "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "%7B", "%7C", "%7D", "~", "%7F", + "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87", "%88", "%89", "%8A", "%8B", "%8C", "%8D", "%8E", "%8F", + "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97", "%98", "%99", "%9A", "%9B", "%9C", "%9D", "%9E", "%9F", + "%A0", "%A1", "%A2", "%A3", "%A4", "%A5", "%A6", "%A7", "%A8", "%A9", "%AA", "%AB", "%AC", "%AD", "%AE", "%AF", + "%B0", "%B1", "%B2", "%B3", "%B4", "%B5", "%B6", "%B7", "%B8", "%B9", "%BA", "%BB", "%BC", "%BD", "%BE", "%BF", + "%C0", "%C1", "%C2", "%C3", "%C4", "%C5", "%C6", "%C7", "%C8", "%C9", "%CA", "%CB", "%CC", "%CD", "%CE", "%CF", + "%D0", "%D1", "%D2", "%D3", "%D4", "%D5", "%D6", "%D7", "%D8", "%D9", "%DA", "%DB", "%DC", "%DD", "%DE", "%DF", + "%E0", "%E1", "%E2", "%E3", "%E4", "%E5", "%E6", "%E7", "%E8", "%E9", "%EA", "%EB", "%EC", "%ED", "%EE", "%EF", + "%F0", "%F1", "%F2", "%F3", "%F4", "%F5", "%F6", "%F7", "%F8", "%F9", "%FA", "%FB", "%FC", "%FD", "%FE", "%FF" ) private val ALL_BITS_ENABLED = 0xFF private val utf8Mask = booleanArrayOf( - false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, false, false, false, false, false, true, true, false, - true, true, true, true, true, true, true, true, true, true, false, false, false, false, false, false, - false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, true, false, false, false, false, true, - false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, true, false, false, false, true, false, - false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, true, true, false, + true, true, true, true, true, true, true, true, true, true, false, false, false, false, false, false, + false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, false, false, false, false, true, + false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, false, false, false, true, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false ) /** @@ -105,9 +105,10 @@ internal object UrlUtilities { encodedString.toString() } catch (e: UnsupportedEncodingException) { MobileCore.log( - LoggingMode.ERROR, - LOG_TAG, - "Failed to url encode string $unencodedString $e") + LoggingMode.ERROR, + LOG_TAG, + "Failed to url encode string $unencodedString $e" + ) null } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt index 2355b1141..0048fef3d 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt @@ -76,8 +76,8 @@ class LaunchRulesConsequence( LoggingMode.VERBOSE, logTag, "Unable to process dispatch consequence, max chained " + - "dispatch consequences limit of $MAX_CHAINED_CONSEQUENCE_COUNT" + - "met for this event uuid ${event.uniqueIdentifier}" + "dispatch consequences limit of $MAX_CHAINED_CONSEQUENCE_COUNT" + + "met for this event uuid ${event.uniqueIdentifier}" ) continue } @@ -141,7 +141,8 @@ class LaunchRulesConsequence( is String -> mutableDetail[key] = replaceToken(value, tokenFinder) is Map<*, *> -> mutableDetail[key] = replaceToken( EventDataUtils.castFromGenericType(value), - tokenFinder) + tokenFinder + ) else -> continue } } @@ -291,7 +292,8 @@ class LaunchRulesConsequence( return Event.Builder( CONSEQUENCE_EVENT_NAME, EVENT_TYPE_RULES_ENGINE, - EVENT_SOURCE_RESPONSE_CONTENT) + EVENT_SOURCE_RESPONSE_CONTENT + ) .setEventData(mapOf(CONSEQUENCE_EVENT_DATA_KEY_CONSEQUENCE to eventData)) .build() } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt index 24e4dd063..8ce674d2e 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt @@ -19,8 +19,8 @@ import com.adobe.marketing.mobile.internal.utility.TimeUtil import com.adobe.marketing.mobile.internal.utility.flattening import com.adobe.marketing.mobile.internal.utility.serializeToQueryString import com.adobe.marketing.mobile.rulesengine.TokenFinder -import java.security.SecureRandom import org.json.JSONObject +import java.security.SecureRandom internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionApi) : TokenFinder { @@ -81,8 +81,9 @@ internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionAp } KEY_ALL_JSON -> { if (event.eventData == null) { - MobileCore.log(LoggingMode.DEBUG, - LOG_TAG, + MobileCore.log( + LoggingMode.DEBUG, + LOG_TAG, "Triggering event data is null, can not use it to generate a json string" ) return EMPTY_STRING @@ -90,7 +91,8 @@ internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionAp try { JSONObject(event.eventData).toString() } catch (e: Exception) { - MobileCore.log(LoggingMode.DEBUG, + MobileCore.log( + LoggingMode.DEBUG, LOG_TAG, "Failed to generate a json string ${e.message}" ) @@ -120,7 +122,8 @@ internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionAp val (sharedStateName, dataKeyName) = sharedStateKeyString.split(SHARED_STATE_KEY_DELIMITER) // TODO change once map flattening logic is finalized val sharedStateMap = extensionApi.getSharedEventState(sharedStateName, event) { - MobileCore.log(LoggingMode.DEBUG, + MobileCore.log( + LoggingMode.DEBUG, LOG_TAG, String.format("Unable to replace the token %s, token not found in shared state for the event", key) ) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt index 6cea1588b..e6db681a5 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/json/HistoricalCondition.kt @@ -46,14 +46,17 @@ internal class HistoricalCondition(private val definition: JSONDefinition) : JSO EventHistoryRequest(it, fromDate, toDate) } return ComparisonExpression( - OperandFunction({ - try { - @Suppress("UNCHECKED_CAST") - historicalEventsQuerying(it[0] as List, it[1] as String) - } catch (e: Exception) { - 0 - } - }, requestEvents, searchType), + OperandFunction( + { + try { + @Suppress("UNCHECKED_CAST") + historicalEventsQuerying(it[0] as List, it[1] as String) + } catch (e: Exception) { + 0 + } + }, + requestEvents, searchType + ), operationName, OperandLiteral(valueAsInt) ) diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/JSONExtensionsTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/JSONExtensionsTests.kt index 2bfcfb110..24ebb2bee 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/JSONExtensionsTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/JSONExtensionsTests.kt @@ -10,12 +10,12 @@ */ package com.adobe.marketing.mobile.internal.utility -import kotlin.test.assertEquals -import kotlin.test.assertTrue import org.json.JSONArray import org.json.JSONObject import org.json.JSONTokener import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue class JSONExtensionsTests { @@ -113,9 +113,12 @@ class JSONExtensionsTests { "b", "c" ) - assertEquals(expectedList, jsonArray.map { - if (it is String) it else "" - }) + assertEquals( + expectedList, + jsonArray.map { + if (it is String) it else "" + } + ) } @Test @@ -144,9 +147,12 @@ class JSONExtensionsTests { "key" to "value" ) ) - assertEquals(expectedList, jsonArray.map { - if (it is JSONObject) it.toMap() else null - }) + assertEquals( + expectedList, + jsonArray.map { + if (it is JSONObject) it.toMap() else null + } + ) } @Test fun testJSONArrayToAnyList() { diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt index 683d9fe4c..ef405609b 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt @@ -10,11 +10,11 @@ */ package com.adobe.marketing.mobile.internal.utility +import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNull import kotlin.test.assertTrue -import org.junit.Test class MapExtensionsTests { diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformerTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformerTests.kt index 67310c38b..72c44671f 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformerTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRuleTransformerTests.kt @@ -11,11 +11,11 @@ package com.adobe.marketing.mobile.launch.rulesengine -import java.util.ArrayList -import java.util.HashMap import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Test +import java.util.ArrayList +import java.util.HashMap class LaunchRuleTransformerTests { diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequenceTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequenceTests.kt index b17d507b7..eef93007a 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequenceTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequenceTests.kt @@ -15,9 +15,6 @@ import com.adobe.marketing.mobile.ExtensionApi import com.adobe.marketing.mobile.MobileCore import com.adobe.marketing.mobile.launch.rulesengine.json.JSONRulesParser import com.adobe.marketing.mobile.test.utility.readTestResources -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNull import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -29,6 +26,9 @@ import org.mockito.Mockito.times import org.powermock.api.mockito.PowerMockito import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.modules.junit4.PowerMockRunner +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull @RunWith(PowerMockRunner::class) @PrepareForTest(ExtensionApi::class, MobileCore::class) @@ -235,7 +235,8 @@ class LaunchRulesConsequenceTests { val event = Event.Builder( "Application Launch", "com.adobe.eventType.lifecycle", - "com.adobe.eventSource.applicationLaunch") + "com.adobe.eventSource.applicationLaunch" + ) .setEventData(mapOf("xdm" to "test data")) .build() @@ -269,7 +270,8 @@ class LaunchRulesConsequenceTests { val event = Event.Builder( "Application Launch", "com.adobe.eventType.lifecycle", - "com.adobe.eventSource.applicationLaunch") + "com.adobe.eventSource.applicationLaunch" + ) .setEventData(null) .build() @@ -309,7 +311,8 @@ class LaunchRulesConsequenceTests { val event = Event.Builder( "Application Launch", "com.adobe.eventType.lifecycle", - "com.adobe.eventSource.applicationLaunch") + "com.adobe.eventSource.applicationLaunch" + ) .setEventData(mapOf("xdm" to "test data")) .build() @@ -345,7 +348,8 @@ class LaunchRulesConsequenceTests { val event = Event.Builder( "Application Launch", "com.adobe.eventType.lifecycle", - "com.adobe.eventSource.applicationLaunch") + "com.adobe.eventSource.applicationLaunch" + ) .setEventData(mapOf("xdm" to "test data")) .build() @@ -380,7 +384,8 @@ class LaunchRulesConsequenceTests { val event = Event.Builder( "Application Launch", "com.adobe.eventType.lifecycle", - "com.adobe.eventSource.applicationLaunch") + "com.adobe.eventSource.applicationLaunch" + ) .setEventData(mapOf("xdm" to "test data")) .build() @@ -411,7 +416,8 @@ class LaunchRulesConsequenceTests { val event = Event.Builder( "Application Launch", "com.adobe.eventType.lifecycle", - "com.adobe.eventSource.applicationLaunch") + "com.adobe.eventSource.applicationLaunch" + ) .setEventData(mapOf("xdm" to "test data")) .build() @@ -441,7 +447,8 @@ class LaunchRulesConsequenceTests { val event = Event.Builder( "Application Launch", "com.adobe.eventType.lifecycle", - "com.adobe.eventSource.applicationLaunch") + "com.adobe.eventSource.applicationLaunch" + ) .setEventData(mapOf("xdm" to "test data")) .build() @@ -471,7 +478,8 @@ class LaunchRulesConsequenceTests { val event = Event.Builder( "Application Launch", "com.adobe.eventType.lifecycle", - "com.adobe.eventSource.applicationLaunch") + "com.adobe.eventSource.applicationLaunch" + ) .setEventData(mapOf("xdm" to "test data")) .build() @@ -525,7 +533,8 @@ class LaunchRulesConsequenceTests { val event = Event.Builder( "Edge Request", "com.adobe.eventType.edge", - "com.adobe.eventSource.requestContent") + "com.adobe.eventSource.requestContent" + ) .setEventData(mapOf("xdm" to "test data")) .build() @@ -582,7 +591,8 @@ class LaunchRulesConsequenceTests { val event = Event.Builder( "Edge Request", "com.adobe.eventType.edge", - "com.adobe.eventSource.requestContent") + "com.adobe.eventSource.requestContent" + ) .setEventData(mapOf("xdm" to "test data")) .build() @@ -652,7 +662,8 @@ class LaunchRulesConsequenceTests { val event = Event.Builder( "Edge Request", "com.adobe.eventType.edge", - "com.adobe.eventSource.requestContent") + "com.adobe.eventSource.requestContent" + ) .setEventData(mapOf("xdm" to "test data")) .build() @@ -755,7 +766,8 @@ class LaunchRulesConsequenceTests { val eventEdgeRequest = Event.Builder( "Edge Request", "com.adobe.eventType.edge", - "com.adobe.eventSource.requestContent") + "com.adobe.eventSource.requestContent" + ) .setEventData(mapOf("xdm" to "test data")) .build() @@ -763,7 +775,8 @@ class LaunchRulesConsequenceTests { val eventLaunch = Event.Builder( "Application Launch", "com.adobe.eventType.lifecycle", - "com.adobe.eventSource.applicationLaunch") + "com.adobe.eventSource.applicationLaunch" + ) .setEventData(mapOf("xdm" to "test data")) .build() @@ -857,7 +870,8 @@ class LaunchRulesConsequenceTests { val event = Event.Builder( "Edge Request", "com.adobe.eventType.edge", - "com.adobe.eventSource.requestContent") + "com.adobe.eventSource.requestContent" + ) .setEventData(mapOf("dispatch" to "yes")) .build() diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngineModuleTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngineModuleTests.kt index b90b8fca3..7fb6cec6a 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngineModuleTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngineModuleTests.kt @@ -8,8 +8,6 @@ import com.adobe.marketing.mobile.internal.eventhub.history.EventHistoryRequest import com.adobe.marketing.mobile.internal.eventhub.history.EventHistoryResultHandler import com.adobe.marketing.mobile.launch.rulesengine.json.JSONRulesParser import com.adobe.marketing.mobile.test.utility.readTestResources -import kotlin.test.assertEquals -import kotlin.test.assertNotNull import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -19,6 +17,8 @@ import org.mockito.BDDMockito import org.powermock.api.mockito.PowerMockito import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.modules.junit4.PowerMockRunner +import kotlin.test.assertEquals +import kotlin.test.assertNotNull @RunWith(PowerMockRunner::class) @PrepareForTest(ExtensionApi::class, MobileCore::class) diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt index f53492638..355064411 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt @@ -13,9 +13,6 @@ package com.adobe.marketing.mobile.launch.rulesengine import com.adobe.marketing.mobile.Event import com.adobe.marketing.mobile.ExtensionApi import com.adobe.marketing.mobile.MobileCore -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNull import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -30,6 +27,9 @@ import org.powermock.api.mockito.PowerMockito import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.modules.junit4.PowerMockRunner import org.powermock.reflect.Whitebox +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull @RunWith(PowerMockRunner::class) @PrepareForTest(ExtensionApi::class, MobileCore::class) diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConditionTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConditionTests.kt index 96ee843c8..06b8e065c 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConditionTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConditionTests.kt @@ -14,8 +14,8 @@ import com.adobe.marketing.mobile.rulesengine.ComparisonExpression import com.adobe.marketing.mobile.rulesengine.Evaluable import com.adobe.marketing.mobile.rulesengine.LogicalExpression import com.adobe.marketing.mobile.test.utility.buildJSONObject -import kotlin.test.assertTrue import org.junit.Test +import kotlin.test.assertTrue class JSONConditionTests { @Test diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequenceTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequenceTests.kt index a070347f5..88b44de62 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequenceTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONConsequenceTests.kt @@ -11,9 +11,9 @@ package com.adobe.marketing.mobile.launch.rulesengine.json import com.adobe.marketing.mobile.test.utility.buildJSONObject +import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull -import org.junit.Test class JSONConsequenceTests { @Test diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRootTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRootTests.kt index 2ed0e8f2d..6f330b9cf 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRootTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleRootTests.kt @@ -12,9 +12,9 @@ package com.adobe.marketing.mobile.launch.rulesengine.json import com.adobe.marketing.mobile.test.utility.buildJSONObject +import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertTrue -import org.junit.Test class JSONRuleRootTests { @Test diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleTests.kt index d8049a848..d4cd6b079 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRuleTests.kt @@ -14,11 +14,11 @@ package com.adobe.marketing.mobile.launch.rulesengine.json import com.adobe.marketing.mobile.launch.rulesengine.LaunchRule import com.adobe.marketing.mobile.rulesengine.ComparisonExpression import com.adobe.marketing.mobile.test.utility.buildJSONObject +import org.json.JSONException +import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertNull import kotlin.test.assertTrue -import org.json.JSONException -import org.junit.Test class JSONRuleTests { @Test diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParserTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParserTests.kt index dfc761eac..e5964ac32 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParserTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/json/JSONRulesParserTests.kt @@ -11,10 +11,10 @@ package com.adobe.marketing.mobile.launch.rulesengine.json +import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull -import org.junit.Test class JSONRulesParserTests { @Test diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/EventDataMergerTests.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/EventDataMergerTests.kt index 638f97db8..54cb8d6f1 100644 --- a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/EventDataMergerTests.kt +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/EventDataMergerTests.kt @@ -10,8 +10,8 @@ */ package com.adobe.marketing.mobile.internal.utility -import kotlin.test.assertEquals import org.junit.Test +import kotlin.test.assertEquals class EventDataMergerTests { From eb08692e7e45bdd3d3b029257e5bff7245a6a2bd Mon Sep 17 00:00:00 2001 From: Yansong Date: Tue, 12 Jul 2022 10:15:16 -0600 Subject: [PATCH 095/476] format --- .../adobe/marketing/mobile/MobileCore.java | 16 +++++++ .../adobe/marketing/mobile/MobileCoreTests.kt | 45 ++++++++++++------- code/settings.gradle | 2 +- 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java index f9845f16f..27ea51dd1 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java @@ -16,6 +16,10 @@ import android.content.Context; import androidx.annotation.VisibleForTesting; +import com.adobe.marketing.mobile.internal.eventhub.history.AndroidEventHistory; +import com.adobe.marketing.mobile.internal.eventhub.history.EventHistory; +import com.adobe.marketing.mobile.internal.eventhub.history.EventHistoryDatabaseCreationException; + import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -28,10 +32,15 @@ final public class MobileCore { private static boolean startActionCalled; @VisibleForTesting private static EventHub eventHub; + private static EventHistory eventHistory; private MobileCore() { } + public static EventHistory getEventHistory() { + return eventHistory; + } + /** * Returns the version for the {@code MobileCore} extension * @@ -79,6 +88,13 @@ public static void setApplication(final Application app) { if (App.getPlatformServices() == null) { App.setPlatformServices(new AndroidPlatformServices()); } + try { + eventHistory = new AndroidEventHistory(); + } catch (EventHistoryDatabaseCreationException e) { + Log.warning(LOG_TAG, "Failed to create the android event history service: %s", + e.getMessage()); + eventHistory = null; + } com.adobe.marketing.mobile.internal.context.App.getInstance().initializeApp( new com.adobe.marketing.mobile.internal.context.App.AppContextProvider() { @Override diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MobileCoreTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MobileCoreTests.kt index aed9cbad9..398343427 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MobileCoreTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/MobileCoreTests.kt @@ -11,9 +11,6 @@ package com.adobe.marketing.mobile import android.app.Application -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNull import org.junit.After import org.junit.Before import org.junit.Ignore @@ -27,6 +24,9 @@ import org.powermock.api.mockito.PowerMockito import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.modules.junit4.PowerMockRunner import org.powermock.reflect.Whitebox +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull @RunWith(PowerMockRunner::class) @PrepareForTest(DataMarshaller::class, MobileCore::class) @@ -73,7 +73,8 @@ class MobileCoreTests { @Test fun `test TrackState()`() { MobileCore.trackState( - "state", mapOf( + "state", + mapOf( "key" to "value" ) ) @@ -96,7 +97,8 @@ class MobileCoreTests { "contextdata" to mapOf( "key" to "value" ) - ), dispatchedEvent.eventData + ), + dispatchedEvent.eventData ) } @@ -120,14 +122,16 @@ class MobileCoreTests { mapOf( "state" to "state", "contextdata" to emptyMap() - ), dispatchedEvent.eventData + ), + dispatchedEvent.eventData ) } @Test fun `test trackAction()`() { MobileCore.trackAction( - "action", mapOf( + "action", + mapOf( "key" to "value" ) ) @@ -150,7 +154,8 @@ class MobileCoreTests { "contextdata" to mapOf( "key" to "value" ) - ), dispatchedEvent.eventData + ), + dispatchedEvent.eventData ) } @@ -174,7 +179,8 @@ class MobileCoreTests { mapOf( "action" to "action", "contextdata" to emptyMap() - ), dispatchedEvent.eventData + ), + dispatchedEvent.eventData ) } @@ -203,7 +209,8 @@ class MobileCoreTests { "contextdata" to mapOf( "key" to "value" ) - ), dispatchedEvent.eventData + ), + dispatchedEvent.eventData ) } @@ -232,7 +239,8 @@ class MobileCoreTests { assertEquals( mapOf( "advertisingidentifier" to "advid" - ), dispatchedEvent.eventData + ), + dispatchedEvent.eventData ) } @@ -255,7 +263,8 @@ class MobileCoreTests { assertEquals( mapOf( "advertisingidentifier" to "pushid" - ), dispatchedEvent.eventData + ), + dispatchedEvent.eventData ) } @@ -286,7 +295,8 @@ class MobileCoreTests { "additionalcontextdata" to mapOf( "key" to "value" ) - ), dispatchedEvent.eventData + ), + dispatchedEvent.eventData ) } @@ -309,7 +319,8 @@ class MobileCoreTests { assertEquals( mapOf( "action" to "pause" - ), dispatchedEvent.eventData + ), + dispatchedEvent.eventData ) } @@ -698,7 +709,8 @@ class MobileCoreTests { "config.update" to mapOf( "global.privacy" to MobilePrivacyStatus.OPT_OUT.value ) - ), dispatchedEvent.eventData + ), + dispatchedEvent.eventData ) } @@ -726,7 +738,8 @@ class MobileCoreTests { "config.update" to mapOf( "key" to "value" ) - ), dispatchedEvent.eventData + ), + dispatchedEvent.eventData ) } } diff --git a/code/settings.gradle b/code/settings.gradle index 7fd24ab17..6e02a9d05 100644 --- a/code/settings.gradle +++ b/code/settings.gradle @@ -11,7 +11,7 @@ rootProject.name = 'aepsdk-core-android' include ':android-core-library', // ':android-lifecycle-library', // ':android-identity-library', - ':android-signal-library', +// ':android-signal-library', ':android-core-library-maven-root', ':test-third-party-extension', ':testapp' From 5f3f5b3fcd42417a9f704bc4597e4c2e022c9773 Mon Sep 17 00:00:00 2001 From: Yansong Date: Tue, 12 Jul 2022 10:40:26 -0600 Subject: [PATCH 096/476] catch null pointer exception --- .../src/phone/java/com/adobe/marketing/mobile/MobileCore.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java index 27ea51dd1..c4df202d7 100644 --- a/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java +++ b/code/android-core-library/src/phone/java/com/adobe/marketing/mobile/MobileCore.java @@ -90,7 +90,7 @@ public static void setApplication(final Application app) { } try { eventHistory = new AndroidEventHistory(); - } catch (EventHistoryDatabaseCreationException e) { + } catch (Exception e) { Log.warning(LOG_TAG, "Failed to create the android event history service: %s", e.getMessage()); eventHistory = null; From b90f8d3e0dc43dd0f384e01506315ac8c599457b Mon Sep 17 00:00:00 2001 From: Yansong Date: Tue, 12 Jul 2022 11:00:49 -0600 Subject: [PATCH 097/476] add signal back in setting.gradle to avoid a warning. --- .../com/adobe/marketing/mobile/Signal.java | 26 +++++++++---------- code/settings.gradle | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/code/android-signal-library/src/phone/java/com/adobe/marketing/mobile/Signal.java b/code/android-signal-library/src/phone/java/com/adobe/marketing/mobile/Signal.java index d63a43e55..7a0331589 100644 --- a/code/android-signal-library/src/phone/java/com/adobe/marketing/mobile/Signal.java +++ b/code/android-signal-library/src/phone/java/com/adobe/marketing/mobile/Signal.java @@ -26,18 +26,18 @@ public static String extensionVersion() { } public static void registerExtension() throws InvalidInitException { - Core core = MobileCore.getCore(); - - if (core == null) { - throw new InvalidInitException(); - } - - try { - //MobileCore may not be loaded or present (because may be Core extension was not - //available). In that case, the Signal extension will not initialize itself - signalCore = new SignalCore(core.eventHub, new SignalModuleDetails()); - } catch (Exception e) { - throw new InvalidInitException(); - } +// Core core = MobileCore.getCore(); +// +// if (core == null) { +// throw new InvalidInitException(); +// } +// +// try { +// //MobileCore may not be loaded or present (because may be Core extension was not +// //available). In that case, the Signal extension will not initialize itself +// signalCore = new SignalCore(core.eventHub, new SignalModuleDetails()); +// } catch (Exception e) { +// throw new InvalidInitException(); +// } } } diff --git a/code/settings.gradle b/code/settings.gradle index 6e02a9d05..7fd24ab17 100644 --- a/code/settings.gradle +++ b/code/settings.gradle @@ -11,7 +11,7 @@ rootProject.name = 'aepsdk-core-android' include ':android-core-library', // ':android-lifecycle-library', // ':android-identity-library', -// ':android-signal-library', + ':android-signal-library', ':android-core-library-maven-root', ':test-third-party-extension', ':testapp' From e804834b3ba53608177c22e05e044f56915407f4 Mon Sep 17 00:00:00 2001 From: praveek Date: Tue, 12 Jul 2022 11:58:32 -0700 Subject: [PATCH 098/476] Update ExtensionAPIs and implement event listeners --- .../com/adobe/marketing/mobile/Event.java | 24 + .../com/adobe/marketing/mobile/EventHub.java | 28 +- .../adobe/marketing/mobile/EventSource.java | 7 +- .../com/adobe/marketing/mobile/EventType.java | 6 +- .../com/adobe/marketing/mobile/Extension.java | 18 + .../adobe/marketing/mobile/ExtensionApi.java | 180 +++++- .../mobile/ExtensionEventListener.java | 19 + .../marketing/mobile/ExtensionHelper.java | 32 +- .../marketing/mobile/ExtensionListener.java | 6 +- .../mobile/ExtensionUnexpectedError.java | 1 + .../mobile/SharedStateResolution.java | 17 + .../marketing/mobile/SharedStateResolver.java | 18 + .../marketing/mobile/SharedStateResult.java | 24 + .../marketing/mobile/SharedStateStatus.java | 18 + .../mobile/internal/eventhub/EventHub.kt | 123 +++- .../eventhub/EventListenerContainer.kt | 74 +++ .../internal/eventhub/ExtensionContainer.kt | 392 +++++++------ .../mobile/internal/eventhub/ExtensionExt.kt | 37 +- .../mobile/internal/eventhub/EventHubTests.kt | 546 +++++++++++++++--- .../eventhub/EventListenerContainerTests.kt | 123 ++++ .../eventhub/ExtensionContainerTest.kt | 25 +- 21 files changed, 1375 insertions(+), 343 deletions(-) create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionEventListener.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResolution.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResolver.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResult.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateStatus.java create mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventListenerContainer.kt create mode 100644 code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventListenerContainerTests.kt diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java index 192454d1a..cfe6f4c56 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Event.java @@ -31,6 +31,7 @@ public final class Event { private String pairID; private String responsePairID; private Map data; + private String responseID; private long timestamp; private int eventNumber; // Specifies the properties in the Event and its data that should be used in the hash for EventHistory storage. @@ -79,6 +80,7 @@ public static class Builder { event.type = type; event.source = source; event.responsePairID = UUID.randomUUID().toString(); + event.responseID = null; event.eventNumber = 0; event.mask = mask; didBuild = false; @@ -219,6 +221,19 @@ Builder setPairID(final String pairId) { return this; } + /** + * Sets the triggering event for this {@code Event} + * + * @param triggerEvent {@code Event} event + * @return this Event {@link Builder} + * @throws UnsupportedOperationException if this method is called after {@link Builder#build()} was called + */ + public Builder setTriggerEvent(final Event triggerEvent) { + throwIfAlreadyBuilt(); + event.responseID = triggerEvent.uniqueIdentifier; + return this; + } + /** * Sets the responsePairId for this {@code Event} * @@ -413,6 +428,15 @@ String getResponsePairID() { return responsePairID; } + /** + * Pair ID for events dispatched by the receiver(s) in response to this event + * + * @return String response pair ID + */ + public String getResponseID() { + return responseID; + } + /** * @return event timestamp in seconds */ diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java index 1e31aa855..210327e17 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventHub.java @@ -187,31 +187,11 @@ public void run() { * @param e the event to be added to the queue */ void dispatch(final Event e) { - synchronized (bootMutex) { - e.setEventNumber(this.currentEventNumber.getAndIncrement()); - - if (!this.isBooted) { - Log.debug(logPrefix, "Event (%s, %s) was dispatched before module registration was finished", - e.getEventType().getName(), e.getEventSource().getName()); - preBootEvents.add(e); - } else { - this.eventHubThreadService.submit(new EventRunnable(e)); - } -// Todo -// final EventHistory eventHistory = EventHistoryProvider.getEventHistory(); -// -// // record the event in the event history database if the event has a mask -// if (eventHistory != null && e.getMask() != null) { -// final EventHistoryResultHandler handler = new EventHistoryResultHandler() { -// @Override -// public void call(final Boolean value) { -// Log.trace(logPrefix, value ? "Successfully inserted an Event into EventHistory database" : -// "Failed to insert an Event into EventHistory database"); -// } -// }; -// eventHistory.recordEvent(e, handler); -// } + if (e == null) { + return; } + + com.adobe.marketing.mobile.internal.eventhub.EventHub.Companion.getShared().dispatch(e); } /** diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventSource.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventSource.java index f7ab9ead1..e9386b988 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventSource.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventSource.java @@ -22,7 +22,12 @@ * @see EventHub * @see EventType */ -final class EventSource { +public final class EventSource { + + // Todo - Expose String constants. Remove 'TYPE' prefix after fixing build issues + public static final String TYPE_WILDCARD = "com.adobe.eventSource._wildcard_"; + + private static final String ADOBE_PREFIX = "com.adobe.eventSource."; private static final Map knownSources = new HashMap(); private static final Object knownSourcesMutex = new Object(); diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventType.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventType.java index 1a5f2ff29..a47e29fd2 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventType.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventType.java @@ -22,7 +22,11 @@ * @see EventHub * @see EventSource */ -final class EventType { +public final class EventType { + + // Todo - Expose String constants. Remove 'TYPE' prefix after fixing build issues + public static final String TYPE_WILDCARD = "com.adobe.eventType._wildcard_"; + private static final String ADOBE_PREFIX = "com.adobe.eventType."; private static final Map knownTypes = new HashMap(); private static final Object knownTypesMutex = new Object(); diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Extension.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Extension.java index 1b597565e..61ad45f44 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Extension.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Extension.java @@ -53,6 +53,14 @@ protected String getVersion() { return null; } + /** + * Called when the extension is registered by the core. + * Implementers can implement this method to clean up resources when the extension is released. + */ + protected void onRegistered() { + Log.debug(getLogTag(), "Extension registered successfully."); + } + /** * Called when the extension is unregistered by the core. * Implementers can implement this method to clean up resources when the extension is released. @@ -68,6 +76,7 @@ protected void onUnregistered() { * * @param extensionUnexpectedError the {@link ExtensionUnexpectedError} returned from the core */ + @Deprecated protected void onUnexpectedError(final ExtensionUnexpectedError extensionUnexpectedError) { ExtensionError error = extensionUnexpectedError != null ? extensionUnexpectedError.getErrorCode() : null; @@ -77,6 +86,15 @@ protected void onUnexpectedError(final ExtensionUnexpectedError extensionUnexpec } } + /** + * Called before each Event is processed by any ExtensionListener owned by this Extension + * Should be overridden by any extension that wants to control its own Event flow on a per Event basis. + * + * @param event {@link Event} that will be processed next + * @return {@code boolean} to denote if event processing should continue for this `Extension` + */ + public boolean readyForEvent(Event event) { return true; } + /** * This provides the services the extension will need. * diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionApi.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionApi.java index 43eaa481e..a8e9189dd 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionApi.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionApi.java @@ -17,6 +17,142 @@ * Class that defines all the public methods an {@code Extension} may call to interface with the AEP SDK. */ public abstract class ExtensionApi { + + /** + * Registers a new event listener for current extension for the provided event type and source. + * + * @param eventType required parameter, the event type as a valid string (not null or empty) + * @param eventSource required parameter, the event source as a valid string (not null or empty) + * @param eventListener required parameter, the listener which extends the {@link ExtensionEventListener} interface + * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if any error occurs during registration + * @return a {@code boolean} indicating the listener registration status + */ + public abstract boolean registerEventListener(final String eventType, + final String eventSource, + final ExtensionEventListener eventListener, + final ExtensionErrorCallback errorCallback); + + // Shared state + /** + * Creates a new shared state for this extension. + * If event is null, one of two behaviors will be observed: + *
      + *
    • If this extension has not previously published a shared state, shared state will be versioned at 0
    • + *
    • If this extension has previously published a shared state, shared state will be versioned at the latest
    • + *
    + * @param state {@code Map} representing current state of this extension + * @param event The {@link Event} for which the state is being set. Passing null will set the state for the next shared + * state version + * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred + * @return {@code boolean} indicating if the shared state was successfully set + */ + public abstract boolean createSharedState(final Map state, + final Event event, + final ExtensionErrorCallback errorCallback); + /** + * Creates a pending shared state for this extension. + *
      + *
    • If this extension has not previously published a shared state, shared state will be versioned at 0
    • + *
    • If this extension has previously published a shared state, shared state will be versioned at the latest
    • + *
    + * @param event The {@link Event} for which pending shared state is being set. Passing null will set the state for the next shared + * state version. + * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred + * @return {@link SharedStateResolver} that should be called with the shared state data when it is ready + */ + public abstract SharedStateResolver createPendingSharedState(final Event event, + final ExtensionErrorCallback errorCallback); + /** + * Gets the shared state data for a specified extension. + * @param extensionName extension name for which to retrieve data. See documentation for the list of available states. + * @param event the {@link Event} for which the state is being requested. Passing null will retrieve latest state available. + * @param barrier If true, the {@code EventHub} will only return {@code set} if extensionName has moved past event. + * @param resolution the {@link SharedStateResolution} to resolve for + * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred + * return {@code SharedStateResult} for the requested extensionName and event + */ + public abstract SharedStateResult getSharedState(final String extensionName, + final Event event, + final boolean barrier, + final SharedStateResolution resolution, + final ExtensionErrorCallback errorCallback); + + /** + * Called by extension to clear all shared state it has previously set. Usually called during {@code Extension.onUnregistered()}. + * + * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred + * @return {@code boolean} indicating if the shared states were successfully cleared + * @see Extension#onUnregistered() + */ + public abstract boolean clearSharedEventStates(final ExtensionErrorCallback errorCallback); + + // XDM Shared state + /** + * Creates a new XDM shared state for this extension. The state passed to this API needs to be mapped to known XDM mixins. If an extension uses multiple mixins, the current data for all of them should be provided when the XDM shared state is set. + * If event is null, one of two behaviors will be observed: + *
      + *
    • If this extension has not previously published a shared state, shared state will be versioned at 0
    • + *
    • If this extension has previously published a shared state, shared state will be versioned at the latest
    • + *
    + * @param state {@code Map} representing current state of this extension + * @param event The {@link Event} for which the state is being set. Passing null will set the state for the next shared + * state version + * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred + * @return {@code boolean} indicating if the shared state was successfully set + */ + public abstract boolean createXDMSharedState(final Map state, + final Event event, + final ExtensionErrorCallback errorCallback); + + /** + * Creates a pending XDM shared state for this extension. + *
      + *
    • If this extension has not previously published a shared state, shared state will be versioned at 0
    • + *
    • If this extension has previously published a shared state, shared state will be versioned at the latest
    • + *
    + * @param event The {@link Event} for which pending shared state is being set. Passing null will set the state for the next shared + * state version. + * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred + * @return {@link SharedStateResolver} that should be called with the shared state data when it is ready + */ + public abstract SharedStateResolver createPendingXDMSharedState(final Event event, + final ExtensionErrorCallback errorCallback); + + /** + * Gets the XDM shared state data for a specified extension. If the stateName extension populates multiple mixins in their shared state, all the data will be returned at once and it can be accessed using path discovery. + * @param extensionName extension name for which to retrieve data. See documentation for the list of available states. + * @param event the {@link Event} for which the state is being requested. Passing null will retrieve latest state available. + * @param barrier If true, the {@code EventHub} will only return {@code set} if extensionName has moved past event. + * @param resolution the {@link SharedStateResolution} to resolve for + * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred + * return {@code SharedStateResult} for the requested extensionName and event + */ + public abstract SharedStateResult getXDMSharedState(final String extensionName, + final Event event, + final boolean barrier, + final SharedStateResolution resolution, + final ExtensionErrorCallback errorCallback); + + /** + * Called by extension to clear XDM shared state it has previously set. Usually called during {@code Extension.onUnregistered()}. + * + * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred + * @return {@code boolean} indicating if the shared states were successfully cleared + * @see Extension#onUnregistered() + */ + public abstract boolean clearXDMSharedEventStates(final ExtensionErrorCallback errorCallback); + + /** + * Unregisters current extension. + *
    + * This method executes asynchronously, unregistering the extension on the event hub thread. {@link Extension#onUnregistered} + * method will be called at the end of this operation. + * + * @see Extension#onUnregistered() + */ + public abstract void unregisterExtension(); + + // Deprecated Methods /** * Registers a new event listener for current extension for the provided event type and source. *

    @@ -29,7 +165,9 @@ public abstract class ExtensionApi { * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if any error occurs during registration * @param type of current event listener * @return {@code boolean} indicating the listener registration status + * @deprecated Use {@link ExtensionApi#registerEventListener(String, String, ExtensionEventListener, ExtensionErrorCallback)}} */ + @Deprecated public abstract boolean registerEventListener(final String eventType, final String eventSource, final Class extensionListenerClass, @@ -52,12 +190,14 @@ public abstract boolean registerEventListener(fina * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if any error occurs during registration * @param type of current event listener * @return {@code boolean} indicating the listener registration status + * @deprecated Use {@link ExtensionApi#registerEventListener(String, String, ExtensionEventListener, ExtensionErrorCallback)}} */ + @Deprecated public abstract boolean registerWildcardListener( final Class extensionListenerClass, final ExtensionErrorCallback errorCallback); - /** + /** * Called by extension to set a shared state for itself. Usually called from a listener during event processing. * * @param state {@code Map} representing current state of this extension. Passing null will set the extension's @@ -67,11 +207,12 @@ public abstract boolean registerWildcardListener( * state version. * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred * @return {@code boolean} indicating if the shared state was successfully set + * @deprecated Use {@link ExtensionApi#createSharedState(Map, Event, ExtensionErrorCallback)} and {@link ExtensionApi#createPendingSharedState(Event, ExtensionErrorCallback)} */ + @Deprecated public abstract boolean setSharedEventState(final Map state, final Event event, final ExtensionErrorCallback errorCallback); - /** * Called by extension to set an XDM shared state for itself. Usually called from a listener during event processing. * The state passed to this API needs to be mapped to known XDM mixins. @@ -84,28 +225,12 @@ public abstract boolean setSharedEventState(final Map state, fin * state version. * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred * @return {@code boolean} indicating if the XDM shared state was successfully set + * @deprecated Use {@link ExtensionApi#createXDMSharedState(Map, Event, ExtensionErrorCallback)} and {@link ExtensionApi#createPendingXDMSharedState(Event, ExtensionErrorCallback)} */ + @Deprecated public abstract boolean setXDMSharedEventState(final Map state, final Event event, final ExtensionErrorCallback errorCallback); - /** - * Called by extension to clear all shared state it has previously set. Usually called during {@code Extension.onUnregistered()}. - * - * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred - * @return {@code boolean} indicating if the shared states were successfully cleared - * @see Extension#onUnregistered() - */ - public abstract boolean clearSharedEventStates(final ExtensionErrorCallback errorCallback); - - /** - * Called by extension to clear XDM shared state it has previously set. Usually called during {@code Extension.onUnregistered()}. - * - * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred - * @return {@code boolean} indicating if the shared states were successfully cleared - * @see Extension#onUnregistered() - */ - public abstract boolean clearXDMSharedEventStates(final ExtensionErrorCallback errorCallback); - /** * Called by extension to get another extension's shared state. Usually called from a listener during event processing. * @@ -114,7 +239,9 @@ public abstract boolean setXDMSharedEventState(final Map state, * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred or if {@code stateName} is null * @return {@code Map} containing shared state data at that version. Returns null if state does not exists, * is PENDING, or an error is returned in the {@code errorCallback} + * @deprecated Use {@link ExtensionApi#getSharedState(String, Event, boolean, SharedStateResolution, ExtensionErrorCallback)} */ + @Deprecated public abstract Map getSharedEventState(final String stateName, final Event event, final ExtensionErrorCallback errorCallback); @@ -128,18 +255,9 @@ public abstract Map getSharedEventState(final String stateName, * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred or if {@code stateName} is null * @return {@code Map} containing XDM shared state data at that version. Returns null if state does not exists, * is PENDING, or an error is returned in the {@code errorCallback} + * @deprecated Use {@link ExtensionApi#getXDMSharedState(String, Event, boolean, SharedStateResolution, ExtensionErrorCallback)} */ + @Deprecated public abstract Map getXDMSharedEventState(final String stateName, final Event event, final ExtensionErrorCallback errorCallback); - - - /** - * Unregisters current extension. - *

    - * This method executes asynchronously, unregistering the extension on the event hub thread. {@link Extension#onUnregistered} - * method will be called at the end of this operation. - * - * @see Extension#onUnregistered() - */ - public abstract void unregisterExtension(); } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionEventListener.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionEventListener.java new file mode 100644 index 000000000..c4fc65ed3 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionEventListener.java @@ -0,0 +1,19 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ +package com.adobe.marketing.mobile; + +/** + * Defines a generic listener that can hear a specific kind of {@code Event} on an {@code EventHub} + */ +public interface ExtensionEventListener { + + void hear(final Event event); +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionHelper.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionHelper.java index 7d33fa84b..d10696078 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionHelper.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionHelper.java @@ -10,9 +10,7 @@ public static String getName(Extension extension) { if (extension != null) { return extension.getName(); } - } catch (Exception ex) { - - } + } catch (Exception ex) { } return null; } @@ -21,9 +19,7 @@ public static String getFriendlyName(Extension extension) { if (extension != null) { return extension.getFriendlyName(); } - } catch (Exception ex) { - - } + } catch (Exception ex) { } return null; } @@ -32,19 +28,31 @@ public static String getVersion(Extension extension) { if (extension != null) { return extension.getVersion(); } - } catch (Exception ex) { - - } + } catch (Exception ex) { } return null; } - public static void onUnregistered(Extension extension) { + public static void notifyUnregistered(Extension extension) { try { if (extension != null) { extension.onUnregistered(); } - } catch (Exception ex) { + } catch (Exception ex) { } + } + + public static void notifyRegistered(Extension extension) { + try { + if (extension != null) { + extension.onRegistered(); + } + } catch (Exception ex) { } + } - } + public static void notifyError(Extension extension, ExtensionUnexpectedError error) { + try { + if (extension != null) { + extension.onUnexpectedError(error); + } + } catch (Exception ex) { } } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionListener.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionListener.java index 98d5750ad..89f8cc286 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionListener.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionListener.java @@ -11,14 +11,14 @@ package com.adobe.marketing.mobile; -import com.adobe.marketing.mobile.internal.eventhub.ExtensionRuntime; - +import com.adobe.marketing.mobile.internal.eventhub.ExtensionContainer; /** * Abstract class that defines the {@code Event} listener for an {@code Extension}. * * @author Adobe Systems Incorporated * @version 5.0 */ +@Deprecated public abstract class ExtensionListener { private static final String LOG_TAG = "ExtensionListener"; private final ExtensionApi extensionApi; @@ -55,6 +55,6 @@ public void onUnregistered() { * @return the {@link Extension} registered with the {@link EventHub} */ protected Extension getParentExtension() { - return ((ExtensionRuntime)this.extensionApi).getExtension(); + return ((ExtensionContainer)extensionApi).getExtension(); } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionUnexpectedError.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionUnexpectedError.java index 7d12eae60..7fa7c4325 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionUnexpectedError.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionUnexpectedError.java @@ -19,6 +19,7 @@ * @version 5.0 */ @SuppressWarnings("unused") +@Deprecated public class ExtensionUnexpectedError extends Exception { private static final long serialVersionUID = 1L; private ExtensionError errorCode; diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResolution.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResolution.java new file mode 100644 index 000000000..13ce8908e --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResolution.java @@ -0,0 +1,17 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile; + +public enum SharedStateResolution { + LAST_SET, + ANY +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResolver.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResolver.java new file mode 100644 index 000000000..b3750b8a0 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResolver.java @@ -0,0 +1,18 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile; + +import java.util.Map; + +public interface SharedStateResolver { + void resolve(Map state); +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResult.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResult.java new file mode 100644 index 000000000..eb6d70a32 --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResult.java @@ -0,0 +1,24 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile; + +import java.util.Map; + +public class SharedStateResult { + public final SharedStateStatus status; + public final Map value; + + public SharedStateResult(SharedStateStatus status, Map value) { + this.status = status; + this.value = value; + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateStatus.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateStatus.java new file mode 100644 index 000000000..6ad85711b --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateStatus.java @@ -0,0 +1,18 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile; + +public enum SharedStateStatus { + SET, + PENDING, + NONE +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index 1d07a4794..4c6b0cefa 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -12,6 +12,9 @@ package com.adobe.marketing.mobile.internal.eventhub import androidx.annotation.NonNull +import com.adobe.marketing.mobile.AdobeCallback +import com.adobe.marketing.mobile.AdobeCallbackWithError +import com.adobe.marketing.mobile.AdobeError import com.adobe.marketing.mobile.Event import com.adobe.marketing.mobile.Extension import com.adobe.marketing.mobile.ExtensionError @@ -19,25 +22,43 @@ import com.adobe.marketing.mobile.ExtensionErrorCallback import com.adobe.marketing.mobile.LoggingMode import com.adobe.marketing.mobile.MobileCore import com.adobe.marketing.mobile.util.SerialWorkDispatcher +import java.lang.Exception import java.util.concurrent.Callable import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ExecutorService import java.util.concurrent.Executors +import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger /** * EventHub class is responsible for delivering events to listeners and maintaining registered extension's lifecycle. */ internal class EventHub { - constructor() companion object { const val LOG_TAG = "EventHub" - public var shared = EventHub() + var shared = EventHub() } + /** + * Executor to initialize and shutdown extensions + */ + private val extensionInitExecutor: ExecutorService by lazy { Executors.newCachedThreadPool() } + + /** + * Executor for scheduled response listeners + */ + private val scheduledExecutor: ScheduledExecutorService by lazy { Executors.newSingleThreadScheduledExecutor() } + + /** + * Executor to serialize EventHub operations + */ private val eventHubExecutor: ExecutorService by lazy { Executors.newSingleThreadExecutor() } + private val registeredExtensions: ConcurrentHashMap = ConcurrentHashMap() + private val responseEventListeners: ConcurrentLinkedQueue = ConcurrentLinkedQueue() private val lastEventNumber: AtomicInteger = AtomicInteger(0) private var hubStarted = false @@ -45,18 +66,37 @@ internal class EventHub { * Implementation of [SerialWorkDispatcher.WorkHandler] that is responsible for dispatching * an [Event] "e". Dispatch is regarded complete when [SerialWorkDispatcher.WorkHandler.doWork] finishes for "e". */ - private val dispatchJob: SerialWorkDispatcher.WorkHandler = SerialWorkDispatcher.WorkHandler { + private val dispatchJob: SerialWorkDispatcher.WorkHandler = SerialWorkDispatcher.WorkHandler { event -> // TODO: Perform pre-processing - // TODO: Notify response event listeners + // Handle response event listeners + if (event.responseID != null) { + val matchingResponseListeners = responseEventListeners.filterRemove { listener -> + if (listener.shouldNotify(event)) { + listener.timeoutTask?.cancel(false) + true + } else { + false + } + } + + matchingResponseListeners.forEach { listener -> + listener.notify(event) + } + } + + // Notify to extensions for processing + registeredExtensions.values.forEach { + it.eventProcessor.offer(event) + } - // TODO: Send Event to each Extension Container + // TODO: Record events in event history database. } /** * Responsible for processing and dispatching each event. */ - private val eventDispatcher: SerialWorkDispatcher = SerialWorkDispatcher("EventHub", dispatchJob) + private val eventDispatcher: SerialWorkDispatcher = SerialWorkDispatcher("EventHub", dispatchJob) /** * A cache that maps UUID of an Event to an internal sequence of its dispatch. @@ -129,8 +169,7 @@ internal class EventHub { return@submit } - val executor = Executors.newSingleThreadExecutor() - val container = ExtensionContainer(extensionClass, ExtensionRuntime(), executor, completion) + val container = ExtensionContainer(extensionClass, extensionInitExecutor, completion) registeredExtensions[extensionTypeName] = container } } @@ -155,6 +194,37 @@ internal class EventHub { } } + fun registerResponseListener(triggerEvent: Event, timeoutMS: Long, callback: AdobeCallbackWithError) { + eventHubExecutor.submit { + val triggerEventId = triggerEvent.uniqueIdentifier + val timeoutCallable: Callable = Callable { + responseEventListeners.filterRemove { it.triggerEventId == triggerEventId } + try { + callback.fail(AdobeError.CALLBACK_TIMEOUT) + } catch (ex: Exception) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Exception thrown from ResponseListener. $ex") + } + } + val timeoutTask = + scheduledExecutor.schedule(timeoutCallable, timeoutMS, TimeUnit.MILLISECONDS) + + responseEventListeners.add( + ResponseListenerContainer( + triggerEventId, + timeoutTask, + callback + ) + ) + } + } + + fun registerListener(eventType: String, eventSource: String, listener: AdobeCallback) { + eventHubExecutor.submit { + val eventHubContainer = getExtensionContainer(EventHubPlaceholderExtension::class.java) + eventHubContainer?.registerEventListener(eventType, eventSource, { listener.call(it) }, null) + } + } + /** * Sets the shared state for the extension - [extensionName] with [data] * TODO : Make the [data] parameter immutable when EventData#toImmutableMap() is implemented. @@ -180,7 +250,7 @@ internal class EventHub { val setSharedStateCallable: Callable = Callable { - if (extensionName.isNullOrEmpty() || extensionName.isBlank()) { + if (extensionName.isNullOrBlank()) { MobileCore.log(LoggingMode.ERROR, LOG_TAG, "Unable to set SharedState for extension: [$extensionName]. ExtensionName is invalid.") errorCallback?.error(ExtensionError.BAD_NAME) @@ -289,7 +359,7 @@ internal class EventHub { fun clearSharedState( sharedStateType: SharedStateType, extensionName: String?, - errorCallback: ExtensionErrorCallback? + errorCallback: ExtensionErrorCallback?, ): Boolean { val clearSharedStateCallable: Callable = Callable { if (extensionName.isNullOrEmpty() || extensionName.isBlank()) { @@ -352,6 +422,22 @@ internal class EventHub { } else eventNumberMap[eventUUID] } + /** + * Retrieves a registered [ExtensionContainer] with [extensionClass] provided. + * + * @param [extensionClass] the extension class for which an [ExtensionContainer] should be fetched. + * @return [ExtensionContainer] with [extensionName] provided if one was registered, + * null if no extension is registered with the [extensionName] + */ + internal fun getExtensionContainer(extensionClass: Class): ExtensionContainer? { + val extensionTypeName = extensionClass.extensionTypeName + return if (extensionTypeName != null) { + registeredExtensions[extensionTypeName] + } else { + null + } + } + /** * Retrieves a registered [ExtensionContainer] with [extensionTypeName] provided. * @@ -383,8 +469,15 @@ internal class EventHub { } } -/** - * Helper to get extension type name - */ -private val Class.extensionTypeName - get() = this.name +private fun MutableCollection.filterRemove(predicate: (T) -> Boolean): MutableCollection { + val ret = mutableListOf() + this.removeAll { + if (predicate(it)) { + ret.add(it) + true + } else { + false + } + } + return ret +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventListenerContainer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventListenerContainer.kt new file mode 100644 index 000000000..7b0af5f6f --- /dev/null +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventListenerContainer.kt @@ -0,0 +1,74 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.internal.eventhub + +import com.adobe.marketing.mobile.AdobeCallbackWithError +import com.adobe.marketing.mobile.Event +import com.adobe.marketing.mobile.EventSource +import com.adobe.marketing.mobile.EventType +import com.adobe.marketing.mobile.ExtensionEventListener +import com.adobe.marketing.mobile.LoggingMode +import com.adobe.marketing.mobile.MobileCore +import java.lang.Exception +import java.util.concurrent.ScheduledFuture + +sealed class EventListenerContainer { + abstract fun shouldNotify(event: Event): Boolean + + abstract fun notify(event: Event) +} + +class ResponseListenerContainer( + val triggerEventId: String, + val timeoutTask: ScheduledFuture?, + val listener: AdobeCallbackWithError +) : EventListenerContainer() { + override fun shouldNotify(event: Event): Boolean { + return event.responseID == triggerEventId + } + + override fun notify(event: Event) { + try { + listener.call(event) + } catch (ex: Exception) { + MobileCore.log( + LoggingMode.DEBUG, + "OneTimeListenerContainer", + "Exception thrown for EventId ${event.uniqueIdentifier}. $ex" + ) + } + } +} + +class ExtensionListenerContainer(val eventType: String, val eventSource: String, val listener: ExtensionEventListener) : EventListenerContainer() { + override fun shouldNotify(event: Event): Boolean { + // Wildcard listeners should only be notified of paired response events. + return if (event.responseID != null) { + (eventType == EventType.TYPE_WILDCARD && eventSource == EventSource.TYPE_WILDCARD) + } else { + eventType.equals(event.type, ignoreCase = true) && eventSource.equals(event.source, ignoreCase = true) || + eventType == EventType.TYPE_WILDCARD && eventSource == EventSource.TYPE_WILDCARD + } + } + + override fun notify(event: Event) { + try { + listener.hear(event) + } catch (ex: Exception) { + MobileCore.log( + LoggingMode.DEBUG, + "ExtensionListenerContainer", + "Exception thrown for EventId ${event.uniqueIdentifier}. $ex" + ) + } + } +} diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt index 72d5da218..92626a17f 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt @@ -12,209 +12,108 @@ package com.adobe.marketing.mobile.internal.eventhub import com.adobe.marketing.mobile.Event +import com.adobe.marketing.mobile.EventSource +import com.adobe.marketing.mobile.EventType import com.adobe.marketing.mobile.Extension import com.adobe.marketing.mobile.ExtensionApi import com.adobe.marketing.mobile.ExtensionError import com.adobe.marketing.mobile.ExtensionErrorCallback +import com.adobe.marketing.mobile.ExtensionEventListener import com.adobe.marketing.mobile.ExtensionListener import com.adobe.marketing.mobile.LoggingMode import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.SharedStateResolution +import com.adobe.marketing.mobile.SharedStateResolver +import com.adobe.marketing.mobile.SharedStateResult +import com.adobe.marketing.mobile.util.SerialWorkDispatcher import java.util.concurrent.Callable +import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ExecutorService import kotlin.Exception -internal class ExtensionRuntime() : ExtensionApi() { +internal class ExtensionContainer constructor( + private val extensionClass: Class, + private val taskExecutor: ExecutorService, + callback: (EventHubError) -> Unit +) : ExtensionApi() { companion object { - const val LOG_TAG = "ExtensionApi" + const val LOG_TAG = "ExtensionContainer" } - var extension: Extension? = null - set(value) { - field = value - extensionName = value?.extensionName - extensionFriendlyName = value?.extensionFriendlyName - extensionVersion = value?.extensionVersion - } - - // Fetch these values on initialization - var extensionName: String? = null - private set - var extensionFriendlyName: String? = null - private set - var extensionVersion: String? = null + var sharedStateName: String? = null private set - override fun registerEventListener( - eventType: String?, - eventSource: String?, - extensionListenerClass: Class?, - errorCallback: ExtensionErrorCallback? - ): Boolean { - return false - } - - override fun registerWildcardListener( - extensionListenerClass: Class?, - errorCallback: ExtensionErrorCallback? - ): Boolean { - return false - } - - override fun setSharedEventState( - state: MutableMap?, - event: Event?, - errorCallback: ExtensionErrorCallback? - ): Boolean { - try { - return EventHub.shared.setSharedState(SharedStateType.STANDARD, extensionName, state, event, errorCallback) - } catch (exception: Exception) { - MobileCore.log( - LoggingMode.ERROR, getTag(), - "Failed to set shared state at EventID: ${event?.uniqueIdentifier}. $exception" - ) - errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) - } - - return false - } - - override fun setXDMSharedEventState( - state: MutableMap?, - event: Event?, - errorCallback: ExtensionErrorCallback? - ): Boolean { - try { - // TODO : Convert the [state] parameter to be immutable before propagating when EventData#toImmutableMap() is implemented. - return EventHub.shared.setSharedState(SharedStateType.XDM, extensionName, state, event, errorCallback) - } catch (exception: Exception) { - MobileCore.log( - LoggingMode.ERROR, getTag(), - "Failed to set XDM shared state at EventID: ${event?.uniqueIdentifier}. $exception" - ) - errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) - } - - return false - } + var friendlyName: String? = null + private set - override fun clearSharedEventStates(errorCallback: ExtensionErrorCallback?): Boolean { - try { - EventHub.shared.clearSharedState(SharedStateType.STANDARD, extensionName, errorCallback) - } catch (exception: Exception) { - MobileCore.log(LoggingMode.ERROR, getTag(), "Failed to clear shared state. $exception") - errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) - } + var version: String? = null + private set - return false - } + var lastProcessedEvent: Event? = null + private set - override fun clearXDMSharedEventStates(errorCallback: ExtensionErrorCallback?): Boolean { - try { - EventHub.shared.clearSharedState(SharedStateType.XDM, extensionName, errorCallback) - } catch (exception: Exception) { - MobileCore.log(LoggingMode.ERROR, getTag(), "Failed to clear XDM shared state. $exception") - errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) - } + var extension: Extension? = null + private set - return false - } + private var sharedStateManagers: Map? = null + private val eventListeners: ConcurrentLinkedQueue = ConcurrentLinkedQueue() - override fun getSharedEventState( - stateName: String?, - event: Event?, - errorCallback: ExtensionErrorCallback? - ): Map? { - try { - return EventHub.shared.getSharedState(SharedStateType.STANDARD, stateName, event, errorCallback) - } catch (exception: Exception) { - MobileCore.log( - LoggingMode.ERROR, getTag(), - "Failed to get shared state at EventID: ${event?.uniqueIdentifier}. $exception" - ) - errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) + /** + * Implementation of [SerialWorkDispatcher.WorkHandler] that is responsible for dispatching + * an [Event] "e". Dispatch is regarded complete when [SerialWorkDispatcher.WorkHandler.doWork] finishes for "e". + */ + private val dispatchJob: SerialWorkDispatcher.WorkHandler = SerialWorkDispatcher.WorkHandler { event -> + if (extension?.readyForEvent(event) != true) { + return@WorkHandler } - return null - } - override fun getXDMSharedEventState( - stateName: String?, - event: Event?, - errorCallback: ExtensionErrorCallback? - ): Map? { - try { - return EventHub.shared.getSharedState(SharedStateType.STANDARD, stateName, event, errorCallback) - } catch (exception: Exception) { - MobileCore.log( - LoggingMode.ERROR, getTag(), - "Failed to get XDM shared state at EventID: ${event?.uniqueIdentifier}. $exception" - ) - errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) + eventListeners.forEach { + if (it.shouldNotify(event)) { + it.notify(event) + } } - return null - } - - override fun unregisterExtension() { - if (extension == null) { - return - } - EventHub.shared.unregisterExtension(extension?.javaClass) {} + lastProcessedEvent = event } - private fun getTag(): String { - if (extension == null) { - return LOG_TAG - } - return "$extensionName-$extensionVersion" - } -} - -internal class ExtensionContainer constructor( - private val extensionClass: Class, - private val extensionRuntime: ExtensionRuntime, - private val taskExecutor: ExecutorService, - callback: (EventHubError) -> Unit -) { - - val sharedStateName: String? - get() = extensionRuntime.extensionName - - val friendlyName: String? - get() = extensionRuntime.extensionFriendlyName - - val version: String? - get() = extensionRuntime.extensionVersion - - private val sharedStateManagers: Map = mapOf( - SharedStateType.XDM to SharedStateManager(sharedStateName ?: ""), - SharedStateType.STANDARD to SharedStateManager(sharedStateName ?: "") - ) + val eventProcessor: SerialWorkDispatcher = SerialWorkDispatcher(extensionClass.extensionTypeName, dispatchJob) init { taskExecutor.submit { - val extension = extensionClass.initWith(extensionRuntime) + val extension = extensionClass.initWith(this) if (extension == null) { callback(EventHubError.ExtensionInitializationFailure) return@submit } - if (extension.extensionName == null) { + val extensionName = extension.extensionName + if (extensionName.isNullOrBlank()) { callback(EventHubError.InvalidExtensionName) return@submit } - // We set this circular reference because ExtensionApi exposes an API to get the underlying extension. - extensionRuntime.extension = extension + this.extension = extension + sharedStateName = extensionName + friendlyName = extension.extensionFriendlyName + version = extension.extensionVersion + sharedStateManagers = mapOf( + SharedStateType.XDM to SharedStateManager(extensionName), + SharedStateType.STANDARD to SharedStateManager(extensionName) + ) + eventProcessor.start() callback(EventHubError.None) + + // Notify that the extension is registered + extension.onExtensionRegistered() } } fun shutdown() { taskExecutor.run { - extensionRuntime.extension?.onExtensionUnregistered() + eventProcessor.shutdown() + extension?.onExtensionUnregistered() } - taskExecutor.shutdown() } /** @@ -234,11 +133,9 @@ internal class ExtensionContainer constructor( data: MutableMap?, version: Int ): SharedState.Status { - if (taskExecutor.isShutdown) return SharedState.Status.NOT_SET - return taskExecutor.submit( Callable { - val stateManager: SharedStateManager = sharedStateManagers[sharedStateType] + val stateManager: SharedStateManager = sharedStateManagers?.get(sharedStateType) ?: return@Callable SharedState.Status.NOT_SET // Existing public API infers a pending state as one with no data @@ -271,7 +168,7 @@ internal class ExtensionContainer constructor( return taskExecutor.submit( Callable { - sharedStateManagers[sharedStateType]?.clearSharedState() + sharedStateManagers?.get(sharedStateType)?.clearSharedState() return@Callable true } ).get() @@ -294,8 +191,181 @@ internal class ExtensionContainer constructor( return taskExecutor.submit( Callable { - return@Callable sharedStateManagers[sharedStateType]?.getSharedState(version) + return@Callable sharedStateManagers?.get(sharedStateType)?.getSharedState(version) } ).get() } + + private fun getTag(): String { + if (extension == null) { + return LOG_TAG + } + return "$sharedStateName-$version" + } + + // Override ExtensionApi Methods + override fun registerEventListener( + eventType: String?, + eventSource: String?, + eventListener: ExtensionEventListener?, + errorCallback: ExtensionErrorCallback? + ): Boolean { + + if (eventType == null) { + errorCallback?.error(ExtensionError.EVENT_TYPE_NOT_SUPPORTED) + return false + } + + if (eventSource == null) { + errorCallback?.error(ExtensionError.EVENT_SOURCE_NOT_SUPPORTED) + return false + } + + if (eventListener == null) { + errorCallback?.error(ExtensionError.CALLBACK_NULL) + return false + } + + eventListeners.add(ExtensionListenerContainer(eventType, eventSource, eventListener)) + return true + } + + override fun createSharedState( + state: MutableMap?, + event: Event?, + errorCallback: ExtensionErrorCallback?, + ): Boolean { + TODO("Not yet implemented") + } + + override fun createPendingSharedState( + event: Event?, + errorCallback: ExtensionErrorCallback?, + ): SharedStateResolver? { + TODO("Not yet implemented") + } + + override fun getSharedState( + extensionName: String?, + event: Event?, + barrier: Boolean, + resolution: SharedStateResolution?, + ): SharedStateResult { + TODO("Not yet implemented") + } + + override fun clearSharedEventStates(errorCallback: ExtensionErrorCallback?): Boolean { + TODO("Not yet implemented") + } + + override fun createXDMSharedState( + state: MutableMap?, + event: Event?, + errorCallback: ExtensionErrorCallback?, + ): Boolean { + TODO("Not yet implemented") + } + + override fun createPendingXDMSharedState( + event: Event?, + errorCallback: ExtensionErrorCallback?, + ): SharedStateResolver? { + TODO("Not yet implemented") + } + + override fun getXDMSharedState( + extensionName: String?, + event: Event?, + barrier: Boolean, + resolution: SharedStateResolution?, + ): SharedStateResult { + TODO("Not yet implemented") + } + + override fun clearXDMSharedEventStates(errorCallback: ExtensionErrorCallback?): Boolean { + TODO("Not yet implemented") + } + + override fun unregisterExtension() { + TODO("Not yet implemented") + } + + // Deprecated ExtensionApi methods + override fun setSharedEventState( + state: MutableMap?, + event: Event?, + errorCallback: ExtensionErrorCallback?, + ): Boolean { + try { + return EventHub.shared.setSharedState(SharedStateType.STANDARD, sharedStateName, state, event, errorCallback) + } catch (exception: Exception) { + MobileCore.log( + LoggingMode.ERROR, getTag(), + "Failed to set shared state at EventID: ${event?.uniqueIdentifier}. $exception" + ) + errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) + } + + return false + } + + override fun setXDMSharedEventState( + state: MutableMap?, + event: Event?, + errorCallback: ExtensionErrorCallback?, + ): Boolean { + try { + return EventHub.shared.setSharedState(SharedStateType.XDM, sharedStateName, state, event, errorCallback) + } catch (exception: Exception) { + MobileCore.log( + LoggingMode.ERROR, getTag(), + "Failed to set shared state at EventID: ${event?.uniqueIdentifier}. $exception" + ) + errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) + } + + return false + } + + override fun getSharedEventState( + stateName: String?, + event: Event?, + errorCallback: ExtensionErrorCallback?, + ): MutableMap { + TODO("Not yet implemented") + } + + override fun getXDMSharedEventState( + stateName: String?, + event: Event?, + errorCallback: ExtensionErrorCallback?, + ): MutableMap { + TODO("Not yet implemented") + } + + override fun registerEventListener( + eventType: String?, + eventSource: String?, + extensionListenerClass: Class?, + errorCallback: ExtensionErrorCallback?, + ): Boolean { + val extensionListener = extensionListenerClass?.initWith(this, eventType, eventSource) + if (extensionListener == null) { + errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) + return false + } + return registerEventListener(eventType, eventSource, { extensionListener.hear(it) }, errorCallback) + } + + override fun registerWildcardListener( + extensionListenerClass: Class?, + errorCallback: ExtensionErrorCallback?, + ): Boolean { + val extensionListener = extensionListenerClass?.initWith(this, EventType.TYPE_WILDCARD, EventSource.TYPE_WILDCARD) + if (extensionListener == null) { + errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) + return false + } + return registerEventListener(EventType.TYPE_WILDCARD, EventSource.TYPE_WILDCARD, { extensionListener.hear(it) }, errorCallback) + } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt index b4652ef1b..7e7b6e436 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionExt.kt @@ -15,6 +15,7 @@ import com.adobe.marketing.mobile.Extension import com.adobe.marketing.mobile.ExtensionApi import com.adobe.marketing.mobile.ExtensionHelper import com.adobe.marketing.mobile.ExtensionListener +import com.adobe.marketing.mobile.ExtensionUnexpectedError import com.adobe.marketing.mobile.LoggingMode import com.adobe.marketing.mobile.MobileCore import java.lang.Exception @@ -58,19 +59,45 @@ internal val Extension.extensionFriendlyName: String? * Function to notify that the Extension has been unregistered */ internal fun Extension.onExtensionUnregistered() { - ExtensionHelper.onUnregistered(this) + ExtensionHelper.notifyUnregistered(this) } +/** + * Function to notify that the Extension has been registered + */ +internal fun Extension.onExtensionRegistered() { + ExtensionHelper.notifyRegistered(this) +} + +/** + * Function to notify that an unexpected error occurred when initializing the extension + */ +internal fun Extension.onExtensionUnexpectedError(error: ExtensionUnexpectedError) { + ExtensionHelper.notifyError(this, error) +} + +/** + * Helper to get extension type name + */ +internal val Class.extensionTypeName + get() = this.name + // Type extensions for [ExtensionListener] to allow for easier usage /** * Function to initialize ExtensionListener with [ExtensionApi], type and source. */ -internal fun Class.initWith(extensionApi: ExtensionApi, type: String, source: String): ExtensionListener? { +internal fun Class.initWith(extensionApi: ExtensionApi, type: String?, source: String?): ExtensionListener? { try { - val extensionListenerConstructor = this.getDeclaredConstructor(ExtensionApi::class.java, String::class.java, String::class.java) - extensionListenerConstructor.setAccessible(true) - return extensionListenerConstructor.newInstance(extensionApi, type, source) + if (type != null && source != null) { + val extensionListenerConstructor = this.getDeclaredConstructor( + ExtensionApi::class.java, + String::class.java, + String::class.java + ) + extensionListenerConstructor.setAccessible(true) + return extensionListenerConstructor.newInstance(extensionApi, type, source) + } } catch (ex: Exception) { MobileCore.log(LoggingMode.DEBUG, "Extension", "Initializing Extension $this failed with $ex") } diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt index 772c32f67..00eee6dfa 100644 --- a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt @@ -11,10 +11,15 @@ package com.adobe.marketing.mobile.internal.eventhub +import com.adobe.marketing.mobile.AdobeCallbackWithError +import com.adobe.marketing.mobile.AdobeError import com.adobe.marketing.mobile.Event +import com.adobe.marketing.mobile.EventSource +import com.adobe.marketing.mobile.EventType import com.adobe.marketing.mobile.Extension import com.adobe.marketing.mobile.ExtensionApi import com.adobe.marketing.mobile.ExtensionError +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -86,6 +91,7 @@ private object MockExtensions { @RunWith(PowerMockRunner::class) internal class EventHubTests { + private lateinit var eventHub: EventHub private val eventType = "Type" private val eventSource = "Source" private val event1: Event = Event.Builder("Event1", eventType, eventSource).build() @@ -96,7 +102,7 @@ internal class EventHubTests { var ret: EventHubError = EventHubError.Unknown val latch = CountDownLatch(1) - EventHub.shared.registerExtension(extensionClass) { error -> + eventHub.registerExtension(extensionClass) { error -> ret = error latch.countDown() } @@ -108,7 +114,7 @@ internal class EventHubTests { var ret: EventHubError = EventHubError.Unknown val latch = CountDownLatch(1) - EventHub.shared.unregisterExtension(extensionClass) { error -> + eventHub.unregisterExtension(extensionClass) { error -> ret = error latch.countDown() } @@ -118,13 +124,18 @@ internal class EventHubTests { @Before fun setup() { - EventHub.shared.shutdown() - EventHub.shared = EventHub() + eventHub = EventHub() + registerExtension(MockExtensions.TestExtension::class.java) + } + + @After + fun teardown() { + eventHub.shutdown() } + // Register, Unregister tests @Test fun testRegisterExtensionSuccess() { - var ret = registerExtension(MockExtension::class.java) assertEquals(EventHubError.None, ret) @@ -183,18 +194,18 @@ internal class EventHubTests { assertEquals(EventHubError.None, ret) } + // Shared state tests @Test fun testSetSharedState_NullOrEmptyExtensionName() { var result: ExtensionError? = null - registerExtension(MockExtensions.TestExtension::class.java) - EventHub.shared.dispatch(event1) // Dispatch Event1 + eventHub.dispatch(event1) // Dispatch Event1 val stateAtEvent1: MutableMap = mutableMapOf("One" to 1, "Yes" to true) // Set state at event1 with null extension name assertFalse( - EventHub.shared.setSharedState( + eventHub.setSharedState( SharedStateType.STANDARD, null, stateAtEvent1, event1 ) { @@ -205,7 +216,7 @@ internal class EventHubTests { // Set state at event1 with empty extension name assertFalse( - EventHub.shared.setSharedState( + eventHub.setSharedState( SharedStateType.STANDARD, "", stateAtEvent1, event1 ) { @@ -217,15 +228,15 @@ internal class EventHubTests { @Test fun testSetSharedState_ExtensionNotRegistered() { - EventHub.shared.dispatch(event1) // Dispatch Event1 + eventHub = EventHub() + eventHub.dispatch(event1) // Dispatch Event1 val stateAtEvent1: MutableMap = mutableMapOf("One" to 1, "Yes" to true) var result: ExtensionError? = null - // Set state at event1 assertFalse( - EventHub.shared.setSharedState( + eventHub.setSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, stateAtEvent1, event1 ) { @@ -237,12 +248,11 @@ internal class EventHubTests { @Test fun testSetSharedState_PendingState() { - registerExtension(MockExtensions.TestExtension::class.java) - EventHub.shared.dispatch(event1) // Dispatch Event1 + eventHub.dispatch(event1) // Dispatch Event1 // Set state at event1 assertTrue( - EventHub.shared.setSharedState( + eventHub.setSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, null, event1 ) { @@ -253,12 +263,12 @@ internal class EventHubTests { @Test fun testSetSharedState_OverwritePendingStateWithNonPendingState() { - registerExtension(MockExtensions.TestExtension::class.java) - EventHub.shared.dispatch(event1) // Dispatch Event1 + + eventHub.dispatch(event1) // Dispatch Event1 // Set pending state at event1 assertTrue( - EventHub.shared.setSharedState( + eventHub.setSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, null, event1 ) { @@ -269,7 +279,7 @@ internal class EventHubTests { val stateAtEvent: MutableMap = mutableMapOf("One" to 1, "Yes" to true) assertTrue( - EventHub.shared.setSharedState( + eventHub.setSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, stateAtEvent, event1 ) { @@ -280,13 +290,13 @@ internal class EventHubTests { @Test fun testSetSharedState_NoPendingStateAtEvent() { - registerExtension(MockExtensions.TestExtension::class.java) - EventHub.shared.dispatch(event1) // Dispatch Event1 + + eventHub.dispatch(event1) // Dispatch Event1 // Set non pending state at event1 val stateAtEvent1: MutableMap = mutableMapOf("One" to 1, "Yes" to true) assertTrue( - EventHub.shared.setSharedState( + eventHub.setSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, stateAtEvent1, event1 ) { @@ -298,7 +308,7 @@ internal class EventHubTests { val overwriteState: MutableMap = mutableMapOf("Two" to 2, "No" to false) assertFalse( - EventHub.shared.setSharedState( + eventHub.setSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, overwriteState, event1 ) { @@ -309,13 +319,13 @@ internal class EventHubTests { @Test fun testSetSharedState_OverwriteNonPendingStateWithPendingState() { - registerExtension(MockExtensions.TestExtension::class.java) - EventHub.shared.dispatch(event1) // Dispatch Event1 + + eventHub.dispatch(event1) // Dispatch Event1 // Set non pending state at Event 1 val stateAtEvent1: MutableMap = mutableMapOf("One" to 1, "Yes" to true) assertTrue( - EventHub.shared.setSharedState( + eventHub.setSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, stateAtEvent1, event1 ) { @@ -327,7 +337,7 @@ internal class EventHubTests { val overwriteState: MutableMap? = null assertFalse( - EventHub.shared.setSharedState( + eventHub.setSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, overwriteState, event1 ) { @@ -341,7 +351,7 @@ internal class EventHubTests { var result: ExtensionError? = null // Get state at event1 with null extension name - EventHub.shared.getSharedState( + eventHub.getSharedState( SharedStateType.STANDARD, null, event1 ) { @@ -350,7 +360,7 @@ internal class EventHubTests { assertEquals(result, ExtensionError.BAD_NAME) // Get state at event1 with empty extension name - EventHub.shared.getSharedState( + eventHub.getSharedState( SharedStateType.STANDARD, "", event1 ) { result = it @@ -362,8 +372,9 @@ internal class EventHubTests { fun testGetSharedState_ExtensionNotRegistered() { var result: ExtensionError? = null + eventHub = EventHub() // Set state at event1 - EventHub.shared.getSharedState( + eventHub.getSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, event1 ) { result = it @@ -373,16 +384,16 @@ internal class EventHubTests { @Test fun testGetSharedState_NoStateExistsYet() { - registerExtension(MockExtensions.TestExtension::class.java) + val errorCallback: (ExtensionError) -> Unit = { fail("Test failed ${it.errorCode} - ${it.errorName}") } // Dispatch Event 1 - EventHub.shared.dispatch(event1) + eventHub.dispatch(event1) assertNull( - EventHub.shared.getSharedState( + eventHub.getSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, event1, errorCallback ) @@ -391,18 +402,18 @@ internal class EventHubTests { @Test fun testGetSharedState_StateExistsAtVersion() { - registerExtension(MockExtensions.TestExtension::class.java) + val errorCallback: (ExtensionError) -> Unit = { fail("Test failed ${it.errorCode} - ${it.errorName}") } // Dispatch event1 - EventHub.shared.dispatch(event1) + eventHub.dispatch(event1) // Set non pending state at event1 val stateAtEvent1: MutableMap = mutableMapOf("One" to 1, "Yes" to true) assertTrue( - EventHub.shared.setSharedState( + eventHub.setSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, stateAtEvent1, event1 ) { @@ -411,12 +422,12 @@ internal class EventHubTests { ) // Dispatch event2 - EventHub.shared.dispatch(event2) + eventHub.dispatch(event2) // Set state at event2 val stateAtEvent2: MutableMap = mutableMapOf("Two" to 1, "No" to false) assertTrue( - EventHub.shared.setSharedState( + eventHub.setSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, stateAtEvent2, event2, errorCallback ) @@ -425,14 +436,14 @@ internal class EventHubTests { // Verify that the state at event1 and event2 assertEquals( stateAtEvent1, - EventHub.shared.getSharedState( + eventHub.getSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, event1, errorCallback ) ) assertEquals( stateAtEvent2, - EventHub.shared.getSharedState( + eventHub.getSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, event2, errorCallback ) @@ -441,19 +452,19 @@ internal class EventHubTests { @Test fun testGetSharedState_PreviousStateDoesNotExist() { - registerExtension(MockExtensions.TestExtension::class.java) + val errorCallback: (ExtensionError) -> Unit = { fail("Test failed ${it.errorCode} - ${it.errorName}") } // Dispatch event 1 & event2 - EventHub.shared.dispatch(event1) - EventHub.shared.dispatch(event2) + eventHub.dispatch(event1) + eventHub.dispatch(event2) // Set state at event2 val stateAtEvent2: MutableMap = mutableMapOf("One" to 1, "Yes" to true) assertTrue( - EventHub.shared.setSharedState( + eventHub.setSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, stateAtEvent2, event2, errorCallback ) @@ -461,14 +472,14 @@ internal class EventHubTests { // Verify that the state at event1 is still null assertNull( - EventHub.shared.getSharedState( + eventHub.getSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, event1, errorCallback ) ) assertEquals( stateAtEvent2, - EventHub.shared.getSharedState( + eventHub.getSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, event2, errorCallback ) @@ -477,18 +488,18 @@ internal class EventHubTests { @Test fun testGetSharedState_FetchesLatestStateOnNullEvent() { - registerExtension(MockExtensions.TestExtension::class.java) + val errorCallback: (ExtensionError) -> Unit = { fail("Test failed ${it.errorCode} - ${it.errorName}") } // Dispatch event1 - EventHub.shared.dispatch(event1) + eventHub.dispatch(event1) // Set state at event1 val state: MutableMap = mutableMapOf("One" to 1, "Yes" to true) assertTrue( - EventHub.shared.setSharedState( + eventHub.setSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, state, event1, errorCallback ) @@ -496,7 +507,7 @@ internal class EventHubTests { assertEquals( state, - EventHub.shared.getSharedState( + eventHub.getSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, null, errorCallback ) @@ -505,37 +516,37 @@ internal class EventHubTests { @Test fun testGetSharedState_OlderStateExists() { - registerExtension(MockExtensions.TestExtension::class.java) + val errorCallback: (ExtensionError) -> Unit = { fail("Test failed ${it.errorCode} - ${it.errorName}") } // Dispatch event1 - EventHub.shared.dispatch(event1) + eventHub.dispatch(event1) // Set state at event1 val stateAtEvent1: MutableMap = mutableMapOf("One" to 1, "Yes" to true) assertTrue( - EventHub.shared.setSharedState( + eventHub.setSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, stateAtEvent1, event1, errorCallback ) ) // Dispatch event2 - EventHub.shared.dispatch(event2) + eventHub.dispatch(event2) // Verify that the state at event1 and event2 are the same and they equal [stateAtEvent1] assertEquals( stateAtEvent1, - EventHub.shared.getSharedState( + eventHub.getSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, event1, errorCallback ) ) assertEquals( stateAtEvent1, - EventHub.shared.getSharedState( + eventHub.getSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, event2, errorCallback ) @@ -546,7 +557,7 @@ internal class EventHubTests { fun testClearSharedState_NullOrEmptyExtensionName() { var result: ExtensionError? = null assertFalse( - EventHub.shared.clearSharedState( + eventHub.clearSharedState( SharedStateType.STANDARD, null ) { @@ -556,7 +567,7 @@ internal class EventHubTests { assertEquals(result, ExtensionError.BAD_NAME) assertFalse( - EventHub.shared.clearSharedState( + eventHub.clearSharedState( SharedStateType.STANDARD, "" ) { result = it @@ -568,8 +579,9 @@ internal class EventHubTests { @Test fun testClearSharedState_ExtensionNotRegistered() { var result: ExtensionError? = null + eventHub = EventHub() assertFalse( - EventHub.shared.clearSharedState( + eventHub.clearSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, ) { result = it @@ -581,10 +593,9 @@ internal class EventHubTests { @Test fun testClearSharedState_NoStateYet() { - registerExtension(MockExtensions.TestExtension::class.java) assertTrue( - EventHub.shared.clearSharedState( + eventHub.clearSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, ) { fail("State should have been cleared successfully") @@ -594,16 +605,16 @@ internal class EventHubTests { @Test fun testClearSharedState() { - registerExtension(MockExtensions.TestExtension::class.java) + val errorCallback: (ExtensionError) -> Unit = { fail("Test failed ${it.errorCode} - ${it.errorName}") } - EventHub.shared.dispatch(event1) - EventHub.shared.dispatch(event2) + eventHub.dispatch(event1) + eventHub.dispatch(event2) val stateAtEvent1: MutableMap = mutableMapOf("One" to 1, "Yes" to true) assertTrue( - EventHub.shared.setSharedState( + eventHub.setSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, stateAtEvent1, event1, errorCallback ) @@ -611,7 +622,7 @@ internal class EventHubTests { val stateAtEvent2: MutableMap = mutableMapOf("Twi" to 2, "No" to false) assertTrue( - EventHub.shared.setSharedState( + eventHub.setSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, stateAtEvent2, event2, errorCallback ) @@ -619,19 +630,19 @@ internal class EventHubTests { // Verify that all the states are cleared assertTrue( - EventHub.shared.clearSharedState( + eventHub.clearSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, errorCallback ) ) assertNull( - EventHub.shared.getSharedState( + eventHub.getSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, event1, errorCallback ) ) assertNull( - EventHub.shared.getSharedState( + eventHub.getSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, event2, errorCallback ) @@ -640,18 +651,18 @@ internal class EventHubTests { @Test fun testClearSharedState_DifferentStateType() { - registerExtension(MockExtensions.TestExtension::class.java) + val errorCallback: (ExtensionError) -> Unit = { fail("Test failed ${it.errorCode} - ${it.errorName}") } - EventHub.shared.dispatch(event1) + eventHub.dispatch(event1) val stateAtEvent1: MutableMap = mutableMapOf("One" to 1, "Yes" to true) val xdmStateAtEvent1: MutableMap = mutableMapOf("Two" to 1, "No" to false) // Set Standard shared state assertTrue( - EventHub.shared.setSharedState( + eventHub.setSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, stateAtEvent1, event1, errorCallback ) @@ -659,7 +670,7 @@ internal class EventHubTests { // Set Standard XDM shared state assertTrue( - EventHub.shared.setSharedState( + eventHub.setSharedState( SharedStateType.XDM, MockExtensions.TestExtension.extensionName, xdmStateAtEvent1, event1, errorCallback ) @@ -667,7 +678,7 @@ internal class EventHubTests { // Set Standard Standard shared state assertTrue( - EventHub.shared.clearSharedState( + eventHub.clearSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, errorCallback ) @@ -675,17 +686,400 @@ internal class EventHubTests { // Verify that only standard state is cleared. assertNull( - EventHub.shared.getSharedState( + eventHub.getSharedState( SharedStateType.STANDARD, MockExtensions.TestExtension.extensionName, event1, errorCallback ) ) assertEquals( xdmStateAtEvent1, - EventHub.shared.getSharedState( + eventHub.getSharedState( SharedStateType.XDM, MockExtensions.TestExtension.extensionName, event1, errorCallback ) ) } + + // Event listener tests + @Test + fun testExtensionListener() { + val latch = CountDownLatch(1) + val testEvent = Event.Builder("Sample event", eventType, eventSource).build() + + val extensionContainer = eventHub.getExtensionContainer(MockExtensions.TestExtension::class.java) + extensionContainer?.registerEventListener( + eventType, eventSource, + { + assertTrue { it == testEvent } + latch.countDown() + }, + null + ) + + eventHub.start() + eventHub.dispatch(testEvent) + + assertTrue { + latch.await(250, TimeUnit.MILLISECONDS) + } + } + + @Test + fun testExtensionListener_UnmatchedEvent() { + val latch = CountDownLatch(1) + val testEvent = Event.Builder("Test event", eventType, eventSource).build() + + val extensionContainer = eventHub.getExtensionContainer(MockExtensions.TestExtension::class.java) + extensionContainer?.registerEventListener( + "customEventType", "customEventSource", + { + latch.countDown() + }, + null + ) + + eventHub.start() + eventHub.dispatch(testEvent) + + assertFalse { + latch.await(250, TimeUnit.MILLISECONDS) + } + } + + @Test + fun testExtensionListener_NeverDispatchEventsWithoutStart() { + val latch = CountDownLatch(1) + val testEvent = Event.Builder("Test event", eventType, eventSource).build() + + val extensionContainer = eventHub.getExtensionContainer(MockExtensions.TestExtension::class.java) + extensionContainer?.registerEventListener( + eventType, eventSource, + { + latch.countDown() + }, + null + ) + + eventHub.dispatch(testEvent) + + assertFalse { + latch.await(250, TimeUnit.MILLISECONDS) + } + } + + @Test + fun testExtensionListener_QueuesEventsBeforeStart() { + val latch = CountDownLatch(2) + val testEvent = Event.Builder("Test event", eventType, eventSource).build() + + val extensionContainer = eventHub.getExtensionContainer(MockExtensions.TestExtension::class.java) + extensionContainer?.registerEventListener( + eventType, eventSource, + { + assertTrue { it == testEvent } + latch.countDown() + }, + null + ) + + eventHub.dispatch(testEvent) + eventHub.dispatch(testEvent) + eventHub.start() + assertTrue { + latch.await(250, TimeUnit.MILLISECONDS) + } + } + + @Test + fun testExtensionListener_IgnoresNonMatchingEvent() { + val latch = CountDownLatch(1) + val testEvent = Event.Builder("Test event", eventType, eventSource).build() + val testEvent1 = Event.Builder("Test event 2", "customEventType", "customEventSource").build() + + val extensionContainer = eventHub.getExtensionContainer(MockExtensions.TestExtension::class.java) + extensionContainer?.registerEventListener( + eventType, eventSource, + { + assertTrue { it == testEvent } + latch.countDown() + }, + null + ) + + eventHub.dispatch(testEvent) + eventHub.dispatch(testEvent1) + eventHub.start() + assertTrue { + latch.await(250, TimeUnit.MILLISECONDS) + } + } + + @Test + fun testRegisterListener_DispatchesEventToListener() { + val latch = CountDownLatch(1) + val testEvent = Event.Builder("Sample event", eventType, eventSource).build() + + eventHub.registerListener(eventType, eventSource) { + assertTrue { it == testEvent } + latch.countDown() + } + + eventHub.start() + eventHub.dispatch(testEvent) + + assertTrue { + latch.await(250, TimeUnit.MILLISECONDS) + } + } + + @Test + fun testRegisterListener_NotInvokeListenerWrongType() { + val latch = CountDownLatch(1) + val testEvent = Event.Builder("Test event", eventType, eventSource).build() + + eventHub.registerListener("customEventType", "customEventSource") { + latch.countDown() + } + + eventHub.start() + eventHub.dispatch(testEvent) + + assertFalse { + latch.await(250, TimeUnit.MILLISECONDS) + } + } + + @Test + fun testRegisterListener_NeverDispatchEventsWithoutStart() { + val latch = CountDownLatch(1) + val testEvent = Event.Builder("Test event", eventType, eventSource).build() + + eventHub.registerListener(eventType, eventSource) { + latch.countDown() + } + + eventHub.dispatch(testEvent) + + assertFalse { + latch.await(250, TimeUnit.MILLISECONDS) + } + } + + @Test + fun testRegisterListener_QueuesEventsBeforeStart() { + val latch = CountDownLatch(2) + val testEvent = Event.Builder("Test event", eventType, eventSource).build() + + eventHub.registerListener(eventType, eventSource) { + assertTrue { it == testEvent } + latch.countDown() + } + + eventHub.dispatch(testEvent) + eventHub.dispatch(testEvent) + eventHub.start() + assertTrue { + latch.await(250, TimeUnit.MILLISECONDS) + } + } + + @Test + fun testRegisterListener_IgnoresNonMatchingEvent() { + val latch = CountDownLatch(1) + val testEvent = Event.Builder("Test event", eventType, eventSource).build() + val testEvent1 = Event.Builder("Test event 2", "customEventType", "customEventSource").build() + + eventHub.registerListener(eventType, eventSource) { + assertTrue { it == testEvent } + latch.countDown() + } + + eventHub.dispatch(testEvent) + eventHub.dispatch(testEvent1) + eventHub.start() + assertTrue { + latch.await(250, TimeUnit.MILLISECONDS) + } + } + + @Test + fun testRegisterListener_NotInvokedForPairedResponseEvent() { + val latch = CountDownLatch(2) + val capturedEvents = mutableListOf() + + val testEvent = Event.Builder("Test event", eventType, eventSource).build() + val testResponseEvent = Event.Builder("Test response event", eventType, eventSource).setTriggerEvent(testEvent).build() + eventHub.registerListener(eventType, eventSource) { + capturedEvents.add(it) + latch.countDown() + } + + eventHub.start() + eventHub.dispatch(testEvent) + eventHub.dispatch(testResponseEvent) + assertFalse { + latch.await(250, TimeUnit.MILLISECONDS) + } + assertEquals(capturedEvents, listOf(testEvent)) + } + + @Test + fun testRegisterListener_WildcardEvents() { + val latch = CountDownLatch(2) + val capturedEvents = mutableListOf() + + val testEvent = Event.Builder("Test event", eventType, eventSource).build() + val testResponseEvent = Event.Builder("Test response event", eventType, eventSource).setTriggerEvent(testEvent).build() + eventHub.registerListener(EventType.TYPE_WILDCARD, EventSource.TYPE_WILDCARD) { + capturedEvents.add(it) + latch.countDown() + } + + eventHub.start() + eventHub.dispatch(testEvent) + eventHub.dispatch(testResponseEvent) + assertTrue { + latch.await(250, TimeUnit.MILLISECONDS) + } + assertEquals(capturedEvents, listOf(testEvent, testResponseEvent)) + } + + @Test + fun testResponseListener() { + val latch = CountDownLatch(1) + val capturedEvents = mutableListOf>() + + val testEvent = Event.Builder("Test event", eventType, eventSource).build() + val testResponseEvent = Event.Builder("Test response event", eventType, eventSource).setTriggerEvent(testEvent).build() + + eventHub.registerResponseListener( + testEvent, 250, + object : AdobeCallbackWithError { + override fun call(value: Event?) { + capturedEvents.add(Pair(value, null)) + latch.countDown() + } + + override fun fail(error: AdobeError?) { + capturedEvents.add(Pair(null, error)) + latch.countDown() + } + } + ) + + eventHub.start() + eventHub.dispatch(testResponseEvent) + assertTrue { + latch.await(250, TimeUnit.MILLISECONDS) + } + assertEquals(capturedEvents, listOf(Pair(testResponseEvent, null))) + } + + @Test + fun testResponseListener_RemovedAfterInvoked() { + val latch = CountDownLatch(2) + val capturedEvents = mutableListOf>() + + val testEvent = Event.Builder("Test event", eventType, eventSource).build() + val testResponseEvent = Event.Builder("Test response event", eventType, eventSource).setTriggerEvent(testEvent).build() + + eventHub.registerResponseListener( + testEvent, 5000, + object : AdobeCallbackWithError { + override fun call(value: Event?) { + capturedEvents.add(Pair(value, null)) + latch.countDown() + } + + override fun fail(error: AdobeError?) { + capturedEvents.add(Pair(null, error)) + latch.countDown() + } + } + ) + + eventHub.start() + eventHub.dispatch(testResponseEvent) + eventHub.dispatch(testResponseEvent) + assertFalse { + latch.await(500, TimeUnit.MILLISECONDS) + } + + assertEquals(capturedEvents, listOf(Pair(testResponseEvent, null))) + } + + @Test + fun testResponseListenerTimeout() { + val latch = CountDownLatch(1) + val capturedEvents = mutableListOf>() + + val testEvent = Event.Builder("Test event", eventType, eventSource).build() + val testResponseEvent = Event.Builder("Test response event", eventType, eventSource).setTriggerEvent(testEvent).build() + + eventHub.registerResponseListener( + testEvent, 250, + object : AdobeCallbackWithError { + override fun call(value: Event?) { + capturedEvents.add(Pair(value, null)) + latch.countDown() + } + + override fun fail(error: AdobeError?) { + capturedEvents.add(Pair(null, error)) + latch.countDown() + } + } + ) + + eventHub.start() + assertTrue { + latch.await(500, TimeUnit.MILLISECONDS) + } + assertEquals(capturedEvents, listOf(Pair(null, AdobeError.CALLBACK_TIMEOUT))) + } + + @Test + fun testListener_LongRunningListenerShouldNotBlockOthers() { + class Extension1(api: ExtensionApi) : Extension(api) { + override fun getName(): String { + return "ext1" + } + } + + class Extension2(api: ExtensionApi) : Extension(api) { + override fun getName(): String { + return "ext2" + } + } + + val latch = CountDownLatch(2) + + val testEvent = Event.Builder("Test event", eventType, eventSource).build() + + registerExtension(Extension1::class.java) + registerExtension(Extension2::class.java) + + eventHub.getExtensionContainer(Extension1::class.java)?.registerEventListener( + eventType, eventSource, + { + latch.countDown() + Thread.sleep(5000) + }, + null + ) + + eventHub.getExtensionContainer(Extension2::class.java)?.registerEventListener( + eventType, eventSource, + { + latch.countDown() + }, + null + ) + + eventHub.start() + eventHub.dispatch(testEvent) + assertTrue { + latch.await(500, TimeUnit.MILLISECONDS) + } + } } diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventListenerContainerTests.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventListenerContainerTests.kt new file mode 100644 index 000000000..cb813b27c --- /dev/null +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventListenerContainerTests.kt @@ -0,0 +1,123 @@ +/* + Copyright 2022 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.mobile.internal.eventhub + +import com.adobe.marketing.mobile.AdobeCallbackWithError +import com.adobe.marketing.mobile.AdobeError +import com.adobe.marketing.mobile.Event +import com.adobe.marketing.mobile.EventSource +import com.adobe.marketing.mobile.EventType +import org.junit.Test +import java.lang.Exception +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import kotlin.test.fail + +internal class EventListenerContainerTests { + + val eventType = "eventtype" + val eventSource = "eventsource" + + @Test + fun testResponseListener_MatchingTrigger() { + val testEvent = Event.Builder("Test event", eventType, eventSource).build() + val listener = ResponseListenerContainer( + testEvent.uniqueIdentifier, null, + object : AdobeCallbackWithError { + override fun call(value: Event?) {} + override fun fail(error: AdobeError?) {} + } + ) + val testResponseEvent = Event.Builder("Test response event", eventType, eventSource).setTriggerEvent(testEvent).build() + assertTrue { listener.shouldNotify(testResponseEvent) } + } + + @Test + fun testResponseListener_ListenerException() { + val testEvent = Event.Builder("Test event", eventType, eventSource).build() + val listener = ResponseListenerContainer( + testEvent.uniqueIdentifier, null, + object : AdobeCallbackWithError { + override fun call(value: Event?) { + throw Exception() + } + + override fun fail(error: AdobeError?) { + throw Exception() + } + } + ) + + try { + listener.notify(testEvent) + } catch (ex: Exception) { + fail() + } + } + + @Test + fun testEventListener_MatchingTypeSource() { + val listener = ExtensionListenerContainer(eventType, eventSource) {} + + val testEvent = Event.Builder("Test event", eventType, eventSource).build() + assertTrue { listener.shouldNotify(testEvent) } + + val testEvent1 = Event.Builder("Test event 1", "customType", eventSource).build() + assertFalse { listener.shouldNotify(testEvent1) } + } + + @Test + fun testEventListener_WildcardListener() { + val listener = ExtensionListenerContainer(EventType.TYPE_WILDCARD, EventSource.TYPE_WILDCARD) {} + + val testEvent = Event.Builder("Test event", eventType, eventSource).build() + assertTrue { listener.shouldNotify(testEvent) } + + val testEvent1 = Event.Builder("Test event 1", "customType", eventSource).build() + assertTrue { listener.shouldNotify(testEvent1) } + } + + @Test + fun testEventListener_NotTriggerForResponseEvent() { + val listener = ExtensionListenerContainer(eventType, eventSource) {} + + val testEvent = Event.Builder("Test event", eventType, eventSource).build() + val testResponseEvent = Event.Builder("Test response event", eventType, eventSource).setTriggerEvent(testEvent).build() + + assertFalse { listener.shouldNotify(testResponseEvent) } + } + + @Test + fun testEventListener_WildcardTriggerForResponseEvent() { + val listener = ExtensionListenerContainer(EventType.TYPE_WILDCARD, EventSource.TYPE_WILDCARD) {} + + val testEvent = Event.Builder("Test event", eventType, eventSource).build() + val testResponseEvent = Event.Builder("Test response event", eventType, eventSource).setTriggerEvent(testEvent).build() + + assertTrue { listener.shouldNotify(testResponseEvent) } + } + + @Test + fun testEventListener_HandleListenerException() { + val listener = ExtensionListenerContainer(EventType.TYPE_WILDCARD, EventSource.TYPE_WILDCARD) { + throw Exception() + } + + val testEvent = Event.Builder("Test event", eventType, eventSource).build() + + try { + listener.notify(testEvent) + } catch (ex: Exception) { + fail() + } + } +} diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainerTest.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainerTest.kt index 0d48a5fff..6a4fff16f 100644 --- a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainerTest.kt +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainerTest.kt @@ -23,7 +23,6 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.stubbing.Answer -import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.modules.junit4.PowerMockRunner import java.util.concurrent.Callable import java.util.concurrent.ExecutorService @@ -33,11 +32,8 @@ import kotlin.test.assertNull import kotlin.test.assertTrue @RunWith(PowerMockRunner::class) -@PrepareForTest(ExtensionRuntime::class) class ExtensionContainerTest { - @Mock - private lateinit var mockExtensionRuntime: ExtensionRuntime @Mock private lateinit var mockExecutorService: ExecutorService @Mock @@ -60,9 +56,19 @@ class ExtensionContainerTest { } ).`when`(mockExecutorService).submit(any(Callable::class.java)) + // Todo: Cleanup as part of Shared state updates + doAnswer( + Answer { + val mockFuture: Future<*> = Mockito.mock(Future::class.java) + val runnableArgument = it.getArgument(0) + runnableArgument.run() + return@Answer mockFuture + } + ).`when`(mockExecutorService).submit(any(Runnable::class.java)) + extensionContainer = ExtensionContainer( MockExtension::class.java, - mockExtensionRuntime, mockExecutorService, mockErrorCallback + mockExecutorService, mockErrorCallback ) } @@ -104,15 +110,6 @@ class ExtensionContainerTest { assertEquals(SharedState.Status.NOT_SET, ret) } - @Test - fun testSetSharedState_ExecutorShutDown() { - `when`(mockExecutorService.isShutdown).thenReturn(true) - - val ret: SharedState.Status = extensionContainer.setSharedState(SharedStateType.STANDARD, mutableMapOf(), 0) - verify(mockExecutorService, times(0)).submit(any(Callable::class.java)) - assertEquals(SharedState.Status.NOT_SET, ret) - } - @Test fun testGetSharedState_StateExistsAtVersion() { val dataAtV1 = mutableMapOf ("One" to 1, "Yes" to true) From 550456375662b1f4b0c1769d8dd1319b3d06f3e2 Mon Sep 17 00:00:00 2001 From: praveek Date: Tue, 12 Jul 2022 12:08:38 -0700 Subject: [PATCH 099/476] Fix build error --- .../marketing/mobile/internal/eventhub/ExtensionContainer.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt index 92626a17f..97aa2988f 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt @@ -250,6 +250,7 @@ internal class ExtensionContainer constructor( event: Event?, barrier: Boolean, resolution: SharedStateResolution?, + errorCallback: ExtensionErrorCallback? ): SharedStateResult { TODO("Not yet implemented") } @@ -278,6 +279,7 @@ internal class ExtensionContainer constructor( event: Event?, barrier: Boolean, resolution: SharedStateResolution?, + errorCallback: ExtensionErrorCallback? ): SharedStateResult { TODO("Not yet implemented") } From d0af123af7b9bfab876acbb3debf18b064e2ac9f Mon Sep 17 00:00:00 2001 From: praveek Date: Tue, 12 Jul 2022 12:49:00 -0700 Subject: [PATCH 100/476] Make classes internal --- .../mobile/internal/eventhub/EventListenerContainer.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventListenerContainer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventListenerContainer.kt index 7b0af5f6f..cd67bd791 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventListenerContainer.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventListenerContainer.kt @@ -21,13 +21,13 @@ import com.adobe.marketing.mobile.MobileCore import java.lang.Exception import java.util.concurrent.ScheduledFuture -sealed class EventListenerContainer { +internal sealed class EventListenerContainer { abstract fun shouldNotify(event: Event): Boolean abstract fun notify(event: Event) } -class ResponseListenerContainer( +internal class ResponseListenerContainer( val triggerEventId: String, val timeoutTask: ScheduledFuture?, val listener: AdobeCallbackWithError @@ -49,7 +49,7 @@ class ResponseListenerContainer( } } -class ExtensionListenerContainer(val eventType: String, val eventSource: String, val listener: ExtensionEventListener) : EventListenerContainer() { +internal class ExtensionListenerContainer(val eventType: String, val eventSource: String, val listener: ExtensionEventListener) : EventListenerContainer() { override fun shouldNotify(event: Event): Boolean { // Wildcard listeners should only be notified of paired response events. return if (event.responseID != null) { From badbc6bb43fc224b57fab32fe9eb35e0ac6d25b3 Mon Sep 17 00:00:00 2001 From: praveek Date: Tue, 12 Jul 2022 14:22:07 -0700 Subject: [PATCH 101/476] Update dokka version --- code/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/gradle.properties b/code/gradle.properties index db378c8e8..3ed1521a6 100644 --- a/code/gradle.properties +++ b/code/gradle.properties @@ -29,4 +29,4 @@ android.useAndroidX=true # incompatible library is used. #android.enableJetifier=true -dokka_version = 1.6.20 +dokka_version = 1.7.0 From b5e6f4010dd630535c4f3ece9ba63078a99c7632 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Tue, 12 Jul 2022 15:10:18 -0700 Subject: [PATCH 102/476] uncomment tests for map flattening --- .../mobile/internal/utility/MapExtensions.kt | 34 +---- .../launch/rulesengine/LaunchTokenFinder.kt | 2 - .../internal/utility/MapExtensionsTests.kt | 27 ---- .../rulesengine/LaunchTokenFinderTest.kt | 141 +++++++++--------- 4 files changed, 71 insertions(+), 133 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt index e39cd2462..278b09e00 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt @@ -68,35 +68,6 @@ internal fun Map.flattening(prefix: String = ""): Map.getFlattenedDataMap(prefix: String = ""): Map { - val keyPrefix = if (prefix.isNotEmpty()) "$prefix." else prefix - val flattenedMap = mutableMapOf() - this.forEach { entry -> - val expandedKey = keyPrefix + entry.key - val value = entry.value - flattenedMap[expandedKey] = value - if (value is Map<*, *> && value.keys.isAllString()) { - @Suppress("UNCHECKED_CAST") - flattenedMap.putAll((value as Map).flattening(expandedKey)) - } - } - return flattenedMap -}*/ - /** * Serializes a map to key value pairs for url string. * This method is recursive to handle the nested data objects. @@ -108,10 +79,9 @@ internal fun Map.serializeToQueryString(): String { val builder = StringBuilder() for ((key, value) in this.entries) { val encodedKey = urlEncode(key) ?: continue - var encodedValue: String? // TODO add serializing for custom objects - encodedValue = if (value is List<*>) { + var encodedValue: String? = if (value is List<*>) { urlEncode(join(value, ",")) } else { urlEncode(value?.toString()) @@ -141,6 +111,8 @@ internal fun Map.prettify(): String { } private fun Set<*>.isAllString(): Boolean { + if(this.isEmpty()) + return false this.forEach { if (it !is String) return false } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt index 24e4dd063..159661d5d 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt @@ -118,7 +118,6 @@ internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionAp return null } val (sharedStateName, dataKeyName) = sharedStateKeyString.split(SHARED_STATE_KEY_DELIMITER) - // TODO change once map flattening logic is finalized val sharedStateMap = extensionApi.getSharedEventState(sharedStateName, event) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, @@ -147,7 +146,6 @@ internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionAp ) return EMPTY_STRING } - // TODO uncomment once map flattening logic is finalized val eventDataMap = event.eventData.flattening() return eventDataMap[key] } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt index 683d9fe4c..9d94725e3 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/internal/utility/MapExtensionsTests.kt @@ -124,33 +124,6 @@ class MapExtensionsTests { assertEquals(expectedMap, flattenedMap) } - // TODO uncomment when map flattening logic is finalized - /* @Test - @Throws(Exception::class) - fun getFlattenedMap_ReturnsFlattenedMap_WhenEventDataNotNull() { - val map = mapOf( - "boolKey" to "true", - "intKey" to 1, - "longKey" to 100L, - "stringKey" to "stringValue", - "mapStrKey" to mapOf( - "mapKey" to "mapValue" - ) - ) - val flattenedMap = map.getFlattenedDataMap() - val expectedMap = mapOf( - "boolKey" to "true", - "intKey" to 1, - "longKey" to 100L, - "stringKey" to "stringValue", - "mapStrKey" to mapOf( - "mapKey" to "mapValue" - ), - "mapStrKey.mapKey" to "mapValue" - ) - assertEquals(expectedMap, flattenedMap) - } */ - @Test fun testSerializeToQueryString() { val dict = HashMap() diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt index 3c065b475..372348256 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt @@ -22,6 +22,7 @@ import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers import org.powermock.api.mockito.PowerMockito import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.modules.junit4.PowerMockRunner @@ -73,7 +74,7 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return current unix timestamp on valid event`() { // setup - val testEvent = getDefaultEvent()!! + val testEvent = getDefaultEvent() val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("~timestampu") @@ -84,7 +85,7 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return current ISO8601 timestamp on valid event`() { // setup - val testEvent = getDefaultEvent()!! + val testEvent = getDefaultEvent() val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("~timestampz") @@ -95,7 +96,7 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return current ISO8601 date timezone on valid event`() { // setup - val testEvent = getDefaultEvent()!! + val testEvent = getDefaultEvent() val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("~timestampp") @@ -106,7 +107,7 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return current sdk version on valid event`() { // setup - val testEvent = getDefaultEvent()!! + val testEvent = getDefaultEvent() val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("~sdkver") @@ -244,7 +245,7 @@ class LaunchTokenFinderTest : BaseTest() { } */ @Test - fun `get should return empty string on event with no event data for json`() { + fun `get should return empty json on event with no event data for json`() { // setup val testEvent = getDefaultEvent(null) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) @@ -254,16 +255,18 @@ class LaunchTokenFinderTest : BaseTest() { assertEquals("{}", result) } - // TODO uncomment when map flattening logic is finalized - /* @Test + @Test fun `get should return nested value from shared state of the module on valid event`() { // setup - val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) - val lcdata = EventData() - val lifecycleSharedState: MutableMap = HashMap() - lifecycleSharedState["akey"] = "avalue" - lcdata.putStringMap("analytics.contextData", lifecycleSharedState) - eventHub.setSharedState("com.adobe.marketing.mobile.Analytics", lcdata) + val testEvent = getDefaultEvent(null) + val lcData = mapOf("analytics.contextData" to mutableMapOf("akey" to "avalue")) + PowerMockito.`when`(extensionApi.getSharedEventState( + ArgumentMatchers.eq("com.adobe.marketing.mobile.Analytics"), + ArgumentMatchers.any(), + ArgumentMatchers.any() + )).thenReturn( + lcData + ) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("~state.com.adobe.marketing.mobile.Analytics/analytics.contextData.akey") @@ -272,37 +275,42 @@ class LaunchTokenFinderTest : BaseTest() { } @Test - fun `get should return shared state list of the module on valid event`() { + fun `get should return null when top level key is used from shared state of the module on valid event`() { // setup - val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) - val lcdata = EventData() - val identitySharedState: MutableList = ArrayList() - identitySharedState.add("vid1") - identitySharedState.add("vid2") - lcdata.putStringList("visitoridslist", identitySharedState) - eventHub.setSharedState("com.adobe.marketing.mobile.identity", lcdata) + val testEvent = getDefaultEvent(null) + val lcData = mapOf("analytics.contextData" to mapOf("akey" to "avalue")) + PowerMockito.`when`(extensionApi.getSharedEventState( + ArgumentMatchers.eq("com.adobe.marketing.mobile.Analytics"), + ArgumentMatchers.any(), + ArgumentMatchers.any() + )).thenReturn( + lcData + ) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test - val result = launchTokenFinder.get("~state.com.adobe.marketing.mobile.identity/visitoridslist") + val result = launchTokenFinder.get("~state.com.adobe.marketing.mobile.Analytics/analytics") // verify - assertEquals(identitySharedState, result) + assertNull( result) } @Test - fun `get should return shared state of the module on valid event`() { + fun `get should return shared state list of the module on valid event`() { // setup - val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, null) - val lcdata = EventData() - val lifecycleSharedState: MutableMap = HashMap() - lifecycleSharedState["akey"] = "avalue" - lcdata.putStringMap("analytics.contextData", lifecycleSharedState) - eventHub.setSharedState("com.adobe.marketing.mobile.Analytics", lcdata) + val testEvent = getDefaultEvent(null) + val lcdata = mapOf("visitoridslist" to listOf("vid1", "vid2")) + PowerMockito.`when`(extensionApi.getSharedEventState( + ArgumentMatchers.eq("com.adobe.marketing.mobile.identity"), + ArgumentMatchers.any(), + ArgumentMatchers.any() + )).thenReturn( + lcdata + ) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test - val result = launchTokenFinder.get("~state.com.adobe.marketing.mobile.Analytics/analytics.contextData") + val result = launchTokenFinder.get("~state.com.adobe.marketing.mobile.identity/visitoridslist") // verify - assertEquals(lifecycleSharedState, result) - } */ + assertEquals(listOf("vid1", "vid2"), result) + } @Test fun `get should return null when key does not have shared state name`() { @@ -340,8 +348,7 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return null when key does not exist in shared state`() { // setup - val testEventData = mapOf() - val testEvent = getDefaultEvent(testEventData) + val testEvent = getDefaultEvent(null) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("~state.com.adobe.marketing.mobile.Analytics/analytics.contextData.akey") @@ -349,8 +356,7 @@ class LaunchTokenFinderTest : BaseTest() { assertNull(result) } - // TODO uncomment when map flattening logic is finalized - /* @Test + @Test fun `get should return value of the key from event data on valid event`() { // setup val testEvent = getDefaultEvent() @@ -359,7 +365,7 @@ class LaunchTokenFinderTest : BaseTest() { val result = launchTokenFinder.get("key1") // verify assertEquals("value1", result) - } */ + } // TODO change if we decide to keep event data as null instead of empty map by default @Test @@ -373,8 +379,7 @@ class LaunchTokenFinderTest : BaseTest() { assertEquals(null, result) } - // TODO uncomment when map flattening logic is finalized - /* @Test + @Test fun `get should return null when key does not exist in event data on valid event`() { // setup val testEvent = getDefaultEvent() @@ -388,9 +393,7 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun `get should return null when value for the key in event data is null on valid event`() { // setup - val testEventData = EventData() - testEventData.putNull("key1") - val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val testEvent = getDefaultEvent(mapOf("key1" to null)) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("key1") @@ -399,65 +402,57 @@ class LaunchTokenFinderTest : BaseTest() { } @Test - fun `get should return empty string on list`() { + fun `get should return list on list value`() { // setup - val testEventData = EventData() - val stringList: MutableList = ArrayList() - stringList.add("String1") - stringList.add("String2") - testEventData.putStringList("key6", stringList) - val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val testEventData = mapOf("key6" to listOf("String1", "String2")) + val testEvent = getDefaultEvent(testEventData) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("key6") // verify - assertEquals(stringList, result) + assertEquals(listOf("String1", "String2"), result) } - /* @Test + @Test fun get_ReturnsMap_When_KeyIsNotSpecialKeyAndValueIsEmptyMap() { //setup - val testEventData = EventData() - testEventData.putVariantMap("key1", HashMap()) - val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) - val launchTokenFinder = LaunchTokenFinder(testEvent!!, configuration!!, - platformServices) + val testEventData = mapOf("key1" to emptyMap()) + val testEvent = getDefaultEvent(testEventData) + val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) //test val result = launchTokenFinder.get("key1") //verify - assertEquals("get should return empty map on empty map variant", HashMap(), result) - } */ + assertEquals("get should return empty map on empty map value", emptyMap(), result) + } @Test - fun `get should return map on map`() { + fun `get should return null on top level key`() { // setup - val testEventData = EventData() - val stringMap: MutableMap = HashMap() - stringMap["innerKey1"] = "inner val1" - testEventData.putStringMap("key1", stringMap) - val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val testEventData = mapOf("key1" to mapOf("innerKey1" to "inner val1")) + val testEvent = getDefaultEvent(testEventData) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("key1") // verify - assertEquals(stringMap, result) + assertNull(result) } @Test fun `get should return nested value for valid flattened key on a valid event`() { // setup - val testEventData = EventData() - val stringMap: MutableMap = HashMap() - stringMap["innerKey1"] = "inner val1" - stringMap["innerKey2"] = "innerVal2" - testEventData.putStringMap("key7", stringMap) - val testEvent = getEvent(EventType.ANALYTICS, EventSource.REQUEST_CONTENT, testEventData) + val testEventData = mapOf( + "key7" to mapOf( + "innerKey1" to "inner val1", + "innerKey2" to "innerVal2" + ) + ) + val testEvent = getDefaultEvent(testEventData) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("key7.innerKey1") // verify assertEquals("inner val1", result) - } */ + } private fun getDefaultEvent(eventData: Map?): Event { return Event.Builder( From 1bb0e11c15aa8e2c21f399c22fed51c70e2a4432 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Tue, 12 Jul 2022 15:20:06 -0700 Subject: [PATCH 103/476] fix formatting --- .../marketing/mobile/internal/utility/MapExtensions.kt | 2 +- .../mobile/launch/rulesengine/LaunchTokenFinderTest.kt | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt index 278b09e00..1c6162a59 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/MapExtensions.kt @@ -111,7 +111,7 @@ internal fun Map.prettify(): String { } private fun Set<*>.isAllString(): Boolean { - if(this.isEmpty()) + if (this.isEmpty()) return false this.forEach { if (it !is String) return false diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt index 372348256..391a75bc2 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt @@ -290,7 +290,7 @@ class LaunchTokenFinderTest : BaseTest() { // test val result = launchTokenFinder.get("~state.com.adobe.marketing.mobile.Analytics/analytics") // verify - assertNull( result) + assertNull(result) } @Test @@ -415,20 +415,20 @@ class LaunchTokenFinderTest : BaseTest() { @Test fun get_ReturnsMap_When_KeyIsNotSpecialKeyAndValueIsEmptyMap() { - //setup + // setup val testEventData = mapOf("key1" to emptyMap()) val testEvent = getDefaultEvent(testEventData) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) - //test + // test val result = launchTokenFinder.get("key1") - //verify + // verify assertEquals("get should return empty map on empty map value", emptyMap(), result) } @Test fun `get should return null on top level key`() { // setup - val testEventData = mapOf("key1" to mapOf("innerKey1" to "inner val1")) + val testEventData = mapOf("key1" to mapOf("innerKey1" to "inner val1")) val testEvent = getDefaultEvent(testEventData) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test From c871fdb00d0844eec61f32fc68321b49f5c8b63f Mon Sep 17 00:00:00 2001 From: praveek Date: Tue, 12 Jul 2022 15:37:41 -0700 Subject: [PATCH 104/476] Add an API to dispatch events in ExtensionAPI --- .../com/adobe/marketing/mobile/ExtensionApi.java | 10 ++++++++++ .../mobile/internal/eventhub/ExtensionContainer.kt | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionApi.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionApi.java index a8e9189dd..93c5260b0 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionApi.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionApi.java @@ -32,6 +32,16 @@ public abstract boolean registerEventListener(final String eventType, final ExtensionEventListener eventListener, final ExtensionErrorCallback errorCallback); + /** + * Dispatches an `Event` to the `EventHub` + * + * @param event An Event to be dispatched to the {@code EventHub} + * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if any error occurs during registration + * @return a {@code boolean} indicating whether the event was successfully dispatched + */ + public abstract boolean dispatch(final Event event, + final ExtensionErrorCallback errorCallback); + // Shared state /** * Creates a new shared state for this extension. diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt index 97aa2988f..7c74dbc97 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt @@ -359,6 +359,19 @@ internal class ExtensionContainer constructor( return registerEventListener(eventType, eventSource, { extensionListener.hear(it) }, errorCallback) } + override fun dispatch( + event: Event?, + errorCallback: ExtensionErrorCallback?, + ): Boolean { + if (event == null) { + errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) + return false + } + + EventHub.shared.dispatch(event) + return true + } + override fun registerWildcardListener( extensionListenerClass: Class?, errorCallback: ExtensionErrorCallback?, From e0a4290b73f34e8c4a707ebfaef9062c083b70c5 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Tue, 12 Jul 2022 15:51:35 -0700 Subject: [PATCH 105/476] mock ServiceProvider and DeviceInfoService for NetworkServiceTests --- .../mobile/services/NetworkServiceTests.java | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/NetworkServiceTests.java b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/NetworkServiceTests.java index fc7c144a7..3df56d85f 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/NetworkServiceTests.java +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/services/NetworkServiceTests.java @@ -32,17 +32,26 @@ import java.util.concurrent.TimeUnit; @RunWith(PowerMockRunner.class) -@PrepareForTest(NetworkService.class) +@PrepareForTest({NetworkService.class, ServiceProvider.class}) public class NetworkServiceTests { private NetworkService networkService; + @Mock + ServiceProvider serviceProvider; + + @Mock + DeviceInfoService deviceInfoService; + @Mock private HttpConnectionHandler httpConnectionHandler; @Before public void setup() throws Exception { networkService = new NetworkService(currentThreadExecutorService()); + PowerMockito.mockStatic(ServiceProvider.class); + PowerMockito.when(ServiceProvider.getInstance()).thenReturn(serviceProvider); + PowerMockito.when(ServiceProvider.getInstance().getDeviceInfoService()).thenReturn(deviceInfoService); PowerMockito.whenNew(HttpConnectionHandler.class).withAnyArguments().thenReturn(httpConnectionHandler); Mockito.when(httpConnectionHandler.setCommand(Mockito.any(HttpMethod.class))).thenReturn(true); } @@ -111,6 +120,10 @@ public void testConnectAsync_UnsupportedUrlProtocol() throws InterruptedExceptio @Test public void testConnectAsync_DefaultHeaders() throws InterruptedException { + final String mockDefaultUserAgent = "mock default user agent"; + final String mockLocaleString = "mock locale string"; + PowerMockito.when(ServiceProvider.getInstance().getDeviceInfoService().getDefaultUserAgent()).thenReturn(mockDefaultUserAgent); + PowerMockito.when(ServiceProvider.getInstance().getDeviceInfoService().getLocaleString()).thenReturn(mockLocaleString); final CountDownLatch latch = new CountDownLatch(1); networkService.connectAsync( new NetworkRequest("https://www.adobe.com", HttpMethod.GET, null, null, 10, 10), @@ -123,12 +136,16 @@ public void testConnectAsync_DefaultHeaders() throws InterruptedException { final ArgumentCaptor> captor = ArgumentCaptor.forClass(Map.class); Mockito.verify(httpConnectionHandler).setRequestProperty(captor.capture()); - assertEquals("Mozilla/5.0 (Linux; U; Android null; en-US; unknown Build/unknown)", captor.getValue().get("User-Agent")); - assertEquals("en-US", captor.getValue().get("Accept-Language")); + assertEquals(mockDefaultUserAgent, captor.getValue().get("User-Agent")); + assertEquals(mockLocaleString, captor.getValue().get("Accept-Language")); } @Test public void testConnectAsync_WithHeader() throws InterruptedException { + final String mockDefaultUserAgent = "mock default user agent"; + final String mockLocaleString = "mock locale string"; + PowerMockito.when(ServiceProvider.getInstance().getDeviceInfoService().getDefaultUserAgent()).thenReturn(mockDefaultUserAgent); + PowerMockito.when(ServiceProvider.getInstance().getDeviceInfoService().getLocaleString()).thenReturn(mockLocaleString); Map properties = new HashMap<>(); properties.put("testing", "header"); @@ -144,8 +161,8 @@ public void testConnectAsync_WithHeader() throws InterruptedException { final ArgumentCaptor> captor = ArgumentCaptor.forClass(Map.class); Mockito.verify(httpConnectionHandler).setRequestProperty(captor.capture()); - assertEquals("Mozilla/5.0 (Linux; U; Android null; en-US; unknown Build/unknown)", captor.getValue().get("User-Agent")); - assertEquals("en-US", captor.getValue().get("Accept-Language")); + assertEquals(mockDefaultUserAgent, captor.getValue().get("User-Agent")); + assertEquals(mockLocaleString, captor.getValue().get("Accept-Language")); assertEquals("header", captor.getValue().get("testing")); } From 24346c2a5d7ae5bbac5700a35f0a8ef0dfcc6ec2 Mon Sep 17 00:00:00 2001 From: praveek Date: Wed, 13 Jul 2022 17:42:25 -0700 Subject: [PATCH 106/476] Cleanup APIs --- .../adobe/marketing/mobile/ExtensionApi.java | 55 ++++++------ .../mobile/ExtensionEventListener.java | 1 + .../mobile/internal/eventhub/EventHub.kt | 2 +- .../internal/eventhub/ExtensionContainer.kt | 71 ++++++++-------- .../mobile/internal/eventhub/EventHubTests.kt | 84 +++++++------------ 5 files changed, 90 insertions(+), 123 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionApi.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionApi.java index 93c5260b0..c86fc3e5e 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionApi.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionApi.java @@ -24,23 +24,19 @@ public abstract class ExtensionApi { * @param eventType required parameter, the event type as a valid string (not null or empty) * @param eventSource required parameter, the event source as a valid string (not null or empty) * @param eventListener required parameter, the listener which extends the {@link ExtensionEventListener} interface - * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if any error occurs during registration * @return a {@code boolean} indicating the listener registration status */ public abstract boolean registerEventListener(final String eventType, final String eventSource, - final ExtensionEventListener eventListener, - final ExtensionErrorCallback errorCallback); + final ExtensionEventListener eventListener); /** * Dispatches an `Event` to the `EventHub` * * @param event An Event to be dispatched to the {@code EventHub} - * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if any error occurs during registration * @return a {@code boolean} indicating whether the event was successfully dispatched */ - public abstract boolean dispatch(final Event event, - final ExtensionErrorCallback errorCallback); + public abstract boolean dispatch(final Event event); // Shared state /** @@ -53,12 +49,10 @@ public abstract boolean dispatch(final Event event, * @param state {@code Map} representing current state of this extension * @param event The {@link Event} for which the state is being set. Passing null will set the state for the next shared * state version - * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred * @return {@code boolean} indicating if the shared state was successfully set */ public abstract boolean createSharedState(final Map state, - final Event event, - final ExtensionErrorCallback errorCallback); + final Event event); /** * Creates a pending shared state for this extension. *

      @@ -67,25 +61,21 @@ public abstract boolean createSharedState(final Map state, *
    * @param event The {@link Event} for which pending shared state is being set. Passing null will set the state for the next shared * state version. - * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred * @return {@link SharedStateResolver} that should be called with the shared state data when it is ready */ - public abstract SharedStateResolver createPendingSharedState(final Event event, - final ExtensionErrorCallback errorCallback); + public abstract SharedStateResolver createPendingSharedState(final Event event); /** * Gets the shared state data for a specified extension. * @param extensionName extension name for which to retrieve data. See documentation for the list of available states. * @param event the {@link Event} for which the state is being requested. Passing null will retrieve latest state available. * @param barrier If true, the {@code EventHub} will only return {@code set} if extensionName has moved past event. * @param resolution the {@link SharedStateResolution} to resolve for - * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred * return {@code SharedStateResult} for the requested extensionName and event */ public abstract SharedStateResult getSharedState(final String extensionName, final Event event, final boolean barrier, - final SharedStateResolution resolution, - final ExtensionErrorCallback errorCallback); + final SharedStateResolution resolution); /** * Called by extension to clear all shared state it has previously set. Usually called during {@code Extension.onUnregistered()}. @@ -107,12 +97,10 @@ public abstract SharedStateResult getSharedState(final String extensionName, * @param state {@code Map} representing current state of this extension * @param event The {@link Event} for which the state is being set. Passing null will set the state for the next shared * state version - * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred * @return {@code boolean} indicating if the shared state was successfully set */ public abstract boolean createXDMSharedState(final Map state, - final Event event, - final ExtensionErrorCallback errorCallback); + final Event event); /** * Creates a pending XDM shared state for this extension. @@ -122,11 +110,9 @@ public abstract boolean createXDMSharedState(final Map state, * * @param event The {@link Event} for which pending shared state is being set. Passing null will set the state for the next shared * state version. - * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred * @return {@link SharedStateResolver} that should be called with the shared state data when it is ready */ - public abstract SharedStateResolver createPendingXDMSharedState(final Event event, - final ExtensionErrorCallback errorCallback); + public abstract SharedStateResolver createPendingXDMSharedState(final Event event); /** * Gets the XDM shared state data for a specified extension. If the stateName extension populates multiple mixins in their shared state, all the data will be returned at once and it can be accessed using path discovery. @@ -134,14 +120,12 @@ public abstract SharedStateResolver createPendingXDMSharedState(final Event even * @param event the {@link Event} for which the state is being requested. Passing null will retrieve latest state available. * @param barrier If true, the {@code EventHub} will only return {@code set} if extensionName has moved past event. * @param resolution the {@link SharedStateResolution} to resolve for - * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred * return {@code SharedStateResult} for the requested extensionName and event */ public abstract SharedStateResult getXDMSharedState(final String extensionName, final Event event, final boolean barrier, - final SharedStateResolution resolution, - final ExtensionErrorCallback errorCallback); + final SharedStateResolution resolution); /** * Called by extension to clear XDM shared state it has previously set. Usually called during {@code Extension.onUnregistered()}. @@ -162,6 +146,17 @@ public abstract SharedStateResult getXDMSharedState(final String extensionName, */ public abstract void unregisterExtension(); + /** + * Starts the `Event` queue for this extension + */ + public abstract void startEvents(); + + /** + * Stops the `Event` queue for this extension + */ + public abstract void stopEvents(); + + // Deprecated Methods /** * Registers a new event listener for current extension for the provided event type and source. @@ -175,7 +170,7 @@ public abstract SharedStateResult getXDMSharedState(final String extensionName, * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if any error occurs during registration * @param type of current event listener * @return {@code boolean} indicating the listener registration status - * @deprecated Use {@link ExtensionApi#registerEventListener(String, String, ExtensionEventListener, ExtensionErrorCallback)}} + * @deprecated Use {@link ExtensionApi#registerEventListener(String, String, ExtensionEventListener)}} */ @Deprecated public abstract boolean registerEventListener(final String eventType, @@ -200,7 +195,7 @@ public abstract boolean registerEventListener(fina * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if any error occurs during registration * @param type of current event listener * @return {@code boolean} indicating the listener registration status - * @deprecated Use {@link ExtensionApi#registerEventListener(String, String, ExtensionEventListener, ExtensionErrorCallback)}} + * @deprecated Use {@link ExtensionApi#registerEventListener(String, String, ExtensionEventListener)}} */ @Deprecated public abstract boolean registerWildcardListener( @@ -217,7 +212,7 @@ public abstract boolean registerWildcardListener( * state version. * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred * @return {@code boolean} indicating if the shared state was successfully set - * @deprecated Use {@link ExtensionApi#createSharedState(Map, Event, ExtensionErrorCallback)} and {@link ExtensionApi#createPendingSharedState(Event, ExtensionErrorCallback)} + * @deprecated Use {@link ExtensionApi#createSharedState(Map, Event)} and {@link ExtensionApi#createPendingSharedState(Event)} */ @Deprecated public abstract boolean setSharedEventState(final Map state, final Event event, @@ -235,7 +230,7 @@ public abstract boolean setSharedEventState(final Map state, fin * state version. * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred * @return {@code boolean} indicating if the XDM shared state was successfully set - * @deprecated Use {@link ExtensionApi#createXDMSharedState(Map, Event, ExtensionErrorCallback)} and {@link ExtensionApi#createPendingXDMSharedState(Event, ExtensionErrorCallback)} + * @deprecated Use {@link ExtensionApi#createXDMSharedState(Map, Event)} and {@link ExtensionApi#createPendingXDMSharedState(Event)} */ @Deprecated public abstract boolean setXDMSharedEventState(final Map state, final Event event, @@ -249,7 +244,7 @@ public abstract boolean setXDMSharedEventState(final Map state, * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred or if {@code stateName} is null * @return {@code Map} containing shared state data at that version. Returns null if state does not exists, * is PENDING, or an error is returned in the {@code errorCallback} - * @deprecated Use {@link ExtensionApi#getSharedState(String, Event, boolean, SharedStateResolution, ExtensionErrorCallback)} + * @deprecated Use {@link ExtensionApi#getSharedState(String, Event, boolean, SharedStateResolution)} */ @Deprecated public abstract Map getSharedEventState(final String stateName, final Event event, @@ -265,7 +260,7 @@ public abstract Map getSharedEventState(final String stateName, * @param errorCallback optional {@link ExtensionErrorCallback} which will be called if an error occurred or if {@code stateName} is null * @return {@code Map} containing XDM shared state data at that version. Returns null if state does not exists, * is PENDING, or an error is returned in the {@code errorCallback} - * @deprecated Use {@link ExtensionApi#getXDMSharedState(String, Event, boolean, SharedStateResolution, ExtensionErrorCallback)} + * @deprecated Use {@link ExtensionApi#getXDMSharedState(String, Event, boolean, SharedStateResolution)} */ @Deprecated public abstract Map getXDMSharedEventState(final String stateName, final Event event, diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionEventListener.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionEventListener.java index c4fc65ed3..9c4fb1343 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionEventListener.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionEventListener.java @@ -13,6 +13,7 @@ /** * Defines a generic listener that can hear a specific kind of {@code Event} on an {@code EventHub} */ +@FunctionalInterface public interface ExtensionEventListener { void hear(final Event event); diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index 4c6b0cefa..318db6e3b 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -221,7 +221,7 @@ internal class EventHub { fun registerListener(eventType: String, eventSource: String, listener: AdobeCallback) { eventHubExecutor.submit { val eventHubContainer = getExtensionContainer(EventHubPlaceholderExtension::class.java) - eventHubContainer?.registerEventListener(eventType, eventSource, { listener.call(it) }, null) + eventHubContainer?.registerEventListener(eventType, eventSource, { listener.call(it) }) } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt index 7c74dbc97..5cb560906 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/ExtensionContainer.kt @@ -196,6 +196,16 @@ internal class ExtensionContainer constructor( ).get() } + override fun dispatch( + event: Event? + ): Boolean { + if (event == null) { + return false + } + EventHub.shared.dispatch(event) + return true + } + private fun getTag(): String { if (extension == null) { return LOG_TAG @@ -207,22 +217,18 @@ internal class ExtensionContainer constructor( override fun registerEventListener( eventType: String?, eventSource: String?, - eventListener: ExtensionEventListener?, - errorCallback: ExtensionErrorCallback? + eventListener: ExtensionEventListener? ): Boolean { if (eventType == null) { - errorCallback?.error(ExtensionError.EVENT_TYPE_NOT_SUPPORTED) return false } if (eventSource == null) { - errorCallback?.error(ExtensionError.EVENT_SOURCE_NOT_SUPPORTED) return false } if (eventListener == null) { - errorCallback?.error(ExtensionError.CALLBACK_NULL) return false } @@ -232,15 +238,13 @@ internal class ExtensionContainer constructor( override fun createSharedState( state: MutableMap?, - event: Event?, - errorCallback: ExtensionErrorCallback?, + event: Event? ): Boolean { TODO("Not yet implemented") } override fun createPendingSharedState( - event: Event?, - errorCallback: ExtensionErrorCallback?, + event: Event? ): SharedStateResolver? { TODO("Not yet implemented") } @@ -249,27 +253,20 @@ internal class ExtensionContainer constructor( extensionName: String?, event: Event?, barrier: Boolean, - resolution: SharedStateResolution?, - errorCallback: ExtensionErrorCallback? + resolution: SharedStateResolution? ): SharedStateResult { TODO("Not yet implemented") } - override fun clearSharedEventStates(errorCallback: ExtensionErrorCallback?): Boolean { - TODO("Not yet implemented") - } - override fun createXDMSharedState( state: MutableMap?, - event: Event?, - errorCallback: ExtensionErrorCallback?, + event: Event? ): Boolean { TODO("Not yet implemented") } override fun createPendingXDMSharedState( - event: Event?, - errorCallback: ExtensionErrorCallback?, + event: Event? ): SharedStateResolver? { TODO("Not yet implemented") } @@ -278,17 +275,20 @@ internal class ExtensionContainer constructor( extensionName: String?, event: Event?, barrier: Boolean, - resolution: SharedStateResolution?, - errorCallback: ExtensionErrorCallback? + resolution: SharedStateResolution? ): SharedStateResult { TODO("Not yet implemented") } - override fun clearXDMSharedEventStates(errorCallback: ExtensionErrorCallback?): Boolean { + override fun unregisterExtension() { TODO("Not yet implemented") } - override fun unregisterExtension() { + override fun startEvents() { + TODO("Not yet implemented") + } + + override fun stopEvents() { TODO("Not yet implemented") } @@ -345,6 +345,14 @@ internal class ExtensionContainer constructor( TODO("Not yet implemented") } + override fun clearSharedEventStates(errorCallback: ExtensionErrorCallback?): Boolean { + TODO("Not yet implemented") + } + + override fun clearXDMSharedEventStates(errorCallback: ExtensionErrorCallback?): Boolean { + TODO("Not yet implemented") + } + override fun registerEventListener( eventType: String?, eventSource: String?, @@ -356,20 +364,7 @@ internal class ExtensionContainer constructor( errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) return false } - return registerEventListener(eventType, eventSource, { extensionListener.hear(it) }, errorCallback) - } - - override fun dispatch( - event: Event?, - errorCallback: ExtensionErrorCallback?, - ): Boolean { - if (event == null) { - errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) - return false - } - - EventHub.shared.dispatch(event) - return true + return registerEventListener(eventType, eventSource, { extensionListener.hear(it) }) } override fun registerWildcardListener( @@ -381,6 +376,6 @@ internal class ExtensionContainer constructor( errorCallback?.error(ExtensionError.UNEXPECTED_ERROR) return false } - return registerEventListener(EventType.TYPE_WILDCARD, EventSource.TYPE_WILDCARD, { extensionListener.hear(it) }, errorCallback) + return registerEventListener(EventType.TYPE_WILDCARD, EventSource.TYPE_WILDCARD, { extensionListener.hear(it) }) } } diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt index 00eee6dfa..38824cacb 100644 --- a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/eventhub/EventHubTests.kt @@ -147,7 +147,7 @@ internal class EventHubTests { fun testRegisterExtensionFailure_DuplicateExtension() { registerExtension(MockExtension::class.java) - var ret = registerExtension(MockExtension::class.java) + val ret = registerExtension(MockExtension::class.java) assertEquals(EventHubError.DuplicateExtensionName, ret) } @@ -173,13 +173,13 @@ internal class EventHubTests { fun testUnregisterExtensionSuccess() { registerExtension(MockExtensions.MockExtensionKotlin::class.java) - var ret = unregisterExtension(MockExtensions.MockExtensionKotlin::class.java) + val ret = unregisterExtension(MockExtensions.MockExtensionKotlin::class.java) assertEquals(EventHubError.None, ret) } @Test fun testUnregisterExtensionFailure() { - var ret = unregisterExtension(MockExtensions.MockExtensionKotlin::class.java) + val ret = unregisterExtension(MockExtensions.MockExtensionKotlin::class.java) assertEquals(EventHubError.ExtensionNotRegistered, ret) } @@ -707,14 +707,10 @@ internal class EventHubTests { val testEvent = Event.Builder("Sample event", eventType, eventSource).build() val extensionContainer = eventHub.getExtensionContainer(MockExtensions.TestExtension::class.java) - extensionContainer?.registerEventListener( - eventType, eventSource, - { - assertTrue { it == testEvent } - latch.countDown() - }, - null - ) + extensionContainer?.registerEventListener(eventType, eventSource) { + assertTrue { it == testEvent } + latch.countDown() + } eventHub.start() eventHub.dispatch(testEvent) @@ -730,13 +726,9 @@ internal class EventHubTests { val testEvent = Event.Builder("Test event", eventType, eventSource).build() val extensionContainer = eventHub.getExtensionContainer(MockExtensions.TestExtension::class.java) - extensionContainer?.registerEventListener( - "customEventType", "customEventSource", - { - latch.countDown() - }, - null - ) + extensionContainer?.registerEventListener("customEventType", "customEventSource") { + latch.countDown() + } eventHub.start() eventHub.dispatch(testEvent) @@ -756,8 +748,7 @@ internal class EventHubTests { eventType, eventSource, { latch.countDown() - }, - null + } ) eventHub.dispatch(testEvent) @@ -773,14 +764,10 @@ internal class EventHubTests { val testEvent = Event.Builder("Test event", eventType, eventSource).build() val extensionContainer = eventHub.getExtensionContainer(MockExtensions.TestExtension::class.java) - extensionContainer?.registerEventListener( - eventType, eventSource, - { - assertTrue { it == testEvent } - latch.countDown() - }, - null - ) + extensionContainer?.registerEventListener(eventType, eventSource) { + assertTrue { it == testEvent } + latch.countDown() + } eventHub.dispatch(testEvent) eventHub.dispatch(testEvent) @@ -798,13 +785,11 @@ internal class EventHubTests { val extensionContainer = eventHub.getExtensionContainer(MockExtensions.TestExtension::class.java) extensionContainer?.registerEventListener( - eventType, eventSource, - { - assertTrue { it == testEvent } - latch.countDown() - }, - null - ) + eventType, eventSource + ) { + assertTrue { it == testEvent } + latch.countDown() + } eventHub.dispatch(testEvent) eventHub.dispatch(testEvent1) @@ -972,7 +957,7 @@ internal class EventHubTests { assertTrue { latch.await(250, TimeUnit.MILLISECONDS) } - assertEquals(capturedEvents, listOf(Pair(testResponseEvent, null))) + assertEquals(capturedEvents, listOf>(Pair(testResponseEvent, null))) } @Test @@ -1005,7 +990,7 @@ internal class EventHubTests { latch.await(500, TimeUnit.MILLISECONDS) } - assertEquals(capturedEvents, listOf(Pair(testResponseEvent, null))) + assertEquals(capturedEvents, listOf>(Pair(testResponseEvent, null))) } @Test @@ -1014,7 +999,6 @@ internal class EventHubTests { val capturedEvents = mutableListOf>() val testEvent = Event.Builder("Test event", eventType, eventSource).build() - val testResponseEvent = Event.Builder("Test response event", eventType, eventSource).setTriggerEvent(testEvent).build() eventHub.registerResponseListener( testEvent, 250, @@ -1035,7 +1019,7 @@ internal class EventHubTests { assertTrue { latch.await(500, TimeUnit.MILLISECONDS) } - assertEquals(capturedEvents, listOf(Pair(null, AdobeError.CALLBACK_TIMEOUT))) + assertEquals(capturedEvents, listOf>(Pair(null, AdobeError.CALLBACK_TIMEOUT))) } @Test @@ -1059,22 +1043,14 @@ internal class EventHubTests { registerExtension(Extension1::class.java) registerExtension(Extension2::class.java) - eventHub.getExtensionContainer(Extension1::class.java)?.registerEventListener( - eventType, eventSource, - { - latch.countDown() - Thread.sleep(5000) - }, - null - ) + eventHub.getExtensionContainer(Extension1::class.java)?.registerEventListener(eventType, eventSource) { + latch.countDown() + Thread.sleep(5000) + } - eventHub.getExtensionContainer(Extension2::class.java)?.registerEventListener( - eventType, eventSource, - { - latch.countDown() - }, - null - ) + eventHub.getExtensionContainer(Extension2::class.java)?.registerEventListener(eventType, eventSource) { + latch.countDown() + } eventHub.start() eventHub.dispatch(testEvent) From 7bbe3c56fb45288a30b5a834cc1ee628b442c15a Mon Sep 17 00:00:00 2001 From: praveek Date: Thu, 14 Jul 2022 15:27:59 -0700 Subject: [PATCH 107/476] Review comments and missing doc comments --- .../com/adobe/marketing/mobile/Extension.java | 2 +- .../mobile/SharedStateResolution.java | 9 +++++++++ .../marketing/mobile/SharedStateResolver.java | 10 +++++++++- .../marketing/mobile/SharedStateResult.java | 8 ++++++++ .../marketing/mobile/SharedStateStatus.java | 3 +++ .../mobile/internal/eventhub/EventHub.kt | 20 +++++++++++++++---- .../eventhub/EventListenerContainer.kt | 2 +- 7 files changed, 47 insertions(+), 7 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Extension.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Extension.java index 61ad45f44..752c57d83 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Extension.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Extension.java @@ -93,7 +93,7 @@ protected void onUnexpectedError(final ExtensionUnexpectedError extensionUnexpec * @param event {@link Event} that will be processed next * @return {@code boolean} to denote if event processing should continue for this `Extension` */ - public boolean readyForEvent(Event event) { return true; } + public boolean readyForEvent(final Event event) { return true; } /** * This provides the services the extension will need. diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResolution.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResolution.java index 13ce8908e..f851d9e71 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResolution.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResolution.java @@ -11,7 +11,16 @@ package com.adobe.marketing.mobile; +/** + * Type representing the resolution of an extension's `SharedState` + */ public enum SharedStateResolution { + /** + * LAST_SET will resolve for the last set shared state + */ LAST_SET, + /** + * ANY will resolve for the last shared state indiscriminately + */ ANY } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResolver.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResolver.java index b3750b8a0..821af94e3 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResolver.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResolver.java @@ -13,6 +13,14 @@ import java.util.Map; +/** + * A `SharedStateResolver` which is invoked to set pending the `SharedState` versioned at `Event` + */ +@FunctionalInterface public interface SharedStateResolver { - void resolve(Map state); + /** + * + * @param state A Map containing data to resolve pending shared state. + */ + void resolve(final Map state); } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResult.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResult.java index eb6d70a32..a7f4ba936 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResult.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateResult.java @@ -13,10 +13,18 @@ import java.util.Map; +/** + * Contains the status and value for a given shared state + */ public class SharedStateResult { public final SharedStateStatus status; public final Map value; + /** + * Creates a new shared state result with given status and value + * @param status status of the shared state + * @param value value of the shared state + */ public SharedStateResult(SharedStateStatus status, Map value) { this.status = status; this.value = value; diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateStatus.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateStatus.java index 6ad85711b..b6d529970 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateStatus.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/SharedStateStatus.java @@ -11,6 +11,9 @@ package com.adobe.marketing.mobile; +/** + * Type representing the state of an extension's `SharedState` + */ public enum SharedStateStatus { SET, PENDING, diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index 318db6e3b..a8930552b 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -153,8 +153,8 @@ internal class EventHub { /** * Registers a new `Extension` to the `EventHub`. This `Extension` must extends `Extension` class * - * @property extensionClass The class of extension to register - * @property completion Invoked when the extension has been registered or failed to register + * @param extensionClass The class of extension to register + * @param completion Invoked when the extension has been registered or failed to register */ fun registerExtension(extensionClass: Class?, completion: (error: EventHubError) -> Unit) { eventHubExecutor.submit { @@ -176,8 +176,8 @@ internal class EventHub { /** * Unregisters the extension from the `EventHub` if registered - * @property extensionClass The class of extension to unregister - * @property completion Invoked when the extension has been unregistered or failed to unregister + * @param extensionClass The class of extension to unregister + * @param completion Invoked when the extension has been unregistered or failed to unregister */ fun unregisterExtension(extensionClass: Class?, completion: ((error: EventHubError) -> Unit)) { eventHubExecutor.submit { @@ -194,6 +194,12 @@ internal class EventHub { } } + /** + * Registers an event listener which will be invoked when the response event to trigger event is dispatched + * @param triggerEvent An [Event] which will trigger a response event + * @param timeoutMS A timeout in milliseconds, if the response listener is not invoked within the timeout, then the `EventHub` invokes the fail method. + * @param listener An [AdobeCallbackWithError] which will be invoked whenever the [EventHub] receives the response [Event] for trigger event + */ fun registerResponseListener(triggerEvent: Event, timeoutMS: Long, callback: AdobeCallbackWithError) { eventHubExecutor.submit { val triggerEventId = triggerEvent.uniqueIdentifier @@ -218,6 +224,12 @@ internal class EventHub { } } + /** + * Registers an [EventListener] which will be invoked whenever a event with matched type and source is dispatched + * @param type A String indicating the event type the current listener is listening for + * @param source A `String` indicating the event source the current listener is listening for + * @param listener An [AdobeCallback] which will be invoked whenever the [EventHub] receives a event with matched type and source + */ fun registerListener(eventType: String, eventSource: String, listener: AdobeCallback) { eventHubExecutor.submit { val eventHubContainer = getExtensionContainer(EventHubPlaceholderExtension::class.java) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventListenerContainer.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventListenerContainer.kt index cd67bd791..519bf2639 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventListenerContainer.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventListenerContainer.kt @@ -42,7 +42,7 @@ internal class ResponseListenerContainer( } catch (ex: Exception) { MobileCore.log( LoggingMode.DEBUG, - "OneTimeListenerContainer", + "ResponseListenerContainer", "Exception thrown for EventId ${event.uniqueIdentifier}. $ex" ) } From 2f1dbe62e297f750387df124d717910d7d0258fe Mon Sep 17 00:00:00 2001 From: praveek Date: Thu, 14 Jul 2022 15:29:44 -0700 Subject: [PATCH 108/476] Remove trailing comma --- .../com/adobe/marketing/mobile/internal/eventhub/EventHub.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt index a8930552b..763e6d12a 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/EventHub.kt @@ -371,7 +371,7 @@ internal class EventHub { fun clearSharedState( sharedStateType: SharedStateType, extensionName: String?, - errorCallback: ExtensionErrorCallback?, + errorCallback: ExtensionErrorCallback? ): Boolean { val clearSharedStateCallable: Callable = Callable { if (extensionName.isNullOrEmpty() || extensionName.isBlank()) { From 5e6a50164774de75eedb1b90bd30195768058494 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Mon, 18 Jul 2022 14:51:13 -0700 Subject: [PATCH 109/476] change rules engine to use new ExtensionApi --- code/android-core-library/build.gradle | 3 +- .../rulesengine/LaunchRulesConsequence.kt | 18 +- .../rulesengine/LaunchRulesEvaluator.kt | 25 +- .../launch/rulesengine/LaunchTokenFinder.kt | 14 +- .../LaunchRulesConsequenceTests.kt | 199 ++++++------- .../LaunchRulesEngineModuleTests.kt | 276 +++++++++++------- .../rulesengine/LaunchRulesEvaluatorTests.kt | 20 +- .../rulesengine/LaunchTokenFinderTest.kt | 69 +++-- 8 files changed, 358 insertions(+), 266 deletions(-) diff --git a/code/android-core-library/build.gradle b/code/android-core-library/build.gradle index a2f87ae7e..ce68f76c7 100644 --- a/code/android-core-library/build.gradle +++ b/code/android-core-library/build.gradle @@ -91,7 +91,7 @@ task jacocoTestReport(type: JacocoReport, dependsOn: ['testPhoneDebugUnitTest',' android.libraryVariants.all { variant -> tasks.withType(Javadoc) { task -> - + source = [android.sourceSets.main.java.sourceFiles, android.sourceSets.phone.java.sourceFiles] ext.androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar" @@ -131,6 +131,7 @@ dependencies { androidTestImplementation "com.android.support.test:rules:1.0.2" androidTestImplementation 'androidx.test:rules:1.1.1' androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_version" } repositories { diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt index 0048fef3d..7a8ffc4b2 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequence.kt @@ -90,7 +90,13 @@ class LaunchRulesConsequence( logTag, " Generating new dispatch consequence result event $dispatchEvent" ) - MobileCore.dispatchEvent(dispatchEvent) { + if (extensionApi.dispatch(dispatchEvent)) { + MobileCore.log( + LoggingMode.VERBOSE, + logTag, + "Successfully dispatched consequence result event" + ) + } else { MobileCore.log( LoggingMode.WARNING, logTag, @@ -106,11 +112,17 @@ class LaunchRulesConsequence( logTag, "Generating new consequence event $consequenceEvent" ) - MobileCore.dispatchEvent(consequenceEvent) { + if (extensionApi.dispatch(consequenceEvent)) { + MobileCore.log( + LoggingMode.VERBOSE, + logTag, + "Successfully dispatched consequence result event" + ) + } else { MobileCore.log( LoggingMode.WARNING, logTag, - "An error occurred when dispatching consequence result event" + "An error occurred when dispatching dispatch consequence result event" ) } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt index 506efc4e8..521629df4 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluator.kt @@ -19,7 +19,7 @@ import com.adobe.marketing.mobile.MobileCore internal class LaunchRulesEvaluator( private val name: String, private val launchRulesEngine: LaunchRulesEngine, - extensionApi: ExtensionApi + private val extensionApi: ExtensionApi ) : EventPreprocessor { private var cachedEvents: MutableList? = mutableListOf() @@ -70,17 +70,22 @@ internal class LaunchRulesEvaluator( fun replaceRules(rules: List?) { if (rules == null) return launchRulesEngine.replaceRules(rules) - MobileCore.dispatchEvent( - Event.Builder( - name, - EVENT_TYPE_RULES_ENGINE, - EVENT_SOURCE_REQUEST_RESET - ).build() - ) { extensionError -> + val dispatchEvent = Event.Builder( + name, + EVENT_TYPE_RULES_ENGINE, + EVENT_SOURCE_REQUEST_RESET + ).build() + if (extensionApi.dispatch(dispatchEvent)) { MobileCore.log( - LoggingMode.ERROR, + LoggingMode.VERBOSE, logTag, - "Failed to reprocess cached events, caused by the error: ${extensionError.errorName}" + "Successfully dispatched consequence result event" + ) + } else { + MobileCore.log( + LoggingMode.WARNING, + logTag, + "An error occurred when dispatching dispatch consequence result event" ) } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt index efa5753ef..e6c650b41 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinder.kt @@ -15,6 +15,7 @@ import com.adobe.marketing.mobile.Event import com.adobe.marketing.mobile.ExtensionApi import com.adobe.marketing.mobile.LoggingMode import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.SharedStateResolution import com.adobe.marketing.mobile.internal.utility.TimeUtil import com.adobe.marketing.mobile.internal.utility.flattening import com.adobe.marketing.mobile.internal.utility.serializeToQueryString @@ -120,13 +121,12 @@ internal class LaunchTokenFinder(val event: Event, val extensionApi: ExtensionAp return null } val (sharedStateName, dataKeyName) = sharedStateKeyString.split(SHARED_STATE_KEY_DELIMITER) - val sharedStateMap = extensionApi.getSharedEventState(sharedStateName, event) { - MobileCore.log( - LoggingMode.DEBUG, - LOG_TAG, - String.format("Unable to replace the token %s, token not found in shared state for the event", key) - ) - }?.flattening() + val sharedStateMap = extensionApi.getSharedState( + sharedStateName, + event, + false, + SharedStateResolution.ANY + )?.value?.flattening() if (sharedStateMap.isNullOrEmpty() || dataKeyName.isBlank() || !sharedStateMap.containsKey(dataKeyName)) { return null } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequenceTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequenceTests.kt index eef93007a..a4dbcd67e 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequenceTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesConsequenceTests.kt @@ -12,26 +12,28 @@ package com.adobe.marketing.mobile.launch.rulesengine import com.adobe.marketing.mobile.Event import com.adobe.marketing.mobile.ExtensionApi -import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.SharedStateResult +import com.adobe.marketing.mobile.SharedStateStatus import com.adobe.marketing.mobile.launch.rulesengine.json.JSONRulesParser import com.adobe.marketing.mobile.test.utility.readTestResources import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers +import org.mockito.Mockito +import org.mockito.Mockito.`when` import org.mockito.Mockito.any +import org.mockito.Mockito.anyBoolean +import org.mockito.Mockito.anyString import org.mockito.Mockito.never import org.mockito.Mockito.times -import org.powermock.api.mockito.PowerMockito -import org.powermock.core.classloader.annotations.PrepareForTest -import org.powermock.modules.junit4.PowerMockRunner +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnitRunner import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull -@RunWith(PowerMockRunner::class) -@PrepareForTest(ExtensionApi::class, MobileCore::class) +@RunWith(MockitoJUnitRunner.Silent::class) class LaunchRulesConsequenceTests { private lateinit var extensionApi: ExtensionApi @@ -51,8 +53,7 @@ class LaunchRulesConsequenceTests { @Before fun setup() { - extensionApi = PowerMockito.mock(ExtensionApi::class.java) - PowerMockito.mockStatic(MobileCore::class.java) + extensionApi = Mockito.mock(ExtensionApi::class.java) launchRulesEngine = LaunchRulesEngine(extensionApi) launchRulesConsequence = LaunchRulesConsequence(extensionApi) } @@ -80,19 +81,22 @@ class LaunchRulesConsequenceTests { // } // } // -------------------------------------- - PowerMockito.`when`(extensionApi.getSharedEventState(ArgumentMatchers.anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "carriername" to "AT&T" + `when`(extensionApi.getSharedState(anyString(), any(), anyBoolean(), any())).thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "AT&T" + ) ) ) ) + val matchedRules = launchRulesEngine.process(defaultEvent) val processedEvent = launchRulesConsequence.evaluateRulesConsequence(defaultEvent, matchedRules) // / Then: no consequence event will be dispatched - PowerMockito.verifyStatic(MobileCore::class.java, never()) - MobileCore.dispatchEvent(any(), any()) + verify(extensionApi, never()).dispatch(any()) val attachedData = processedEvent?.eventData?.get("attached_data") as Map<*, *> @@ -118,19 +122,11 @@ class LaunchRulesConsequenceTests { resetRulesEngine("rules_module_tests/consequence_rules_testAttachData_invalidJson.json") // / When: evaluating a launch event - PowerMockito.`when`(extensionApi.getSharedEventState(ArgumentMatchers.anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "carriername" to "AT&T" - ) - ) - ) val matchedRules = launchRulesEngine.process(defaultEvent) val processedEvent = launchRulesConsequence.evaluateRulesConsequence(defaultEvent, matchedRules) // / Then: no consequence event will be dispatched - PowerMockito.verifyStatic(MobileCore::class.java, never()) - MobileCore.dispatchEvent(any(), any()) + verify(extensionApi, never()).dispatch(any()) // / Then: no data should not be attached to original launch event val attachedData = processedEvent?.eventData?.get("attached_data") @@ -160,20 +156,23 @@ class LaunchRulesConsequenceTests { // } // } // -------------------------------------- - PowerMockito.`when`(extensionApi.getSharedEventState(ArgumentMatchers.anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "carriername" to "AT&T", - "launches" to 2 + `when`(extensionApi.getSharedState(anyString(), any(), anyBoolean(), any())).thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "AT&T", + "launches" to 2 + ) ) ) ) + val matchedRules = launchRulesEngine.process(defaultEvent) val processedEvent = launchRulesConsequence.evaluateRulesConsequence(defaultEvent, matchedRules) // / Then: no consequence event will be dispatched - PowerMockito.verifyStatic(MobileCore::class.java, never()) - MobileCore.dispatchEvent(any(), any()) + verify(extensionApi, never()).dispatch(any()) // / Then: "launchevent" should be removed from event data val lifecycleContextData = processedEvent?.eventData?.get("lifecyclecontextdata") as Map<*, *> @@ -198,20 +197,11 @@ class LaunchRulesConsequenceTests { resetRulesEngine("rules_module_tests/consequence_rules_testModifyData_invalidJson.json") // / When: evaluating a launch event - PowerMockito.`when`(extensionApi.getSharedEventState(ArgumentMatchers.anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "carriername" to "AT&T", - "launches" to 2 - ) - ) - ) val matchedRules = launchRulesEngine.process(defaultEvent) val processedEvent = launchRulesConsequence.evaluateRulesConsequence(defaultEvent, matchedRules) // / Then: no consequence event will be dispatched - PowerMockito.verifyStatic(MobileCore::class.java, never()) - MobileCore.dispatchEvent(any(), any()) + verify(extensionApi, never()).dispatch(any()) // / Then: "launchevent" should not be removed from event data val lifecycleContextData = processedEvent?.eventData?.get("lifecyclecontextdata") as Map<*, *> @@ -231,6 +221,7 @@ class LaunchRulesConsequenceTests { // } // -------------------------------------- resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventCopy.json") + `when`(extensionApi.dispatch(any())).thenReturn(true) val event = Event.Builder( "Application Launch", @@ -245,8 +236,7 @@ class LaunchRulesConsequenceTests { // / Then: One consequence event will be dispatched val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) - PowerMockito.verifyStatic(MobileCore::class.java, times(1)) - MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + verify(extensionApi, times(1)).dispatch(dispatchedEventCaptor.capture()) assertEquals("com.adobe.eventtype.edge", dispatchedEventCaptor.value.type) assertEquals("com.adobe.eventsource.requestcontent", dispatchedEventCaptor.value.source) assertEquals(event.eventData, dispatchedEventCaptor.value.eventData) @@ -267,6 +257,8 @@ class LaunchRulesConsequenceTests { // } // -------------------------------------- resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventCopy.json") + `when`(extensionApi.dispatch(any())).thenReturn(true) + val event = Event.Builder( "Application Launch", "com.adobe.eventType.lifecycle", @@ -280,8 +272,7 @@ class LaunchRulesConsequenceTests { // / Then: One consequence event will be dispatched val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) - PowerMockito.verifyStatic(MobileCore::class.java, times(1)) - MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + verify(extensionApi, times(1)).dispatch(dispatchedEventCaptor.capture()) assertEquals("com.adobe.eventtype.edge", dispatchedEventCaptor.value.type) assertEquals("com.adobe.eventsource.requestcontent", dispatchedEventCaptor.value.source) assertEquals(mapOf(), dispatchedEventCaptor.value.eventData) @@ -308,6 +299,8 @@ class LaunchRulesConsequenceTests { // -------------------------------------- resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventNewData.json") + `when`(extensionApi.dispatch(any())).thenReturn(true) + val event = Event.Builder( "Application Launch", "com.adobe.eventType.lifecycle", @@ -321,8 +314,7 @@ class LaunchRulesConsequenceTests { // / Then: One consequence event will be dispatched val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) - PowerMockito.verifyStatic(MobileCore::class.java, times(1)) - MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + verify(extensionApi, times(1)).dispatch(dispatchedEventCaptor.capture()) assertEquals("com.adobe.eventtype.edge", dispatchedEventCaptor.value.type) assertEquals("com.adobe.eventsource.requestcontent", dispatchedEventCaptor.value.source) assertEquals("value", dispatchedEventCaptor.value.eventData["key"]) @@ -345,6 +337,8 @@ class LaunchRulesConsequenceTests { // -------------------------------------- resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventNewNoData.json") + `when`(extensionApi.dispatch(any())).thenReturn(true) + val event = Event.Builder( "Application Launch", "com.adobe.eventType.lifecycle", @@ -358,8 +352,7 @@ class LaunchRulesConsequenceTests { // / Then: One consequence event will be dispatched val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) - PowerMockito.verifyStatic(MobileCore::class.java, times(1)) - MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + verify(extensionApi, times(1)).dispatch(dispatchedEventCaptor.capture()) assertEquals("com.adobe.eventtype.edge", dispatchedEventCaptor.value.type) assertEquals("com.adobe.eventsource.requestcontent", dispatchedEventCaptor.value.source) assertEquals(mapOf(), dispatchedEventCaptor.value.eventData) @@ -393,9 +386,7 @@ class LaunchRulesConsequenceTests { val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) // / Then: No consequence event will be dispatched - val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) - PowerMockito.verifyStatic(MobileCore::class.java, never()) - MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + verify(extensionApi, never()).dispatch(any()) // verify original event is unchanged assertEquals(event, processedEvent) @@ -425,9 +416,7 @@ class LaunchRulesConsequenceTests { val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) // / Then: No consequence event will be dispatched - val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) - PowerMockito.verifyStatic(MobileCore::class.java, never()) - MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + verify(extensionApi, never()).dispatch(any()) // verify original event is unchanged assertEquals(event, processedEvent) @@ -456,9 +445,7 @@ class LaunchRulesConsequenceTests { val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) // / Then: No consequence event will be dispatched - val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) - PowerMockito.verifyStatic(MobileCore::class.java, never()) - MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + verify(extensionApi, never()).dispatch(any()) // verify original event is unchanged assertEquals(event, processedEvent) @@ -487,9 +474,7 @@ class LaunchRulesConsequenceTests { val processedEvent = launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) // / Then: No consequence event will be dispatched - val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) - PowerMockito.verifyStatic(MobileCore::class.java, never()) - MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + verify(extensionApi, never()).dispatch(any()) // verify original event is unchanged assertEquals(event, processedEvent) @@ -530,6 +515,8 @@ class LaunchRulesConsequenceTests { // } // -------------------------------------- resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventChain.json") + `when`(extensionApi.dispatch(any())).thenReturn(true) + val event = Event.Builder( "Edge Request", "com.adobe.eventType.edge", @@ -542,15 +529,13 @@ class LaunchRulesConsequenceTests { val matchedRules = launchRulesEngine.process(event) launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) - PowerMockito.verifyStatic(MobileCore::class.java, times(1)) - MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + verify(extensionApi, times(1)).dispatch(dispatchedEventCaptor.capture()) // Process dispatched event; dispatch chain count = 1 // Expect dispatch to not be called max allowed chained events is 1 val matchedRulesDDispatchedEvent = launchRulesEngine.process(dispatchedEventCaptor.value) launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value, matchedRulesDDispatchedEvent) - PowerMockito.verifyStatic(MobileCore::class.java, times(1)) - MobileCore.dispatchEvent(any(), any()) + verify(extensionApi, times(1)).dispatch(dispatchedEventCaptor.capture()) } @Test @@ -588,6 +573,8 @@ class LaunchRulesConsequenceTests { // } // -------------------------------------- resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventChain.json") + `when`(extensionApi.dispatch(any())).thenReturn(true) + val event = Event.Builder( "Edge Request", "com.adobe.eventType.edge", @@ -600,28 +587,24 @@ class LaunchRulesConsequenceTests { val matchedRules = launchRulesEngine.process(event) launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) - PowerMockito.verifyStatic(MobileCore::class.java, times(1)) - MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + verify(extensionApi, times(1)).dispatch(dispatchedEventCaptor.capture()) // Process dispatched event; dispatch chain count = 1 // Expect dispatch to fail as max allowed chained events is 1 val matchedRulesDispatchedEvent = launchRulesEngine.process(dispatchedEventCaptor.value) launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value, matchedRulesDispatchedEvent) - PowerMockito.verifyStatic(MobileCore::class.java, times(1)) - MobileCore.dispatchEvent(any(), any()) + verify(extensionApi, times(1)).dispatch(any()) // Process dispatched event; dispatch chain count = 1 // Expect event to be processed as if first time launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) - PowerMockito.verifyStatic(MobileCore::class.java, times(2)) - MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + verify(extensionApi, times(2)).dispatch(dispatchedEventCaptor.capture()) // Process dispatched event; dispatch chain count = 1 // Expect dispatch to fail as max allowed chained events is 1 val matchedRulesDispatchedEvent2 = launchRulesEngine.process(dispatchedEventCaptor.value) launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value, matchedRulesDispatchedEvent2) - PowerMockito.verifyStatic(MobileCore::class.java, times(2)) - MobileCore.dispatchEvent(any(), any()) + verify(extensionApi, times(2)).dispatch(any()) } @Test @@ -659,6 +642,8 @@ class LaunchRulesConsequenceTests { // } // -------------------------------------- resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventChain.json") + `when`(extensionApi.dispatch(any())).thenReturn(true) + val event = Event.Builder( "Edge Request", "com.adobe.eventType.edge", @@ -671,28 +656,24 @@ class LaunchRulesConsequenceTests { val matchedRules = launchRulesEngine.process(event) launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) - PowerMockito.verifyStatic(MobileCore::class.java, times(1)) - MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + verify(extensionApi, times(1)).dispatch(dispatchedEventCaptor.capture()) // Process dispatched event; dispatch chain count = 1 // Expect dispatch to fail as max allowed chained events is 1 val matchedRulesDispatchedEvent = launchRulesEngine.process(dispatchedEventCaptor.value) launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value, matchedRulesDispatchedEvent) - PowerMockito.verifyStatic(MobileCore::class.java, times(1)) - MobileCore.dispatchEvent(any(), any()) + verify(extensionApi, times(1)).dispatch(any()) // Process dispatched event; dispatch chain count = 1 // Expect event to be processed as if first time launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value, matchedRulesDispatchedEvent) - PowerMockito.verifyStatic(MobileCore::class.java, times(2)) - MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + verify(extensionApi, times(2)).dispatch(dispatchedEventCaptor.capture()) // Process dispatched event; dispatch chain count = 1 // Expect dispatch to fail as max allowed chained events is 1 val matchedRulesDispatchedEvent2 = launchRulesEngine.process(dispatchedEventCaptor.value) launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.value, matchedRulesDispatchedEvent2) - PowerMockito.verifyStatic(MobileCore::class.java, times(2)) - MobileCore.dispatchEvent(any(), any()) + verify(extensionApi, times(2)).dispatch(any()) } @Test @@ -761,6 +742,7 @@ class LaunchRulesConsequenceTests { // } // -------------------------------------- resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventChain.json") + `when`(extensionApi.dispatch(any())).thenReturn(true) // Then: dispatch event to trigger rule 1 val eventEdgeRequest = Event.Builder( @@ -784,29 +766,25 @@ class LaunchRulesConsequenceTests { val matchedRulesEdgeRequestEvent = launchRulesEngine.process(eventEdgeRequest) launchRulesConsequence.evaluateRulesConsequence(eventEdgeRequest, matchedRulesEdgeRequestEvent) val dispatchedEventCaptor1: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) - PowerMockito.verifyStatic(MobileCore::class.java, times(1)) - MobileCore.dispatchEvent(dispatchedEventCaptor1.capture(), any()) + verify(extensionApi, times(1)).dispatch(dispatchedEventCaptor1.capture()) // Process launch event val matchedRulesLaunchEvent = launchRulesEngine.process(eventLaunch) launchRulesConsequence.evaluateRulesConsequence(eventLaunch, matchedRulesLaunchEvent) val dispatchedEventCaptor2: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) - PowerMockito.verifyStatic(MobileCore::class.java, times(2)) - MobileCore.dispatchEvent(dispatchedEventCaptor2.capture(), any()) + verify(extensionApi, times(2)).dispatch(dispatchedEventCaptor2.capture()) // Process first dispatched event; dispatch chain count = 1 // Expect dispatch to fail as max allowed chained events is 1 val matchedRulesDispatchEvent1 = launchRulesEngine.process(dispatchedEventCaptor1.value) launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor1.value, matchedRulesDispatchEvent1) - PowerMockito.verifyStatic(MobileCore::class.java, times(2)) - MobileCore.dispatchEvent(any(), any()) + verify(extensionApi, times(2)).dispatch(any()) // Process second dispatched event; dispatch chain count = 1 // Expect dispatch to fail as max allowed chained events is 1 val matchedRulesDispatchEvent2 = launchRulesEngine.process(dispatchedEventCaptor2.value) launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor2.value, matchedRulesDispatchEvent2) - PowerMockito.verifyStatic(MobileCore::class.java, times(2)) - MobileCore.dispatchEvent(any(), any()) + verify(extensionApi, times(2)).dispatch(any()) } @Test @@ -865,6 +843,7 @@ class LaunchRulesConsequenceTests { // } // -------------------------------------- resetRulesEngine("rules_module_tests/consequence_rules_testDispatchEventChain.json") + `when`(extensionApi.dispatch(any())).thenReturn(true) // Then: dispatch event which will trigger two launch rules val event = Event.Builder( @@ -879,22 +858,19 @@ class LaunchRulesConsequenceTests { val matchedRules = launchRulesEngine.process(event) launchRulesConsequence.evaluateRulesConsequence(event, matchedRules) val dispatchedEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) - PowerMockito.verifyStatic(MobileCore::class.java, times(2)) - MobileCore.dispatchEvent(dispatchedEventCaptor.capture(), any()) + verify(extensionApi, times(2)).dispatch(dispatchedEventCaptor.capture()) // Process dispatched event 1, expect 0 dispatch events // chain count = 1, which is max chained events val matchedRulesDispatchEvent1 = launchRulesEngine.process(dispatchedEventCaptor.allValues[0]) launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.allValues[0], matchedRulesDispatchEvent1) - PowerMockito.verifyStatic(MobileCore::class.java, times(2)) - MobileCore.dispatchEvent(any(), any()) + verify(extensionApi, times(2)).dispatch(any()) // Process dispatched event 2, expect 0 dispatch events // chain count = 1, which is max chained events val matchedRulesDispatchEvent2 = launchRulesEngine.process(dispatchedEventCaptor.allValues[1]) launchRulesConsequence.evaluateRulesConsequence(dispatchedEventCaptor.allValues[1], matchedRulesDispatchEvent2) - PowerMockito.verifyStatic(MobileCore::class.java, times(2)) - MobileCore.dispatchEvent(any(), any()) + verify(extensionApi, times(2)).dispatch(any()) } @Test @@ -909,20 +885,24 @@ class LaunchRulesConsequenceTests { resetRulesEngine("rules_module_tests/consequence_rules_testUrlenc.json") - PowerMockito.`when`(extensionApi.getSharedEventState(ArgumentMatchers.anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "carriername" to "x y" + `when`(extensionApi.getSharedState(anyString(), any(), anyBoolean(), any())).thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "x y" + ) ) ) ) + `when`(extensionApi.dispatch(any())).thenReturn(true) + val matchedRules = launchRulesEngine.process(defaultEvent) launchRulesConsequence.evaluateRulesConsequence(defaultEvent, matchedRules) val consequenceEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) - PowerMockito.verifyStatic(MobileCore::class.java, times(1)) - MobileCore.dispatchEvent(consequenceEventCaptor.capture(), any()) + verify(extensionApi, times(1)).dispatch(consequenceEventCaptor.capture()) assertEquals("com.adobe.eventtype.rulesengine", consequenceEventCaptor.value.type) assertEquals("com.adobe.eventsource.responsecontent", consequenceEventCaptor.value.source) @@ -943,11 +923,15 @@ class LaunchRulesConsequenceTests { // } // } resetRulesEngine("rules_module_tests/consequence_rules_testUrlenc_invalidFnName.json") - - PowerMockito.`when`(extensionApi.getSharedEventState(ArgumentMatchers.anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "carriername" to "x y" + `when`(extensionApi.dispatch(any())).thenReturn(true) + + `when`(extensionApi.getSharedState(anyString(), any(), anyBoolean(), any())).thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "x y" + ) ) ) ) @@ -956,8 +940,7 @@ class LaunchRulesConsequenceTests { launchRulesConsequence.evaluateRulesConsequence(defaultEvent, matchedRules) val consequenceEventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) - PowerMockito.verifyStatic(MobileCore::class.java, times(1)) - MobileCore.dispatchEvent(consequenceEventCaptor.capture(), any()) + verify(extensionApi, times(1)).dispatch(consequenceEventCaptor.capture()) assertEquals("com.adobe.eventtype.rulesengine", consequenceEventCaptor.value.type) assertEquals("com.adobe.eventsource.responsecontent", consequenceEventCaptor.value.source) diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngineModuleTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngineModuleTests.kt index 7fb6cec6a..594e80ce7 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngineModuleTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEngineModuleTests.kt @@ -3,6 +3,8 @@ package com.adobe.marketing.mobile.launch.rulesengine import com.adobe.marketing.mobile.Event import com.adobe.marketing.mobile.ExtensionApi import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.SharedStateResult +import com.adobe.marketing.mobile.SharedStateStatus import com.adobe.marketing.mobile.internal.eventhub.history.EventHistory import com.adobe.marketing.mobile.internal.eventhub.history.EventHistoryRequest import com.adobe.marketing.mobile.internal.eventhub.history.EventHistoryResultHandler @@ -13,7 +15,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyString -import org.mockito.BDDMockito +import org.mockito.Mockito import org.powermock.api.mockito.PowerMockito import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.modules.junit4.PowerMockRunner @@ -41,8 +43,7 @@ class LaunchRulesEngineModuleTests { @Before fun setup() { - extensionApi = PowerMockito.mock(ExtensionApi::class.java) - PowerMockito.mockStatic(MobileCore::class.java) + extensionApi = Mockito.mock(ExtensionApi::class.java) launchRulesEngine = LaunchRulesEngine(extensionApi) } @@ -53,13 +54,17 @@ class LaunchRulesEngineModuleTests { val rules = JSONRulesParser.parse(json) assertNotNull(rules) launchRulesEngine.replaceRules(rules) - PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "carriername" to "AT&T" + Mockito.`when`(extensionApi.getSharedState(anyString(), any(), Mockito.anyBoolean(), any())) + .thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "AT&T" + ) + ) ) ) - ) val matchedRules = launchRulesEngine.process(defaultEvent) assertEquals(1, matchedRules.size) assertEquals(1, matchedRules[0].consequenceList.size) @@ -88,7 +93,8 @@ class LaunchRulesEngineModuleTests { TODO("Not yet implemented") } } - BDDMockito.given(MobileCore.getEventHistory()).willReturn(eventHistory) + PowerMockito.mockStatic(MobileCore::class.java) + PowerMockito.`when`(MobileCore.getEventHistory()).thenReturn(eventHistory) val json = readTestResources("rules_module_tests/rules_testHistory.json") assertNotNull(json) val rules = JSONRulesParser.parse(json) @@ -104,13 +110,17 @@ class LaunchRulesEngineModuleTests { val rules = JSONRulesParser.parse(json) assertNotNull(rules) launchRulesEngine.replaceRules(rules) - PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "carriername" to "Verizon" + Mockito.`when`(extensionApi.getSharedState(anyString(), any(), Mockito.anyBoolean(), any())) + .thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "Verizon" + ) + ) ) ) - ) assertEquals(0, launchRulesEngine.process(defaultEvent).size) } @@ -121,13 +131,17 @@ class LaunchRulesEngineModuleTests { val rules = JSONRulesParser.parse(json) assertNotNull(rules) launchRulesEngine.replaceRules(rules) - PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "carriername" to "AT&T" + Mockito.`when`(extensionApi.getSharedState(anyString(), any(), Mockito.anyBoolean(), any())) + .thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "AT&T" + ) + ) ) ) - ) val matchedRules = launchRulesEngine.process(defaultEvent) assertEquals(1, matchedRules.size) assertEquals(1, matchedRules[0].consequenceList.size) @@ -141,13 +155,17 @@ class LaunchRulesEngineModuleTests { val rules = JSONRulesParser.parse(json) assertNotNull(rules) launchRulesEngine.replaceRules(rules) - PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "launches" to 1 + Mockito.`when`(extensionApi.getSharedState(anyString(), any(), Mockito.anyBoolean(), any())) + .thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "launches" to 1 + ) + ) ) ) - ) assertEquals(0, launchRulesEngine.process(defaultEvent).size) } @@ -158,13 +176,17 @@ class LaunchRulesEngineModuleTests { val rules = JSONRulesParser.parse(json) assertNotNull(rules) launchRulesEngine.replaceRules(rules) - PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "launches" to 2 + Mockito.`when`(extensionApi.getSharedState(anyString(), any(), Mockito.anyBoolean(), any())) + .thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "launches" to 2 + ) + ) ) ) - ) val matchedRules = launchRulesEngine.process(defaultEvent) assertEquals(1, matchedRules.size) assertEquals(1, matchedRules[0].consequenceList.size) @@ -178,13 +200,17 @@ class LaunchRulesEngineModuleTests { val rules = JSONRulesParser.parse(json) assertNotNull(rules) launchRulesEngine.replaceRules(rules) - PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "launches" to 2 + Mockito.`when`(extensionApi.getSharedState(anyString(), any(), Mockito.anyBoolean(), any())) + .thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "launches" to 2 + ) + ) ) ) - ) assertEquals(0, launchRulesEngine.process(defaultEvent).size) } @@ -195,13 +221,17 @@ class LaunchRulesEngineModuleTests { val rules = JSONRulesParser.parse(json) assertNotNull(rules) launchRulesEngine.replaceRules(rules) - PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "launches" to 3 + Mockito.`when`(extensionApi.getSharedState(anyString(), any(), Mockito.anyBoolean(), any())) + .thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "launches" to 3 + ) + ) ) ) - ) val matchedRules = launchRulesEngine.process(defaultEvent) assertEquals(1, matchedRules.size) assertEquals(1, matchedRules[0].consequenceList.size) @@ -215,13 +245,17 @@ class LaunchRulesEngineModuleTests { val rules = JSONRulesParser.parse(json) assertNotNull(rules) launchRulesEngine.replaceRules(rules) - PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "launches" to 2 + Mockito.`when`(extensionApi.getSharedState(anyString(), any(), Mockito.anyBoolean(), any())) + .thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "launches" to 2 + ) + ) ) ) - ) val matchedRules = launchRulesEngine.process(defaultEvent) assertEquals(1, matchedRules.size) assertEquals(1, matchedRules[0].consequenceList.size) @@ -235,13 +269,17 @@ class LaunchRulesEngineModuleTests { val rules = JSONRulesParser.parse(json) assertNotNull(rules) launchRulesEngine.replaceRules(rules) - PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "launches" to 3 + Mockito.`when`(extensionApi.getSharedState(anyString(), any(), Mockito.anyBoolean(), any())) + .thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "launches" to 3 + ) + ) ) ) - ) assertEquals(0, launchRulesEngine.process(defaultEvent).size) } @@ -252,13 +290,17 @@ class LaunchRulesEngineModuleTests { val rules = JSONRulesParser.parse(json) assertNotNull(rules) launchRulesEngine.replaceRules(rules) - PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "launches" to 2 + Mockito.`when`(extensionApi.getSharedState(anyString(), any(), Mockito.anyBoolean(), any())) + .thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "launches" to 2 + ) + ) ) ) - ) val matchedRules = launchRulesEngine.process(defaultEvent) assertEquals(1, matchedRules.size) assertEquals(1, matchedRules[0].consequenceList.size) @@ -272,13 +314,17 @@ class LaunchRulesEngineModuleTests { val rules = JSONRulesParser.parse(json) assertNotNull(rules) launchRulesEngine.replaceRules(rules) - PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "launches" to 2 + Mockito.`when`(extensionApi.getSharedState(anyString(), any(), Mockito.anyBoolean(), any())) + .thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "launches" to 2 + ) + ) ) ) - ) assertEquals(0, launchRulesEngine.process(defaultEvent).size) } @@ -289,13 +335,17 @@ class LaunchRulesEngineModuleTests { val rules = JSONRulesParser.parse(json) assertNotNull(rules) launchRulesEngine.replaceRules(rules) - PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "launches" to 1 + Mockito.`when`(extensionApi.getSharedState(anyString(), any(), Mockito.anyBoolean(), any())) + .thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "launches" to 1 + ) + ) ) ) - ) val matchedRules = launchRulesEngine.process(defaultEvent) assertEquals(1, matchedRules.size) assertEquals(1, matchedRules[0].consequenceList.size) @@ -309,13 +359,17 @@ class LaunchRulesEngineModuleTests { val rules = JSONRulesParser.parse(json) assertNotNull(rules) launchRulesEngine.replaceRules(rules) - PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "carriername" to "AT&T" + Mockito.`when`(extensionApi.getSharedState(anyString(), any(), Mockito.anyBoolean(), any())) + .thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "AT&T" + ) + ) ) ) - ) assertEquals(0, launchRulesEngine.process(defaultEvent).size) } @@ -326,13 +380,17 @@ class LaunchRulesEngineModuleTests { val rules = JSONRulesParser.parse(json) assertNotNull(rules) launchRulesEngine.replaceRules(rules) - PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "carriername" to "Verizon" + Mockito.`when`(extensionApi.getSharedState(anyString(), any(), Mockito.anyBoolean(), any())) + .thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "Verizon" + ) + ) ) ) - ) val matchedRules = launchRulesEngine.process(defaultEvent) assertEquals(1, matchedRules.size) assertEquals(1, matchedRules[0].consequenceList.size) @@ -346,13 +404,17 @@ class LaunchRulesEngineModuleTests { val rules = JSONRulesParser.parse(json) assertNotNull(rules) launchRulesEngine.replaceRules(rules) - PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "carriername" to "AT&T" + Mockito.`when`(extensionApi.getSharedState(anyString(), any(), Mockito.anyBoolean(), any())) + .thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "AT&T" + ) + ) ) ) - ) assertEquals(0, launchRulesEngine.process(defaultEvent).size) } @@ -363,13 +425,17 @@ class LaunchRulesEngineModuleTests { val rules = JSONRulesParser.parse(json) assertNotNull(rules) launchRulesEngine.replaceRules(rules) - PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "carriername" to "Verizon" + Mockito.`when`(extensionApi.getSharedState(anyString(), any(), Mockito.anyBoolean(), any())) + .thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "Verizon" + ) + ) ) ) - ) val matchedRules = launchRulesEngine.process(defaultEvent) assertEquals(1, matchedRules.size) assertEquals(1, matchedRules[0].consequenceList.size) @@ -383,13 +449,17 @@ class LaunchRulesEngineModuleTests { val rules = JSONRulesParser.parse(json) assertNotNull(rules) launchRulesEngine.replaceRules(rules) - PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "carriername" to "AT&T" + Mockito.`when`(extensionApi.getSharedState(anyString(), any(), Mockito.anyBoolean(), any())) + .thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "carriername" to "AT&T" + ) + ) ) ) - ) assertEquals(0, launchRulesEngine.process(defaultEvent).size) } @@ -400,13 +470,17 @@ class LaunchRulesEngineModuleTests { val rules = JSONRulesParser.parse(json) assertNotNull(rules) launchRulesEngine.replaceRules(rules) - PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "key" to "value" + Mockito.`when`(extensionApi.getSharedState(anyString(), any(), Mockito.anyBoolean(), any())) + .thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "key" to "value" + ) + ) ) ) - ) val matchedRules = launchRulesEngine.process(defaultEvent) assertEquals(1, matchedRules.size) assertEquals(1, matchedRules[0].consequenceList.size) @@ -421,13 +495,17 @@ class LaunchRulesEngineModuleTests { val rules = JSONRulesParser.parse(json) assertNotNull(rules) launchRulesEngine.replaceRules(rules) - PowerMockito.`when`(extensionApi.getSharedEventState(anyString(), any(), any())).thenReturn( - mapOf( - "lifecyclecontextdata" to mapOf( - "launches" to 3 + Mockito.`when`(extensionApi.getSharedState(anyString(), any(), Mockito.anyBoolean(), any())) + .thenReturn( + SharedStateResult( + SharedStateStatus.SET, + mapOf( + "lifecyclecontextdata" to mapOf( + "launches" to 3 + ) + ) ) ) - ) val matchedRules = launchRulesEngine.process(defaultEvent) assertEquals(1, matchedRules.size) } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt index 355064411..d7d0d626b 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchRulesEvaluatorTests.kt @@ -12,27 +12,22 @@ package com.adobe.marketing.mobile.launch.rulesengine import com.adobe.marketing.mobile.Event import com.adobe.marketing.mobile.ExtensionApi -import com.adobe.marketing.mobile.MobileCore import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.any -import org.mockito.BDDMockito import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.powermock.api.mockito.PowerMockito -import org.powermock.core.classloader.annotations.PrepareForTest -import org.powermock.modules.junit4.PowerMockRunner +import org.mockito.junit.MockitoJUnitRunner import org.powermock.reflect.Whitebox import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull -@RunWith(PowerMockRunner::class) -@PrepareForTest(ExtensionApi::class, MobileCore::class) +@RunWith(MockitoJUnitRunner.Silent::class) class LaunchRulesEvaluatorTests { @Mock @@ -44,8 +39,7 @@ class LaunchRulesEvaluatorTests { @Before fun setup() { - extensionApi = PowerMockito.mock(ExtensionApi::class.java) - PowerMockito.mockStatic(MobileCore::class.java) + extensionApi = Mockito.mock(ExtensionApi::class.java) launchRulesEvaluator = LaunchRulesEvaluator("", launchRulesEngine, extensionApi) Whitebox.setInternalState(launchRulesEvaluator, "cachedEvents", cachedEvents) } @@ -69,6 +63,7 @@ class LaunchRulesEvaluatorTests { @Test fun `Reprocess cached events when rules are ready`() { + Mockito.`when`(extensionApi.dispatch(any())).thenReturn(true) repeat(10) { launchRulesEvaluator.process( Event.Builder("event-$it", "type", "source").build() @@ -76,8 +71,8 @@ class LaunchRulesEvaluatorTests { } assertEquals(10, cachedEvents.size) val eventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) - BDDMockito.given(MobileCore.dispatchEvent(eventCaptor.capture(), any())).willReturn(true) launchRulesEvaluator.replaceRules(listOf()) + verify(extensionApi, Mockito.times(1)).dispatch(eventCaptor.capture()) assertNotNull(eventCaptor.value) assertEquals("com.adobe.eventtype.rulesengine", eventCaptor.value.type) assertEquals("com.adobe.eventsource.requestreset", eventCaptor.value.source) @@ -87,6 +82,7 @@ class LaunchRulesEvaluatorTests { @Test fun `Reprocess cached events in the right order`() { + Mockito.`when`(extensionApi.dispatch(any())).thenReturn(true) repeat(10) { launchRulesEvaluator.process( Event.Builder("event-$it", "type", "source").build() @@ -94,8 +90,8 @@ class LaunchRulesEvaluatorTests { } assertEquals(10, cachedEvents.size) val eventCaptor: ArgumentCaptor = ArgumentCaptor.forClass(Event::class.java) - BDDMockito.given(MobileCore.dispatchEvent(eventCaptor.capture(), any())).willReturn(true) launchRulesEvaluator.replaceRules(listOf()) + verify(extensionApi, Mockito.times(1)).dispatch(eventCaptor.capture()) assertNotNull(eventCaptor.value) assertEquals("com.adobe.eventtype.rulesengine", eventCaptor.value.type) assertEquals("com.adobe.eventsource.requestreset", eventCaptor.value.source) @@ -120,7 +116,7 @@ class LaunchRulesEvaluatorTests { } assertEquals(10, cachedEvents.size) launchRulesEvaluator.replaceRules(null) - PowerMockito.verifyNoMoreInteractions(MobileCore::class.java) + Mockito.verifyNoInteractions(extensionApi) assertEquals(10, cachedEvents.size) } } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt index 391a75bc2..b250b240b 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt @@ -15,6 +15,8 @@ import com.adobe.marketing.mobile.BaseTest import com.adobe.marketing.mobile.Event import com.adobe.marketing.mobile.ExtensionApi import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.SharedStateResult +import com.adobe.marketing.mobile.SharedStateStatus import com.adobe.marketing.mobile.internal.utility.TimeUtil import org.junit.Assert.assertEquals import org.junit.Assert.assertNull @@ -22,20 +24,17 @@ import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers -import org.powermock.api.mockito.PowerMockito -import org.powermock.core.classloader.annotations.PrepareForTest -import org.powermock.modules.junit4.PowerMockRunner +import org.mockito.Mockito +import org.mockito.junit.MockitoJUnitRunner -@RunWith(PowerMockRunner::class) -@PrepareForTest(ExtensionApi::class) +@RunWith(MockitoJUnitRunner.Silent::class) class LaunchTokenFinderTest : BaseTest() { private lateinit var extensionApi: ExtensionApi @Before fun setup() { - extensionApi = PowerMockito.mock(ExtensionApi::class.java) + extensionApi = Mockito.mock(ExtensionApi::class.java) } @Test @@ -260,12 +259,18 @@ class LaunchTokenFinderTest : BaseTest() { // setup val testEvent = getDefaultEvent(null) val lcData = mapOf("analytics.contextData" to mutableMapOf("akey" to "avalue")) - PowerMockito.`when`(extensionApi.getSharedEventState( - ArgumentMatchers.eq("com.adobe.marketing.mobile.Analytics"), - ArgumentMatchers.any(), - ArgumentMatchers.any() - )).thenReturn( - lcData + Mockito.`when`( + extensionApi.getSharedState( + Mockito.eq("com.adobe.marketing.mobile.Analytics"), + Mockito.any(), + Mockito.anyBoolean(), + Mockito.any() + ) + ).thenReturn( + SharedStateResult( + SharedStateStatus.SET, + lcData + ) ) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test @@ -279,12 +284,18 @@ class LaunchTokenFinderTest : BaseTest() { // setup val testEvent = getDefaultEvent(null) val lcData = mapOf("analytics.contextData" to mapOf("akey" to "avalue")) - PowerMockito.`when`(extensionApi.getSharedEventState( - ArgumentMatchers.eq("com.adobe.marketing.mobile.Analytics"), - ArgumentMatchers.any(), - ArgumentMatchers.any() - )).thenReturn( - lcData + Mockito.`when`( + extensionApi.getSharedState( + Mockito.eq("com.adobe.marketing.mobile.Analytics"), + Mockito.any(), + Mockito.anyBoolean(), + Mockito.any() + ) + ).thenReturn( + SharedStateResult( + SharedStateStatus.SET, + lcData + ) ) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test @@ -297,13 +308,19 @@ class LaunchTokenFinderTest : BaseTest() { fun `get should return shared state list of the module on valid event`() { // setup val testEvent = getDefaultEvent(null) - val lcdata = mapOf("visitoridslist" to listOf("vid1", "vid2")) - PowerMockito.`when`(extensionApi.getSharedEventState( - ArgumentMatchers.eq("com.adobe.marketing.mobile.identity"), - ArgumentMatchers.any(), - ArgumentMatchers.any() - )).thenReturn( - lcdata + val lcData = mapOf("visitoridslist" to listOf("vid1", "vid2")) + Mockito.`when`( + extensionApi.getSharedState( + Mockito.eq("com.adobe.marketing.mobile.identity"), + Mockito.any(), + Mockito.anyBoolean(), + Mockito.any() + ) + ).thenReturn( + SharedStateResult( + SharedStateStatus.SET, + lcData + ) ) val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test From 83004f0945ad9d9866ffb873b64e8d6a6f815d8b Mon Sep 17 00:00:00 2001 From: Yansong Date: Wed, 20 Jul 2022 15:46:18 -0600 Subject: [PATCH 110/476] add a new method -> HitQueuing.handlePrivacyChange --- .../marketing/mobile/services/HitQueuing.java | 83 +++++++++++-------- .../mobile/services/PersistentHitQueue.java | 2 +- 2 files changed, 51 insertions(+), 34 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/HitQueuing.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/HitQueuing.java index 5e87cfb9b..9421b318d 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/HitQueuing.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/HitQueuing.java @@ -11,38 +11,55 @@ package com.adobe.marketing.mobile.services; +import com.adobe.marketing.mobile.MobilePrivacyStatus; + //Provides the functionality for Queuing Hits. -public interface HitQueuing { - - /** - * Queues a {@link DataEntity} to be processed - * @param entity the entity to be processed - * @return a boolean indication whether queuing the entity was successful or not - */ - boolean queue(DataEntity entity); - - /** - * Puts the Queue in non-suspended state and begin processing hits - */ - void beginProcessing(); - - /** - * Puts the Queue in suspended state and discontinue processing hits - */ - void suspend(); - - /** - * Removes all the persisted hits from the queue - */ - void clear(); - - /** - * Returns the number of items in the queue - */ - int count(); - - /** - * Close the current HitQueuing - */ - void close(); +public abstract class HitQueuing { + + /** + * Queues a {@link DataEntity} to be processed + * + * @param entity the entity to be processed + * @return a boolean indication whether queuing the entity was successful or not + */ + public abstract boolean queue(DataEntity entity); + + /** + * Puts the Queue in non-suspended state and begin processing hits + */ + public abstract void beginProcessing(); + + /** + * Puts the Queue in suspended state and discontinue processing hits + */ + public abstract void suspend(); + + /** + * Removes all the persisted hits from the queue + */ + public abstract void clear(); + + /** + * Returns the number of items in the queue + */ + public abstract int count(); + + /** + * Close the current HitQueuing + */ + public abstract void close(); + + private void handlePrivacyChange(MobilePrivacyStatus privacyStatus) { + switch (privacyStatus) { + case OPT_IN: + beginProcessing(); + break; + case OPT_OUT: + suspend(); + clear(); + break; + default: + suspend(); + } + } } diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/PersistentHitQueue.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/PersistentHitQueue.java index 89328a084..2ea4773a6 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/PersistentHitQueue.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/PersistentHitQueue.java @@ -20,7 +20,7 @@ * Provides functionality for asynchronous processing of hits in a synchronous manner * while providing the ability to retry hits. */ -public class PersistentHitQueue implements HitQueuing { +public class PersistentHitQueue extends HitQueuing { private final DataQueue queue; private final HitProcessing processor; From d60c42f59535028d727103c937e32627018affea Mon Sep 17 00:00:00 2001 From: Yansong Date: Wed, 20 Jul 2022 15:47:00 -0600 Subject: [PATCH 111/476] make method public --- .../java/com/adobe/marketing/mobile/services/HitQueuing.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/HitQueuing.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/HitQueuing.java index 9421b318d..799145e83 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/HitQueuing.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/services/HitQueuing.java @@ -49,7 +49,7 @@ public abstract class HitQueuing { */ public abstract void close(); - private void handlePrivacyChange(MobilePrivacyStatus privacyStatus) { + public void handlePrivacyChange(MobilePrivacyStatus privacyStatus) { switch (privacyStatus) { case OPT_IN: beginProcessing(); From 3c91f8040c9f8f7a68aa9e5c8ea68c9d7654f288 Mon Sep 17 00:00:00 2001 From: spoorthipujariadobe Date: Wed, 20 Jul 2022 18:20:28 -0700 Subject: [PATCH 112/476] Update platform timestamp with millisecond precision --- .../marketing/mobile/internal/utility/TimeUtil.kt | 12 ++++++++---- .../launch/rulesengine/LaunchTokenFinderTest.kt | 14 +++++++------- .../mobile/internal/utility/TimeUtilTest.java | 6 ++++++ 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/TimeUtil.kt b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/TimeUtil.kt index 3b16f9677..09dfcbc79 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/TimeUtil.kt +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/utility/TimeUtil.kt @@ -15,12 +15,12 @@ import java.text.DateFormat import java.text.SimpleDateFormat import java.util.Date import java.util.Locale +import java.util.TimeZone internal object TimeUtil { private const val MILLISECONDS_PER_SECOND = 1000L private const val ISO8601_DATE_FORMATTER_TIMEZONE_RFC822 = "yyyy-MM-dd'T'HH:mm:ssZZZ" - private const val ISO8601_DATE_FORMATTER_TIMEZONE_ISO8601 = "yyyy-MM-dd'T'HH:mm:ssXXX" - + private const val ISO8601_DATE_FORMATTER_TIMEZONE_UTC = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" /** * Gets current unix timestamp in seconds. * @@ -51,7 +51,7 @@ internal object TimeUtil { */ @JvmStatic fun getIso8601DateTimeZoneISO8601(): String? { - return getIso8601Date(Date(), ISO8601_DATE_FORMATTER_TIMEZONE_ISO8601) + return getIso8601Date(Date(), ISO8601_DATE_FORMATTER_TIMEZONE_UTC, TimeZone.getTimeZone("GMT")) } /** @@ -61,11 +61,15 @@ internal object TimeUtil { * @return ISO 8601 formatted date [String] */ @JvmStatic - fun getIso8601Date(date: Date?, format: String?): String? { + @JvmOverloads + fun getIso8601Date(date: Date?, format: String?, timeZone: TimeZone? = null): String? { // AMSDK-8374 - // we should explicitly ignore the device's locale when formatting an ISO 8601 timestamp val posixLocale = Locale(Locale.US.language, Locale.US.country, "POSIX") val iso8601Format: DateFormat = SimpleDateFormat(format, posixLocale) + if (timeZone != null) { + iso8601Format.timeZone = timeZone + } return iso8601Format.format(date ?: Date()) } } diff --git a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt index 391a75bc2..a649e1193 100644 --- a/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt +++ b/code/android-core-library/src/test/java/com/adobe/marketing/mobile/launch/rulesengine/LaunchTokenFinderTest.kt @@ -15,7 +15,7 @@ import com.adobe.marketing.mobile.BaseTest import com.adobe.marketing.mobile.Event import com.adobe.marketing.mobile.ExtensionApi import com.adobe.marketing.mobile.MobileCore -import com.adobe.marketing.mobile.internal.utility.TimeUtil +import kotlin.test.assertNotNull import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Assert.assertTrue @@ -79,29 +79,29 @@ class LaunchTokenFinderTest : BaseTest() { // test val result = launchTokenFinder.get("~timestampu") // verify - assertEquals(TimeUtil.getUnixTimeInSeconds().toString(), result) + assertNotNull(result) } @Test - fun `get should return current ISO8601 timestamp on valid event`() { + fun `get should return current ISO8601 timestamp when key is timestampz on valid event`() { // setup val testEvent = getDefaultEvent() val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test val result = launchTokenFinder.get("~timestampz") // verify - assertEquals(TimeUtil.getIso8601Date(), result) + assertNotNull(result) } @Test - fun `get should return current ISO8601 date timezone on valid event`() { + fun `get should return current ISO8601 in UTC with fractional seconds when key is timestampp on valid event`() { // setup val testEvent = getDefaultEvent() val launchTokenFinder = LaunchTokenFinder(testEvent, extensionApi) // test - val result = launchTokenFinder.get("~timestampp") + val result: String = launchTokenFinder.get("~timestampp") as String // verify - assertEquals(TimeUtil.getIso8601DateTimeZoneISO8601(), result) + assertTrue(result.matches(Regex("[0-9]{4}-[0-9]{2}-[0-9]{2}T([0-9]{2}:){2}[0-9]{2}.[0-9]{3}Z"))) } @Test diff --git a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/TimeUtilTest.java b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/TimeUtilTest.java index df20f3e4a..f9d2a39d5 100644 --- a/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/TimeUtilTest.java +++ b/code/android-core-library/src/test/kotlin/com/adobe/marketing/mobile/internal/utility/TimeUtilTest.java @@ -74,4 +74,10 @@ public void testGetIso8601Date_TimeZone_ISO8601_when_NullDate() { assertNotNull(formattedDate); assertTrue(formattedDate.matches(DATE_REGEX_TIMEZONE_ISO8601)); } + + @Test + public void testGetIso8601Date_TimeZone_ISO8601_returns_milliseconds_and_UTC() { + String formattedDate = TimeUtil.getIso8601DateTimeZoneISO8601(); + assertTrue(formattedDate.matches("[0-9]{4}-[0-9]{2}-[0-9]{2}T([0-9]{2}:){2}[0-9]{2}.[0-9]{3}Z")); + } } From ab25fd6e8d719500a4ce3b784aa39bb0f8037b40 Mon Sep 17 00:00:00 2001 From: praveek Date: Thu, 21 Jul 2022 20:30:18 -0700 Subject: [PATCH 113/476] Update shared state APIs --- .../adobe/marketing/mobile/EventSource.java | 2 +- .../com/adobe/marketing/mobile/EventType.java | 1 + .../com/adobe/marketing/mobile/Extension.java | 2 +- .../adobe/marketing/mobile/ExtensionApi.java | 62 +- .../mobile/internal/eventhub/EventHub.kt | 440 +++++---- .../internal/eventhub/EventHubConstants.kt | 6 + .../internal/eventhub/ExtensionContainer.kt | 256 +++--- .../mobile/internal/eventhub/SharedState.kt | 36 - .../internal/eventhub/SharedStateManager.kt | 212 ++--- .../marketing/mobile/ExtensionApiTests.java | 849 ------------------ .../mobile/internal/eventhub/EventHubTests.kt | 770 ++++++++-------- .../eventhub/ExtensionContainerTest.kt | 187 +--- .../eventhub/SharedStateManagerTest.kt | 242 ++--- 13 files changed, 1043 insertions(+), 2022 deletions(-) delete mode 100644 code/android-core-library/src/main/java/com/adobe/marketing/mobile/internal/eventhub/SharedState.kt delete mode 100644 code/android-core-library/src/test/java/com/adobe/marketing/mobile/ExtensionApiTests.java diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventSource.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventSource.java index e9386b988..d973253dc 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventSource.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventSource.java @@ -26,7 +26,7 @@ public final class EventSource { // Todo - Expose String constants. Remove 'TYPE' prefix after fixing build issues public static final String TYPE_WILDCARD = "com.adobe.eventSource._wildcard_"; - + public static final String TYPE_SHARED_STATE = "com.adobe.eventSource.sharedState"; private static final String ADOBE_PREFIX = "com.adobe.eventSource."; private static final Map knownSources = new HashMap(); diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventType.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventType.java index a47e29fd2..60837afda 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventType.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/EventType.java @@ -26,6 +26,7 @@ public final class EventType { // Todo - Expose String constants. Remove 'TYPE' prefix after fixing build issues public static final String TYPE_WILDCARD = "com.adobe.eventType._wildcard_"; + public static final String TYPE_HUB = "com.adobe.eventType.hub"; private static final String ADOBE_PREFIX = "com.adobe.eventType."; private static final Map knownTypes = new HashMap(); diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Extension.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Extension.java index 752c57d83..257356bb2 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Extension.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/Extension.java @@ -55,7 +55,7 @@ protected String getVersion() { /** * Called when the extension is registered by the core. - * Implementers can implement this method to clean up resources when the extension is released. + * Implementers can implement this method to setup event listeners and shared states. */ protected void onRegistered() { Log.debug(getLogTag(), "Extension registered successfully."); diff --git a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionApi.java b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionApi.java index c86fc3e5e..bd2681035 100644 --- a/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionApi.java +++ b/code/android-core-library/src/main/java/com/adobe/marketing/mobile/ExtensionApi.java @@ -38,6 +38,16 @@ public abstract boolean registerEventListener(final String eventType, */ public abstract boolean dispatch(final Event event); + /** + * Starts the `Event` queue for this extension + */ + public abstract void startEvents(); + + /** + * Stops the `Event` queue for this extension + */ + public abstract void stopEvents(); + // Shared state /** * Creates a new shared state for this extension. @@ -53,6 +63,7 @@ public abstract boolean registerEventListener(final String eventType, */ public abstract boolean createSharedState(final Map state, final Event event); + /** * Creates a pending shared state for this extension. *