From 5a6c272da1f7771f758518921e8a3891bf1a24d8 Mon Sep 17 00:00:00 2001 From: Oguzhan Unlu Date: Fri, 3 May 2019 00:41:33 +0300 Subject: [PATCH 1/6] Add support for global contexts (close #307) --- .../contexts/global/GlobalContextTest.java | 279 ++++++++++++++++++ .../snowplow/tracker/Tracker.java | 60 +++- .../global/ConditionalContextProvider.java | 19 ++ .../contexts/global/ContextFilter.java | 26 ++ .../contexts/global/ContextGenerator.java | 28 ++ .../contexts/global/ContextPrimitive.java | 19 ++ .../contexts/global/FilterProvider.java | 70 +++++ .../contexts/global/GlobalContext.java | 8 + .../contexts/global/GlobalContextUtils.java | 264 +++++++++++++++++ .../tracker/contexts/global/RuleSet.java | 66 +++++ .../contexts/global/RuleSetProvider.java | 70 +++++ .../tracker/payload/SelfDescribingJson.java | 55 +++- .../tracker/payload/TrackerPayload.java | 2 +- 13 files changed, 955 insertions(+), 11 deletions(-) create mode 100644 snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/contexts/global/GlobalContextTest.java create mode 100644 snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/ConditionalContextProvider.java create mode 100644 snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/ContextFilter.java create mode 100644 snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/ContextGenerator.java create mode 100644 snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/ContextPrimitive.java create mode 100644 snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/FilterProvider.java create mode 100644 snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/GlobalContext.java create mode 100644 snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/GlobalContextUtils.java create mode 100644 snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/RuleSet.java create mode 100644 snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/RuleSetProvider.java diff --git a/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/contexts/global/GlobalContextTest.java b/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/contexts/global/GlobalContextTest.java new file mode 100644 index 000000000..1d428f832 --- /dev/null +++ b/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/contexts/global/GlobalContextTest.java @@ -0,0 +1,279 @@ +package com.snowplowanalytics.snowplow.tracker.contexts.global; + +import android.test.AndroidTestCase; + +import com.snowplowanalytics.snowplow.tracker.DevicePlatforms; +import com.snowplowanalytics.snowplow.tracker.Emitter; +import com.snowplowanalytics.snowplow.tracker.Subject; +import com.snowplowanalytics.snowplow.tracker.Tracker; +import com.snowplowanalytics.snowplow.tracker.constants.Parameters; +import com.snowplowanalytics.snowplow.tracker.constants.TrackerConstants; +import com.snowplowanalytics.snowplow.tracker.payload.SelfDescribingJson; +import com.snowplowanalytics.snowplow.tracker.payload.TrackerPayload; +import com.snowplowanalytics.snowplow.tracker.utils.LogLevel; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static com.snowplowanalytics.snowplow.tracker.constants.TrackerConstants.SCHEMA_APPLICATION_INSTALL; + +public class GlobalContextTest extends AndroidTestCase { + + private Tracker tracker; + + private GlobalContext mobileContext; + private GlobalContext appContext; + + private RuleSet acceptRuleSet1; + private RuleSet rejectRuleSet1; + + @Override + protected void setUp() { + + rejectRuleSet1 = + new RuleSet(null, "iglu:com.snowplowanalytics.snowplow/*/jsonschema/1-0-1"); + + acceptRuleSet1 = + new RuleSet("iglu:com.snowplowanalytics.snowplow/*/jsonschema/*-*-*", null); + + mobileContext = new SelfDescribingJson("iglu:com.snowplowanalytics.snowplow/mobile_context/jsonschema/1-0-1"); + + appContext = new ContextGenerator() { + @Override + public String tag() { + return "appContext"; + } + + @Override + public SelfDescribingJson generate(TrackerPayload payload, String eventType, String eventSchema) { + return new SelfDescribingJson("iglu:com.snowplowanalytics.mobile/application/jsonschema/1-0-0"); + } + }; + + // prepare a tracker + Tracker.close(); + + Emitter emitter = new Emitter + .EmitterBuilder("testUrl", getContext()) + .tick(0) + .emptyLimit(0) + .build(); + + Subject subject = new Subject + .SubjectBuilder() + .context(getContext()) + .build(); + + tracker = new Tracker.TrackerBuilder(emitter, "GlobalContextTest", "myAppId", getContext()) + .subject(subject) + .platform(DevicePlatforms.ServerSideApp) + .base64(false) + .level(LogLevel.VERBOSE) + .threadCount(1) + .sessionContext(false) + .mobileContext(false) + .geoLocationContext(false) + .foregroundTimeout(5) + .backgroundTimeout(5) + .sessionCheckInterval(15) + .timeUnit(TimeUnit.SECONDS) + .build(); + } + + public void testIsValidRule() { + assertTrue(GlobalContextUtils.isValidRule(acceptRuleSet1.getAccept().get(0))); + assertFalse(GlobalContextUtils.isValidRule(acceptRuleSet1.getReject().get(0))); + assertTrue(GlobalContextUtils.isValidRule(rejectRuleSet1.getReject().get(0))); + assertFalse(GlobalContextUtils.isValidRule(rejectRuleSet1.getAccept().get(0))); + } + + public void testClearGlobalContexts() { + tracker.addGlobalContexts(Arrays.asList(mobileContext, appContext)); + assertEquals(tracker.getGlobalContexts().size(), 2); + tracker.clearGlobalContexts(); + assertEquals(tracker.getGlobalContexts().size(), 0); + } + + public void testMatchSchemaAgainstRuleSet() { + String mobileContext = "iglu:com.snowplowanalytics.snowplow/mobile_context/jsonschema/1-0-1"; + assertTrue(GlobalContextUtils.matchSchemaAgainstRuleSet(acceptRuleSet1, mobileContext)); + assertFalse(GlobalContextUtils.matchSchemaAgainstRuleSet(rejectRuleSet1, mobileContext)); + } + + public void testMatchSchemaAgainstRule() { + String rule1 = "iglu:com.snowplowanalytics.*/*/jsonschema/1-*-*"; + String rule2 = "iglu:com.*.*/*/jsonschema/1-*-*"; + String rule3 = "iglu:com.snowplowanalytics.*/payload_data/jsonschema/1-*-4"; + String rule4 = "iglu:com.snowplowanalytics.snowplow/*/jsonschema/*-*-*"; + String firstClassSchema = "iglu:com.snowplowanalytics.snowplow/payload_data/jsonschema/1-0-4"; + + assertTrue(GlobalContextUtils.matchSchemaAgainstRule(rule1, firstClassSchema)); + assertFalse(GlobalContextUtils.matchSchemaAgainstRule(rule2, firstClassSchema)); + assertFalse(GlobalContextUtils.matchSchemaAgainstRule(rule3, firstClassSchema)); + assertTrue(GlobalContextUtils.matchSchemaAgainstRule(rule4, firstClassSchema)); + } + + public void testGetUriSubparts() { + String uri = "iglu:com.snowplowanalytics.snowplow/mobile_context/jsonschema/1-0-1"; + String[] uriSubparts = GlobalContextUtils.getUriSubparts(uri); + assertEquals(uriSubparts[0], "com.snowplowanalytics.snowplow"); + assertEquals(uriSubparts[1], "mobile_context"); + assertEquals(uriSubparts[2], "1"); + assertEquals(uriSubparts[3], "0"); + assertEquals(uriSubparts[4], "1"); + + String rule = "iglu:com.snowplowanalytics.*/mobile_context/jsonschema/*-*-*"; + String[] ruleSubparts = GlobalContextUtils.getUriSubparts(rule); + assertEquals(ruleSubparts[0], "com.snowplowanalytics.*"); + assertEquals(ruleSubparts[1], "mobile_context"); + assertEquals(ruleSubparts[2], "*"); + assertEquals(ruleSubparts[3], "*"); + assertEquals(ruleSubparts[4], "*"); + } + + public void testValidateVersion() { + assertTrue(GlobalContextUtils.validateVersion(new String[]{"*", "*", "*"})); + assertTrue(GlobalContextUtils.validateVersion(new String[]{"1", "*", "*"})); + assertTrue(GlobalContextUtils.validateVersion(new String[]{"1", "0", "*"})); + assertTrue(GlobalContextUtils.validateVersion(new String[]{"1", "0", "0"})); + + assertFalse(GlobalContextUtils.validateVersion(new String[]{"1", "*", "0"})); + assertFalse(GlobalContextUtils.validateVersion(new String[]{"*", "0", "0"})); + assertFalse(GlobalContextUtils.validateVersion(new String[]{"*", "*", "0"})); + } + + public void testValidateVendor() { + // A valid vendor without wildcard is accepted + assertTrue(GlobalContextUtils.validateVendor("com.acme.marketing")); + + // A valid vendor with wildcard after the 2 leftmost sub-part is accepted + assertTrue(GlobalContextUtils.validateVendor("com.acme.*")); + + // A wildcard can not be used in the 2 leftmost sub-part of a vendor + assertFalse(GlobalContextUtils.validateVendor("*.acme.*")); + + // A vendor with asterisk out of order is rejected + assertFalse(GlobalContextUtils.validateVendor("com.acme.*.marketing")); + } + + public void testValidateVendorParts() { + assertTrue(GlobalContextUtils.validateVendorParts(new String[]{"com", "acme", "*"})); + assertTrue(GlobalContextUtils.validateVendorParts(new String[]{"com", "acme"})); + assertTrue(GlobalContextUtils.validateVendorParts(new String[]{"com", "acme", "marketing"})); + assertTrue(GlobalContextUtils.validateVendorParts(new String[]{"com", "acme", "marketing", "*", "*"})); + + assertFalse(GlobalContextUtils.validateVendorParts(new String[]{"com", "*", "marketing"})); + assertFalse(GlobalContextUtils.validateVendorParts(new String[]{"*", "acme"})); + assertFalse(GlobalContextUtils.validateVendorParts(new String[]{"com", "acme", "*", "en"})); + } + + public void testMatchVendor() { + assertTrue(GlobalContextUtils.matchVendor("com.acme.marketing", "com.acme.marketing")); + assertTrue(GlobalContextUtils.matchVendor("com.acme.*", "com.acme.marketing")); + + assertFalse(GlobalContextUtils.matchVendor("com.acme", "com.acme.marketing")); + assertFalse(GlobalContextUtils.matchVendor("*.*", "com.acme")); + assertFalse(GlobalContextUtils.matchVendor("*.*.*", "com.acme.marketing")); + } + + public void testMatchPart() { + assertFalse(GlobalContextUtils.matchPart(null, "")); + assertFalse(GlobalContextUtils.matchPart("", null)); + + assertTrue(GlobalContextUtils.matchPart("*", "com")); + assertTrue(GlobalContextUtils.matchPart("acme", "acme")); + } + + public void testComputeContextPrimitive() { + ArrayList computedGlobalContexts = new ArrayList<>(); + ContextPrimitive primitive = (ContextPrimitive) mobileContext; + TrackerPayload payload = new TrackerPayload(); + payload.add("e", "pv"); + String eventType = "pv"; + String eventSchema = TrackerConstants.SCHEMA_PAYLOAD_DATA; + + GlobalContextUtils.computeContextPrimitive(payload, computedGlobalContexts, primitive, eventType, eventSchema); + assertEquals(computedGlobalContexts.size(), 1); + assertEquals(computedGlobalContexts.get(0), primitive); + + final SelfDescribingJson appCtx = new SelfDescribingJson("iglu:com.snowplowanalytics.snowplow/application/jsonschema/1-0-1"); + ContextPrimitive primitive2 = new ContextGenerator() { + @Override + public String tag() { + return "primitive2"; + } + + @Override + public SelfDescribingJson generate(TrackerPayload payload, String eventType, String eventSchema) { + return appCtx; + } + }; + + GlobalContextUtils.computeContextPrimitive(payload, computedGlobalContexts, primitive2, eventType, eventSchema); + assertEquals(computedGlobalContexts.size(), 2); + assertEquals(computedGlobalContexts.get(1), appCtx); + } + + public void testGetSchema() { + TrackerPayload payload = new TrackerPayload(); + assertEquals(GlobalContextUtils.getSchema(payload), TrackerConstants.SCHEMA_PAYLOAD_DATA); + + SelfDescribingJson sdj = + new SelfDescribingJson("iglu:com.snowplowanalytics.snowplow/unstruct_event/jsonschema/1-0-0", + new SelfDescribingJson("iglu:com.snowplowanalytics.snowplow/link_click/jsonschema/1-0-1")); + payload.add(Parameters.UNSTRUCTURED, sdj.toString()); + + assertEquals(GlobalContextUtils.getSchema(payload), "iglu:com.snowplowanalytics.snowplow/link_click/jsonschema/1-0-1"); + + TrackerPayload payload2 = new TrackerPayload(); + String encodedPayload = "eyJzY2hlbWEiOiJpZ2x1OmNvbS5zbm93cGxvd2FuYWx5dGljcy5zbm93cGxvd1wvdW5zdHJ1Y3RfZXZlbn" + + "RcL2pzb25zY2hlbWFcLzEtMC0wIiwiZGF0YSI6eyJzY2hlbWEiOiJpZ2x1OmNvbS5zbm93cGxvd" + + "2FuYWx5dGljcy5zbm93cGxvd1wvbGlua19jbGlja1wvanNvbnNjaGVtYVwvMS0wLTEiLCJkYXRhIjp7fX19"; + payload2.add(Parameters.UNSTRUCTURED_ENCODED, encodedPayload); + + assertEquals(GlobalContextUtils.getSchema(payload2), "iglu:com.snowplowanalytics.snowplow/link_click/jsonschema/1-0-1"); + } + + public void testRemoveContext() { + + tracker.addGlobalContext( + new RuleSetProvider( + "ruleSetExample", + new RuleSet("iglu:com.snowplowanalytics.*/*/jsonschema/*-*-*", null), + (ContextPrimitive) mobileContext + ) + ); + + Map attributes = new HashMap<>(); + attributes.put("test-key-1", "test-value-1"); + SelfDescribingJson testSDJ = new SelfDescribingJson("sdjExample", "iglu:com.snowplowanalytics.snowplow/test_sdj/jsonschema/1-0-1", attributes); + tracker.addGlobalContext(testSDJ); + + tracker.addGlobalContext( + new ContextGenerator() { + @Override + public SelfDescribingJson generate(TrackerPayload payload, String eventType, String eventSchema) { + return new SelfDescribingJson(SCHEMA_APPLICATION_INSTALL); + } + + @Override + public String tag() { + return "testCtx"; + } + } + ); + + assertEquals(tracker.getGlobalContexts().size(), 3); + + tracker.removeGlobalContext("ruleSetExample"); + + assertEquals(tracker.getGlobalContexts().size(), 2); + + tracker.removeGlobalContexts(Arrays.asList("testCtx", "sdjExample")); + + assertEquals(tracker.getGlobalContexts().size(), 0); + } +} diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/Tracker.java b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/Tracker.java index 48a49be83..34f43cfca 100755 --- a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/Tracker.java +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/Tracker.java @@ -19,6 +19,7 @@ import android.os.Build; import android.arch.lifecycle.ProcessLifecycleOwner; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -30,6 +31,8 @@ import com.snowplowanalytics.snowplow.tracker.constants.TrackerConstants; import com.snowplowanalytics.snowplow.tracker.constants.Parameters; +import com.snowplowanalytics.snowplow.tracker.contexts.global.GlobalContext; +import com.snowplowanalytics.snowplow.tracker.contexts.global.GlobalContextUtils; import com.snowplowanalytics.snowplow.tracker.events.Event; import com.snowplowanalytics.snowplow.tracker.events.Timing; import com.snowplowanalytics.snowplow.tracker.payload.TrackerPayload; @@ -52,6 +55,7 @@ import com.snowplowanalytics.snowplow.tracker.payload.SelfDescribingJson; import com.snowplowanalytics.snowplow.tracker.utils.Util; + /** * Builds a Tracker object which is used to * send events to a Snowplow Collector. @@ -584,9 +588,8 @@ private void addEventPayload(TrackerPayload payload, List co if (this.subject != null) { payload.addMap(new HashMap(this.subject.getSubject())); } - // Build the final context and add it - SelfDescribingJson envelope = getFinalContext(context, eventId); + SelfDescribingJson envelope = getFinalContext(payload, context, eventId); if (envelope != null) { payload.addMap(envelope.getMap(), this.base64Encoded, Parameters.CONTEXT_ENCODED, Parameters.CONTEXT); @@ -600,36 +603,43 @@ private void addEventPayload(TrackerPayload payload, List co /** * Builds the final event context. * + * @param payload the tracker payload to be used to evaluate global contexts * @param contexts the base event context * @param eventId the event id * @return the final event context json with * many contexts inside */ - private SelfDescribingJson getFinalContext(List contexts, String eventId) { + private SelfDescribingJson getFinalContext(TrackerPayload payload, + List contexts, String eventId) { // Add session context if (this.sessionContext && this.trackerSession.getHasLoadedFromFile()) { - contexts.add(this.trackerSession.getSessionContext(eventId)); + addGlobalContext(this.trackerSession.getSessionContext(eventId)); } // Add Geo-Location Context if (this.geoLocationContext) { - contexts.add(Util.getGeoLocationContext(this.context)); + addGlobalContext(Util.getGeoLocationContext(this.context)); } // Add Mobile Context if (this.mobileContext) { - contexts.add(Util.getMobileContext(this.context)); + addGlobalContext(Util.getMobileContext(this.context)); } // Add screen context if (this.screenContext) { - contexts.add(screenState.getCurrentScreen(true)); + addGlobalContext(screenState.getCurrentScreen(true)); } // Add application context if (this.applicationContext) { - contexts.add(InstallTracker.getApplicationContext(this.context)); + addGlobalContext(InstallTracker.getApplicationContext(this.context)); + } + + // Add global contexts + if (!globalContexts.isEmpty()) { + contexts.addAll(GlobalContextUtils.evalGlobalContexts(payload, globalContexts)); } // If there are contexts to nest @@ -898,4 +908,38 @@ public void trackScreen(String name) { .build() ); } + + // global contexts + private ArrayList globalContexts = new ArrayList<>(); + + public void clearGlobalContexts() { + globalContexts.clear(); + } + + public void addGlobalContext(GlobalContext context) { + globalContexts.add(context); + } + + public void addGlobalContexts(List contexts) { + for (GlobalContext context : contexts) { + addGlobalContext(context); + } + } + + public ArrayList getGlobalContexts() { + return globalContexts; + } + + public void setGlobalContexts(List contexts) { + clearGlobalContexts(); + addGlobalContexts(contexts); + } + + public void removeGlobalContexts(List tags) { + GlobalContextUtils.removeContexts(tags); + } + + public void removeGlobalContext(String tag) { + GlobalContextUtils.removeContext(tag); + } } diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/ConditionalContextProvider.java b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/ConditionalContextProvider.java new file mode 100644 index 000000000..1470eb536 --- /dev/null +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/ConditionalContextProvider.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Snowplow Analytics Ltd. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package com.snowplowanalytics.snowplow.tracker.contexts.global; + +/** + * ConditionalContextProvider is either FilterProvider or RuleSetProvider + */ +public interface ConditionalContextProvider extends GlobalContext {} diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/ContextFilter.java b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/ContextFilter.java new file mode 100644 index 000000000..566457c9a --- /dev/null +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/ContextFilter.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019 Snowplow Analytics Ltd. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package com.snowplowanalytics.snowplow.tracker.contexts.global; + +import com.snowplowanalytics.snowplow.tracker.payload.TrackerPayload; + +/** + * ContextFilter represents a callback checking an event payload on a user-provided + * condition to decide whether a context or a set of contexts should be attached to + * the event or not + */ +public interface ContextFilter { + + boolean filter(TrackerPayload payload, String eventType, String eventSchema); +} diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/ContextGenerator.java b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/ContextGenerator.java new file mode 100644 index 000000000..cf900870b --- /dev/null +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/ContextGenerator.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 Snowplow Analytics Ltd. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package com.snowplowanalytics.snowplow.tracker.contexts.global; + +import com.snowplowanalytics.snowplow.tracker.payload.SelfDescribingJson; +import com.snowplowanalytics.snowplow.tracker.payload.TrackerPayload; + +/** + * ContextGenerator represents a callback which generates an SDJ + */ +public interface ContextGenerator extends ContextPrimitive { + + /** + * A callback function to be executed depending on event payload, event type and schema + */ + SelfDescribingJson generate(TrackerPayload payload, String eventType, String eventSchema); +} diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/ContextPrimitive.java b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/ContextPrimitive.java new file mode 100644 index 000000000..cbea52a39 --- /dev/null +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/ContextPrimitive.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Snowplow Analytics Ltd. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package com.snowplowanalytics.snowplow.tracker.contexts.global; + +/** + * ContextPrimitive is either SelfDescribingJson or ContextGenerator + */ +public interface ContextPrimitive extends GlobalContext {} diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/FilterProvider.java b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/FilterProvider.java new file mode 100644 index 000000000..71ca8eb8d --- /dev/null +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/FilterProvider.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019 Snowplow Analytics Ltd. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package com.snowplowanalytics.snowplow.tracker.contexts.global; + +import com.snowplowanalytics.snowplow.tracker.utils.Preconditions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class FilterProvider implements ConditionalContextProvider { + + private String tag; + private ContextFilter contextFilter; + private ArrayList contextPrimitives = new ArrayList<>(); + + public FilterProvider(String tag, ContextFilter contextFilter, List contextPrimitives) { + setTag(tag); + this.contextFilter = contextFilter; + Collections.addAll(this.contextPrimitives, contextPrimitives.toArray(new ContextPrimitive[0])); + } + + public FilterProvider(String tag, ContextFilter contextFilter, ContextPrimitive contextPrimitive) { + setTag(tag); + this.contextFilter = contextFilter; + this.contextPrimitives.add(contextPrimitive); + } + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + Preconditions.checkNotNull(tag, "tag cannot be null"); + this.tag = tag; + } + + public ContextFilter getContextFilter() { + return contextFilter; + } + + public void setContextFilter(ContextFilter contextFilter) { + this.contextFilter = contextFilter; + } + + public ArrayList getContextPrimitives() { + return contextPrimitives; + } + + public void setContextPrimitives(List primitives) { + this.contextPrimitives.clear(); + Collections.addAll(this.contextPrimitives, primitives.toArray(new ContextPrimitive[0])); + } + + @Override + public String tag() { + return tag; + } +} diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/GlobalContext.java b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/GlobalContext.java new file mode 100644 index 000000000..d99694316 --- /dev/null +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/GlobalContext.java @@ -0,0 +1,8 @@ +package com.snowplowanalytics.snowplow.tracker.contexts.global; + +/** + * A tagging interface for all global context types + */ +public interface GlobalContext { + String tag(); +} diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/GlobalContextUtils.java b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/GlobalContextUtils.java new file mode 100644 index 000000000..fd0ecd643 --- /dev/null +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/GlobalContextUtils.java @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2019 Snowplow Analytics Ltd. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package com.snowplowanalytics.snowplow.tracker.contexts.global; + +import android.util.Base64; + +import com.snowplowanalytics.snowplow.tracker.Tracker; +import com.snowplowanalytics.snowplow.tracker.constants.Parameters; +import com.snowplowanalytics.snowplow.tracker.constants.TrackerConstants; +import com.snowplowanalytics.snowplow.tracker.payload.SelfDescribingJson; +import com.snowplowanalytics.snowplow.tracker.payload.TrackerPayload; + +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + + +public class GlobalContextUtils { + + static final String TAG = GlobalContextUtils.class.getSimpleName(); + + /** + * Re-evaluates global contexts for the provided payload + * @return An ArrayList of SDJs representing global contexts ready to be appended to the event's contexts + */ + public static synchronized ArrayList + evalGlobalContexts(TrackerPayload payload, ArrayList globalContexts) { + + ArrayList computedGlobalContexts = new ArrayList<>(); + + String eventType = (String) payload.getMap().get(Parameters.EVENT); + String eventSchema = getSchema(payload); + for (GlobalContext context : globalContexts) { + if (context instanceof ContextPrimitive) { + computeContextPrimitive(payload, computedGlobalContexts, + (ContextPrimitive) context, eventType, eventSchema); + } else if (context instanceof ConditionalContextProvider) { + if (context instanceof FilterProvider) { + if (((FilterProvider) context).getContextFilter().filter(payload, eventType, eventSchema)) { + computeContextPrimitives(payload, computedGlobalContexts, + ((FilterProvider) context).getContextPrimitives(), eventType, eventSchema); + } + } else if (context instanceof RuleSetProvider) { + boolean ruleSetCheck = + matchSchemaAgainstRuleSet((((RuleSetProvider) context).getRuleSet()), eventSchema); + if (ruleSetCheck) { + computeContextPrimitives(payload, computedGlobalContexts, + ((RuleSetProvider) context).getContextPrimitives(), eventType, eventSchema); + + } + } + } + } + return computedGlobalContexts; + } + + static boolean matchSchemaAgainstRuleSet(RuleSet ruleSet, String eventSchema) { + ArrayList acceptRules = ruleSet.getAccept(); + ArrayList rejectRules = ruleSet.getReject(); + int acceptCount = 0; + int rejectCount = 0; + + for (String rule : acceptRules) { + if (matchSchemaAgainstRule(rule, eventSchema)) { + ++acceptCount; + } + } + + for (String rule : rejectRules) { + if (matchSchemaAgainstRule(rule, eventSchema)) { + ++rejectCount; + } + } + + return (acceptCount > 0 && rejectCount == 0); + } + + static boolean matchSchemaAgainstRule(String rule, String eventSchema) { + if ((!isValidRule(rule)) || eventSchema == null) { + return false; + } + String[] ruleParts = getUriSubparts(rule); + String[] schemaParts = getUriSubparts(eventSchema); + + if (ruleParts.length > 0 && schemaParts.length > 0) { + if (!matchVendor(ruleParts[0], schemaParts[0])) { + return false; + } + for (int i=1; i<=4; i++) { + if (!matchPart(ruleParts[i], schemaParts[i])) { + return false; + } + } + return true; + } + return false; + } + + static boolean isValidRule(String rule) { + if (rule != null) { + String[] ruleParts = getUriSubparts(rule); + if (ruleParts.length == 5) { + String vendor = ruleParts[0]; + String[] versionParts = Arrays.copyOfRange(ruleParts, 2, ruleParts.length); + return validateVendor(vendor) && validateVersion(versionParts); + } + return false; + } + return false; + } + + /** + * Splits an Iglu URI or a Global Context Rule to extract vendor, name and version parts (model, revision, addition) + * @param uri an Iglu URI or a Global Context Rule + * @return Vendor, name and version parts (model, revision, addition) + */ + static String[] getUriSubparts(String uri) { + // split after excluding protocol + String[] parts = uri.substring(5).split("/"); + if (parts.length == 4) { + String[] versionParts = parts[3].split("-"); + if (versionParts.length == 3) { + // parts[2] corresponds to the format of the uri, always same, skipped intentionally + return new String[]{ parts[0], parts[1], versionParts[0], versionParts[1], versionParts[2] }; + } + return new String[]{}; + } else { + return new String[]{}; + } + } + + static boolean validateVersion(String[] versionParts) { + if (versionParts.length == 3) { + boolean asterisk = false; + for (String part : versionParts) { + if (part.equals("*")) { + asterisk = true; + } else if (asterisk) { + return false; + } + } + return true; + } + return false; + } + + static boolean validateVendor(String vendor) { + String[] parts = vendor.split("\\."); + return (parts.length > 1) && validateVendorParts(parts); + } + + static boolean validateVendorParts(String[] parts) { + if (parts[0].equals("*") || parts[1].equals("*")) { + return false; + } + if (parts.length == 2) { + return true; + } else { + String[] wildcardAllowedParts = Arrays.copyOfRange(parts, 2, parts.length); + boolean asterisk = false; + for (String part: wildcardAllowedParts) { + if (part.equals("*")) { + asterisk = true; + } else if (asterisk) { + return false; + } + } + return true; + } + } + + static boolean matchVendor(String ruleVendor, String schemaVendor) { + String[] ruleVendorParts = ruleVendor.split("\\."); + String[] schemaVendorParts = schemaVendor.split("\\."); + + if ((ruleVendorParts.length > 1) && (schemaVendorParts.length > 1)) { + if ((ruleVendorParts.length == schemaVendorParts.length) && validateVendorParts(ruleVendorParts)) { + for (int i=0; i < ruleVendorParts.length; ++i) { + if (!matchPart(ruleVendorParts[i], schemaVendorParts[i])) { + return false; + } + } + return true; + } + return false; + } + return false; + } + + static boolean matchPart(String rulePart, String schemaPart) { + return (rulePart != null) && (schemaPart != null) && (rulePart.equals("*") || (rulePart.equals(schemaPart))); + } + + static void computeContextPrimitive(TrackerPayload payload, + ArrayList computedGlobalContexts, + ContextPrimitive primitive, + String eventType, String eventSchema) { + if (primitive instanceof ContextGenerator) { + computedGlobalContexts.add(((ContextGenerator) primitive).generate(payload, eventType, eventSchema)); + } else if (primitive instanceof SelfDescribingJson) { + computedGlobalContexts.add((SelfDescribingJson) primitive); + } + } + + static void computeContextPrimitives(TrackerPayload payload, + ArrayList computedGlobalContexts, + ArrayList primitives, + String eventType, String eventSchema) { + for (ContextPrimitive primitive : primitives) { + computeContextPrimitive(payload, computedGlobalContexts, primitive, eventType, eventSchema); + } + } + + static String getSchema(TrackerPayload payload) { + HashMap trackerLoad = payload.getMap(); + try { + if (trackerLoad.containsKey(Parameters.UNSTRUCTURED)) { + String unstructPayload = (String) trackerLoad.get(Parameters.UNSTRUCTURED); + return new JSONObject(unstructPayload).getJSONObject("data").getString("schema"); + } else if (trackerLoad.containsKey(Parameters.UNSTRUCTURED_ENCODED)) { + String encodedSchema = (String) (trackerLoad.get(Parameters.UNSTRUCTURED_ENCODED)); + JSONObject decodedSchema = new JSONObject(new String(Base64.decode(encodedSchema, Base64.DEFAULT))); + return decodedSchema.getJSONObject("data").getString("schema"); + } else { // the case of first class Snowplow events + return TrackerConstants.SCHEMA_PAYLOAD_DATA; + } + } catch (Exception e) { + return ""; + } + } + + public static synchronized void removeContext(String tag) { + ArrayList globalContexts = Tracker.instance().getGlobalContexts(); + Iterator it = globalContexts.iterator(); + + while(it.hasNext()){ + GlobalContext globalContext = it.next(); + if (globalContext.tag().equals(tag)) { + it.remove(); + } + } + } + + public static synchronized void removeContexts(List tags) { + for (String tag: tags) { + removeContext(tag); + } + } +} diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/RuleSet.java b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/RuleSet.java new file mode 100644 index 000000000..2a4735e14 --- /dev/null +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/RuleSet.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Snowplow Analytics Ltd. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package com.snowplowanalytics.snowplow.tracker.contexts.global; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class RuleSet { + + private ArrayList accept = new ArrayList<>();; + private ArrayList reject = new ArrayList<>();; + + public RuleSet(List accept, List reject) { + Collections.addAll(accept, accept.toArray(new String[0])); + Collections.addAll(reject, reject.toArray(new String[0])); + } + + public RuleSet(String accept, String reject) { + this.accept.add(accept); + this.reject.add(reject); + } + + public ArrayList getAccept() { + return accept; + } + + public void setAccept(ArrayList accept) { + this.accept = accept; + } + + public ArrayList getReject() { + return reject; + } + + public void setReject(ArrayList reject) { + this.reject = reject; + } + + public boolean isValid() { + for (String rule: accept) { + if (!GlobalContextUtils.isValidRule(rule)) { + return false; + } + } + + for (String rule: reject) { + if (!GlobalContextUtils.isValidRule(rule)) { + return false; + } + } + + return true; + } +} diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/RuleSetProvider.java b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/RuleSetProvider.java new file mode 100644 index 000000000..324431598 --- /dev/null +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/contexts/global/RuleSetProvider.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019 Snowplow Analytics Ltd. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package com.snowplowanalytics.snowplow.tracker.contexts.global; + +import com.snowplowanalytics.snowplow.tracker.utils.Preconditions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class RuleSetProvider implements ConditionalContextProvider { + + private String tag; + private RuleSet ruleSet; + private ArrayList contextPrimitives = new ArrayList<>(); + + public RuleSetProvider(String tag, RuleSet ruleSet, List contextPrimitives) { + setTag(tag); + this.ruleSet = ruleSet; + Collections.addAll(this.contextPrimitives, contextPrimitives.toArray(new ContextPrimitive[0])); + } + + public RuleSetProvider(String tag, RuleSet ruleSet, ContextPrimitive contextPrimitive) { + setTag(tag); + this.ruleSet = ruleSet; + this.contextPrimitives.add(contextPrimitive); + } + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + Preconditions.checkNotNull(tag, "tag cannot be null"); + this.tag = tag; + } + + public RuleSet getRuleSet() { + return ruleSet; + } + + public void setRuleSet(RuleSet ruleSet) { + this.ruleSet = ruleSet; + } + + public ArrayList getContextPrimitives() { + return contextPrimitives; + } + + public void setContextPrimitives(List contextPrimitives) { + this.contextPrimitives.clear(); + Collections.addAll(this.contextPrimitives, contextPrimitives.toArray(new ContextPrimitive[0])); + } + + @Override + public String tag() { + return tag; + } +} diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/payload/SelfDescribingJson.java b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/payload/SelfDescribingJson.java index 3e2f0ee3a..33f4d4189 100755 --- a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/payload/SelfDescribingJson.java +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/payload/SelfDescribingJson.java @@ -17,6 +17,7 @@ import java.util.Map; import com.snowplowanalytics.snowplow.tracker.constants.Parameters; +import com.snowplowanalytics.snowplow.tracker.contexts.global.ContextPrimitive; import com.snowplowanalytics.snowplow.tracker.utils.Preconditions; import com.snowplowanalytics.snowplow.tracker.utils.Logger; import com.snowplowanalytics.snowplow.tracker.utils.Util; @@ -25,11 +26,12 @@ * Returns a SelfDescribingJson object which will contain * both the Schema and Data. */ -public class SelfDescribingJson implements Payload { +public class SelfDescribingJson implements Payload, ContextPrimitive { private final String TAG = SelfDescribingJson.class.getSimpleName(); private final HashMap payload = new HashMap<>(); - + private String tag = ""; + /** * Builds a SelfDescribingJson object * @@ -39,6 +41,13 @@ public SelfDescribingJson(String schema) { this(schema, new HashMap<>()); } + /** + * Builds a SelfDescribingJson object + */ + public SelfDescribingJson(String tag, String schema) { + this(tag, schema, new HashMap<>()); + } + /** * Builds a SelfDescribingJson object * @@ -51,6 +60,15 @@ public SelfDescribingJson(String schema, TrackerPayload data) { setData(data); } + /** + * Builds a SelfDescribingJson object + */ + public SelfDescribingJson(String tag, String schema, TrackerPayload data) { + setTag(tag); + setSchema(schema); + setData(data); + } + /** * Builds a SelfDescribingJson object * @@ -63,6 +81,15 @@ public SelfDescribingJson(String schema, SelfDescribingJson data) { setData(data); } + /** + * Builds a SelfDescribingJson object + */ + public SelfDescribingJson(String tag, String schema, SelfDescribingJson data) { + setTag(tag); + setSchema(schema); + setData(data); + } + /** * Builds a SelfDescribingJson object * @@ -75,6 +102,15 @@ public SelfDescribingJson(String schema, Object data) { setData(data); } + /** + * Builds a SelfDescribingJson object + */ + public SelfDescribingJson(String tag, String schema, Object data) { + setTag(tag); + setSchema(schema); + setData(data); + } + /** * Sets the Schema for the SelfDescribingJson * @@ -89,6 +125,16 @@ public SelfDescribingJson setSchema(String schema) { return this; } + /** + * Sets the tag for the SelfDescribingJson + * @param tag The identification string for the SelfDescribingJson + */ + public SelfDescribingJson setTag(String tag) { + Preconditions.checkNotNull(tag, "tag cannot be null"); + this.tag = tag; + return this; + } + /** * Adds data to the SelfDescribingJson * - Accepts a TrackerPayload object @@ -186,4 +232,9 @@ public String toString() { public long getByteSize() { return Util.getUTF8Length(toString()); } + + @Override + public String tag() { + return this.tag; + } } diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/payload/TrackerPayload.java b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/payload/TrackerPayload.java index 57686172a..8bba3341f 100755 --- a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/payload/TrackerPayload.java +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/payload/TrackerPayload.java @@ -76,7 +76,7 @@ public void addMap(Map map, Boolean base64_encoded, String type_encoded, String } } - public Map getMap() { + public HashMap getMap() { return payload; } From 0569adf3aed4717dade2559e034faad365ee7161 Mon Sep 17 00:00:00 2001 From: Oguzhan Unlu Date: Mon, 27 May 2019 15:39:28 +0300 Subject: [PATCH 2/6] Update Travis setup (close #309) --- .travis.yml | 49 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index cfa39a8ae..81e438934 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,18 @@ -sudo: required +# use the Travis Container-Based Infrastructure +sudo: false + language: android jdk: oraclejdk8 +cache: false +dist: trusty + env: global: - - EMULATOR_API=22 + - EMULATOR_API=24 - ANDROID_API=28 - ANDROID_BUILD_TOOLS=28.0.3 + - ADB_INSTALL_TIMEOUT=20 - ANDROID_TRAVIS=1 - secure: iXVs65+lPjyTr60MsX9K2WpjJ5ijt4cnBE9PT6ouHSjkZjugotaNoX3nXZev8fKFLsH0JPxpsxBVy66jz6iOddMfLdzSiL2oOeeYNxyNThOdGp9R/JmYtivgrMm6v3e1zzCLvj9onOwBUVdWVLD/BXgYi7wP96rJFENlVjuszys= - secure: BXwZhWDFFXzdaTAPAvMKeOfnLHxkmdXoeMZ/GtVYkT62JxeuiaTW6Z6rT0gZKAEyxmUBxFX1CP+1m1Gi0cWrQHfTBbFDJO7iKu7mG9LcZhQ8Cm657yt2TBc+fnVqbG5OxXWhSBs1lF1gZJ6+ehEIJn3YjP6fDYv0fNnTGpYHCYo= @@ -26,24 +32,37 @@ android: - addon-google_apis-google-$ANDROID_API # google play services - sys-img-armeabi-v7a-addon-google_apis-google-$ANDROID_API - sys-img-armeabi-v7a-android-$EMULATOR_API # the fix! - - licenses: - - ".+" - before_install: - - yes | sdkmanager "platforms;android-28" + +licenses: + - android-sdk-license-.+ + - '.+' + +before_install: echo 'count=0' > /home/travis/.android/repositories.cfg + +install: + - sdkmanager --list --verbose || true + - yes | sdkmanager "tools" + - yes | sdkmanager "platform-tools" + - yes | sdkmanager "build-tools;"$ANDROID_BUILD_TOOLS + - yes | sdkmanager "platforms;android-"$ANDROID_API + - yes | sdkmanager "emulator" + - yes | sdkmanager "extras;android;m2repository" + - yes | sdkmanager "extras;google;m2repository" + - yes | sdkmanager --update + - yes | sdkmanager --licenses + - sdkmanager --list --verbose || true before_script: - - echo "y" | android update sdk -a --no-ui --filter android-28 - - echo "y" | android update sdk -a --no-ui --filter sys-img-armeabi-v7a-android-28 - - android list targets | grep -E '^id:' | awk -F '"' '{$1=""; print $2}' # list all targets - - echo no | android create avd --force -n test -t android-$EMULATOR_API --abi armeabi-v7a - - emulator -avd test -no-skin -no-audio -no-window & - - chmod +x ./ci/wait_for_emulator - - ./ci/wait_for_emulator + - export PATH="$ANDROID_HOME/emulator:$PATH" + - sdkmanager "system-images;android-"$EMULATOR_API";default;armeabi-v7a" + - echo no | avdmanager -v create avd -f -n test -k "system-images;android-"$EMULATOR_API";default;armeabi-v7a" + - avdmanager list + - emulator -avd test -no-audio -no-window & + - android-wait-for-emulator - adb shell input keyevent 82 & script: - - ./gradlew createDebugCoverageReport coveralls + - travis_wait 45 ./gradlew createDebugCoverageReport coveralls deploy: skip_cleanup: true From e4d7360d586309da317eeda24996c434586d6f96 Mon Sep 17 00:00:00 2001 From: mhadam Date: Mon, 27 May 2019 15:27:43 -0400 Subject: [PATCH 3/6] Set emitter status when event store not instantiated (close #306) --- .../java/com/snowplowanalytics/snowplow/tracker/Emitter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/Emitter.java b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/Emitter.java index f58ba2507..5c7efc121 100755 --- a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/Emitter.java +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/Emitter.java @@ -454,6 +454,7 @@ private void attemptEmit() { } } else { Logger.d(TAG, "Event store not instantiated."); + isRunning.compareAndSet(true, false); } } From 66e86b9b54384b5a5d567639e3db199052138d52 Mon Sep 17 00:00:00 2001 From: antonkazakov Date: Sat, 23 Feb 2019 12:49:24 +0300 Subject: [PATCH 4/6] Add ability to customize timeout for Emitter (close #311) --- .../snowplow/tracker/Emitter.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/Emitter.java b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/Emitter.java index 5c7efc121..4818bc859 100755 --- a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/Emitter.java +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/tracker/Emitter.java @@ -78,6 +78,7 @@ public class Emitter { private int sendLimit; private long byteLimitGet; private long byteLimitPost; + private int emitTimeout; private TimeUnit timeUnit; private EventStore eventStore; @@ -103,6 +104,7 @@ public static class EmitterBuilder { int emptyLimit = 5; // Optional long byteLimitGet = 40000; // Optional long byteLimitPost = 40000; // Optional + private int emitTimeout = 5; // Optional TimeUnit timeUnit = TimeUnit.SECONDS; OkHttpClient client = null; //Optional @@ -217,6 +219,16 @@ public EmitterBuilder byteLimitPost(long byteLimitPost) { return this; } + /** + * @param emitTimeout The maximum timeout for emitting events. If emit time exceeds this value + * TimeOutException will be thrown + * @return itself + */ + public EmitterBuilder emitTimeout(int emitTimeout){ + this.emitTimeout = emitTimeout; + return this; + } + /** * @param timeUnit a valid TimeUnit * @return itself @@ -264,6 +276,7 @@ private Emitter(EmitterBuilder builder) { this.sendLimit = builder.sendLimit; this.byteLimitGet = builder.byteLimitGet; this.byteLimitPost = builder.byteLimitPost; + this.emitTimeout = builder.emitTimeout; this.uri = builder.uri; this.timeUnit = builder.timeUnit; this.eventStore = null; @@ -480,12 +493,12 @@ protected LinkedList performAsyncEmit(LinkedList re Logger.d(TAG, "Request Futures: %s", futures.size()); // Get results of futures - // - Wait up to 5 seconds for the request + // - Wait up to emitTimeout seconds for the request for (int i = 0; i < futures.size(); i++) { int code = -1; try { - code = (int) futures.get(i).get(5, TimeUnit.SECONDS); + code = (int) futures.get(i).get(emitTimeout, TimeUnit.SECONDS); } catch (InterruptedException ie) { Logger.e(TAG, "Request Future was interrupted: %s", ie.getMessage()); } catch (ExecutionException ee) { From d4f62f4d3702baaf9fe04cdd38c0e4bcd800c222 Mon Sep 17 00:00:00 2001 From: Oguzhan Unlu Date: Tue, 11 Jun 2019 18:33:20 +0300 Subject: [PATCH 5/6] Prepared for release --- CHANGELOG | 7 +++++++ README.md | 2 +- VERSION | 2 +- build.gradle | 2 +- .../snowplowanalytics/snowplow/tracker/TrackerTest.java | 2 +- .../snowplow/tracker/integration/EventSendingTest.java | 4 ++-- 6 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8a29fb9e8..884c8651d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,10 @@ +Version 1.2.0 (2019-07-01) +-------------------------- +Add ability to customize timeout for Emitter (#311) +Set emitter status when event store not instantiated (#306) +Update Travis setup (#309) +Add support for global contexts (#307) + Version 1.1.0 (2019-05-06) -------------------------- Add instrumented tests to debug coverage (#302) diff --git a/README.md b/README.md index fce626583..409f273a4 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ limitations under the License. [travis]: https://travis-ci.org/snowplow/snowplow-android-tracker [travis-image]: https://travis-ci.org/snowplow/snowplow-android-tracker.svg?branch=master -[release-image]: http://img.shields.io/badge/release-1.1.0-blue.svg?style=flat +[release-image]: http://img.shields.io/badge/release-1.2.0-blue.svg?style=flat [releases]: https://github.com/snowplow/snowplow-android-tracker/releases [license-image]: http://img.shields.io/badge/license-Apache--2-blue.svg?style=flat diff --git a/VERSION b/VERSION index 9084fa2f7..26aaba0e8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.0 +1.2.0 diff --git a/build.gradle b/build.gradle index 35a350c9a..7f2b8d58c 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { subprojects { group = 'com.snowplowanalytics' - version = '1.1.0' + version = '1.2.0' repositories { google() maven { diff --git a/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/TrackerTest.java b/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/TrackerTest.java index 27b4362e2..e47c0bc4b 100755 --- a/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/TrackerTest.java +++ b/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/TrackerTest.java @@ -94,7 +94,7 @@ public void testSetValues() { assertEquals(false, tracker.getBase64Encoded()); assertNotNull(tracker.getEmitter()); assertNotNull(tracker.getSubject()); - assertEquals("andr-1.1.0", tracker.getTrackerVersion()); + assertEquals("andr-1.2.0", tracker.getTrackerVersion()); assertEquals(LogLevel.VERBOSE, tracker.getLogLevel()); assertEquals(2, tracker.getThreadCount()); assertEquals(false, tracker.getApplicationCrash()); diff --git a/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/integration/EventSendingTest.java b/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/integration/EventSendingTest.java index ced79fa97..c0b108326 100644 --- a/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/integration/EventSendingTest.java +++ b/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/tracker/integration/EventSendingTest.java @@ -215,7 +215,7 @@ public void checkGetRequest(LinkedList requests) throws Excepti assertEquals("mob", query.get("p")); assertEquals("myAppId", query.get("aid")); assertEquals("myNamespace", query.get("tna")); - assertEquals("andr-1.1.0", query.get("tv")); + assertEquals("andr-1.2.0", query.get("tv")); assertEquals("English", query.get("lang")); assertTrue(query.has("dtm")); assertTrue(query.has("stm")); @@ -256,7 +256,7 @@ public void checkPostRequest(LinkedList requests) throws Except assertEquals("mob", json.getString("p")); assertEquals("myAppId", json.getString("aid")); assertEquals("myNamespace", json.getString("tna")); - assertEquals("andr-1.1.0", json.getString("tv")); + assertEquals("andr-1.2.0", json.getString("tv")); assertEquals("English", json.getString("lang")); assertTrue(json.has("dtm")); assertTrue(json.has("stm")); From 92129327a6cbcba547a67d23a41d32b16e45f048 Mon Sep 17 00:00:00 2001 From: Oguzhan Unlu Date: Mon, 1 Jul 2019 15:21:54 +0300 Subject: [PATCH 6/6] travis trials --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 81e438934..6632e7835 100644 --- a/.travis.yml +++ b/.travis.yml @@ -62,7 +62,10 @@ before_script: - adb shell input keyevent 82 & script: - - travis_wait 45 ./gradlew createDebugCoverageReport coveralls + # travis builds timeout after 10 min of not seeing any output, hence the below work-around + # default travis_wait method doesn't show output until command finishes while this way allows us to see it + - (while sleep 9m; do echo "=====[ $SECONDS seconds still running ]====="; done &) + - ./gradlew createDebugCoverageReport coveralls deploy: skip_cleanup: true