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/30 07:00:45 UTC

[unomi] branch unomi-1.5.x updated: Merge pull request #271 from apache/UNOMI-449-move-wab-endpoints-to-rest

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

dgriffon pushed a commit to branch unomi-1.5.x
in repository https://gitbox.apache.org/repos/asf/unomi.git


The following commit(s) were added to refs/heads/unomi-1.5.x by this push:
     new 3bae2fa  Merge pull request #271 from apache/UNOMI-449-move-wab-endpoints-to-rest
3bae2fa is described below

commit 3bae2faa0f58e0ab18f0fedaa5b0c05a891b01b5
Author: David Griffon <dg...@jahia.com>
AuthorDate: Mon Mar 29 17:41:17 2021 +0200

    Merge pull request #271 from apache/UNOMI-449-move-wab-endpoints-to-rest
    
    [UNOMI-449] copy servlet endpoints to jax-rs endpoints.
---
 rest/pom.xml                                       |  13 +
 .../java/org/apache/unomi/rest/ClientEndpoint.java | 162 +++++++
 .../org/apache/unomi/rest/ContextJsonEndpoint.java | 504 +++++++++++++++++++++
 .../apache/unomi/rest/EventCollectorResponse.java  |  32 ++
 .../apache/unomi/rest/EventsCollectorEndpoint.java | 194 ++++++++
 .../main/java/org/apache/unomi/utils/Changes.java  |  53 +++
 .../java/org/apache/unomi/utils/HttpUtils.java     | 104 +++++
 .../java/org/apache/unomi/utils/ServletCommon.java | 121 +++++
 .../java/org/apache/unomi/web/ClientServlet.java   |   9 +
 .../resources/OSGI-INF/blueprint/blueprint.xml     |   1 +
 10 files changed, 1193 insertions(+)

diff --git a/rest/pom.xml b/rest/pom.xml
index bd713b3..d766d85 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..fb7f046
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/rest/ClientEndpoint.java
@@ -0,0 +1,162 @@
+/*
+ * 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;
+    @Reference
+    private ConfigSharingService configSharingService;
+
+    private final String FILE_NAME_WO_EXT = "my-profile";
+
+    @Context
+    HttpServletRequest request;
+    @Context
+    HttpServletResponse response;
+
+    @GET
+    @Path("/client/{operation}/{param}")
+    public Response getClient(@PathParam("operation") String operation, @PathParam("param") String param) throws JsonProcessingException {
+        switch (operation) {
+            case "myprofile":
+                if (((String) configSharingService.getProperty("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 (configSharingService.getProperty("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();
+    }
+}
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..9a205ca
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/rest/ContextJsonEndpoint.java
@@ -0,0 +1,504 @@
+/*
+ * 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.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jws.WebService;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+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.util.*;
+
+@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 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, (String) configSharingService.getProperty("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(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());
+                    }
+
+                    // 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);
+        }
+        // Set profile cookie
+        if (!(profile instanceof Persona)) {
+            response.setHeader("Set-Cookie", HttpUtils.getProfileCookieString(profile, configSharingService));
+        }
+        return contextResponse;
+    }
+
+    private Changes checkMergedProfile(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;
+                }
+            } 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);
+        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..a673fd3
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/rest/EventsCollectorEndpoint.java
@@ -0,0 +1,194 @@
+/*
+ * 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.ConfigSharingService;
+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());
+
+    @Reference
+    private EventService eventService;
+    @Reference
+    private ProfileService profileService;
+    @Reference
+    private PrivacyService privacyService;
+    @Reference
+    private ConfigSharingService configSharingService;
+
+    @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, (String) configSharingService.getProperty("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, (String) configSharingService.getProperty("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);
+    }
+}
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..c3fab07
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/utils/HttpUtils.java
@@ -0,0 +1,104 @@
+/*
+ * 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.Profile;
+import org.apache.unomi.api.services.ConfigSharingService;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import java.util.Enumeration;
+
+
+/**
+ * This is duplicate of the class from the wab bundle, the original file will be removed once endpoints forwarded
+ */
+public class HttpUtils {
+
+    private static final int MAX_COOKIE_AGE_IN_SECONDS = 60 * 60 * 24 * 365; // 1 year
+
+    /**
+     * Utility to dump request info for a given http request.
+     * @param httpServletRequest request to dump
+     * @return the info as a String
+     */
+    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();
+    }
+
+    /**
+     * Return the cookie string for the given profile
+     * We can't use the build in NewCookie jax-rs object as it does not support the SameSite value.
+     * @param profile to parse
+     * @param configSharingService shared config location.
+     * @return the cookie string to set in the header.
+     */
+    public static String getProfileCookieString(Profile profile, ConfigSharingService configSharingService) {
+        final String profileIdCookieDomain = (String) configSharingService.getProperty("profileIdCookieDomain");
+        final String profileIdCookieName = (String) configSharingService.getProperty("profileIdCookieName");
+        final Integer profileIdCookieMaxAgeInSeconds = (Integer) configSharingService.getProperty("profileIdCookieMaxAgeInSeconds") ;
+        return profileIdCookieName + "=" + profile.getItemId() +
+                "; Path=/" +
+                "; Max-Age=" + profileIdCookieMaxAgeInSeconds +
+                (StringUtils.isNotBlank(profileIdCookieDomain) ? ("; Domain=" + profileIdCookieDomain) : "")  +
+                "; SameSite=Lax";
+    }
+}
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..98e70a7
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/utils/ServletCommon.java
@@ -0,0 +1,121 @@
+/*
+ * 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();
+                    // TODO : Replace this header protection by a more global one while working on input validation
+                    cookieProfileId = cookieProfileId.replaceAll("\\n\\r","");
+                }
+            }
+        }
+
+        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.getSourceId(), event.getSource(),
+                            event.getTarget(), event.getProperties(), timestamp, event.isPersistent());
+                    if (!eventService.isEventAllowed(event, thirdPartyId)) {
+                        logger.warn("Event is not allowed : {}", event.getEventType());
+                        continue;
+                    }
+                    if (thirdPartyId != null && event.getItemId() != null) {
+                        eventToSend = new Event(event.getItemId(), event.getEventType(), session, profile, event.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);
+    }
+}
+
diff --git a/wab/src/main/java/org/apache/unomi/web/ClientServlet.java b/wab/src/main/java/org/apache/unomi/web/ClientServlet.java
index 59de0ce..7f3e923 100644
--- a/wab/src/main/java/org/apache/unomi/web/ClientServlet.java
+++ b/wab/src/main/java/org/apache/unomi/web/ClientServlet.java
@@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
 import com.opencsv.CSVWriter;
 import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.services.ConfigSharingService;
 import org.apache.unomi.api.services.ProfileService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -51,17 +52,21 @@ public class ClientServlet extends HttpServlet {
     private String profileIdCookieName = "context-profile-id";
     private String allowedProfileDownloadFormats;
 
+    private ConfigSharingService configSharingService;
+
     private final String FILE_NAME_WO_EXT = "my-profile";
 
     @Override
     public void init(ServletConfig config) throws ServletException {
         super.init(config);
+        configSharingService.setProperty("allowedProfileDownloadFormats", allowedProfileDownloadFormats);
         logger.info("ClientServlet initialized.");
     }
 
     @Override
     public void destroy() {
         super.destroy();
+
         logger.info("Client servlet shutdown.");
     }
 
@@ -206,4 +211,8 @@ public class ClientServlet extends HttpServlet {
         this.profileIdCookieName = profileIdCookieName;
     }
 
+    public void setConfigSharingService(ConfigSharingService configSharingService) {
+        this.configSharingService = configSharingService;
+    }
+
 }
diff --git a/wab/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/wab/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 9dc06b8..ccdd8ed 100644
--- a/wab/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/wab/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -88,6 +88,7 @@
 
     <bean id="clientServlet" class="org.apache.unomi.web.ClientServlet">
         <property name="profileService" ref="profileService"/>
+        <property name="configSharingService" ref="configSharingService"/>
         <property name="allowedProfileDownloadFormats" value="${web.allowed.profile.download.formats}" />
         <property name="profileIdCookieName" value="${web.contextserver.profileIdCookieName}"/>
     </bean>