You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@unomi.apache.org by sh...@apache.org on 2021/03/10 15:01:31 UTC

[unomi] branch master updated: UNOMI-350 Implement CDP_Source definition & retrieval (#252)

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

shuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/unomi.git


The following commit(s) were added to refs/heads/master by this push:
     new da88130  UNOMI-350 Implement CDP_Source definition & retrieval (#252)
da88130 is described below

commit da88130b4bf924718513c3fbdfe021535a17f4bb
Author: anatol-sialitski <53...@users.noreply.github.com>
AuthorDate: Wed Mar 10 18:01:24 2021 +0300

    UNOMI-350 Implement CDP_Source definition & retrieval (#252)
---
 api/src/main/java/org/apache/unomi/api/Event.java  |  18 +++
 .../main/java/org/apache/unomi/api/SourceItem.java |  37 +++--
 .../apache/unomi/api/services/SourceService.java   |  56 ++++++++
 .../unomi/lists/actions/AddToListsAction.java      |   2 +-
 .../commands/CreateOrUpdateSourceCommand.java      |  20 ++-
 .../graphql/commands/DeleteSourceCommand.java      |   5 +-
 .../unomi/graphql/fetchers/SourceDataFetcher.java  |  16 ++-
 .../apache/unomi/itests/graphql/GraphQLListIT.java | 152 +++++++--------------
 .../unomi/itests/graphql/GraphQLSegmentIT.java     |   5 +-
 .../unomi/itests/graphql/GraphQLSourceIT.java      |  69 ++++++++++
 .../unomi/itests/graphql/GraphQLTopicIT.java       |   8 +-
 .../graphql/list/add-profile-to-list.json          |   2 +-
 .../test/resources/graphql/list/delete-list.json   |   2 +-
 .../test/resources/graphql/list/find-lists.json    |   4 +-
 .../src/test/resources/graphql/list/get-list.json  |   2 +-
 .../graphql/list/remove-profile-from-list.json     |   2 +-
 .../test/resources/graphql/list/update-list.json   |   4 +-
 .../resources/graphql/source/create-source.json    |   9 ++
 .../resources/graphql/source/delete-source.json    |   7 +
 .../test/resources/graphql/source/get-sources.json |   5 +
 .../resources/graphql/source/update-source.json    |  10 ++
 .../main/resources/etc/custom.system.properties    |   5 +
 .../unomi/persistence/spi/CustomObjectMapper.java  |   1 +
 .../actions/MergeProfilesOnPropertyAction.java     |   4 +-
 .../baseplugin/actions/SendEventAction.java        |   2 +-
 .../baseplugin/actions/SetPropertyAction.java      |   2 +-
 .../unomi/training/TrainedNotificationAction.java  |   2 +-
 .../actions/IncrementTweetNumberAction.java        |   4 +-
 .../services/impl/events/EventServiceImpl.java     |  22 ++-
 .../services/impl/rules/RulesServiceImpl.java      |   4 +-
 .../services/impl/source/SourceServiceImpl.java    |  81 +++++++++++
 .../resources/OSGI-INF/blueprint/blueprint.xml     |  14 ++
 .../main/resources/org.apache.unomi.services.cfg   |   3 +
 .../org/apache/unomi/shell/commands/EventList.java |   2 +-
 .../apache/unomi/shell/commands/EventSearch.java   |   2 +-
 .../org/apache/unomi/shell/commands/EventTail.java |   2 +-
 .../org/apache/unomi/shell/commands/RuleTail.java  |   2 +-
 .../org/apache/unomi/shell/commands/RuleWatch.java |   2 +-
 .../apache/unomi/web/EventsCollectorServlet.java   |   4 +-
 .../java/org/apache/unomi/web/ServletCommon.java   |   4 +-
 40 files changed, 447 insertions(+), 150 deletions(-)

diff --git a/api/src/main/java/org/apache/unomi/api/Event.java b/api/src/main/java/org/apache/unomi/api/Event.java
index 6b4e1e8..cd77586 100644
--- a/api/src/main/java/org/apache/unomi/api/Event.java
+++ b/api/src/main/java/org/apache/unomi/api/Event.java
@@ -57,8 +57,11 @@ public class Event extends Item implements TimestampedItem {
     private transient Session session;
     private transient List<ActionPostExecutor> actionPostExecutors;
 
+    @Deprecated
     private String scope;
 
+    private String sourceId;
+
     private Item source;
     private Item target;
 
@@ -369,10 +372,12 @@ public class Event extends Item implements TimestampedItem {
     /**
      * @return the scope
      */
+    @Deprecated
     public String getScope() {
         return scope;
     }
 
+    @Deprecated
     public void setScope(String scope) {
         this.scope = scope;
     }
@@ -431,4 +436,17 @@ public class Event extends Item implements TimestampedItem {
     public void setActionPostExecutors(List<ActionPostExecutor> actionPostExecutors) {
         this.actionPostExecutors = actionPostExecutors;
     }
+
+    public String getSourceId() {
+        if ( sourceId == null && scope != null ) {
+            return scope;
+        } else {
+            return sourceId;
+        }
+    }
+
+    public void setSourceId( final String sourceId )
+    {
+        this.sourceId = sourceId;
+    }
 }
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/SourceDataFetcher.java b/api/src/main/java/org/apache/unomi/api/SourceItem.java
similarity index 54%
copy from graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/SourceDataFetcher.java
copy to api/src/main/java/org/apache/unomi/api/SourceItem.java
index 782d4a0..2e68269 100644
--- a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/SourceDataFetcher.java
+++ b/api/src/main/java/org/apache/unomi/api/SourceItem.java
@@ -14,21 +14,38 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.unomi.graphql.fetchers;
+package org.apache.unomi.api;
 
-import graphql.schema.DataFetcher;
-import graphql.schema.DataFetchingEnvironment;
-import org.apache.unomi.graphql.types.output.CDPSource;
+public class SourceItem extends Item {
 
-import java.util.Collections;
-import java.util.List;
+    public static final String ITEM_TYPE = "source";
 
-public class SourceDataFetcher implements DataFetcher<List<CDPSource>> {
+    private String sourceId;
+
+    private Boolean thirdParty;
+
+    public String getSourceId() {
+        return sourceId;
+    }
+
+    public void setSourceId(final String sourceId) {
+        this.sourceId = sourceId;
+    }
+
+    public Boolean getThirdParty() {
+        return thirdParty;
+    }
+
+    public void setThirdParty(final Boolean thirdParty) {
+        this.thirdParty = thirdParty;
+    }
 
     @Override
-    public List<CDPSource> get(final DataFetchingEnvironment environment) throws Exception {
-        // Unomi doesn't have an API for that yet, so return a stub
-        return Collections.emptyList();
+    public String toString() {
+        return "SourceItem{" +
+                "sourceId='" + sourceId + '\'' +
+                ", thirdParty='" + thirdParty + '\'' +
+                '}';
     }
 
 }
diff --git a/api/src/main/java/org/apache/unomi/api/services/SourceService.java b/api/src/main/java/org/apache/unomi/api/services/SourceService.java
new file mode 100644
index 0000000..6c794e7
--- /dev/null
+++ b/api/src/main/java/org/apache/unomi/api/services/SourceService.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.api.services;
+
+import org.apache.unomi.api.SourceItem;
+
+import java.util.List;
+
+public interface SourceService {
+
+    /**
+     * Retrieves the source identified by the specified identifier.
+     *
+     * @param sourceId the identifier of the source to retrieve
+     * @return the topic identified by the specified identifier or {@code null} if no such source exists
+     */
+    SourceItem load(final String sourceId);
+
+    /**
+     * Saves the specified source in the context server.
+     *
+     * @param source the source to be saved
+     * @return the newly saved topic if the creation or update was successful, {@code null} otherwise
+     */
+    SourceItem save(final SourceItem source);
+
+    /**
+     * Retrieves all sources.
+     *
+     * @return a {@link List} of {@link SourceItem} metadata
+     */
+    List<SourceItem> getAll();
+
+    /**
+     * Removes the source identified by the specified identifier.
+     *
+     * @param sourceId the identifier of the profile or persona to delete
+     * @return {@code true} if the deletion was successful, {@code false} otherwise
+     */
+    boolean delete(final String sourceId);
+
+}
diff --git a/extensions/lists-extension/actions/src/main/java/org/apache/unomi/lists/actions/AddToListsAction.java b/extensions/lists-extension/actions/src/main/java/org/apache/unomi/lists/actions/AddToListsAction.java
index f120310..01c6eac 100644
--- a/extensions/lists-extension/actions/src/main/java/org/apache/unomi/lists/actions/AddToListsAction.java
+++ b/extensions/lists-extension/actions/src/main/java/org/apache/unomi/lists/actions/AddToListsAction.java
@@ -65,7 +65,7 @@ public class AddToListsAction implements ActionExecutor {
 
         if (listsChanged) {
             profile.getSystemProperties().put("lists", existingListIdentifiers);
-            Event profileUpdated = new Event("profileUpdated", null, profile, event.getScope(), null, profile, new Date());
+            Event profileUpdated = new Event("profileUpdated", null, profile, event.getSourceId(), null, profile, new Date());
             profileUpdated.setPersistent(false);
             eventService.send(profileUpdated);
             return EventService.PROFILE_UPDATED;
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/CreateOrUpdateSourceCommand.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/CreateOrUpdateSourceCommand.java
index 5302282..ee2a275 100644
--- a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/CreateOrUpdateSourceCommand.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/CreateOrUpdateSourceCommand.java
@@ -16,6 +16,8 @@
  */
 package org.apache.unomi.graphql.commands;
 
+import org.apache.unomi.api.SourceItem;
+import org.apache.unomi.api.services.SourceService;
 import org.apache.unomi.graphql.types.input.CDPSourceInput;
 import org.apache.unomi.graphql.types.output.CDPSource;
 
@@ -33,8 +35,22 @@ public class CreateOrUpdateSourceCommand extends BaseCommand<CDPSource> {
 
     @Override
     public CDPSource execute() {
-        // Unomi doesn't have an API for that yet, so return a stub
-        return new CDPSource(sourceInput.getId(), sourceInput.getThirdParty());
+        SourceService sourceService = serviceManager.getService(SourceService.class);
+
+        SourceItem source = sourceService.load(sourceInput.getId());
+
+        if (source == null) {
+            source = new SourceItem();
+
+            source.setItemId(sourceInput.getId());
+            source.setSourceId(sourceInput.getId());
+        }
+
+        source.setThirdParty(sourceInput.getThirdParty());
+
+        SourceItem persistedSource = sourceService.save(source);
+
+        return new CDPSource(persistedSource.getSourceId(), persistedSource.getThirdParty());
     }
 
     public static Builder create(final CDPSourceInput topicInput) {
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/DeleteSourceCommand.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/DeleteSourceCommand.java
index d3ec952..d9146fe 100644
--- a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/DeleteSourceCommand.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/commands/DeleteSourceCommand.java
@@ -16,6 +16,8 @@
  */
 package org.apache.unomi.graphql.commands;
 
+import org.apache.unomi.api.services.SourceService;
+
 import java.util.Objects;
 
 public class DeleteSourceCommand extends BaseCommand<Boolean> {
@@ -30,8 +32,7 @@ public class DeleteSourceCommand extends BaseCommand<Boolean> {
 
     @Override
     public Boolean execute() {
-        // Unomi doesn't have an API for that yet, so return a stub
-        return false;
+        return serviceManager.getService(SourceService.class).delete(sourceId);
     }
 
     public static Builder create(final String sourceId) {
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/SourceDataFetcher.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/SourceDataFetcher.java
index 782d4a0..bed77cf 100644
--- a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/SourceDataFetcher.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/SourceDataFetcher.java
@@ -18,17 +18,27 @@ package org.apache.unomi.graphql.fetchers;
 
 import graphql.schema.DataFetcher;
 import graphql.schema.DataFetchingEnvironment;
+import org.apache.unomi.api.SourceItem;
+import org.apache.unomi.api.services.SourceService;
+import org.apache.unomi.graphql.services.ServiceManager;
 import org.apache.unomi.graphql.types.output.CDPSource;
 
-import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 public class SourceDataFetcher implements DataFetcher<List<CDPSource>> {
 
     @Override
     public List<CDPSource> get(final DataFetchingEnvironment environment) throws Exception {
-        // Unomi doesn't have an API for that yet, so return a stub
-        return Collections.emptyList();
+        ServiceManager serviceManager = environment.getContext();
+
+        SourceService sourceService = serviceManager.getService(SourceService.class);
+
+        List<SourceItem> sources = sourceService.getAll();
+
+        return sources.stream().
+                map(sourceItem -> new CDPSource(sourceItem.getSourceId(), sourceItem.getThirdParty())).
+                collect(Collectors.toList());
     }
 
 }
diff --git a/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLListIT.java b/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLListIT.java
index 96fbdb7..fdbb1fe 100644
--- a/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLListIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLListIT.java
@@ -17,17 +17,13 @@
 package org.apache.unomi.itests.graphql;
 
 import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.unomi.api.Metadata;
 import org.apache.unomi.api.Profile;
 import org.apache.unomi.api.services.ProfileService;
-import org.apache.unomi.lists.UserList;
-import org.apache.unomi.services.UserListService;
 import org.junit.Assert;
 import org.junit.Test;
 import org.ops4j.pax.exam.util.Filter;
 
 import javax.inject.Inject;
-import java.io.IOException;
 import java.util.Objects;
 
 public class GraphQLListIT extends BaseGraphQLIT {
@@ -36,126 +32,78 @@ public class GraphQLListIT extends BaseGraphQLIT {
     @Filter(timeout = 600000)
     protected ProfileService profileService;
 
-    @Inject
-    @Filter(timeout = 600000)
-    protected UserListService userListService;
-
-    @Test
-    public void testCreateList() throws IOException {
-        try (CloseableHttpResponse response = post("graphql/list/create-list.json")) {
-            final ResponseContext context = ResponseContext.parse(response.getEntity());
-
-            Assert.assertEquals("testListId", context.getValue("data.cdp.createOrUpdateList.id"));
-            Assert.assertEquals("testListName", context.getValue("data.cdp.createOrUpdateList.name"));
-            Assert.assertEquals("testSite", context.getValue("data.cdp.createOrUpdateList.view.name"));
-        }
-    }
-
     @Test
-    public void testUpdateList() throws Exception {
-        final UserList userList = createList("listIdToUpdate", "listName", "listView");
+    public void testCRUD() throws Exception {
+        Profile persistedProfile = null;
 
-        keepTrying("Failed waiting for the creation of the list",
-                () -> userListService.load(userList.getItemId()), Objects::nonNull, 1000, 100);
+        try {
+            final Profile profile = new Profile("test_profile_id");
+            profile.setProperty("firstName", "TestFirstName");
+            profile.setProperty("lastName", "TestLastName");
 
-        try (CloseableHttpResponse response = post("graphql/list/update-list.json")) {
-            final ResponseContext context = ResponseContext.parse(response.getEntity());
+            persistedProfile = profileService.save(profile);
 
-            Assert.assertEquals("listIdToUpdate", context.getValue("data.cdp.createOrUpdateList.id"));
-            Assert.assertEquals("listNameUpdated", context.getValue("data.cdp.createOrUpdateList.name"));
-            Assert.assertEquals("testSiteUpdated", context.getValue("data.cdp.createOrUpdateList.view.name"));
-        }
-    }
+            refreshPersistence();
 
-    @Test
-    public void testDeleteList() throws Exception {
-        final UserList userList = createList("listIdToDelete", "listName", "listView");
+            keepTrying("Failed waiting for the creation of the profile",
+                    () -> profileService.load(profile.getItemId()), Objects::nonNull, 1000, 100);
 
-        keepTrying("Failed waiting for the creation of the list",
-                () -> userListService.load(userList.getItemId()), Objects::nonNull, 1000, 100);
-
-        try (CloseableHttpResponse response = post("graphql/list/delete-list.json")) {
-            final ResponseContext context = ResponseContext.parse(response.getEntity());
-
-            Assert.assertTrue("listIdToUpdate", context.getValue("data.cdp.deleteList"));
-        }
-    }
+            try (CloseableHttpResponse response = post("graphql/list/create-list.json")) {
+                final ResponseContext context = ResponseContext.parse(response.getEntity());
 
-    @Test
-    public void testGetList() throws Exception {
-        final UserList userList = createList("myListId", "listName", "listView");
+                Assert.assertEquals("testListId", context.getValue("data.cdp.createOrUpdateList.id"));
+                Assert.assertEquals("testListName", context.getValue("data.cdp.createOrUpdateList.name"));
+                Assert.assertEquals("testSite", context.getValue("data.cdp.createOrUpdateList.view.name"));
+            }
 
-        keepTrying("Failed waiting for the creation of the list",
-                () -> userListService.load(userList.getItemId()), Objects::nonNull, 1000, 100);
+            refreshPersistence();
 
-        try (CloseableHttpResponse response = post("graphql/list/get-list.json")) {
-            final ResponseContext context = ResponseContext.parse(response.getEntity());
+            try (CloseableHttpResponse response = post("graphql/list/update-list.json")) {
+                final ResponseContext context = ResponseContext.parse(response.getEntity());
 
-            Assert.assertEquals("myListId", context.getValue("data.cdp.getList.id"));
-            Assert.assertEquals("listName", context.getValue("data.cdp.getList.name"));
-            Assert.assertEquals("listView", context.getValue("data.cdp.getList.view.name"));
-        }
-    }
+                Assert.assertEquals("testListId", context.getValue("data.cdp.createOrUpdateList.id"));
+                Assert.assertEquals("testListNameUpdated", context.getValue("data.cdp.createOrUpdateList.name"));
+                Assert.assertEquals("testSiteUpdated", context.getValue("data.cdp.createOrUpdateList.view.name"));
+            }
 
-    @Test
-    public void testAdd_Find_Remove_ProfileToList() throws Exception {
-        final UserList userList = createList("addedToProfileTestListId", "addedToProfileTestListName", "addedToProfileTestListView");
+            refreshPersistence();
 
-        keepTrying("Failed waiting for the creation of the list",
-                () -> userListService.load(userList.getItemId()), Objects::nonNull, 1000, 100);
+            try (CloseableHttpResponse response = post("graphql/list/get-list.json")) {
+                final ResponseContext context = ResponseContext.parse(response.getEntity());
 
-        final Profile profile = new Profile("test_profile_id");
-        profile.setProperty("firstName", "TestFirstName");
-        profile.setProperty("lastName", "TestLastName");
-        profileService.save(profile);
-        refreshPersistence();
+                Assert.assertEquals("testListId", context.getValue("data.cdp.getList.id"));
+                Assert.assertEquals("testListNameUpdated", context.getValue("data.cdp.getList.name"));
+                Assert.assertEquals("testSiteUpdated", context.getValue("data.cdp.getList.view.name"));
+            }
 
-        keepTrying("Failed waiting for the creation of the profile",
-                () -> profileService.load(profile.getItemId()), Objects::nonNull, 1000, 100);
+            try (CloseableHttpResponse response = post("graphql/list/add-profile-to-list.json")) {
+                final ResponseContext context = ResponseContext.parse(response.getEntity());
 
-        try (CloseableHttpResponse response = post("graphql/list/add-profile-to-list.json")) {
-            final ResponseContext context = ResponseContext.parse(response.getEntity());
+                Assert.assertEquals("testListId", context.getValue("data.cdp.addProfileToList.id"));
+            }
 
-            Assert.assertEquals(userList.getItemId(), context.getValue("data.cdp.addProfileToList.id"));
-            Assert.assertEquals(userList.getMetadata().getName(), context.getValue("data.cdp.addProfileToList.name"));
-            Assert.assertEquals(userList.getMetadata().getScope(), context.getValue("data.cdp.addProfileToList.view.name"));
-        }
+            refreshPersistence();
 
-        refreshPersistence();
+            Thread.sleep(3000);
 
-        try (CloseableHttpResponse response = post("graphql/list/find-lists.json")) {
-            final ResponseContext context = ResponseContext.parse(response.getEntity());
+            try (CloseableHttpResponse response = post("graphql/list/find-lists.json")) {
+                final ResponseContext context = ResponseContext.parse(response.getEntity());
 
-            Assert.assertEquals(1, ((Integer) context.getValue("data.cdp.findLists.totalCount")).intValue());
-            Assert.assertEquals(userList.getItemId(), context.getValue("data.cdp.findLists.edges[0].node.id"));
-            Assert.assertEquals(userList.getMetadata().getName(), context.getValue("data.cdp.findLists.edges[0].node.name"));
-            Assert.assertEquals(userList.getMetadata().getScope(), context.getValue("data.cdp.findLists.edges[0].node.view.name"));
-            Assert.assertEquals(profile.getItemId(), context.getValue("data.cdp.findLists.edges[0].node.active.edges[0].node.cdp_profileIDs[0].id"));
-        }
+                Assert.assertEquals(1, ((Integer) context.getValue("data.cdp.findLists.totalCount")).intValue());
+                Assert.assertEquals("testListId", context.getValue("data.cdp.findLists.edges[0].node.id"));
+                Assert.assertEquals(profile.getItemId(), context.getValue("data.cdp.findLists.edges[0].node.active.edges[0].node.cdp_profileIDs[0].id"));
+            }
 
-        try (CloseableHttpResponse response = post("graphql/list/remove-profile-from-list.json")) {
-            final ResponseContext context = ResponseContext.parse(response.getEntity());
+            try (CloseableHttpResponse response = post("graphql/list/delete-list.json")) {
+                final ResponseContext context = ResponseContext.parse(response.getEntity());
 
-            Assert.assertTrue(context.getValue("data.cdp.removeProfileFromList"));
+                Assert.assertTrue("testListId", context.getValue("data.cdp.deleteList"));
+            }
+        } finally {
+            if (persistedProfile != null) {
+                profileService.delete(persistedProfile.getItemId(), false);
+            }
         }
     }
 
-    private UserList createList(final String id, final String name, final String scope) throws InterruptedException {
-        final Metadata metadata = new Metadata();
-
-        metadata.setId(id);
-        metadata.setName(name);
-        metadata.setScope(scope);
-
-        final UserList userList = new UserList();
-
-        userList.setItemType(UserList.ITEM_TYPE);
-        userList.setMetadata(metadata);
-
-        userListService.save(userList);
-        refreshPersistence();
-
-        return userList;
-    }
-
 }
diff --git a/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLSegmentIT.java b/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLSegmentIT.java
index be6b536..bed6367 100644
--- a/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLSegmentIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLSegmentIT.java
@@ -48,7 +48,7 @@ public class GraphQLSegmentIT extends BaseGraphQLIT {
     }
 
     @Test
-    public void testCreateThenGetAndDeleteSegment() throws IOException {
+    public void testCreateThenGetAndDeleteSegment() throws IOException, InterruptedException {
         try (CloseableHttpResponse response = post("graphql/segment/create-or-update-segment.json")) {
             final ResponseContext context = ResponseContext.parse(response.getEntity());
 
@@ -57,6 +57,8 @@ public class GraphQLSegmentIT extends BaseGraphQLIT {
             Assert.assertEquals("http://www.domain.com", context.getValue("data.cdp.createOrUpdateSegment.view.name"));
         }
 
+        refreshPersistence();
+
         try (CloseableHttpResponse response = post("graphql/segment/get-segment.json")) {
             final ResponseContext context = ResponseContext.parse(response.getEntity());
 
@@ -82,6 +84,7 @@ public class GraphQLSegmentIT extends BaseGraphQLIT {
         keepTrying("Failed waiting for the creation of the profile for the \"testCreateSegmentAndApplyToProfile\" test",
                 () -> profileService.load(profile.getItemId()), Objects::nonNull, 1000, 100);
 
+        refreshPersistence();
 
         try (CloseableHttpResponse response = post("graphql/segment/create-segment-with-properties-filter.json")) {
             final ResponseContext context = ResponseContext.parse(response.getEntity());
diff --git a/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLSourceIT.java b/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLSourceIT.java
new file mode 100644
index 0000000..bb03585
--- /dev/null
+++ b/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLSourceIT.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.itests.graphql;
+
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.unomi.api.services.SourceService;
+import org.junit.Test;
+import org.ops4j.pax.exam.util.Filter;
+
+import javax.inject.Inject;
+import java.io.IOException;
+
+import static org.junit.Assert.*;
+
+public class GraphQLSourceIT extends BaseGraphQLIT {
+
+    @Inject
+    @Filter(timeout = 600000)
+    SourceService sourceService;
+
+    @Test
+    public void testCRUD() throws IOException, InterruptedException {
+        try (CloseableHttpResponse response = post("graphql/source/create-source.json")) {
+            final ResponseContext context = ResponseContext.parse(response.getEntity());
+
+            assertEquals("testSourceId", context.getValue("data.cdp.createOrUpdateSource.id"));
+            assertNull(context.getValue("data.cdp.createOrUpdateSource.thirdParty"));
+        }
+
+        refreshPersistence();
+
+        try (CloseableHttpResponse response = post("graphql/source/update-source.json")) {
+            final ResponseContext context = ResponseContext.parse(response.getEntity());
+
+            assertEquals("testSourceId", context.getValue("data.cdp.createOrUpdateSource.id"));
+            assertTrue(context.getValue("data.cdp.createOrUpdateSource.thirdParty"));
+        }
+
+        refreshPersistence();
+
+        try (CloseableHttpResponse response = post("graphql/source/get-sources.json")) {
+            final ResponseContext context = ResponseContext.parse(response.getEntity());
+
+            assertEquals("testSourceId", context.getValue("data.cdp.getSources[0].id"));
+            assertTrue(context.getValue("data.cdp.getSources[0].thirdParty"));
+        }
+
+        try (CloseableHttpResponse response = post("graphql/source/delete-source.json")) {
+            final ResponseContext context = ResponseContext.parse(response.getEntity());
+
+            assertTrue(context.getValue("data.cdp.deleteSource"));
+        }
+    }
+
+}
diff --git a/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLTopicIT.java b/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLTopicIT.java
index 482c38f..96274c4 100644
--- a/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLTopicIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLTopicIT.java
@@ -32,13 +32,15 @@ public class GraphQLTopicIT extends BaseGraphQLIT {
     protected TopicService topicService;
 
     @Test
-    public void testCRUD() throws IOException {
+    public void testCRUD() throws IOException, InterruptedException {
         try (CloseableHttpResponse response = post("graphql/topic/create-topic.json")) {
             final ResponseContext context = ResponseContext.parse(response.getEntity());
 
             Assert.assertEquals("testTopic", context.getValue("data.cdp.createOrUpdateTopic.id"));
         }
 
+        refreshPersistence();
+
         try (CloseableHttpResponse response = post("graphql/topic/update-topic.json")) {
             final ResponseContext context = ResponseContext.parse(response.getEntity());
 
@@ -47,6 +49,8 @@ public class GraphQLTopicIT extends BaseGraphQLIT {
             Assert.assertEquals("testTopicView", context.getValue("data.cdp.createOrUpdateTopic.view.name"));
         }
 
+        refreshPersistence();
+
         try (CloseableHttpResponse response = post("graphql/topic/get-topic.json")) {
             final ResponseContext context = ResponseContext.parse(response.getEntity());
 
@@ -68,6 +72,8 @@ public class GraphQLTopicIT extends BaseGraphQLIT {
             Assert.assertTrue(context.getValue("data.cdp.deleteTopic"));
         }
 
+        refreshPersistence();
+
         try (CloseableHttpResponse response = post("graphql/topic/get-topic.json")) {
             final ResponseContext context = ResponseContext.parse(response.getEntity());
 
diff --git a/itests/src/test/resources/graphql/list/add-profile-to-list.json b/itests/src/test/resources/graphql/list/add-profile-to-list.json
index 5a2bfd1..2962c65 100644
--- a/itests/src/test/resources/graphql/list/add-profile-to-list.json
+++ b/itests/src/test/resources/graphql/list/add-profile-to-list.json
@@ -1,7 +1,7 @@
 {
   "operationName": "addProfileToList",
   "variables": {
-    "listID": "addedToProfileTestListId",
+    "listID": "testListId",
     "profileID": {
       "id": "test_profile_id",
       "client": {
diff --git a/itests/src/test/resources/graphql/list/delete-list.json b/itests/src/test/resources/graphql/list/delete-list.json
index 85250b1..e09079c 100644
--- a/itests/src/test/resources/graphql/list/delete-list.json
+++ b/itests/src/test/resources/graphql/list/delete-list.json
@@ -1,7 +1,7 @@
 {
   "operationName": "deleteList",
   "variables": {
-    "listID": "listIdToDelete"
+    "listID": "testListId"
   },
   "query": "mutation deleteList($listID: ID) {\n  cdp {\n    deleteList(listID: $listID)\n  }\n}\n"
 }
diff --git a/itests/src/test/resources/graphql/list/find-lists.json b/itests/src/test/resources/graphql/list/find-lists.json
index 89259ed..b733a3d 100644
--- a/itests/src/test/resources/graphql/list/find-lists.json
+++ b/itests/src/test/resources/graphql/list/find-lists.json
@@ -2,8 +2,8 @@
   "operationName": "findLists",
   "variables": {
     "filter": {
-      "name_equals": "addedToProfileTestListName",
-      "view_equals": "addedToProfileTestListView"
+      "name_equals": "testListNameUpdated",
+      "view_equals": "testSiteUpdated"
     }
   },
   "query": "query findLists($filter: CDP_ListFilterInput) {\n  cdp {\n    findLists(filter: $filter) {\n      totalCount\n      edges {\n        node {\n          id\n          name\n          view {\n            name\n          }\n          active {\n            edges {\n              node {\n                cdp_profileIDs {\n                  id\n                }\n              }\n            }\n            pageInfo {\n              hasNextPage\n              hasPreviousPage\n         [...]
diff --git a/itests/src/test/resources/graphql/list/get-list.json b/itests/src/test/resources/graphql/list/get-list.json
index cc4ddb7..ae335d5 100644
--- a/itests/src/test/resources/graphql/list/get-list.json
+++ b/itests/src/test/resources/graphql/list/get-list.json
@@ -1,7 +1,7 @@
 {
   "operationName": "getList",
   "variables": {
-    "listID": "myListId"
+    "listID": "testListId"
   },
   "query": "query getList($listID: ID) {\n  cdp {\n    getList(listID: $listID) {\n      id\n      view {\n        name\n      }\n      name\n    }\n  }\n}\n"
 }
diff --git a/itests/src/test/resources/graphql/list/remove-profile-from-list.json b/itests/src/test/resources/graphql/list/remove-profile-from-list.json
index 1835550..d0015be 100644
--- a/itests/src/test/resources/graphql/list/remove-profile-from-list.json
+++ b/itests/src/test/resources/graphql/list/remove-profile-from-list.json
@@ -1,7 +1,7 @@
 {
   "operationName": "removeProfileFromList",
   "variables": {
-    "listID": "addedToProfileTestListId",
+    "listID": "testListId",
     "profileID": {
       "id": "test_profile_id",
       "client": {
diff --git a/itests/src/test/resources/graphql/list/update-list.json b/itests/src/test/resources/graphql/list/update-list.json
index 45c521a..bad58a4 100644
--- a/itests/src/test/resources/graphql/list/update-list.json
+++ b/itests/src/test/resources/graphql/list/update-list.json
@@ -2,8 +2,8 @@
   "operationName": "createOrUpdateList",
   "variables": {
     "list": {
-      "id": "listIdToUpdate",
-      "name": "listNameUpdated",
+      "id": "testListId",
+      "name": "testListNameUpdated",
       "view": "testSiteUpdated"
     }
   },
diff --git a/itests/src/test/resources/graphql/source/create-source.json b/itests/src/test/resources/graphql/source/create-source.json
new file mode 100644
index 0000000..72faae8
--- /dev/null
+++ b/itests/src/test/resources/graphql/source/create-source.json
@@ -0,0 +1,9 @@
+{
+  "operationName": "createSource",
+  "variables": {
+    "source": {
+      "id": "testSourceId"
+    }
+  },
+  "query": "mutation createSource($source: CDP_SourceInput) {\n  cdp {\n    createOrUpdateSource(source: $source) {\n      id\n      thirdParty\n    }\n  }\n}\n"
+}
diff --git a/itests/src/test/resources/graphql/source/delete-source.json b/itests/src/test/resources/graphql/source/delete-source.json
new file mode 100644
index 0000000..b8ef911
--- /dev/null
+++ b/itests/src/test/resources/graphql/source/delete-source.json
@@ -0,0 +1,7 @@
+{
+  "operationName": "deleteSource",
+  "variables": {
+    "sourceID": "testSourceId"
+  },
+  "query": "mutation deleteSource($sourceID: ID!) {\n  cdp {\n    deleteSource(sourceID: $sourceID)\n  }\n}\n"
+}
diff --git a/itests/src/test/resources/graphql/source/get-sources.json b/itests/src/test/resources/graphql/source/get-sources.json
new file mode 100644
index 0000000..2906d53
--- /dev/null
+++ b/itests/src/test/resources/graphql/source/get-sources.json
@@ -0,0 +1,5 @@
+{
+  "operationName": "getSources",
+  "variables": {},
+  "query": "query getSources {\n  cdp {\n    getSources {\n      id\n      thirdParty\n    }\n  }\n}\n"
+}
diff --git a/itests/src/test/resources/graphql/source/update-source.json b/itests/src/test/resources/graphql/source/update-source.json
new file mode 100644
index 0000000..57b081a
--- /dev/null
+++ b/itests/src/test/resources/graphql/source/update-source.json
@@ -0,0 +1,10 @@
+{
+  "operationName": "updateSource",
+  "variables": {
+    "source": {
+      "id": "testSourceId",
+      "thirdParty": true
+    }
+  },
+  "query": "mutation updateSource($source: CDP_SourceInput) {\n  cdp {\n    createOrUpdateSource(source: $source) {\n      id\n      thirdParty\n    }\n  }\n}\n"
+}
diff --git a/package/src/main/resources/etc/custom.system.properties b/package/src/main/resources/etc/custom.system.properties
index 77c8d0d..bc39473 100644
--- a/package/src/main/resources/etc/custom.system.properties
+++ b/package/src/main/resources/etc/custom.system.properties
@@ -388,3 +388,8 @@ org.apache.unomi.weatherUpdate.url.attributes=${env:UNOMI_WEATHERUPDATE_URL_ATTR
 org.apache.unomi.interests.min_value=${env:UNOMI_INTERESTS_MIN_VALUE:-0.0}
 org.apache.unomi.interests.max_value=${env:UNOMI_INTERESTS_MAX_VALUE:-150.0}
 org.apache.unomi.interests.divider_value=${env:UNOMI_INTERESTS_DIVIDER_VALUE:-100.0}
+
+#######################################################################################################################
+## Settings for EventService                                                                                         ##
+#######################################################################################################################
+org.apache.unomi.events.shouldBeCheckedEventSourceId=${env:UNOMI_SHOULD_BE_CHECKED_EVENT_SOURCE_ID:-false}
diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java
index c2af0d5..786bbdc 100644
--- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java
+++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java
@@ -78,6 +78,7 @@ public class CustomObjectMapper extends ObjectMapper {
         classes.put(ConditionType.ITEM_TYPE, ConditionType.class);
         classes.put(ActionType.ITEM_TYPE, ActionType.class);
         classes.put(Topic.ITEM_TYPE, Topic.class);
+        classes.put(SourceItem.ITEM_TYPE, SourceItem.class);
         for (Map.Entry<String, Class<? extends Item>> entry : classes.entrySet()) {
             propertyTypedObjectDeserializer.registerMapping("itemType=" + entry.getKey(), entry.getValue());
             itemDeserializer.registerMapping(entry.getKey(), entry.getValue());
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/MergeProfilesOnPropertyAction.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/MergeProfilesOnPropertyAction.java
index 93036d7..4a89894 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/MergeProfilesOnPropertyAction.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/MergeProfilesOnPropertyAction.java
@@ -118,7 +118,7 @@ public class MergeProfilesOnPropertyAction implements ActionExecutor {
 
             if (currentSession != null) {
                 currentSession.setProfile(profile);
-                eventService.send(new Event("sessionReassigned", currentSession, profile, event.getScope(), event, currentSession, event.getTimeStamp()));
+                eventService.send(new Event("sessionReassigned", currentSession, profile, event.getSourceId(), event, currentSession, event.getTimeStamp()));
             }
 
             return EventService.PROFILE_UPDATED + EventService.SESSION_UPDATED;
@@ -165,7 +165,7 @@ public class MergeProfilesOnPropertyAction implements ActionExecutor {
                 if (currentSession != null){
                     currentSession.setProfile(masterProfile);
                     if (privacyService.isRequireAnonymousBrowsing(profile)) {
-                        privacyService.setRequireAnonymousBrowsing(masterProfileId, true, event.getScope());
+                        privacyService.setRequireAnonymousBrowsing(masterProfileId, true, event.getSourceId());
                     }
 
                     if (anonymousBrowsing) {
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SendEventAction.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SendEventAction.java
index 9f52246..25adf9a 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SendEventAction.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SendEventAction.java
@@ -44,7 +44,7 @@ public class SendEventAction implements ActionExecutor {
 //            Item targetItem = new CustomItem();
 //            BeanUtils.populate(targetItem, target);
 
-        Event subEvent = new Event(eventType, event.getSession(), event.getProfile(), event.getScope(), event, target, event.getTimeStamp());
+        Event subEvent = new Event(eventType, event.getSession(), event.getProfile(), event.getSourceId(), event, target, event.getTimeStamp());
         subEvent.setProfileId(event.getProfileId());
         subEvent.getAttributes().putAll(event.getAttributes());
         subEvent.getProperties().putAll(eventProperties);
diff --git a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SetPropertyAction.java b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SetPropertyAction.java
index 1e89e0b..16bf99e 100644
--- a/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SetPropertyAction.java
+++ b/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/actions/SetPropertyAction.java
@@ -110,7 +110,7 @@ public class SetPropertyAction implements ActionExecutor {
                 Map<String, Object> propertyToUpdate = new HashMap<>();
                 propertyToUpdate.put(propertyName, propertyValue);
 
-                Event updateProperties = new Event("updateProperties", event.getSession(), event.getProfile(), event.getScope(), null, event.getProfile(), new Date());
+                Event updateProperties = new Event("updateProperties", event.getSession(), event.getProfile(), event.getSourceId(), null, event.getProfile(), new Date());
                 updateProperties.setPersistent(false);
 
                 updateProperties.setProperty(UpdatePropertiesAction.PROPS_TO_UPDATE, propertyToUpdate);
diff --git a/samples/trainingplugin/src/main/java/org/apache/unomi/training/TrainedNotificationAction.java b/samples/trainingplugin/src/main/java/org/apache/unomi/training/TrainedNotificationAction.java
index 74c2242..75c388f 100644
--- a/samples/trainingplugin/src/main/java/org/apache/unomi/training/TrainedNotificationAction.java
+++ b/samples/trainingplugin/src/main/java/org/apache/unomi/training/TrainedNotificationAction.java
@@ -48,7 +48,7 @@ public class TrainedNotificationAction implements ActionExecutor {
 
         if (trained == null) {
             // create trained flag property type
-            Metadata propertyTypeMetadata = new Metadata(event.getScope(), TRAINED_NB_PROPERTY, TRAINED_NB_PROPERTY, "Am I trained");
+            Metadata propertyTypeMetadata = new Metadata(event.getSourceId(), TRAINED_NB_PROPERTY, TRAINED_NB_PROPERTY, "Am I trained");
             propertyTypeMetadata.setSystemTags(Collections.singleton("training"));
             PropertyType propertyType = new PropertyType();
             propertyType.setValueTypeId("boolean");
diff --git a/samples/tweet-button-plugin/src/main/java/org/apache/unomi/samples/tweet_button_plugin/actions/IncrementTweetNumberAction.java b/samples/tweet-button-plugin/src/main/java/org/apache/unomi/samples/tweet_button_plugin/actions/IncrementTweetNumberAction.java
index c6fa4e5..b062425 100644
--- a/samples/tweet-button-plugin/src/main/java/org/apache/unomi/samples/tweet_button_plugin/actions/IncrementTweetNumberAction.java
+++ b/samples/tweet-button-plugin/src/main/java/org/apache/unomi/samples/tweet_button_plugin/actions/IncrementTweetNumberAction.java
@@ -43,14 +43,14 @@ public class IncrementTweetNumberAction implements ActionExecutor {
 
         if (tweetNb == null || tweetedFrom == null) {
             // create tweet number property type
-            PropertyType propertyType = new PropertyType(new Metadata(event.getScope(), TWEET_NB_PROPERTY, TWEET_NB_PROPERTY, "Number of times a user tweeted"));
+            PropertyType propertyType = new PropertyType(new Metadata(event.getSourceId(), TWEET_NB_PROPERTY, TWEET_NB_PROPERTY, "Number of times a user tweeted"));
             propertyType.setValueTypeId("integer");
             propertyType.getMetadata().setTags(Collections.singleton("social"));
             propertyType.setTarget(TARGET);
             service.setPropertyType(propertyType);
 
             // create tweeted from property type
-            propertyType = new PropertyType(new Metadata(event.getScope(), TWEETED_FROM_PROPERTY, TWEETED_FROM_PROPERTY, "The list of pages a user tweeted from"));
+            propertyType = new PropertyType(new Metadata(event.getSourceId(), TWEETED_FROM_PROPERTY, TWEETED_FROM_PROPERTY, "The list of pages a user tweeted from"));
             propertyType.setValueTypeId("string");
             propertyType.getMetadata().setTags(Collections.singleton("social"));
             propertyType.setTarget(TARGET);
diff --git a/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
index c086443..a163d32 100644
--- a/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
+++ b/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
@@ -35,6 +35,7 @@ import org.apache.unomi.api.services.DefinitionsService;
 import org.apache.unomi.api.services.EventListenerService;
 import org.apache.unomi.api.services.EventService;
 import org.apache.unomi.api.services.EventTypeRegistry;
+import org.apache.unomi.api.services.SourceService;
 import org.apache.unomi.persistence.spi.PersistenceService;
 import org.apache.unomi.persistence.spi.aggregate.TermsAggregate;
 import org.apache.unomi.services.impl.ParserHelper;
@@ -63,6 +64,8 @@ public class EventServiceImpl implements EventService {
 
     private DefinitionsService definitionsService;
 
+    private SourceService sourceService;
+
     private BundleContext bundleContext;
 
     private EventTypeRegistry eventTypeRegistry;
@@ -73,7 +76,9 @@ public class EventServiceImpl implements EventService {
 
     private Map<String, ThirdPartyServer> thirdPartyServers = new HashMap<>();
 
-    public void setThirdPartyConfiguration(Map<String,String> thirdPartyConfiguration) {
+    private Boolean shouldBeCheckedEventSourceId;
+
+    public void setThirdPartyConfiguration(Map<String, String> thirdPartyConfiguration) {
         this.thirdPartyServers = new HashMap<>();
         for (Map.Entry<String, String> entry : thirdPartyConfiguration.entrySet()) {
             String[] keys = StringUtils.split(entry.getKey(),'.');
@@ -108,6 +113,10 @@ public class EventServiceImpl implements EventService {
         this.restrictedEventTypeIds = restrictedEventTypeIds;
     }
 
+    public void setShouldBeCheckedEventSourceId(boolean shouldBeCheckedEventSourceId) {
+        this.shouldBeCheckedEventSourceId = shouldBeCheckedEventSourceId;
+    }
+
     public void setEventTypeRegistry(EventTypeRegistry eventTypeRegistry) {
         this.eventTypeRegistry = eventTypeRegistry;
     }
@@ -120,6 +129,10 @@ public class EventServiceImpl implements EventService {
         this.definitionsService = definitionsService;
     }
 
+    public void setSourceService(SourceService sourceService) {
+        this.sourceService = sourceService;
+    }
+
     public void setBundleContext(BundleContext bundleContext) {
         this.bundleContext = bundleContext;
     }
@@ -159,6 +172,11 @@ public class EventServiceImpl implements EventService {
     }
 
     private int send(Event event, int depth) {
+        if (shouldBeCheckedEventSourceId == Boolean.TRUE && sourceService.load(event.getSourceId()) == null) {
+            logger.warn("Event sending was rejected, because source with sourceId=\"{}\" does not registered in the system.", event.getSourceId());
+            return NO_CHANGE;
+        }
+
         if (depth > MAX_RECURSION_DEPTH) {
             logger.warn("Max recursion depth reached");
             return NO_CHANGE;
@@ -190,7 +208,7 @@ public class EventServiceImpl implements EventService {
                 }
 
                 if ((changes & PROFILE_UPDATED) == PROFILE_UPDATED) {
-                    Event profileUpdated = new Event("profileUpdated", session, event.getProfile(), event.getScope(), event.getSource(), event.getProfile(), event.getTimeStamp());
+                    Event profileUpdated = new Event("profileUpdated", session, event.getProfile(), event.getSourceId(), event.getSource(), event.getProfile(), event.getTimeStamp());
                     profileUpdated.setPersistent(false);
                     profileUpdated.getAttributes().putAll(event.getAttributes());
                     changes |= send(profileUpdated, depth + 1);
diff --git a/services/src/main/java/org/apache/unomi/services/impl/rules/RulesServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/rules/RulesServiceImpl.java
index 734d856..e99bb7e 100644
--- a/services/src/main/java/org/apache/unomi/services/impl/rules/RulesServiceImpl.java
+++ b/services/src/main/java/org/apache/unomi/services/impl/rules/RulesServiceImpl.java
@@ -171,7 +171,7 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn
             RuleStatistics ruleStatistics = getLocalRuleStatistics(rule);
             long ruleConditionStartTime = System.currentTimeMillis();
             String scope = rule.getMetadata().getScope();
-            if (scope.equals(Metadata.SYSTEM_SCOPE) || scope.equals(event.getScope())) {
+            if (scope.equals(Metadata.SYSTEM_SCOPE) || scope.equals(event.getSourceId())) {
                 Condition eventCondition = definitionsService.extractConditionBySystemTag(rule.getCondition(), "eventCondition");
 
                 if (eventCondition == null) {
@@ -272,7 +272,7 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn
                 changes |= actionExecutorDispatcher.execute(action, event);
             }
             long totalActionsTime = System.currentTimeMillis() - actionsStartTime;
-            Event ruleFired = new Event("ruleFired", event.getSession(), event.getProfile(), event.getScope(), event, rule, event.getTimeStamp());
+            Event ruleFired = new Event("ruleFired", event.getSession(), event.getProfile(), event.getSourceId(), event, rule, event.getTimeStamp());
             ruleFired.getAttributes().putAll(event.getAttributes());
             ruleFired.setPersistent(false);
             changes |= eventService.send(ruleFired);
diff --git a/services/src/main/java/org/apache/unomi/services/impl/source/SourceServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/source/SourceServiceImpl.java
new file mode 100644
index 0000000..47c4bed
--- /dev/null
+++ b/services/src/main/java/org/apache/unomi/services/impl/source/SourceServiceImpl.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.services.impl.source;
+
+import org.apache.unomi.api.SourceItem;
+import org.apache.unomi.api.services.SourceService;
+import org.apache.unomi.persistence.spi.PersistenceService;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.SynchronousBundleListener;
+
+import java.util.List;
+
+public class SourceServiceImpl implements SourceService, SynchronousBundleListener {
+
+    private PersistenceService persistenceService;
+
+    private BundleContext bundleContext;
+
+    @Override
+    public SourceItem load(String sourceId) {
+        return persistenceService.load(sourceId, SourceItem.class);
+    }
+
+    @Override
+    public SourceItem save(SourceItem source) {
+        if (persistenceService.save(source)) {
+            persistenceService.refreshIndex(SourceItem.class, null);
+
+            return source;
+        }
+
+        return null;
+    }
+
+    @Override
+    public List<SourceItem> getAll() {
+        return persistenceService.getAllItems(SourceItem.class);
+    }
+
+    @Override
+    public boolean delete(String sourceId) {
+        return persistenceService.remove(sourceId, SourceItem.class);
+    }
+
+    @Override
+    public void bundleChanged(BundleEvent bundleEvent) {
+        // do nothing
+    }
+
+    public void setPersistenceService(PersistenceService persistenceService) {
+        this.persistenceService = persistenceService;
+    }
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
+    public void postConstruct() {
+        bundleContext.addBundleListener(this);
+    }
+
+    public void preDestroy() {
+        bundleContext.removeBundleListener(this);
+    }
+
+}
diff --git a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 4a9ab5b..b647959 100644
--- a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -41,6 +41,7 @@
             <cm:property name="segment.send.profile.update.event" value="true"/>
             <cm:property name="rules.refresh.interval" value="1000"/>
             <cm:property name="rules.statistics.refresh.interval" value="10000"/>
+            <cm:property name="events.shouldBeCheckedEventSourceId" value="false"/>
         </cm:default-properties>
     </cm:property-placeholder>
 
@@ -104,6 +105,7 @@
     <bean id="eventServiceImpl" class="org.apache.unomi.services.impl.events.EventServiceImpl">
         <property name="persistenceService" ref="persistenceService"/>
         <property name="definitionsService" ref="definitionsServiceImpl"/>
+        <property name="sourceService" ref="sourceServiceImpl"/>
         <property name="bundleContext" ref="blueprintBundleContext"/>
         <property name="eventTypeRegistry" ref="eventTypeRegistryImpl"/>
         <property name="predefinedEventTypeIds">
@@ -127,6 +129,7 @@
         <property name="thirdPartyConfiguration">
             <cm:cm-properties persistent-id="org.apache.unomi.thirdparty" update="true"/>
         </property>
+        <property name="shouldBeCheckedEventSourceId" value="${services.events.shouldBeCheckedEventSourceId}"/>
     </bean>
     <service id="eventService" ref="eventServiceImpl" interface="org.apache.unomi.api.services.EventService"/>
 
@@ -285,6 +288,17 @@
         </interfaces>
     </service>
 
+    <bean id="sourceServiceImpl" class="org.apache.unomi.services.impl.source.SourceServiceImpl"
+          init-method="postConstruct" destroy-method="preDestroy">
+        <property name="persistenceService" ref="persistenceService"/>
+        <property name="bundleContext" ref="blueprintBundleContext"/>
+    </bean>
+    <service id="sourceService" ref="sourceServiceImpl">
+        <interfaces>
+            <value>org.apache.unomi.api.services.SourceService</value>
+            <value>org.osgi.framework.SynchronousBundleListener</value>
+        </interfaces>
+    </service>
 
     <!-- We use a listener here because using the list directly for listening to proxies coming from the same bundle didn't seem to work -->
     <reference-list id="eventListenerServices"
diff --git a/services/src/main/resources/org.apache.unomi.services.cfg b/services/src/main/resources/org.apache.unomi.services.cfg
index 37c9881..bce4f2f 100644
--- a/services/src/main/resources/org.apache.unomi.services.cfg
+++ b/services/src/main/resources/org.apache.unomi.services.cfg
@@ -63,3 +63,6 @@ rules.refresh.interval=${org.apache.unomi.rules.refresh.interval:-1000}
 
 # The interval in milliseconds to use to reload the rules statistics
 rules.statistics.refresh.interval=${org.apache.unomi.rules.statistics.refresh.interval:-10000}
+
+# The indicator should be checked is there a sourceId in the system or not
+events.shouldBeCheckedEventSourceId=${org.apache.unomi.events.shouldBeCheckedEventSourceId:-false}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventList.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventList.java
index b996a70..985aa1c 100644
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventList.java
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventList.java
@@ -81,7 +81,7 @@ public class EventList extends ListCommandSupport {
             rowData.add(event.getSessionId());
             rowData.add(event.getProfileId());
             rowData.add(event.getTimeStamp().toString());
-            rowData.add(event.getScope());
+            rowData.add(event.getSourceId());
             rowData.add(Boolean.toString(event.isPersistent()));
             dataTable.addRow(rowData.toArray(new Comparable[rowData.size()]));
         }
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventSearch.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventSearch.java
index 082f1d3..290f60c 100644
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventSearch.java
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventSearch.java
@@ -90,7 +90,7 @@ public class EventSearch extends ListCommandSupport  {
             rowData.add(event.getSessionId());
             rowData.add(event.getProfileId());
             rowData.add(event.getTimeStamp().toString());
-            rowData.add(event.getScope());
+            rowData.add(event.getSourceId());
             rowData.add(Boolean.toString(event.isPersistent()));
             dataTable.addRow(rowData.toArray(new Comparable[rowData.size()]));
         }
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventTail.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventTail.java
index 36ecdec..75f76c0 100644
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventTail.java
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/EventTail.java
@@ -84,7 +84,7 @@ public class EventTail extends TailCommandSupport  {
             eventInfo.add(event.getSessionId());
             eventInfo.add(event.getProfileId());
             eventInfo.add(event.getTimeStamp().toString());
-            eventInfo.add(event.getScope());
+            eventInfo.add(event.getSourceId());
             eventInfo.add(Boolean.toString(event.isPersistent()));
             outputLine(out, eventInfo);
             return EventService.NO_CHANGE;
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/RuleTail.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/RuleTail.java
index 6cf66b2..f381539 100644
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/RuleTail.java
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/RuleTail.java
@@ -86,7 +86,7 @@ public class RuleTail extends TailCommandSupport {
             ruleExecutionInfo.add(event.getSessionId());
             ruleExecutionInfo.add(event.getProfileId());
             ruleExecutionInfo.add(event.getTimeStamp().toString());
-            ruleExecutionInfo.add(event.getScope());
+            ruleExecutionInfo.add(event.getSourceId());
             outputLine(out, ruleExecutionInfo);
         }
     }
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/RuleWatch.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/RuleWatch.java
index c943944..c922445 100644
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/RuleWatch.java
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/RuleWatch.java
@@ -101,7 +101,7 @@ public class RuleWatch extends TailCommandSupport {
             ruleExecutionInfo.add(event.getSessionId());
             ruleExecutionInfo.add(event.getProfileId());
             ruleExecutionInfo.add(event.getTimeStamp().toString());
-            ruleExecutionInfo.add(event.getScope());
+            ruleExecutionInfo.add(event.getSourceId());
             outputLine(out, ruleExecutionInfo);
         }
 
diff --git a/wab/src/main/java/org/apache/unomi/web/EventsCollectorServlet.java b/wab/src/main/java/org/apache/unomi/web/EventsCollectorServlet.java
index 313b25b..79f71ca 100644
--- a/wab/src/main/java/org/apache/unomi/web/EventsCollectorServlet.java
+++ b/wab/src/main/java/org/apache/unomi/web/EventsCollectorServlet.java
@@ -138,8 +138,8 @@ public class EventsCollectorServlet extends HttpServlet {
             // Get the first available scope that is not equal to systemscope to create the session otherwise systemscope will be used
             for (Event event : eventsCollectorRequest.getEvents()) {
                 if (StringUtils.isNotBlank(event.getEventType())) {
-                    if (StringUtils.isNotBlank(event.getScope()) && !event.getScope().equals("systemscope")) {
-                        scope = event.getScope();
+                    if (StringUtils.isNotBlank(event.getSourceId()) && !event.getSourceId().equals("systemscope")) {
+                        scope = event.getSourceId();
                         break;
                     } else if (event.getSource() != null && StringUtils.isNotBlank(event.getSource().getScope()) && !event.getSource().getScope().equals("systemscope")) {
                         scope = event.getSource().getScope();
diff --git a/wab/src/main/java/org/apache/unomi/web/ServletCommon.java b/wab/src/main/java/org/apache/unomi/web/ServletCommon.java
index 6c48edd..e8722c3 100644
--- a/wab/src/main/java/org/apache/unomi/web/ServletCommon.java
+++ b/wab/src/main/java/org/apache/unomi/web/ServletCommon.java
@@ -74,14 +74,14 @@ public class ServletCommon {
                         continue;
                     }
 
-                    Event eventToSend = new Event(event.getEventType(), session, profile, event.getScope(), event.getSource(),
+                    Event eventToSend = new Event(event.getEventType(), session, profile, event.getSourceId(), event.getSource(),
                             event.getTarget(), event.getProperties(), timestamp, event.isPersistent());
                     if (!eventService.isEventAllowed(event, thirdPartyId)) {
                         logger.warn("Event is not allowed : {}", event.getEventType());
                         continue;
                     }
                     if (thirdPartyId != null && event.getItemId() != null) {
-                        eventToSend = new Event(event.getItemId(), event.getEventType(), session, profile, event.getScope(), event.getSource(), event.getTarget(), event.getProperties(), timestamp, event.isPersistent());
+                        eventToSend = new Event(event.getItemId(), event.getEventType(), session, profile, event.getSourceId(), event.getSource(), event.getTarget(), event.getProperties(), timestamp, event.isPersistent());
                     }
                     if (filteredEventTypes != null && filteredEventTypes.contains(event.getEventType())) {
                         logger.debug("Profile is filtering event type {}", event.getEventType());