You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@unomi.apache.org by as...@apache.org on 2021/09/01 14:58:47 UTC

[unomi] branch UNOMI-445-v2 created (now d3e3c98)

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

asi pushed a change to branch UNOMI-445-v2
in repository https://gitbox.apache.org/repos/asf/unomi.git.


      at d3e3c98  UNOMI-445 Implement support for multiple profile IDs on Unomi profiles

This branch includes the following new commits:

     new d3e3c98  UNOMI-445 Implement support for multiple profile IDs on Unomi profiles

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


[unomi] 01/01: UNOMI-445 Implement support for multiple profile IDs on Unomi profiles

Posted by as...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

asi pushed a commit to branch UNOMI-445-v2
in repository https://gitbox.apache.org/repos/asf/unomi.git

commit d3e3c98b3cdc655777188cfa001b7187f48cc2c9
Author: Anatol Sialitski <as...@enonic.com>
AuthorDate: Wed Sep 1 17:57:05 2021 +0300

    UNOMI-445 Implement support for multiple profile IDs on Unomi profiles
---
 .../java/org/apache/unomi/api/ContextRequest.java  | 10 +++
 api/src/main/java/org/apache/unomi/api/Event.java  |  6 ++
 .../java/org/apache/unomi/api/ProfileAlias.java    | 68 +++++++++++++++++++
 .../apache/unomi/api/services/ProfileService.java  |  2 +
 .../fetchers/profile/ProfileDataFetcher.java       |  3 +
 .../org/apache/unomi/itests/ProfileMergeIT.java    | 61 +++++++++++++++++
 .../org/apache/unomi/itests/ProfileServiceIT.java  | 40 +++++++++++
 .../unomi/persistence/spi/CustomObjectMapper.java  |  1 +
 .../actions/MergeProfilesOnPropertyAction.java     | 78 ++++++++++------------
 .../unomi/rest/endpoints/ContextJsonEndpoint.java  | 17 ++++-
 .../services/impl/profiles/ProfileServiceImpl.java | 28 ++++++++
 11 files changed, 270 insertions(+), 44 deletions(-)

diff --git a/api/src/main/java/org/apache/unomi/api/ContextRequest.java b/api/src/main/java/org/apache/unomi/api/ContextRequest.java
index 7a67c06..78c4720 100644
--- a/api/src/main/java/org/apache/unomi/api/ContextRequest.java
+++ b/api/src/main/java/org/apache/unomi/api/ContextRequest.java
@@ -73,6 +73,8 @@ public class ContextRequest {
     @Pattern(regexp = ValidationPattern.TEXT_VALID_CHARACTERS_PATTERN)
     private String profileId;
 
+    private String clientId;
+
     /**
      * Retrieves the source of the context request.
      *
@@ -271,4 +273,12 @@ public class ContextRequest {
     public void setProfileId(String profileId) {
         this.profileId = profileId;
     }
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public void setClientId(String clientId) {
+        this.clientId = clientId;
+    }
 }
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 ab7e1b0..45a1296 100644
--- a/api/src/main/java/org/apache/unomi/api/Event.java
+++ b/api/src/main/java/org/apache/unomi/api/Event.java
@@ -46,6 +46,12 @@ public class Event extends Item implements TimestampedItem {
      * A constant for the name of the attribute that can be used to retrieve the current HTTP response.
      */
     public static final String HTTP_RESPONSE_ATTRIBUTE = "http_response";
+
+    /**
+     * A constant for the name of the attribute that can be used to retrieve the current clientID.
+     */
+    public static final String CLIENT_ID_ATTRIBUTE = "client_id";
+
     private static final long serialVersionUID = -1096874942838593575L;
     private String eventType;
     private String sessionId = null;
diff --git a/api/src/main/java/org/apache/unomi/api/ProfileAlias.java b/api/src/main/java/org/apache/unomi/api/ProfileAlias.java
new file mode 100644
index 0000000..1434b02
--- /dev/null
+++ b/api/src/main/java/org/apache/unomi/api/ProfileAlias.java
@@ -0,0 +1,68 @@
+/*
+ * 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;
+
+import java.util.Date;
+
+public class ProfileAlias extends Item {
+
+    public static final String ITEM_TYPE = "profileAlias";
+
+    private String profileID;
+
+    private String clientID;
+
+    private Date creationTime;
+
+    private Date modifiedTime;
+
+    public ProfileAlias() {
+    }
+
+    public String getProfileID() {
+        return profileID;
+    }
+
+    public void setProfileID(String profileID) {
+        this.profileID = profileID;
+    }
+
+    public String getClientID() {
+        return clientID;
+    }
+
+    public void setClientID(String clientID) {
+        this.clientID = clientID;
+    }
+
+    public Date getCreationTime() {
+        return creationTime;
+    }
+
+    public void setCreationTime(Date creationTime) {
+        this.creationTime = creationTime;
+    }
+
+    public Date getModifiedTime() {
+        return modifiedTime;
+    }
+
+    public void setModifiedTime(Date modifiedTime) {
+        this.modifiedTime = modifiedTime;
+    }
+}
diff --git a/api/src/main/java/org/apache/unomi/api/services/ProfileService.java b/api/src/main/java/org/apache/unomi/api/services/ProfileService.java
index 3b94ed9..b2698c4 100644
--- a/api/src/main/java/org/apache/unomi/api/services/ProfileService.java
+++ b/api/src/main/java/org/apache/unomi/api/services/ProfileService.java
@@ -108,6 +108,8 @@ public interface ProfileService {
      */
     Profile save(Profile profile);
 
+    void addAliasToProfile(String profileID, String alias, String clientID);
+
     /**
      * Merge the specified profile properties in an existing profile,or save new profile if it does not exist yet
      *
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/profile/ProfileDataFetcher.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/profile/ProfileDataFetcher.java
index 0a566af..0000065 100644
--- a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/profile/ProfileDataFetcher.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/fetchers/profile/ProfileDataFetcher.java
@@ -52,6 +52,9 @@ public class ProfileDataFetcher extends BaseDataFetcher<CDPProfile> {
             profile.setItemType("profile");
 
             profile = profileService.save(profile);
+
+            profileService.addAliasToProfile(profile.getItemId(), profile.getItemId(), profileIDInput.getClient().getId());
+
             return new CDPProfile(profile);
         }
 
diff --git a/itests/src/test/java/org/apache/unomi/itests/ProfileMergeIT.java b/itests/src/test/java/org/apache/unomi/itests/ProfileMergeIT.java
index aa19ae4..9e451d6 100644
--- a/itests/src/test/java/org/apache/unomi/itests/ProfileMergeIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/ProfileMergeIT.java
@@ -19,12 +19,15 @@ package org.apache.unomi.itests;
 import org.apache.unomi.api.Event;
 import org.apache.unomi.api.Metadata;
 import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.ProfileAlias;
 import org.apache.unomi.api.actions.Action;
 import org.apache.unomi.api.conditions.Condition;
 import org.apache.unomi.api.rules.Rule;
 import org.apache.unomi.api.services.DefinitionsService;
 import org.apache.unomi.api.services.EventService;
+import org.apache.unomi.api.services.ProfileService;
 import org.apache.unomi.api.services.RulesService;
+import org.apache.unomi.persistence.spi.PersistenceService;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Test;
@@ -38,6 +41,7 @@ import javax.inject.Inject;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.List;
 
 /**
  * Integration test for MergeProfilesOnPropertyAction
@@ -52,6 +56,10 @@ public class ProfileMergeIT extends BaseIT {
     protected RulesService rulesService;
     @Inject @Filter(timeout = 600000)
     protected DefinitionsService definitionsService;
+    @Inject @Filter(timeout = 600000)
+    protected ProfileService profileService;
+    @Inject @Filter(timeout = 600000)
+    protected PersistenceService persistenceService;
 
     private final static String TEST_EVENT_TYPE = "mergeProfileTestEventType";
     private final static String TEST_RULE_ID = "mergeOnPropertyTest";
@@ -81,6 +89,59 @@ public class ProfileMergeIT extends BaseIT {
         Assert.assertEquals(sendEvent().getProfile().getItemId(), TEST_PROFILE_ID);
     }
 
+    @Test
+    public void test() throws InterruptedException {
+        // create rule
+        Condition condition = new Condition(definitionsService.getConditionType("eventTypeCondition"));
+        condition.setParameter("eventTypeId", TEST_EVENT_TYPE);
+
+        final Action action = new Action( definitionsService.getActionType( "mergeProfilesOnPropertyAction"));
+        action.setParameter("mergeProfilePropertyValue", "eventProperty::target.properties(email)");
+        action.setParameter("mergeProfilePropertyName", "mergeIdentifier");
+        action.setParameter("forceEventProfileAsMaster", false);
+
+        Rule rule = new Rule();
+        rule.setMetadata(new Metadata(null, TEST_RULE_ID, TEST_RULE_ID, "Description"));
+        rule.setCondition(condition);
+        rule.setActions(Collections.singletonList(action));
+
+        rulesService.setRule(rule);
+        refreshPersistence();
+
+        // create master profile
+        Profile masterProfile = new Profile();
+        masterProfile.setItemId("masterProfileID");
+        masterProfile.setProperty("email", "username@domain.com");
+        masterProfile.setSystemProperty("mergeIdentifier", "username@domain.com");
+        profileService.save(masterProfile);
+
+        // create event profile
+        Profile eventProfile = new Profile();
+        eventProfile.setItemId("eventProfileID");
+        eventProfile.setProperty("email", "username@domain.com");
+        profileService.save(eventProfile);
+
+        refreshPersistence();
+
+        Event event = new Event(TEST_EVENT_TYPE, null, eventProfile, null, null, eventProfile, new Date());
+        eventService.send(event);
+
+        refreshPersistence();
+
+        Assert.assertNotNull(event.getProfile());
+
+        List<ProfileAlias> profileAliases = persistenceService.getAllItems(ProfileAlias.class);
+
+        Assert.assertFalse(profileAliases.isEmpty());
+
+        List<ProfileAlias> aliases = persistenceService.query("profileID", masterProfile.getItemId(), null, ProfileAlias.class);
+
+        Assert.assertFalse(aliases.isEmpty());
+        Assert.assertEquals(masterProfile.getItemId(), aliases.get(0).getProfileID());
+        Assert.assertEquals(eventProfile.getItemId(), aliases.get(0).getItemId());
+        Assert.assertEquals("defaultClientId", aliases.get(0).getClientID());
+    }
+
     private Event sendEvent() {
         Profile profile = new Profile();
         profile.setProperties(new HashMap<>());
diff --git a/itests/src/test/java/org/apache/unomi/itests/ProfileServiceIT.java b/itests/src/test/java/org/apache/unomi/itests/ProfileServiceIT.java
index 3095ab3..9359d97 100644
--- a/itests/src/test/java/org/apache/unomi/itests/ProfileServiceIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/ProfileServiceIT.java
@@ -17,6 +17,7 @@
 package org.apache.unomi.itests;
 
 import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.ProfileAlias;
 import org.apache.unomi.api.query.Query;
 import org.apache.unomi.api.services.ProfileService;
 import org.apache.unomi.persistence.spi.PersistenceService;
@@ -25,6 +26,7 @@ import org.apache.unomi.api.PartialList;
 import org.apache.unomi.persistence.elasticsearch.*;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -41,6 +43,7 @@ import org.slf4j.LoggerFactory;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.stream.IntStream;
 
 import javax.inject.Inject;
 
@@ -159,5 +162,42 @@ public class ProfileServiceIT extends BaseIT {
         assertEquals(testValue, value);
     }
 
+    @Test
+    public void testLoadProfileByAlias() throws Exception {
+        String profileID = "profileID_testLoadProfileByAlias";
+        try {
+            Profile profile = new Profile();
+            profile.setItemId(profileID);
+            profileService.save(profile);
+
+            refreshPersistence();
+
+            IntStream.range(1, 3).forEach(index -> {
+                final String profileAlias = profileID + "_alias_" + index;
+                profileService.addAliasToProfile(profileID, profileAlias, "clientID");
+            });
+
+            refreshPersistence();
+
+            Profile storedProfile = profileService.load(profileID);
+            assertNotNull(storedProfile);
+            assertEquals(profileID, storedProfile.getItemId());
+
+            storedProfile = profileService.load(profileID + "_alias_1");
+            assertNotNull(storedProfile);
+            assertEquals(profileID, storedProfile.getItemId());
+
+            storedProfile = profileService.load(profileID + "_alias_2");
+            assertNotNull(storedProfile);
+            assertEquals(profileID, storedProfile.getItemId());
+        } finally {
+            profileService.delete(profileID, false);
+
+            IntStream.range(1, 3).forEach(index -> {
+                final String profileAlias = profileID + "_alias_" + index;
+                persistenceService.remove(profileAlias, ProfileAlias.class);
+            });
+        }
+    }
 
 }
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 786bbdc..21dbcbd 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
@@ -79,6 +79,7 @@ public class CustomObjectMapper extends ObjectMapper {
         classes.put(ActionType.ITEM_TYPE, ActionType.class);
         classes.put(Topic.ITEM_TYPE, Topic.class);
         classes.put(SourceItem.ITEM_TYPE, SourceItem.class);
+        classes.put(ProfileAlias.ITEM_TYPE, ProfileAlias.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 24294ff..0784def 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
@@ -32,9 +32,10 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.servlet.ServletResponse;
-import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletResponse;
-import java.util.*;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
 
 public class MergeProfilesOnPropertyAction implements ActionExecutor {
     private static final Logger logger = LoggerFactory.getLogger(MergeProfilesOnPropertyAction.class.getName());
@@ -48,9 +49,9 @@ public class MergeProfilesOnPropertyAction implements ActionExecutor {
     private int maxProfilesInOneMerge = -1;
 
     public int execute(Action action, Event event) {
-        String profileIdCookieName = (String) configSharingService.getProperty("profileIdCookieName");
-        String profileIdCookieDomain = (String) configSharingService.getProperty("profileIdCookieDomain");
-        Integer profileIdCookieMaxAgeInSeconds = (Integer) configSharingService.getProperty("profileIdCookieMaxAgeInSeconds");
+//        String profileIdCookieName = (String) configSharingService.getProperty("profileIdCookieName");
+//        String profileIdCookieDomain = (String) configSharingService.getProperty("profileIdCookieDomain");
+//        Integer profileIdCookieMaxAgeInSeconds = (Integer) configSharingService.getProperty("profileIdCookieMaxAgeInSeconds");
 
         Profile profile = event.getProfile();
         if (profile instanceof Persona || profile.isAnonymousProfile()) {
@@ -98,10 +99,10 @@ public class MergeProfilesOnPropertyAction implements ActionExecutor {
                 // Take existing profile
                 profile = profiles.get(0);
             } else {
-                // Create a new profile
-                if (forceEventProfileAsMaster)
+                if (forceEventProfileAsMaster) {
                     profile = event.getProfile();
-                else {
+                } else {
+                    // Create a new profile
                     profile = new Profile(UUID.randomUUID().toString());
                     profile.setProperty("firstVisit", event.getTimeStamp());
                 }
@@ -110,8 +111,8 @@ public class MergeProfilesOnPropertyAction implements ActionExecutor {
 
             logger.info("Different users, switch to " + profile.getItemId());
 
-            HttpServletResponse httpServletResponse = (HttpServletResponse) event.getAttributes().get(Event.HTTP_RESPONSE_ATTRIBUTE);
-            sendProfileCookie(profile, httpServletResponse, profileIdCookieName, profileIdCookieDomain, profileIdCookieMaxAgeInSeconds);
+//            HttpServletResponse httpServletResponse = (HttpServletResponse) event.getAttributes().get(Event.HTTP_RESPONSE_ATTRIBUTE);
+//            sendProfileCookie(profile, httpServletResponse, profileIdCookieName, profileIdCookieDomain, profileIdCookieMaxAgeInSeconds);
 
             // At the end of the merge, we must set the merged profile as profile event to process other Actions
             event.setProfileId(profile.getItemId());
@@ -153,8 +154,8 @@ public class MergeProfilesOnPropertyAction implements ActionExecutor {
                 HttpServletResponse httpServletResponse = (HttpServletResponse) event.getAttributes().get(Event.HTTP_RESPONSE_ATTRIBUTE);
                 // we still send back the current profile cookie. It will be changed on the next request to the ContextServlet.
                 // The current profile will be deleted only then because we cannot delete it right now (too soon)
-                sendProfileCookie(profile, httpServletResponse,
-                        profileIdCookieName, profileIdCookieDomain, profileIdCookieMaxAgeInSeconds);
+//                sendProfileCookie(profile, httpServletResponse,
+//                        profileIdCookieName, profileIdCookieDomain, profileIdCookieMaxAgeInSeconds);
 
                 final String masterProfileId = masterProfile.getItemId();
                 // At the end of the merge, we must set the merged profile as profile event to process other Actions
@@ -163,7 +164,7 @@ public class MergeProfilesOnPropertyAction implements ActionExecutor {
 
                 final Boolean anonymousBrowsing = privacyService.isRequireAnonymousBrowsing(masterProfileId);
 
-                if (currentSession != null){
+                if (currentSession != null) {
                     currentSession.setProfile(masterProfile);
                     if (privacyService.isRequireAnonymousBrowsing(profile)) {
                         privacyService.setRequireAnonymousBrowsing(masterProfileId, true, event.getSourceId());
@@ -185,13 +186,14 @@ public class MergeProfilesOnPropertyAction implements ActionExecutor {
                             if (!StringUtils.equals(profileId, masterProfileId)) {
                                 if (currentEvent.isPersistent()) {
                                     persistenceService.update(currentEvent, currentEvent.getTimeStamp(), Event.class, "profileId", anonymousBrowsing ? null : masterProfileId);
-                                }                            }
+                                }
+                            }
 
                             for (Profile profile : profiles) {
                                 String profileId = profile.getItemId();
                                 if (!StringUtils.equals(profileId, masterProfileId)) {
                                     List<Session> sessions = persistenceService.query("profileId", profileId, null, Session.class);
-                                    if (currentSession != null){
+                                    if (currentSession != null) {
                                         if (masterProfileId.equals(profileId) && !sessions.contains(currentSession)) {
                                             sessions.add(currentSession);
                                         }
@@ -207,21 +209,15 @@ public class MergeProfilesOnPropertyAction implements ActionExecutor {
                                             persistenceService.update(event, event.getTimeStamp(), Event.class, "profileId", anonymousBrowsing ? null : masterProfileId);
                                         }
                                     }
-                                    // we must mark all the profiles that we merged into the master as merged with the master, and they will
-                                    // be deleted upon next load
-                                    profile.setMergedWith(masterProfileId);
-                                    Map<String,Object> sourceMap = new HashMap<>();
-                                    sourceMap.put("mergedWith", masterProfileId);
-                                    profile.setSystemProperty("lastUpdated", new Date());
-                                    sourceMap.put("systemProperties", profile.getSystemProperties());
 
-                                    boolean isExist  = persistenceService.load(profile.getItemId(), Profile.class) != null;
-
-                                    if (isExist == false) //save the original event profile is it has been changed
-                                        persistenceService.save(profile);
-                                    else
-                                      persistenceService.update(profile, null, Profile.class, sourceMap,true);
+                                    final String clientIdFromEvent = (String) event.getAttributes().get(Event.CLIENT_ID_ATTRIBUTE);
+                                    String clientId = clientIdFromEvent != null ? clientIdFromEvent : "defaultClientId";
+                                    profileService.addAliasToProfile(masterProfileId, profile.getItemId(), clientId);
 
+                                    boolean isExist = profileService.load(profile.getItemId()) != null;
+                                    if (isExist) {
+                                        profileService.delete(profileId, false);
+                                    }
                                 }
                             }
                         } catch (Exception e) {
@@ -238,19 +234,19 @@ public class MergeProfilesOnPropertyAction implements ActionExecutor {
         }
     }
 
-    private static void sendProfileCookie(Profile profile, ServletResponse response, String profileIdCookieName, String profileIdCookieDomain, int cookieAgeInSeconds) {
-        if (response instanceof HttpServletResponse) {
-            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
-            if (!(profile instanceof Persona)) {
-                httpServletResponse.addHeader("Set-Cookie",
-                        profileIdCookieName + "=" + profile.getItemId() +
-                                "; Path=/" +
-                                "; Max-Age=" + cookieAgeInSeconds +
-                                (StringUtils.isNotBlank(profileIdCookieDomain) ? ("; Domain=" + profileIdCookieDomain) : "")  +
-                                "; SameSite=Lax");
-            }
-        }
-    }
+//    private static void sendProfileCookie(Profile profile, ServletResponse response, String profileIdCookieName, String profileIdCookieDomain, int cookieAgeInSeconds) {
+//        if (response instanceof HttpServletResponse) {
+//            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
+//            if (!(profile instanceof Persona)) {
+//                httpServletResponse.addHeader("Set-Cookie",
+//                        profileIdCookieName + "=" + profile.getItemId() +
+//                                "; Path=/" +
+//                                "; Max-Age=" + cookieAgeInSeconds +
+//                                (StringUtils.isNotBlank(profileIdCookieDomain) ? ("; Domain=" + profileIdCookieDomain) : "") +
+//                                "; SameSite=Lax");
+//            }
+//        }
+//    }
 
     public void setProfileService(ProfileService profileService) {
         this.profileService = profileService;
diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java
index e2df705..aa5ac98 100644
--- a/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java
+++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java
@@ -64,6 +64,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
+import java.util.Objects;
 
 @WebService
 @Consumes(MediaType.APPLICATION_JSON)
@@ -73,6 +74,8 @@ import java.util.UUID;
 public class ContextJsonEndpoint {
     private static final Logger logger = LoggerFactory.getLogger(ContextJsonEndpoint.class.getName());
 
+    private static final String DEFAULT_CLIENT_ID = "defaultClientId";
+
     private boolean sanitizeConditions = Boolean
             .parseBoolean(System.getProperty("org.apache.unomi.security.personalization.sanitizeConditions", "true"));
 
@@ -204,10 +207,11 @@ public class ContextJsonEndpoint {
         }
 
         int changes = EventService.NO_CHANGE;
-        if (profile == null) {
-            // Not a persona, resolve profile now
-            boolean profileCreated = false;
 
+        // Not a persona, resolve profile now
+        boolean profileCreated = false;
+
+        if (profile == null) {
             if (profileId == null || invalidateProfile) {
                 // no profileId cookie was found or the profile has to be invalidated, we generate a new one and create the profile in the profile service
                 profile = createNewProfile(null, timestamp);
@@ -295,6 +299,7 @@ public class ContextJsonEndpoint {
                 profileUpdated.setPersistent(false);
                 profileUpdated.getAttributes().put(Event.HTTP_REQUEST_ATTRIBUTE, request);
                 profileUpdated.getAttributes().put(Event.HTTP_RESPONSE_ATTRIBUTE, response);
+                profileUpdated.getAttributes().put(Event.CLIENT_ID_ATTRIBUTE, DEFAULT_CLIENT_ID);
 
                 if (logger.isDebugEnabled()) {
                     logger.debug("Received event {} for profile={} {} target={} timestamp={}", profileUpdated.getEventType(),
@@ -322,6 +327,12 @@ public class ContextJsonEndpoint {
         if ((changes & EventService.PROFILE_UPDATED) == EventService.PROFILE_UPDATED) {
             profileService.save(profile);
             contextResponse.setProfileId(profile.getItemId());
+
+            if (profileCreated) {
+                String clientId = contextRequest != null && contextRequest.getClientId() != null ? contextRequest.getClientId() : DEFAULT_CLIENT_ID;
+                String profileMasterId = profile.getMergedWith() != null ? profile.getMergedWith() : profile.getItemId();
+                profileService.addAliasToProfile(profileMasterId, profile.getItemId(), clientId );
+            }
         }
         if ((changes & EventService.SESSION_UPDATED) == EventService.SESSION_UPDATED && session != null) {
             profileService.saveSession(session);
diff --git a/services/src/main/java/org/apache/unomi/services/impl/profiles/ProfileServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/profiles/ProfileServiceImpl.java
index 99d54b0..d65479b 100644
--- a/services/src/main/java/org/apache/unomi/services/impl/profiles/ProfileServiceImpl.java
+++ b/services/src/main/java/org/apache/unomi/services/impl/profiles/ProfileServiceImpl.java
@@ -27,6 +27,7 @@ import org.apache.unomi.api.Persona;
 import org.apache.unomi.api.PersonaSession;
 import org.apache.unomi.api.PersonaWithSessions;
 import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.ProfileAlias;
 import org.apache.unomi.api.PropertyMergeStrategyExecutor;
 import org.apache.unomi.api.PropertyMergeStrategyType;
 import org.apache.unomi.api.PropertyType;
@@ -68,6 +69,7 @@ import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.TimerTask;
 import java.util.TreeSet;
@@ -546,6 +548,10 @@ public class ProfileServiceImpl implements ProfileService, SynchronousBundleList
     }
 
     public Profile load(String profileId) {
+        ProfileAlias profileAlias = persistenceService.load(profileId, ProfileAlias.class);
+        if (profileAlias != null) {
+            profileId = profileAlias.getProfileID();
+        }
         return persistenceService.load(profileId, Profile.class);
     }
 
@@ -553,6 +559,28 @@ public class ProfileServiceImpl implements ProfileService, SynchronousBundleList
         return save(profile, forceRefreshOnSave);
     }
 
+    @Override
+    public void addAliasToProfile(String profileID, String alias, String clientID) {
+        ProfileAlias profileAlias = persistenceService.load(alias, ProfileAlias.class);
+
+        if (profileAlias == null) {
+            profileAlias = new ProfileAlias();
+
+            profileAlias.setItemId(alias);
+            profileAlias.setItemType(ProfileAlias.ITEM_TYPE);
+            profileAlias.setProfileID(profileID);
+            profileAlias.setClientID(clientID);
+
+            Date creationTime = new Date();
+            profileAlias.setCreationTime(creationTime);
+            profileAlias.setModifiedTime(creationTime);
+
+            persistenceService.save(profileAlias);
+        } else if (!Objects.equals(profileAlias.getProfileID(), profileID)) {
+            throw new IllegalArgumentException("Alias \"" + alias + "\" already used by profile with ID = \"" + profileID + "\"");
+        }
+    }
+
     private Profile save(Profile profile, boolean forceRefresh) {
         if (profile.getItemId() == null) {
             return null;