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

[unomi] branch UNOMI-449-move-wab-endpoints-to-rest created (now 49587a3)

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

dgriffon pushed a change to branch UNOMI-449-move-wab-endpoints-to-rest
in repository https://gitbox.apache.org/repos/asf/unomi.git.


      at 49587a3  UNOMI-449 : copy servlet endpoints to jax-rs endpoints.

This branch includes the following new commits:

     new 49587a3  UNOMI-449 : copy servlet endpoints to jax-rs endpoints.

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-449 : copy servlet endpoints to jax-rs endpoints.

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

dgriffon pushed a commit to branch UNOMI-449-move-wab-endpoints-to-rest
in repository https://gitbox.apache.org/repos/asf/unomi.git

commit 49587a38661f80ad42c4d6ca441f9d8a7529cb18
Author: David Griffon <dg...@jahia.com>
AuthorDate: Fri Mar 26 16:18:48 2021 +0100

    UNOMI-449 : copy servlet endpoints to jax-rs endpoints.
---
 rest/pom.xml                                       |  13 +
 .../java/org/apache/unomi/rest/ClientEndpoint.java | 172 +++++++
 .../org/apache/unomi/rest/ContextJsonEndpoint.java | 526 +++++++++++++++++++++
 .../apache/unomi/rest/EventCollectorResponse.java  |  32 ++
 .../apache/unomi/rest/EventsCollectorEndpoint.java | 199 ++++++++
 .../main/java/org/apache/unomi/utils/Changes.java  |  53 +++
 .../java/org/apache/unomi/utils/HttpUtils.java     | 133 ++++++
 .../java/org/apache/unomi/utils/ServletCommon.java | 119 +++++
 8 files changed, 1247 insertions(+)

diff --git a/rest/pom.xml b/rest/pom.xml
index 8ed6e07..f1133e3 100644
--- a/rest/pom.xml
+++ b/rest/pom.xml
@@ -120,6 +120,19 @@
             <version>${cxf.version}</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>com.opencsv</groupId>
+            <artifactId>opencsv</artifactId>
+            <version>3.10</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-beanutils</groupId>
+            <artifactId>commons-beanutils</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-collections</groupId>
+            <artifactId>commons-collections</artifactId>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/rest/src/main/java/org/apache/unomi/rest/ClientEndpoint.java b/rest/src/main/java/org/apache/unomi/rest/ClientEndpoint.java
new file mode 100644
index 0000000..9989de8
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/rest/ClientEndpoint.java
@@ -0,0 +1,172 @@
+/*
+ * 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.rest;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import com.opencsv.CSVWriter;
+import org.apache.cxf.rs.security.cors.CrossOriginResourceSharing;
+import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.services.ConfigSharingService;
+import org.apache.unomi.api.services.ProfileService;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jws.WebService;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * A servlet filter to serve a context-specific Javascript containing the current request context object.
+ */
+@WebService
+@Produces
+@Consumes(MediaType.TEXT_PLAIN)
+@CrossOriginResourceSharing(
+        allowAllOrigins = true,
+        allowCredentials = true
+)
+@Path("/")
+@Component(service = ClientEndpoint.class, property = "osgi.jaxrs.resource=true")
+public class ClientEndpoint {
+
+    private static final Logger logger = LoggerFactory.getLogger(ClientEndpoint.class.getName());
+    private static final long serialVersionUID = 2928875960103325238L;
+
+    @Reference
+    private ProfileService profileService;
+
+    private String profileIdCookieName = "context-profile-id";
+    private String allowedProfileDownloadFormats;
+
+    private final String FILE_NAME_WO_EXT = "my-profile";
+
+    @Context
+    HttpServletRequest request;
+    @Context
+    HttpServletResponse response;
+    @Reference
+    private ConfigSharingService configSharingService;
+
+    @GET
+    @Path("/client/{operation}/{param}")
+    public Response getClient(@PathParam("operation") String operation, @PathParam("param") String param) throws JsonProcessingException {
+        switch (operation) {
+            case "myprofile":
+                if (allowedProfileDownloadFormats.contains(param)) {
+                    return donwloadCurrentProfile(param);
+                } else {
+                    throw new InternalServerErrorException(String.format("%s is not an allowed param", param));
+                }
+        }
+        throw new NotFoundException();
+    }
+
+    private Response donwloadCurrentProfile(String downloadFileType) throws JsonProcessingException {
+        String cookieProfileId = null;
+        Cookie[] cookies = request.getCookies();
+        if (cookies != null) {
+            for (Cookie cookie : cookies) {
+                if (profileIdCookieName.equals(cookie.getName())) {
+                    cookieProfileId = cookie.getValue();
+                }
+            }
+        }
+        if (cookieProfileId != null) {
+            Profile currentProfile = profileService.load(cookieProfileId);
+            if (currentProfile != null) {
+                switch (downloadFileType) {
+                    case "yaml":
+                        return prepareYamlFileToDownload(currentProfile, false);
+                    case "json":
+                        return prepareJsonFileToDownload(currentProfile);
+                    case "csv":
+                        return prepareCsvFileToDownload(currentProfile, request.getParameter("vertical") != null);
+                    case "text":
+                        return prepareYamlFileToDownload(currentProfile, true);
+                }
+
+            }
+        }
+        throw new NotFoundException();
+    }
+
+    private Response prepareCsvFileToDownload(Profile currentProfile, boolean vertical) {
+        response.setContentType("text/csv");
+        response.setHeader("Content-Disposition", "attachment; filename=\"" + FILE_NAME_WO_EXT + ".csv\"");
+        StringWriter writer = new StringWriter();
+        //using custom delimiter and quote character
+        CSVWriter csvWriter = new CSVWriter(writer);
+        if (vertical) {
+            csvWriter.writeNext(new String[]{"name", "value"});
+            for (Map.Entry<String, Object> entry : currentProfile.getProperties().entrySet()) {
+                csvWriter.writeNext(new String[]{entry.getKey(), entry.getValue().toString().trim().replace("\n", "")});
+            }
+        } else {
+            Set<String> keySet = currentProfile.getProperties().keySet();
+            List<String> values = new ArrayList();
+            for (Object value : currentProfile.getProperties().values()) {
+                values.add(value.toString().trim().replace("\n", ""));
+            }
+            csvWriter.writeNext(keySet.toArray(new String[keySet.size()]));
+            csvWriter.writeNext(values.toArray(new String[values.size()]));
+        }
+        Response.ResponseBuilder responseBuilder = Response.ok(writer.toString());
+        return responseBuilder.build();
+    }
+
+    private Response prepareJsonFileToDownload(Profile currentProfile) throws JsonProcessingException {
+        response.setContentType("text/json");
+        response.setHeader("Content-Disposition", "attachment; filename=\"" + FILE_NAME_WO_EXT + ".json\"");
+        ObjectMapper mapper = new ObjectMapper();
+        String jsonContent = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(currentProfile.getProperties());
+        return Response.ok(jsonContent).build();
+    }
+
+    private Response prepareYamlFileToDownload(Profile currentProfile, boolean asTextFile) throws JsonProcessingException {
+        response.setContentType("text/" + (asTextFile ? "plain" : "yaml"));
+        response.setHeader("Content-Disposition", "attachment; filename=\"" + FILE_NAME_WO_EXT + (asTextFile ? ".txt" : ".yml") + "\"");
+        YAMLFactory yf = new YAMLFactory();
+        ObjectMapper mapper = new ObjectMapper(yf);
+        String yamlContent = mapper.writeValueAsString(currentProfile.getProperties());
+        return Response.ok(yamlContent).build();
+    }
+
+    @Activate
+    public void init() {
+        // TODO DMF-4436 read values from the configuration file: org.apache.unomi.web.cfg
+        profileIdCookieName = "context-profile-id";
+        allowedProfileDownloadFormats = "csv,yaml,json,text";
+    }
+}
diff --git a/rest/src/main/java/org/apache/unomi/rest/ContextJsonEndpoint.java b/rest/src/main/java/org/apache/unomi/rest/ContextJsonEndpoint.java
new file mode 100644
index 0000000..c44ea18
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/rest/ContextJsonEndpoint.java
@@ -0,0 +1,526 @@
+/*
+ * 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.rest;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.cxf.jaxrs.utils.ExceptionUtils;
+import org.apache.cxf.rs.security.cors.CrossOriginResourceSharing;
+import org.apache.unomi.api.*;
+import org.apache.unomi.api.conditions.Condition;
+import org.apache.unomi.api.services.*;
+import org.apache.unomi.persistence.spi.CustomObjectMapper;
+import org.apache.unomi.utils.Changes;
+import org.apache.unomi.utils.HttpUtils;
+import org.apache.unomi.utils.ServletCommon;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jws.WebService;
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+@WebService
+@Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
+@Consumes(MediaType.TEXT_PLAIN)
+@CrossOriginResourceSharing(
+        allowAllOrigins = true,
+        allowCredentials = true
+)
+@Path("/")
+@Component(service = ContextJsonEndpoint.class, property = "osgi.jaxrs.resource=true")
+public class ContextJsonEndpoint {
+    private static final Logger logger = LoggerFactory.getLogger(ContextJsonEndpoint.class.getName());
+
+    private static final int MAX_COOKIE_AGE_IN_SECONDS = 60 * 60 * 24 * 365; // 1 year
+
+    private String profileIdCookieName = "context-profile-id";
+    private String profileIdCookieDomain;
+    private int profileIdCookieMaxAgeInSeconds = MAX_COOKIE_AGE_IN_SECONDS;
+
+    private boolean sanitizeConditions = Boolean.parseBoolean(System.getProperty("org.apache.unomi.security.personalization.sanitizeConditions", "true"));
+
+
+    @Context
+    ServletContext context;
+    @Context
+    HttpServletRequest request;
+    @Context
+    HttpServletResponse response;
+
+    @Reference
+    private ProfileService profileService;
+    @Reference
+    private PrivacyService privacyService;
+    @Reference
+    private EventService eventService;
+    @Reference
+    private RulesService rulesService;
+    @Reference
+    private PersonalizationService personalizationService;
+    @Reference
+    private ConfigSharingService configSharingService;
+
+    @POST
+    @Path("/context.json")
+    public ContextResponse getContextJSON(String contextRequestAsString, @QueryParam("timestamp") Long timestampAsLong, @CookieParam("context-profile-id") String cookieProfileId) {
+        Date timestamp = new Date();
+        if (timestampAsLong != null) {
+            timestamp = new Date(timestampAsLong);
+        }
+
+        // Handle persona
+        Profile profile = null;
+        Session session = null;
+        String personaId = request.getParameter("personaId");
+        if (personaId != null) {
+            PersonaWithSessions personaWithSessions = profileService.loadPersonaWithSessions(personaId);
+            if (personaWithSessions == null) {
+                logger.error("Couldn't find persona, please check your personaId parameter");
+                profile = null;
+            } else {
+                profile = personaWithSessions.getPersona();
+                session = personaWithSessions.getLastSession();
+            }
+        }
+
+        // Extract payload
+        ContextRequest contextRequest = null;
+        String scope = null;
+        String sessionId = null;
+        String profileId = null;
+        ObjectMapper mapper = CustomObjectMapper.getObjectMapper();
+        JsonFactory factory = mapper.getFactory();
+        try {
+            contextRequest = mapper.readValue(factory.createParser(contextRequestAsString), ContextRequest.class);
+        } catch (Exception e) {
+            logger.error("Cannot deserialize the context request payload. See debug level for more information");
+            if (logger.isDebugEnabled()) {
+                logger.debug("Cannot deserialize the context request payload because of {}", e.getMessage(), e);
+            }
+            throw ExceptionUtils.toHttpException(e, null);
+        }
+        if (contextRequest.getSource() != null) {
+            scope = contextRequest.getSource().getScope();
+        }
+        sessionId = contextRequest.getSessionId();
+        profileId = contextRequest.getProfileId();
+
+        if (sessionId == null) {
+            sessionId = request.getParameter("sessionId");
+        }
+
+        if (profileId == null) {
+            // Get profile id from the cookie
+            profileId = ServletCommon.getProfileIdCookieValue(request, profileIdCookieName);
+        }
+
+        if (profileId == null && sessionId == null && personaId == null) {
+            logger.error("Couldn't find profileId, sessionId or personaId in incoming request! Stopped processing request. See debug level for more information");
+            if (logger.isDebugEnabled()) {
+                logger.debug("Request dump: {}", HttpUtils.dumpRequestInfo(request));
+            }
+            throw new InternalServerErrorException("Couldn't find profileId, sessionId or personaId in incoming request!");
+        }
+
+        int changes = EventService.NO_CHANGE;
+        if (profile == null) {
+            // Not a persona, resolve profile now
+            boolean profileCreated = false;
+
+            boolean invalidateProfile = request.getParameter("invalidateProfile") != null ?
+                    new Boolean(request.getParameter("invalidateProfile")) : false;
+            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, response, timestamp);
+                profileCreated = true;
+            } else {
+                profile = profileService.load(profileId);
+                if (profile == null) {
+                    // this can happen if we have an old cookie but have reset the server,
+                    // or if we merged the profiles and somehow this cookie didn't get updated.
+                    profile = createNewProfile(profileId, response, timestamp);
+                    profileCreated = true;
+                } else {
+                    Changes changesObject = checkMergedProfile(response, profile, session);
+                    changes |= changesObject.getChangeType();
+                    profile = changesObject.getProfile();
+                }
+            }
+
+            Profile sessionProfile;
+            boolean invalidateSession = request.getParameter("invalidateSession") != null ?
+                    new Boolean(request.getParameter("invalidateSession")) : false;
+            if (StringUtils.isNotBlank(sessionId) && !invalidateSession) {
+                session = profileService.loadSession(sessionId, timestamp);
+                if (session != null) {
+                    sessionProfile = session.getProfile();
+
+                    boolean anonymousSessionProfile = sessionProfile.isAnonymousProfile();
+                    if (!profile.isAnonymousProfile() && !anonymousSessionProfile && !profile.getItemId().equals(sessionProfile.getItemId())) {
+                        // Session user has been switched, profile id in cookie is not up to date
+                        // We must reload the profile with the session ID as some properties could be missing from the session profile
+                        // #personalIdentifier
+                        profile = profileService.load(sessionProfile.getItemId());
+                        if (profile != null) {
+                            HttpUtils.sendProfileCookie(profile, response, profileIdCookieName, profileIdCookieDomain, profileIdCookieMaxAgeInSeconds);
+                        } else {
+                            logger.warn("Couldn't load profile {} referenced in session {}", sessionProfile.getItemId(), session.getItemId());
+                        }
+                    }
+
+                    // Handle anonymous situation
+                    Boolean requireAnonymousBrowsing = privacyService.isRequireAnonymousBrowsing(profile);
+                    if (requireAnonymousBrowsing && anonymousSessionProfile) {
+                        // User wants to browse anonymously, anonymous profile is already set.
+                    } else if (requireAnonymousBrowsing && !anonymousSessionProfile) {
+                        // User wants to browse anonymously, update the sessionProfile to anonymous profile
+                        sessionProfile = privacyService.getAnonymousProfile(profile);
+                        session.setProfile(sessionProfile);
+                        changes |= EventService.SESSION_UPDATED;
+                    } else if (!requireAnonymousBrowsing && anonymousSessionProfile) {
+                        // User does not want to browse anonymously anymore, update the sessionProfile to real profile
+                        sessionProfile = profile;
+                        session.setProfile(sessionProfile);
+                        changes |= EventService.SESSION_UPDATED;
+                    } else if (!requireAnonymousBrowsing && !anonymousSessionProfile) {
+                        // User does not want to browse anonymously, use the real profile. Check that session contains the current profile.
+                        sessionProfile = profile;
+                        if (!session.getProfileId().equals(sessionProfile.getItemId())) {
+                            changes |= EventService.SESSION_UPDATED;
+                        }
+                        session.setProfile(sessionProfile);
+                    }
+                }
+            }
+
+            if (session == null || invalidateSession) {
+                sessionProfile = privacyService.isRequireAnonymousBrowsing(profile) ? privacyService.getAnonymousProfile(profile) : profile;
+
+                if (StringUtils.isNotBlank(sessionId)) {
+                    // Only save session and send event if a session id was provided, otherwise keep transient session
+                    session = new Session(sessionId, sessionProfile, timestamp, scope);
+                    changes |= EventService.SESSION_UPDATED;
+                    Event event = new Event("sessionCreated", session, profile, scope, null, session, timestamp);
+                    if (sessionProfile.isAnonymousProfile()) {
+                        // Do not keep track of profile in event
+                        event.setProfileId(null);
+                    }
+                    event.getAttributes().put(Event.HTTP_REQUEST_ATTRIBUTE, request);
+                    event.getAttributes().put(Event.HTTP_RESPONSE_ATTRIBUTE, response);
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Received event {} for profile={} session={} target={} timestamp={}",
+                                event.getEventType(), profile.getItemId(), session.getItemId(), event.getTarget(), timestamp);
+                    }
+                    changes |= eventService.send(event);
+                }
+            }
+
+            if (profileCreated) {
+                changes |= EventService.PROFILE_UPDATED;
+
+                Event profileUpdated = new Event("profileUpdated", session, profile, scope, null, profile, timestamp);
+                profileUpdated.setPersistent(false);
+                profileUpdated.getAttributes().put(Event.HTTP_REQUEST_ATTRIBUTE, request);
+                profileUpdated.getAttributes().put(Event.HTTP_RESPONSE_ATTRIBUTE, response);
+
+                if (logger.isDebugEnabled()) {
+                    logger.debug("Received event {} for profile={} {} target={} timestamp={}", profileUpdated.getEventType(), profile.getItemId(),
+                            " session=" + (session != null ? session.getItemId() : null), profileUpdated.getTarget(), timestamp);
+                }
+                changes |= eventService.send(profileUpdated);
+            }
+        }
+
+        ContextResponse contextResponse = new ContextResponse();
+        contextResponse.setProfileId(profile.getItemId());
+        if (session != null) {
+            contextResponse.setSessionId(session.getItemId());
+        } else if (sessionId != null) {
+            contextResponse.setSessionId(sessionId);
+        }
+
+        if (contextRequest != null) {
+            Changes changesObject = handleRequest(contextRequest, session, profile, contextResponse, request, response, timestamp);
+            changes |= changesObject.getChangeType();
+            profile = changesObject.getProfile();
+        }
+
+        if ((changes & EventService.PROFILE_UPDATED) == EventService.PROFILE_UPDATED) {
+            profileService.save(profile);
+            contextResponse.setProfileId(profile.getItemId());
+        }
+        if ((changes & EventService.SESSION_UPDATED) == EventService.SESSION_UPDATED && session != null) {
+            profileService.saveSession(session);
+            contextResponse.setSessionId(session.getItemId());
+        }
+
+        if ((changes & EventService.ERROR) == EventService.ERROR) {
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+        }
+        return contextResponse;
+    }
+
+
+    @Activate
+    public void init() {
+        // TODO DMF-4436 read values from the configuration file: org.apache.unomi.web.cfg
+        profileIdCookieName = "context-profile-id";
+        profileIdCookieDomain = null;
+        profileIdCookieMaxAgeInSeconds = 31536000;
+    }
+
+    private Changes checkMergedProfile(ServletResponse response, Profile profile, Session session) {
+        int changes = EventService.NO_CHANGE;
+        if (profile.getMergedWith() != null && !privacyService.isRequireAnonymousBrowsing(profile) && !profile.isAnonymousProfile()) {
+            Profile currentProfile = profile;
+            String masterProfileId = profile.getMergedWith();
+            Profile masterProfile = profileService.load(masterProfileId);
+            if (masterProfile != null) {
+                logger.info("Current profile {} was merged with profile {}, replacing profile in session", currentProfile.getItemId(), masterProfileId);
+                profile = masterProfile;
+                if (session != null) {
+                    session.setProfile(profile);
+                    changes = EventService.SESSION_UPDATED;
+                }
+                HttpUtils.sendProfileCookie(profile, response, profileIdCookieName, profileIdCookieDomain, profileIdCookieMaxAgeInSeconds);
+            } else {
+                logger.warn("Couldn't find merged profile {}, falling back to profile {}", masterProfileId, currentProfile.getItemId());
+                profile = currentProfile;
+                profile.setMergedWith(null);
+                changes = EventService.PROFILE_UPDATED;
+            }
+        }
+
+        return new Changes(changes, profile);
+    }
+
+    private Changes handleRequest(ContextRequest contextRequest, Session session, Profile profile, ContextResponse data,
+                                  ServletRequest request, ServletResponse response, Date timestamp) {
+        Changes changes = ServletCommon.handleEvents(contextRequest.getEvents(), session, profile, request, response, timestamp,
+                privacyService, eventService);
+        data.setProcessedEvents(changes.getProcessedItems());
+
+        profile = changes.getProfile();
+
+        if (contextRequest.isRequireSegments()) {
+            data.setProfileSegments(profile.getSegments());
+        }
+
+        if (contextRequest.getRequiredProfileProperties() != null) {
+            Map<String, Object> profileProperties = new HashMap<>(profile.getProperties());
+            if (!contextRequest.getRequiredProfileProperties().contains("*")) {
+                profileProperties.keySet().retainAll(contextRequest.getRequiredProfileProperties());
+            }
+            data.setProfileProperties(profileProperties);
+        }
+
+        if (session != null) {
+            data.setSessionId(session.getItemId());
+            if (contextRequest.getRequiredSessionProperties() != null) {
+                Map<String, Object> sessionProperties = new HashMap<>(session.getProperties());
+                if (!contextRequest.getRequiredSessionProperties().contains("*")) {
+                    sessionProperties.keySet().retainAll(contextRequest.getRequiredSessionProperties());
+                }
+                data.setSessionProperties(sessionProperties);
+            }
+        }
+
+        processOverrides(contextRequest, profile, session);
+
+        List<PersonalizationService.PersonalizedContent> filterNodes = contextRequest.getFilters();
+        if (filterNodes != null) {
+            data.setFilteringResults(new HashMap<>());
+            for (PersonalizationService.PersonalizedContent personalizedContent : sanitizePersonalizedContentObjects(filterNodes)) {
+                data.getFilteringResults().put(personalizedContent.getId(), personalizationService.filter(profile,
+                        session, personalizedContent));
+            }
+        }
+
+        List<PersonalizationService.PersonalizationRequest> personalizations = contextRequest.getPersonalizations();
+        if (personalizations != null) {
+            data.setPersonalizations(new HashMap<>());
+            for (PersonalizationService.PersonalizationRequest personalization : sanitizePersonalizations(personalizations)) {
+                data.getPersonalizations().put(personalization.getId(), personalizationService.personalizeList(profile,
+                        session, personalization));
+            }
+        }
+
+        if (!(profile instanceof Persona)) {
+            data.setTrackedConditions(rulesService.getTrackedConditions(contextRequest.getSource()));
+        } else {
+            data.setTrackedConditions(Collections.emptySet());
+        }
+
+        data.setAnonymousBrowsing(privacyService.isRequireAnonymousBrowsing(profile));
+        data.setConsents(profile.getConsents());
+
+        return changes;
+    }
+
+    /**
+     * This function will update the profile if it is from Persona instance.
+     * The profile will be updated using the overrides attributes :
+     * - profileOverrides for profile properties, segments and scores
+     * - sessionPropertiesOverrides for session properties
+     *
+     * @param contextRequest
+     * @param profile
+     * @param session
+     */
+    private void processOverrides(ContextRequest contextRequest, Profile profile, Session session) {
+        if (profile instanceof Persona) {
+            if (contextRequest.getProfileOverrides() != null) {
+                if (contextRequest.getProfileOverrides().getScores() != null) {
+                    profile.setScores(contextRequest.getProfileOverrides().getScores());
+                }
+                if (contextRequest.getProfileOverrides().getSegments() != null) {
+                    profile.setSegments(contextRequest.getProfileOverrides().getSegments());
+                }
+                if (contextRequest.getProfileOverrides().getProperties() != null) {
+                    profile.setProperties(contextRequest.getProfileOverrides().getProperties());
+                }
+                if (contextRequest.getSessionPropertiesOverrides() != null && session != null) {
+                    session.setProperties(contextRequest.getSessionPropertiesOverrides());
+                }
+            }
+        }
+    }
+
+    private Profile createNewProfile(String existingProfileId, ServletResponse response, Date timestamp) {
+        Profile profile;
+        String profileId = existingProfileId;
+        if (profileId == null) {
+            profileId = UUID.randomUUID().toString();
+        }
+        profile = new Profile(profileId);
+        profile.setProperty("firstVisit", timestamp);
+        HttpUtils.sendProfileCookie(profile, response, profileIdCookieName, profileIdCookieDomain, profileIdCookieMaxAgeInSeconds);
+        return profile;
+    }
+
+
+    public void destroy() {
+        logger.info("Context servlet shutdown.");
+    }
+
+    private List<PersonalizationService.PersonalizedContent> sanitizePersonalizedContentObjects(List<PersonalizationService.PersonalizedContent> personalizedContentObjects) {
+        if (!sanitizeConditions) {
+            return personalizedContentObjects;
+        }
+        List<PersonalizationService.PersonalizedContent> result = new ArrayList<>();
+        for (PersonalizationService.PersonalizedContent personalizedContentObject : personalizedContentObjects) {
+            boolean foundInvalidCondition = false;
+            if (personalizedContentObject.getFilters() != null) {
+                for (PersonalizationService.Filter filter : personalizedContentObject.getFilters()) {
+                    if (sanitizeCondition(filter.getCondition()) == null) {
+                        foundInvalidCondition = true;
+                        break;
+                    }
+                }
+            }
+            if (!foundInvalidCondition) {
+                result.add(personalizedContentObject);
+            }
+        }
+
+        return result;
+    }
+
+    private List<PersonalizationService.PersonalizationRequest> sanitizePersonalizations(List<PersonalizationService.PersonalizationRequest> personalizations) {
+        if (!sanitizeConditions) {
+            return personalizations;
+        }
+        List<PersonalizationService.PersonalizationRequest> result = new ArrayList<>();
+        for (PersonalizationService.PersonalizationRequest personalizationRequest : personalizations) {
+            List<PersonalizationService.PersonalizedContent> personalizedContents = sanitizePersonalizedContentObjects(personalizationRequest.getContents());
+            if (personalizedContents != null && personalizedContents.size() > 0) {
+                result.add(personalizationRequest);
+            }
+        }
+        return result;
+    }
+
+    private Condition sanitizeCondition(Condition condition) {
+        Map<String, Object> newParameterValues = new LinkedHashMap<>();
+        for (Map.Entry<String, Object> parameterEntry : condition.getParameterValues().entrySet()) {
+            Object sanitizedValue = sanitizeValue(parameterEntry.getValue());
+            if (sanitizedValue != null) {
+                newParameterValues.put(parameterEntry.getKey(), parameterEntry.getValue());
+            } else {
+                return null;
+            }
+        }
+        return condition;
+    }
+
+    private Object sanitizeValue(Object value) {
+        if (value instanceof String) {
+            String stringValue = (String) value;
+            if (stringValue.startsWith("script::") || stringValue.startsWith("parameter::")) {
+                logger.warn("Scripting detected in context request, filtering out. See debug level for more information");
+                if (logger.isDebugEnabled()) {
+                    logger.debug("Scripting detected in context request with value {}, filtering out...", value);
+                }
+                return null;
+            } else {
+                return stringValue;
+            }
+        } else if (value instanceof List) {
+            List values = (List) value;
+            List newValues = new ArrayList();
+            for (Object listObject : values) {
+                Object newObject = sanitizeValue(listObject);
+                if (newObject != null) {
+                    newValues.add(newObject);
+                }
+            }
+            return values;
+        } else if (value instanceof Map) {
+            Map<Object, Object> newMap = new LinkedHashMap<>();
+            ((Map<?, ?>) value).forEach((key, value1) -> {
+                Object newObject = sanitizeValue(value1);
+                if (newObject != null) {
+                    newMap.put(key, newObject);
+                }
+            });
+            return newMap;
+        } else if (value instanceof Condition) {
+            return sanitizeCondition((Condition) value);
+        } else {
+            return value;
+        }
+    }
+
+}
diff --git a/rest/src/main/java/org/apache/unomi/rest/EventCollectorResponse.java b/rest/src/main/java/org/apache/unomi/rest/EventCollectorResponse.java
new file mode 100644
index 0000000..f5f712b
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/rest/EventCollectorResponse.java
@@ -0,0 +1,32 @@
+/*
+ * 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.rest;
+
+import java.io.Serializable;
+
+public class EventCollectorResponse implements Serializable {
+    private int updated;
+
+    public EventCollectorResponse(int updated) {
+        this.updated = updated;
+    }
+
+    public int getUpdated() {
+        return updated;
+    }
+}
diff --git a/rest/src/main/java/org/apache/unomi/rest/EventsCollectorEndpoint.java b/rest/src/main/java/org/apache/unomi/rest/EventsCollectorEndpoint.java
new file mode 100644
index 0000000..fceb245
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/rest/EventsCollectorEndpoint.java
@@ -0,0 +1,199 @@
+/*
+ * 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.rest;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.cxf.rs.security.cors.CrossOriginResourceSharing;
+import org.apache.unomi.api.Event;
+import org.apache.unomi.api.EventsCollectorRequest;
+import org.apache.unomi.api.Persona;
+import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.Session;
+import org.apache.unomi.api.services.EventService;
+import org.apache.unomi.api.services.PrivacyService;
+import org.apache.unomi.api.services.ProfileService;
+import org.apache.unomi.persistence.spi.CustomObjectMapper;
+import org.apache.unomi.utils.Changes;
+import org.apache.unomi.utils.ServletCommon;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jws.WebService;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import java.io.IOException;
+import java.rmi.RemoteException;
+import java.util.Date;
+import java.util.UUID;
+
+@WebService
+@Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
+@Consumes(MediaType.TEXT_PLAIN)
+@CrossOriginResourceSharing(
+        allowAllOrigins = true,
+        allowCredentials = true
+)
+@Path("/")
+@Component(service = EventsCollectorEndpoint.class, property = "osgi.jaxrs.resource=true")
+public class EventsCollectorEndpoint {
+    private static final Logger logger = LoggerFactory.getLogger(EventsCollectorEndpoint.class.getName());
+
+    private String profileIdCookieName;
+
+    @Reference
+    private EventService eventService;
+    @Reference
+    private ProfileService profileService;
+    @Reference
+    private PrivacyService privacyService;
+
+    @Context
+    HttpServletRequest request;
+    @Context
+    HttpServletResponse response;
+
+    @GET
+    @Path("/eventcollector")
+    public EventCollectorResponse get(String eventsCollectorRequestAsString, @QueryParam("timestamp") Long timestampAsString) throws IOException {
+        return doEvent(eventsCollectorRequestAsString, timestampAsString);
+    }
+
+    @POST
+    @Path("/eventcollector")
+    public EventCollectorResponse post(String eventsCollectorRequestAsString, @QueryParam("timestamp") Long timestampAsLong) throws IOException {
+        return doEvent(eventsCollectorRequestAsString, timestampAsLong);
+    }
+
+    private EventCollectorResponse doEvent(String eventsCollectorRequestAsString, Long timestampAsLong) throws IOException {
+        Date timestamp = new Date();
+        if (timestampAsLong != null) {
+            timestamp = new Date(timestampAsLong);
+        }
+
+
+        ObjectMapper mapper = CustomObjectMapper.getObjectMapper();
+        JsonFactory factory = mapper.getFactory();
+        EventsCollectorRequest eventsCollectorRequest;
+        try {
+            eventsCollectorRequest = mapper.readValue(factory.createParser(eventsCollectorRequestAsString), EventsCollectorRequest.class);
+        } catch (Exception e) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Cannot parse eventsCollectorRequest: {}", eventsCollectorRequestAsString, e);
+            }
+            throw new RemoteException("Cannot read payload. See debug level for more information");
+        }
+        if (eventsCollectorRequest == null || eventsCollectorRequest.getEvents() == null) {
+            throw new InternalServerErrorException("No events found");
+        }
+
+        String sessionId = eventsCollectorRequest.getSessionId();
+        if (sessionId == null) {
+            sessionId = request.getParameter("sessionId");
+        }
+        Session session = null;
+        if (sessionId != null) {
+            session = profileService.loadSession(sessionId, timestamp);
+        }
+        Profile profile = null;
+        if (session == null) {
+            String scope = "systemscope";
+            // 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();
+                        break;
+                    } else if (event.getSource() != null && StringUtils.isNotBlank(event.getSource().getScope()) && !event.getSource().getScope().equals("systemscope")) {
+                        scope = event.getSource().getScope();
+                        break;
+                    }
+                }
+            }
+            String cookieProfileId = ServletCommon.getProfileIdCookieValue(request, profileIdCookieName);
+            if (StringUtils.isNotBlank(cookieProfileId)) {
+                profile = profileService.load(cookieProfileId);
+            }
+            if (profile == null) {
+                // Create non persisted profile to create the session
+                profile = new Profile("temp_" + UUID.randomUUID().toString());
+                profile.setProperty("firstVisit", timestamp);
+            }
+            /*
+            // Create anonymous profile so we don't keep track of the temp profile anywhere
+            Profile anonymousProfile = privacyService.getAnonymousProfile(profile);
+            // Create new session which should not be persisted as well as the temp profile
+            session = new Session(sessionId, anonymousProfile, timestamp, scope);
+            if (logger.isDebugEnabled()) {
+                logger.debug("No session found for sessionId={}, creating new session!", sessionId);
+            }
+            */
+        } else {
+            Profile sessionProfile = session.getProfile();
+            if (sessionProfile.getItemId() != null) {
+                // Reload up-to-date profile
+                profile = profileService.load(sessionProfile.getItemId());
+                if (profile == null || profile instanceof Persona) {
+                    throw new InternalServerErrorException(String.format("No valid profile found or persona found for profileId=%s, aborting request !", session.getProfileId()));
+                }
+            } else {
+                // Session uses anonymous profile, try to find profile from cookie
+                String cookieProfileId = ServletCommon.getProfileIdCookieValue(request, profileIdCookieName);
+                if (StringUtils.isNotBlank(cookieProfileId)) {
+                    profile = profileService.load(cookieProfileId);
+                }
+
+                if (profile == null) {
+                    throw new InternalServerErrorException(String.format("No valid profile found or persona found for profileId=%s, aborting request !", session.getProfileId()));
+                }
+            }
+        }
+
+        Changes changesObject = ServletCommon.handleEvents(eventsCollectorRequest.getEvents(), session, profile, request, response,
+                timestamp, privacyService, eventService);
+        int changes = changesObject.getChangeType();
+        profile = changesObject.getProfile();
+
+        if ((changes & EventService.PROFILE_UPDATED) == EventService.PROFILE_UPDATED) {
+            profileService.save(profile);
+        }
+        if ((changes & EventService.SESSION_UPDATED) == EventService.SESSION_UPDATED && session != null) {
+                profileService.saveSession(session);
+        }
+        if ((changes & EventService.ERROR) == EventService.ERROR) {
+            String errorMessage = "Error processing events. Total number of processed events: " + changesObject.getProcessedItems() + "/" + eventsCollectorRequest.getEvents().size();
+            throw new InternalServerErrorException(errorMessage);
+        }
+
+        return new EventCollectorResponse(changes);
+    }
+
+    @Activate
+    public void init() {
+        // TODO DMF-4436 read values from the configuration file: org.apache.unomi.web.cfg
+        profileIdCookieName = "context-profile-id";
+    }
+}
diff --git a/rest/src/main/java/org/apache/unomi/utils/Changes.java b/rest/src/main/java/org/apache/unomi/utils/Changes.java
new file mode 100644
index 0000000..3ed75a6
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/utils/Changes.java
@@ -0,0 +1,53 @@
+/*
+ * 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.utils;
+
+import org.apache.unomi.api.Profile;
+
+/**
+ * This class is a simple object to get the updated profile without the need of reloading it
+ *
+ * @author dgaillard
+ */
+public class Changes {
+    private int changeType;
+    private int processedItems;
+    private Profile profile;
+
+    public Changes(int changeType, Profile profile) {
+        this(changeType,0,profile);
+    }
+
+    public Changes(int changeType, int processedItems, Profile profile) {
+        this.changeType = changeType;
+        this.processedItems = processedItems;
+        this.profile = profile;
+    }
+
+    public int getChangeType() {
+        return changeType;
+    }
+
+    public int getProcessedItems() {
+        return processedItems;
+    }
+
+    public Profile getProfile() {
+        return profile;
+    }
+}
diff --git a/rest/src/main/java/org/apache/unomi/utils/HttpUtils.java b/rest/src/main/java/org/apache/unomi/utils/HttpUtils.java
new file mode 100644
index 0000000..0b8101f
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/utils/HttpUtils.java
@@ -0,0 +1,133 @@
+/*
+ * 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.utils;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.unomi.api.Persona;
+import org.apache.unomi.api.Profile;
+
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * This is duplicate of the class from the wab bundle, the original file will be removed once endpoints forwarded
+ */
+public class HttpUtils {
+
+    public static String dumpRequestInfo(HttpServletRequest httpServletRequest) {
+        StringBuilder stringBuilder = new StringBuilder();
+        stringBuilder.append("\n");
+        stringBuilder.append("======================================================================================\n");
+        stringBuilder.append(dumpBasicRequestInfo(httpServletRequest));
+        stringBuilder.append(dumpRequestHeaders(httpServletRequest));
+        stringBuilder.append(dumpRequestCookies(httpServletRequest.getCookies()));
+        stringBuilder.append("======================================================================================\n");
+        return stringBuilder.toString();
+    }
+
+    public static String dumpBasicRequestInfo(HttpServletRequest httpServletRequest) {
+        StringBuilder stringBuilder = new StringBuilder();
+        stringBuilder.append(httpServletRequest.getMethod()).append(" ").append(httpServletRequest.getRequestURI());
+        if (httpServletRequest.getQueryString() != null) {
+            stringBuilder.append("?").append(httpServletRequest.getQueryString());
+        }
+        stringBuilder.append(" serverName=").append(httpServletRequest.getServerName()).append(" serverPort=").append(httpServletRequest.getServerPort()).append(" remoteAddr=").append(httpServletRequest.getRemoteAddr()).append(" remotePort=").append(httpServletRequest.getRemotePort()).append("\n");
+        return stringBuilder.toString();
+    }
+
+
+    public static String dumpRequestCookies(Cookie[] cookies) {
+        StringBuilder stringBuilder = new StringBuilder();
+        stringBuilder.append("Cookies:\n");
+        if (cookies == null) {
+            stringBuilder.append("  none");
+        } else {
+            for (Cookie cookie : cookies) {
+                stringBuilder.append("  ").append(cookie.getName()).append("=").append(cookie.getValue()).append(" domain=").append(cookie.getDomain()).append(" path=").append(cookie.getPath()).append(" maxAge=").append(cookie.getMaxAge()).append(" httpOnly=").append(cookie.isHttpOnly()).append(" secure=").append(cookie.getSecure()).append(" version=").append(cookie.getVersion()).append(" comment=").append(cookie.getComment()).append("\n");
+            }
+        }
+        return stringBuilder.toString();
+    }
+
+    public static String dumpRequestHeaders(HttpServletRequest httpServletRequest) {
+        StringBuilder stringBuilder = new StringBuilder();
+        stringBuilder.append("Headers:\n");
+        Enumeration<String> headerNameEnum = httpServletRequest.getHeaderNames();
+        while (headerNameEnum.hasMoreElements()) {
+            String headerName = headerNameEnum.nextElement();
+            stringBuilder.append("  ").append(headerName).append(": ").append(httpServletRequest.getHeader(headerName)).append("\n");
+        }
+        return stringBuilder.toString();
+    }
+
+    public static void sendProfileCookie(Profile profile, ServletResponse response, String profileIdCookieName, String profileIdCookieDomain, int profileIdCookieMaxAgeInSeconds) {
+        if (response instanceof HttpServletResponse) {
+            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
+            if (!(profile instanceof Persona)) {
+                httpServletResponse.addHeader("Set-Cookie",
+                        profileIdCookieName + "=" + profile.getItemId() +
+                                "; Path=/" +
+                                "; Max-Age=" + profileIdCookieMaxAgeInSeconds +
+                                (StringUtils.isNotBlank(profileIdCookieDomain) ? ("; Domain=" + profileIdCookieDomain) : "")  +
+                                "; SameSite=Lax");
+            }
+        }
+    }
+
+    public static void clearCookie(ServletResponse response, String cookieName) {
+        if (response instanceof HttpServletResponse) {
+            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
+            Cookie cookie = new Cookie(cookieName, "");
+            cookie.setPath("/");
+            cookie.setMaxAge(0);
+            httpServletResponse.addCookie(cookie);
+        }
+    }
+
+    public static Map<String, Cookie> getCookieMap(Cookie[] cookieArray) {
+        Map<String, Cookie> cookieMap = new LinkedHashMap<String, Cookie>();
+        for (Cookie cookie : cookieArray) {
+            cookieMap.put(cookie.getName(), cookie);
+        }
+        return cookieMap;
+    }
+
+    public static String getPayload(HttpServletRequest request) throws IOException {
+        if ("post".equals(request.getMethod().toLowerCase())) {
+            StringBuilder buffer = new StringBuilder();
+            String line;
+            BufferedReader reader = request.getReader();
+            while ((line = reader.readLine()) != null) {
+                buffer.append(line);
+            }
+            if (buffer.length() > 0) {
+                return buffer.toString();
+            }
+        } else if ("get".equals(request.getMethod().toLowerCase()) && request.getParameter("payload") != null) {
+            return request.getParameter("payload");
+        }
+        return null;
+    }
+}
diff --git a/rest/src/main/java/org/apache/unomi/utils/ServletCommon.java b/rest/src/main/java/org/apache/unomi/utils/ServletCommon.java
new file mode 100644
index 0000000..5250ff1
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/utils/ServletCommon.java
@@ -0,0 +1,119 @@
+/*
+ * 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.utils;
+
+import org.apache.unomi.api.Event;
+import org.apache.unomi.api.Persona;
+import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.Session;
+import org.apache.unomi.api.services.EventService;
+import org.apache.unomi.api.services.PrivacyService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * // This is duplicate of the class from the wab bundle, the original file will be removed once endpoints forwarded
+ *
+ * @author dgaillard
+ */
+public class ServletCommon {
+    private static final Logger logger = LoggerFactory.getLogger(ServletCommon.class.getName());
+
+
+    public static String getProfileIdCookieValue(HttpServletRequest httpServletRequest, String profileIdCookieName) {
+        String cookieProfileId = null;
+
+        Cookie[] cookies = httpServletRequest.getCookies();
+        if (cookies != null) {
+            for (Cookie cookie : cookies) {
+                if (profileIdCookieName.equals(cookie.getName())) {
+                    cookieProfileId = cookie.getValue();
+                }
+            }
+        }
+
+        return cookieProfileId;
+    }
+
+    public static Changes handleEvents(List<Event> events, Session session, Profile profile,
+                                       ServletRequest request, ServletResponse response, Date timestamp,
+                                       PrivacyService privacyService, EventService eventService) {
+        List<String> filteredEventTypes = privacyService.getFilteredEventTypes(profile);
+
+        String thirdPartyId = eventService.authenticateThirdPartyServer(((HttpServletRequest) request).getHeader("X-Unomi-Peer"),
+                request.getRemoteAddr());
+
+        int changes = EventService.NO_CHANGE;
+        // execute provided events if any
+        int processedEventsCnt = 0;
+        if (events != null && !(profile instanceof Persona)) {
+            for (Event event : events) {
+                processedEventsCnt++;
+                if (event.getEventType() != null) {
+                    if (!eventService.isEventValid(event)) {
+                        logger.warn("Event is not valid : {}", event.getEventType());
+                        continue;
+                    }
+
+                    Event eventToSend = new Event(event.getEventType(), session, profile, event.getScope(), 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.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());
+                        continue;
+                    }
+                    if (profile.isAnonymousProfile()) {
+                        // Do not keep track of profile in event
+                        eventToSend.setProfileId(null);
+                    }
+
+                    eventToSend.getAttributes().put(Event.HTTP_REQUEST_ATTRIBUTE, request);
+                    eventToSend.getAttributes().put(Event.HTTP_RESPONSE_ATTRIBUTE, response);
+                    logger.debug("Received event " + event.getEventType() + " for profile=" + profile.getItemId() + " session="
+                            + (session != null ? session.getItemId() : null) + " target=" + event.getTarget() + " timestamp=" + timestamp);
+                    changes |= eventService.send(eventToSend);
+                    // If the event execution changes the profile we need to update it so the next event use the right profile
+                    if ((changes & EventService.PROFILE_UPDATED) == EventService.PROFILE_UPDATED) {
+                        profile = eventToSend.getProfile();
+                    }
+                    if ((changes & EventService.ERROR) == EventService.ERROR) {
+                        //Don't count the event that failed
+                        processedEventsCnt--;
+                        logger.error("Error processing events. Total number of processed events: {}/{}", processedEventsCnt, events.size());
+                        break;
+                    }
+                }
+            }
+        }
+        return new Changes(changes, processedEventsCnt, profile);
+    }
+}
+