[UNOMI-449] : forward existing endpoints to jax-rs endpoints (#273)

* UNOMI-449 : forward existing endpoints to jax-rs endpoints

* UNOMI-449 : fix following code review

* UNOMI-449 : fix iTests, restore context.js
diff --git a/itests/src/test/java/org/apache/unomi/itests/TestUtils.java b/itests/src/test/java/org/apache/unomi/itests/TestUtils.java
index 97feed0..1045d12 100644
--- a/itests/src/test/java/org/apache/unomi/itests/TestUtils.java
+++ b/itests/src/test/java/org/apache/unomi/itests/TestUtils.java
@@ -23,7 +23,6 @@
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.entity.ContentType;
-import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.http.util.EntityUtils;
 import org.apache.unomi.api.ContextResponse;
 import org.apache.unomi.api.Event;
@@ -31,6 +30,7 @@
 import org.apache.unomi.api.Session;
 import org.apache.unomi.api.conditions.Condition;
 import org.apache.unomi.api.services.DefinitionsService;
+import org.apache.unomi.itests.tools.httpclient.HttpClientThatWaitsForUnomi;
 import org.apache.unomi.persistence.spi.CustomObjectMapper;
 import org.apache.unomi.persistence.spi.PersistenceService;
 import org.junit.Assert;
@@ -64,7 +64,7 @@
 	}
 
 	public static RequestResponse executeContextJSONRequest(HttpPost request, String sessionId) throws IOException {
-		try (CloseableHttpResponse response = HttpClientBuilder.create().build().execute(request)) {
+		try (CloseableHttpResponse response = HttpClientThatWaitsForUnomi.doPost(request)) {
 			// validate mimeType
 			HttpEntity entity = response.getEntity();
 			String mimeType = ContentType.getOrDefault(entity).getMimeType();
diff --git a/itests/src/test/java/org/apache/unomi/itests/tools/httpclient/HttpClientThatWaitsForUnomi.java b/itests/src/test/java/org/apache/unomi/itests/tools/httpclient/HttpClientThatWaitsForUnomi.java
new file mode 100644
index 0000000..9685c01
--- /dev/null
+++ b/itests/src/test/java/org/apache/unomi/itests/tools/httpclient/HttpClientThatWaitsForUnomi.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.unomi.itests.tools.httpclient;
+
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.eclipse.jetty.http.HttpStatus;
+
+import java.io.IOException;
+
+public class HttpClientThatWaitsForUnomi {
+
+    private static final long TIMER = 1000L;
+    private static final int MAX_TRIES = 10;
+
+    public static CloseableHttpResponse doPost(HttpPost request) throws IOException {
+        int count = 0;
+        while (true) {
+            CloseableHttpResponse response = HttpClientBuilder.create().build().execute(request);
+            final int statusCode = response.getStatusLine().getStatusCode();
+            if (statusCode < HttpStatus.BAD_REQUEST_400) {
+                return response;
+            }
+            if (count++ > MAX_TRIES || statusCode > HttpStatus.INTERNAL_SERVER_ERROR_500) {
+                throw new RuntimeException(String.format("connecting to the server failed %s times with status %s %s", MAX_TRIES, statusCode, response.getStatusLine().getReasonPhrase()));
+            }
+            try {
+                // We should find another way to avoid that sleep here.
+                Thread.sleep(TIMER);
+            } catch (InterruptedException e) {
+                // exit .. it should not happen
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}
diff --git a/rest/src/main/java/org/apache/unomi/rest/ClientEndpoint.java b/rest/src/main/java/org/apache/unomi/rest/ClientEndpoint.java
index fb7f046..6d0f689 100644
--- a/rest/src/main/java/org/apache/unomi/rest/ClientEndpoint.java
+++ b/rest/src/main/java/org/apache/unomi/rest/ClientEndpoint.java
@@ -25,7 +25,6 @@
 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;
@@ -37,9 +36,7 @@
 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;
@@ -51,8 +48,6 @@
  * 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
@@ -62,30 +57,40 @@
 public class ClientEndpoint {
 
     private static final Logger logger = LoggerFactory.getLogger(ClientEndpoint.class.getName());
-    private static final long serialVersionUID = 2928875960103325238L;
+    private static final String CONTENT_DISPOSITION_HEADER_KEY = "Content-Disposition";
+
+    private static final String FILE_NAME_WO_EXT = "my-profile";
+
+    private static String getContentDispostionHeader(String extension) {
+        return String.format("attachment; filename=\"%s.%s\"", FILE_NAME_WO_EXT, extension);
+    }
 
     @Reference
     private ProfileService profileService;
     @Reference
     private ConfigSharingService configSharingService;
 
-    private final String FILE_NAME_WO_EXT = "my-profile";
 
     @Context
     HttpServletRequest request;
     @Context
     HttpServletResponse response;
 
+    @OPTIONS
+    @Path("/client/{operation}/{param}")
+    public Response options() {
+        return Response.status(Response.Status.NO_CONTENT).build();
+    }
+
     @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));
-                }
+        if ("myprofile".equals(operation)) {
+            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();
     }
@@ -112,8 +117,9 @@
                         return prepareCsvFileToDownload(currentProfile, request.getParameter("vertical") != null);
                     case "text":
                         return prepareYamlFileToDownload(currentProfile, true);
+                    default:
+                        throw new NotFoundException();
                 }
-
             }
         }
         throw new NotFoundException();
@@ -121,7 +127,8 @@
 
     private Response prepareCsvFileToDownload(Profile currentProfile, boolean vertical) {
         response.setContentType("text/csv");
-        response.setHeader("Content-Disposition", "attachment; filename=\"" + FILE_NAME_WO_EXT + ".csv\"");
+
+        response.setHeader(CONTENT_DISPOSITION_HEADER_KEY, getContentDispostionHeader("csv"));
         StringWriter writer = new StringWriter();
         //using custom delimiter and quote character
         CSVWriter csvWriter = new CSVWriter(writer);
@@ -132,12 +139,12 @@
             }
         } else {
             Set<String> keySet = currentProfile.getProperties().keySet();
-            List<String> values = new ArrayList();
+            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()]));
+            csvWriter.writeNext(keySet.toArray(new String[0]));
+            csvWriter.writeNext(values.toArray(new String[0]));
         }
         Response.ResponseBuilder responseBuilder = Response.ok(writer.toString());
         return responseBuilder.build();
@@ -145,7 +152,8 @@
 
     private Response prepareJsonFileToDownload(Profile currentProfile) throws JsonProcessingException {
         response.setContentType("text/json");
-        response.setHeader("Content-Disposition", "attachment; filename=\"" + FILE_NAME_WO_EXT + ".json\"");
+        response.setHeader(CONTENT_DISPOSITION_HEADER_KEY, getContentDispostionHeader("json"));
+
         ObjectMapper mapper = new ObjectMapper();
         String jsonContent = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(currentProfile.getProperties());
         return Response.ok(jsonContent).build();
@@ -153,7 +161,7 @@
 
     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") + "\"");
+        response.setHeader(CONTENT_DISPOSITION_HEADER_KEY, getContentDispostionHeader((asTextFile ? "txt" : "yml")));
         YAMLFactory yf = new YAMLFactory();
         ObjectMapper mapper = new ObjectMapper(yf);
         String yamlContent = mapper.writeValueAsString(currentProfile.getProperties());
diff --git a/rest/src/main/java/org/apache/unomi/rest/ContextJsonEndpoint.java b/rest/src/main/java/org/apache/unomi/rest/ContextJsonEndpoint.java
index 9a205ca..45f9f62 100644
--- a/rest/src/main/java/org/apache/unomi/rest/ContextJsonEndpoint.java
+++ b/rest/src/main/java/org/apache/unomi/rest/ContextJsonEndpoint.java
@@ -17,10 +17,8 @@
 
 package org.apache.unomi.rest;
 
-import com.fasterxml.jackson.core.JsonFactory;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.core.JsonProcessingException;
 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;
@@ -47,8 +45,7 @@
 import java.util.*;
 
 @WebService
-@Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
-@Consumes(MediaType.TEXT_PLAIN)
+@Consumes(MediaType.APPLICATION_JSON)
 @CrossOriginResourceSharing(
         allowAllOrigins = true,
         allowCredentials = true
@@ -82,9 +79,68 @@
     @Reference
     private ConfigSharingService configSharingService;
 
-    @POST
+    @OPTIONS
     @Path("/context.json")
-    public ContextResponse getContextJSON(String contextRequestAsString, @QueryParam("timestamp") Long timestampAsLong, @CookieParam("context-profile-id") String cookieProfileId) {
+    public Response options() {
+        return Response.status(Response.Status.NO_CONTENT).header("Access-Control-Allow-Origin", "*").build();
+    }
+
+    @POST
+    @Produces(MediaType.TEXT_PLAIN)
+    @Path("/context.js")
+    public Response contextJSAsPost(ContextRequest contextRequest,
+                                    @QueryParam("personaId") String personaId,
+                                    @QueryParam("sessionId") String sessionId,
+                                    @QueryParam("timestamp") Long timestampAsLong,
+                                    @QueryParam("invalidateProfile") boolean invalidateProfile,
+                                    @QueryParam("invalidateSession") boolean invalidateSession) throws JsonProcessingException {
+        return contextJSAsGet(contextRequest, personaId, sessionId, timestampAsLong, invalidateProfile, invalidateSession);
+    }
+
+
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    @Path("/context.js")
+    public Response contextJSAsGet(ContextRequest contextRequest,
+                                   @QueryParam("personaId") String personaId,
+                                   @QueryParam("sessionId") String sessionId,
+                                   @QueryParam("timestamp") Long timestampAsLong,
+                                   @QueryParam("invalidateProfile") boolean invalidateProfile,
+                                   @QueryParam("invalidateSession") boolean invalidateSession) throws JsonProcessingException {
+        ContextResponse contextResponse = contextJSONAsPost(contextRequest, personaId, sessionId, timestampAsLong, invalidateProfile, invalidateSession);
+        String contextAsJSONString = CustomObjectMapper.getObjectMapper().writeValueAsString(contextResponse);
+        StringBuilder responseAsString = new StringBuilder();
+        responseAsString.append("window.digitalData = window.digitalData || {};\n")
+                .append("var cxs = ")
+                .append(contextAsJSONString)
+                .append(";\n");
+        return Response.ok(responseAsString.toString()).build();
+
+    }
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
+    @Path("/context.json")
+    public ContextResponse contextJSONAsGet(ContextRequest contextRequest,
+                                            @QueryParam("personaId") String personaId,
+                                            @QueryParam("sessionId") String sessionId,
+                                            @QueryParam("timestamp") Long timestampAsLong,
+                                            @QueryParam("invalidateProfile") boolean invalidateProfile,
+                                            @QueryParam("invalidateSession") boolean invalidateSession) {
+        return contextJSONAsPost(contextRequest, personaId, sessionId, timestampAsLong, invalidateProfile, invalidateSession);
+    }
+
+    @POST
+    @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
+    @Path("/context.json")
+    public ContextResponse contextJSONAsPost(
+            ContextRequest contextRequest,
+            @QueryParam("personaId") String personaId,
+            @QueryParam("sessionId") String sessionId,
+            @QueryParam("timestamp") Long timestampAsLong,
+            @QueryParam("invalidateProfile") boolean invalidateProfile,
+            @QueryParam("invalidateSession") boolean invalidateSession
+    ) {
         Date timestamp = new Date();
         if (timestampAsLong != null) {
             timestamp = new Date(timestampAsLong);
@@ -93,7 +149,7 @@
         // Handle persona
         Profile profile = null;
         Session session = null;
-        String personaId = request.getParameter("personaId");
+        String profileId = null;
         if (personaId != null) {
             PersonaWithSessions personaWithSessions = profileService.loadPersonaWithSessions(personaId);
             if (personaWithSessions == null) {
@@ -105,32 +161,18 @@
             }
         }
 
-        // 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);
+        if (contextRequest != null) {
+            if (contextRequest.getSource() != null) {
+                scope = contextRequest.getSource().getScope();
             }
-            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 (contextRequest.getSessionId() != null) {
+                sessionId = contextRequest.getSessionId();
+            }
 
+            profileId = contextRequest.getProfileId();
+        }
         if (profileId == null) {
             // Get profile id from the cookie
             profileId = ServletCommon.getProfileIdCookieValue(request, (String) configSharingService.getProperty("profileIdCookieName"));
@@ -141,7 +183,7 @@
             if (logger.isDebugEnabled()) {
                 logger.debug("Request dump: {}", HttpUtils.dumpRequestInfo(request));
             }
-            throw new InternalServerErrorException("Couldn't find profileId, sessionId or personaId in incoming request!");
+            throw new BadRequestException("Couldn't find profileId, sessionId or personaId in incoming request!");
         }
 
         int changes = EventService.NO_CHANGE;
@@ -149,18 +191,16 @@
             // 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);
+                profile = createNewProfile(null, 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);
+                    profile = createNewProfile(profileId, timestamp);
                     profileCreated = true;
                 } else {
                     Changes changesObject = checkMergedProfile(profile, session);
@@ -170,8 +210,6 @@
             }
 
             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) {
@@ -296,7 +334,6 @@
                 }
             } else {
                 logger.warn("Couldn't find merged profile {}, falling back to profile {}", masterProfileId, currentProfile.getItemId());
-                profile = currentProfile;
                 profile.setMergedWith(null);
                 changes = EventService.PROFILE_UPDATED;
             }
@@ -379,25 +416,23 @@
      * @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());
-                }
+        if (profile instanceof Persona && 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) {
+    private Profile createNewProfile(String existingProfileId, Date timestamp) {
         Profile profile;
         String profileId = existingProfileId;
         if (profileId == null) {
@@ -443,7 +478,7 @@
         List<PersonalizationService.PersonalizationRequest> result = new ArrayList<>();
         for (PersonalizationService.PersonalizationRequest personalizationRequest : personalizations) {
             List<PersonalizationService.PersonalizedContent> personalizedContents = sanitizePersonalizedContentObjects(personalizationRequest.getContents());
-            if (personalizedContents != null && personalizedContents.size() > 0) {
+            if (personalizedContents != null && !personalizedContents.isEmpty()) {
                 result.add(personalizationRequest);
             }
         }
diff --git a/rest/src/main/java/org/apache/unomi/rest/EventsCollectorEndpoint.java b/rest/src/main/java/org/apache/unomi/rest/EventsCollectorEndpoint.java
index a673fd3..cf1bb0c 100644
--- a/rest/src/main/java/org/apache/unomi/rest/EventsCollectorEndpoint.java
+++ b/rest/src/main/java/org/apache/unomi/rest/EventsCollectorEndpoint.java
@@ -17,43 +17,33 @@
 
 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.*;
 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 javax.ws.rs.core.Response;
 import java.util.Date;
 import java.util.UUID;
 
 @WebService
 @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
-@Consumes(MediaType.TEXT_PLAIN)
+@Consumes(MediaType.APPLICATION_JSON)
 @CrossOriginResourceSharing(
         allowAllOrigins = true,
         allowCredentials = true
@@ -77,38 +67,32 @@
     @Context
     HttpServletResponse response;
 
+    @OPTIONS
+    @Path("/eventcollector")
+    public Response options() {
+        return Response.status(Response.Status.NO_CONTENT).build();
+    }
+
     @GET
     @Path("/eventcollector")
-    public EventCollectorResponse get(String eventsCollectorRequestAsString, @QueryParam("timestamp") Long timestampAsString) throws IOException {
-        return doEvent(eventsCollectorRequestAsString, timestampAsString);
+    public EventCollectorResponse collectAsGet(EventsCollectorRequest eventsCollectorRequest, @QueryParam("timestamp") Long timestampAsString) {
+        return doEvent(eventsCollectorRequest, timestampAsString);
     }
 
     @POST
     @Path("/eventcollector")
-    public EventCollectorResponse post(String eventsCollectorRequestAsString, @QueryParam("timestamp") Long timestampAsLong) throws IOException {
-        return doEvent(eventsCollectorRequestAsString, timestampAsLong);
+    public EventCollectorResponse collectAsPost(EventsCollectorRequest eventsCollectorRequest, @QueryParam("timestamp") Long timestampAsLong) {
+        return doEvent(eventsCollectorRequest, timestampAsLong);
     }
 
-    private EventCollectorResponse doEvent(String eventsCollectorRequestAsString, Long timestampAsLong) throws IOException {
+    private EventCollectorResponse doEvent(EventsCollectorRequest eventsCollectorRequest, Long timestampAsLong) {
         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");
+            throw new BadRequestException("No events found");
         }
 
         String sessionId = eventsCollectorRequest.getSessionId();
@@ -125,8 +109,8 @@
             // Get the first available scope that is not equal to systemscope to create the session otherwise systemscope will be used
             for (Event event : eventsCollectorRequest.getEvents()) {
                 if (StringUtils.isNotBlank(event.getEventType())) {
-                    if (StringUtils.isNotBlank(event.getScope()) && !event.getScope().equals("systemscope")) {
-                        scope = event.getScope();
+                    if (StringUtils.isNotBlank(event.getSourceId()) && !event.getSourceId().equals("systemscope")) {
+                        scope = event.getSourceId();
                         break;
                     } else if (event.getSource() != null && StringUtils.isNotBlank(event.getSource().getScope()) && !event.getSource().getScope().equals("systemscope")) {
                         scope = event.getSource().getScope();
@@ -134,6 +118,7 @@
                     }
                 }
             }
+            logger.debug("scope is now {}", scope);
             String cookieProfileId = ServletCommon.getProfileIdCookieValue(request, (String) configSharingService.getProperty("profileIdCookieName"));
             if (StringUtils.isNotBlank(cookieProfileId)) {
                 profile = profileService.load(cookieProfileId);
@@ -154,11 +139,13 @@
             */
         } else {
             Profile sessionProfile = session.getProfile();
+            final String errorMessage = String.format("No valid profile found or persona found for profileId=%s, aborting request !", session.getProfileId());
             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()));
+                    logger.error(errorMessage);
+                    throw new BadRequestException(errorMessage);
                 }
             } else {
                 // Session uses anonymous profile, try to find profile from cookie
@@ -168,7 +155,8 @@
                 }
 
                 if (profile == null) {
-                    throw new InternalServerErrorException(String.format("No valid profile found or persona found for profileId=%s, aborting request !", session.getProfileId()));
+                    logger.error(errorMessage);
+                    throw new BadRequestException(errorMessage);
                 }
             }
         }
@@ -186,7 +174,7 @@
         }
         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);
+            throw new BadRequestException(errorMessage);
         }
 
         return new EventCollectorResponse(changes);
diff --git a/rest/src/main/java/org/apache/unomi/rest/authentication/impl/DefaultRestAuthenticationConfig.java b/rest/src/main/java/org/apache/unomi/rest/authentication/impl/DefaultRestAuthenticationConfig.java
index bc41bb9..d6b06be 100644
--- a/rest/src/main/java/org/apache/unomi/rest/authentication/impl/DefaultRestAuthenticationConfig.java
+++ b/rest/src/main/java/org/apache/unomi/rest/authentication/impl/DefaultRestAuthenticationConfig.java
@@ -31,26 +31,38 @@
     private static final String GUEST_ROLES = "ROLE_UNOMI_PUBLIC";
     private static final String ADMIN_ROLES = "ROLE_UNOMI_ADMIN";
 
+    private static final List<Pattern> PUBLIC_PATH_PATTERNS = Arrays.asList(
+            Pattern.compile("(GET|POST|OPTIONS) context\\.js(on|)"),
+            Pattern.compile("(GET|POST|OPTIONS) eventcollector"),
+            Pattern.compile("(GET|OPTIONS) client/.*")
+    );
+
+
+    private static final Map<String, String> ROLES_MAPPING;
+
+    static {
+        Map<String, String> roles = new HashMap<>();
+        roles.put("org.apache.unomi.rest.ContextJsonEndpoint.contextJSAsGet", GUEST_ROLES);
+        roles.put("org.apache.unomi.rest.ContextJsonEndpoint.contextJSAsPost", GUEST_ROLES);
+        roles.put("org.apache.unomi.rest.ContextJsonEndpoint.contextJSONAsGet", GUEST_ROLES);
+        roles.put("org.apache.unomi.rest.ContextJsonEndpoint.contextJSONAsPost", GUEST_ROLES);
+        roles.put("org.apache.unomi.rest.ContextJsonEndpoint.options", GUEST_ROLES);
+        roles.put("org.apache.unomi.rest.EventsCollectorEndpoint.collectAsGet", GUEST_ROLES);
+        roles.put("org.apache.unomi.rest.EventsCollectorEndpoint.collectAsPost", GUEST_ROLES);
+        roles.put("org.apache.unomi.rest.EventsCollectorEndpoint.options", GUEST_ROLES);
+        roles.put("org.apache.unomi.rest.ClientEndpoint.getClient", GUEST_ROLES);
+        roles.put("org.apache.unomi.rest.ClientEndpoint.options", GUEST_ROLES);
+        ROLES_MAPPING = Collections.unmodifiableMap(roles);
+    }
+
     @Override
     public List<Pattern> getPublicPathPatterns() {
-        List<Pattern> publicPaths = new ArrayList<>();
-        publicPaths.add(Pattern.compile("(GET|POST|OPTIONS) context\\.json"));
-        publicPaths.add(Pattern.compile("(GET|POST|OPTIONS) eventcollector"));
-        publicPaths.add(Pattern.compile("GET client/.*"));
-        return publicPaths;
+        return PUBLIC_PATH_PATTERNS;
     }
 
     @Override
     public Map<String, String> getMethodRolesMap() {
-        Map<String, String> roleMappings = new HashMap<>();
-        roleMappings.put("org.apache.unomi.rest.ContextJsonEndpoint.contextJSONAsGet", GUEST_ROLES);
-        roleMappings.put("org.apache.unomi.rest.ContextJsonEndpoint.contextJSONAsPost", GUEST_ROLES);
-        roleMappings.put("org.apache.unomi.rest.ContextJsonEndpoint.options", GUEST_ROLES);
-        roleMappings.put("org.apache.unomi.rest.EventsCollectorEndpoint.collectAsGet", GUEST_ROLES);
-        roleMappings.put("org.apache.unomi.rest.EventsCollectorEndpoint.collectAsPost", GUEST_ROLES);
-        roleMappings.put("org.apache.unomi.rest.EventsCollectorEndpoint.options", GUEST_ROLES);
-        roleMappings.put("org.apache.unomi.rest.ClientEndpoint.getClient", GUEST_ROLES);
-        return roleMappings;
+        return ROLES_MAPPING;
     }
 
     @Override
diff --git a/wab/src/main/java/org/apache/unomi/web/Changes.java b/wab/src/main/java/org/apache/unomi/web/Changes.java
deleted file mode 100644
index 1f1043c..0000000
--- a/wab/src/main/java/org/apache/unomi/web/Changes.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.web;
-
-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/wab/src/main/java/org/apache/unomi/web/ClientServlet.java b/wab/src/main/java/org/apache/unomi/web/ClientServlet.java
index 7f3e923..c105302 100644
--- a/wab/src/main/java/org/apache/unomi/web/ClientServlet.java
+++ b/wab/src/main/java/org/apache/unomi/web/ClientServlet.java
@@ -17,28 +17,16 @@
 
 package org.apache.unomi.web;
 
-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;
 
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletException;
-import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
-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.
@@ -47,15 +35,11 @@
 
     private static final Logger logger = LoggerFactory.getLogger(ClientServlet.class.getName());
     private static final long serialVersionUID = 2928875960103325238L;
-    private ProfileService profileService;
 
-    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);
@@ -71,148 +55,15 @@
     }
 
     @Override
-    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-        try {
-            String[] pathInfo = req.getPathInfo().substring(1).split("\\.");
-            if (pathInfo != null && pathInfo.length > 0) {
-                String operation = pathInfo[0];
-                String param = pathInfo[1];
-                switch (operation) {
-                    case "myprofile":
-                        if (allowedProfileDownloadFormats.contains(param)) {
-                            donwloadCurrentProfile(req, resp, param);
-                        } else {
-                            resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
-                        }
-                        break;
-                    default:
-                        resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
-
-                }
-            } else {
-                resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
-            }
-        } catch (Throwable t) { // Here in order to return generic message instead of the whole stack trace in case of not caught exception
-            logger.error("ClientServlet failed to execute get", t);
-            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal server error");
-        }
-    }
-
-    @Override
-    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-    }
-
-    public void donwloadCurrentProfile(HttpServletRequest request, HttpServletResponse response, String downloadFileType) throws ServletException, IOException {
-        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":
-                        prepareYamlFileToDownload(response, currentProfile, false);
-                        break;
-                    case "json":
-                        prepareJsonFileToDownload(response, currentProfile);
-                        break;
-                    case "csv":
-                        prepareCsvFileToDownload(response, currentProfile, request.getParameter("vertical") != null);
-                        break;
-                    case "text":
-                        prepareYamlFileToDownload(response, currentProfile, true);
-                        break;
-                    default:
-                        return;
-
-                }
-
-            }
-        }
-    }
-
-    private void prepareCsvFileToDownload(HttpServletResponse response, Profile currentProfile, boolean vertical) {
-        response.setContentType("text/csv");
-        response.setHeader("Content-Disposition", "attachment; filename=\"" + FILE_NAME_WO_EXT + ".csv\"");
-        try {
-            StringWriter writer = new StringWriter();
-
-            //using custom delimiter and quote character
-            CSVWriter csvWriter = new CSVWriter(writer);
-            OutputStream outputStream = response.getOutputStream();
-            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()]));
-            }
-            outputStream.write(writer.toString().getBytes());
-            outputStream.flush();
-            outputStream.close();
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-
-    private void prepareJsonFileToDownload(HttpServletResponse response, Profile currentProfile) {
-        response.setContentType("text/json");
-        response.setHeader("Content-Disposition", "attachment; filename=\"" + FILE_NAME_WO_EXT + ".json\"");
-        try {
-            ObjectMapper mapper = new ObjectMapper();
-            String jsonContent = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(currentProfile.getProperties());
-            OutputStream outputStream = response.getOutputStream();
-            outputStream.write(jsonContent.getBytes());
-            outputStream.flush();
-            outputStream.close();
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-
-    private void prepareYamlFileToDownload(HttpServletResponse response, Profile currentProfile, boolean asTextFile) {
-        response.setContentType("text/" + (asTextFile ? "plain" : "yaml"));
-        response.setHeader("Content-Disposition", "attachment; filename=\"" + FILE_NAME_WO_EXT + (asTextFile ? ".txt" : ".yml") + "\"");
-        try {
-            YAMLFactory yf = new YAMLFactory();
-            ObjectMapper mapper = new ObjectMapper(yf);
-            String yamlContent = mapper.writeValueAsString(currentProfile.getProperties());
-            OutputStream outputStream = response.getOutputStream();
-            outputStream.write(yamlContent.getBytes());
-            outputStream.flush();
-            outputStream.close();
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-
-    public void setProfileService(ProfileService profileService) {
-        this.profileService = profileService;
-    }
-
-    public void setAllowedProfileDownloadFormats(String allowedProfileDownloadFormats) {
-        this.allowedProfileDownloadFormats = allowedProfileDownloadFormats;
-    }
-
-    public void setProfileIdCookieName(String profileIdCookieName) {
-        this.profileIdCookieName = profileIdCookieName;
+    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+        HttpServletRequestForwardWrapper.forward(request, response);
     }
 
     public void setConfigSharingService(ConfigSharingService configSharingService) {
         this.configSharingService = configSharingService;
     }
 
+    public void setAllowedProfileDownloadFormats(String allowedProfileDownloadFormats) {
+        this.allowedProfileDownloadFormats = allowedProfileDownloadFormats;
+    }
 }
diff --git a/wab/src/main/java/org/apache/unomi/web/ContextServlet.java b/wab/src/main/java/org/apache/unomi/web/ContextServlet.java
index 2c37611..ee93a2c 100644
--- a/wab/src/main/java/org/apache/unomi/web/ContextServlet.java
+++ b/wab/src/main/java/org/apache/unomi/web/ContextServlet.java
@@ -17,27 +17,16 @@
 
 package org.apache.unomi.web;
 
-import com.fasterxml.jackson.core.JsonFactory;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang3.StringUtils;
-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.api.services.ConfigSharingService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
-import java.io.Writer;
-import java.util.*;
 
 /**
  * A servlet filter to serve a context-specific Javascript containing the current request context object.
@@ -52,15 +41,8 @@
     private String profileIdCookieDomain;
     private int profileIdCookieMaxAgeInSeconds = MAX_COOKIE_AGE_IN_SECONDS;
 
-    private ProfileService profileService;
-    private EventService eventService;
-    private RulesService rulesService;
-    private PrivacyService privacyService;
-    private PersonalizationService personalizationService;
     private ConfigSharingService configSharingService;
 
-    private boolean sanitizeConditions = Boolean.parseBoolean(System.getProperty("org.apache.unomi.security.personalization.sanitizeConditions", "true"));
-
     @Override
     public void init(ServletConfig config) throws ServletException {
         super.init(config);
@@ -71,396 +53,15 @@
     }
 
     @Override
-    public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
-        try {
-            final Date timestamp = new Date();
-            if (request.getParameter("timestamp") != null) {
-                try {
-                    timestamp.setTime(Long.parseLong(request.getParameter("timestamp")));
-                } catch (NumberFormatException e) {
-                    // catch to avoid logging the error with the timestamp value to avoid potential log injection
-                    logger.error("Invalid timestamp parameter");
-                    return;
-                }
-            }
-
-            // set up CORS headers as soon as possible so that errors are not misconstrued on the client for CORS errors
-            HttpUtils.setupCORSHeaders(request, response);
-
-            // Handle OPTIONS request
-            String httpMethod = request.getMethod();
-            if ("options".equals(httpMethod.toLowerCase())) {
-                response.flushBuffer();
-                if (logger.isDebugEnabled()) {
-                    logger.debug("OPTIONS request received. No context will be returned.");
-                }
-                return;
-            }
-
-            // 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;
-            String stringPayload = HttpUtils.getPayload(request);
-            if (stringPayload != null) {
-                ObjectMapper mapper = CustomObjectMapper.getObjectMapper();
-                JsonFactory factory = mapper.getFactory();
-                try {
-                    contextRequest = mapper.readValue(factory.createParser(stringPayload), ContextRequest.class);
-                } catch (Exception e) {
-                    ((HttpServletResponse)response).sendError(HttpServletResponse.SC_BAD_REQUEST, "Check logs for more details");
-                    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: {}", stringPayload, e);
-                    }
-                    return;
-                }
-                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) {
-                ((HttpServletResponse)response).sendError(HttpServletResponse.SC_BAD_REQUEST, "Check logs for more details");
-                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));
-                }
-                return;
-            }
-
-            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);
-            }
-
-            String extension = request.getRequestURI().substring(request.getRequestURI().lastIndexOf(".") + 1);
-            boolean noScript = "json".equals(extension);
-            String contextAsJSONString = CustomObjectMapper.getObjectMapper().writeValueAsString(contextResponse);
-            Writer responseWriter;
-            response.setCharacterEncoding("UTF-8");
-            if (noScript) {
-                responseWriter = response.getWriter();
-                response.setContentType("application/json");
-                IOUtils.write(contextAsJSONString, responseWriter);
-            } else {
-                responseWriter = response.getWriter();
-                responseWriter.append("window.digitalData = window.digitalData || {};\n")
-                        .append("var cxs = ")
-                        .append(contextAsJSONString)
-                        .append(";\n");
-            }
-
-            responseWriter.flush();
-        } catch (Throwable t) { // Here in order to return generic message instead of the whole stack trace in case of not caught exception
-            logger.error("ContextServlet failed to execute request", t);
-            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal server error");
-        }
+    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+        HttpServletRequestForwardWrapper.forward(request, response);
     }
 
-    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;
-    }
-
-
+    @Override
     public void destroy() {
         logger.info("Context servlet shutdown.");
     }
 
-    public void setProfileService(ProfileService profileService) {
-        this.profileService = profileService;
-    }
-
-    public void setEventService(EventService eventService) {
-        this.eventService = eventService;
-    }
-
-    public void setRulesService(RulesService rulesService) {
-        this.rulesService = rulesService;
-    }
-
     public void setProfileIdCookieDomain(String profileIdCookieDomain) {
         this.profileIdCookieDomain = profileIdCookieDomain;
     }
@@ -473,103 +74,8 @@
         this.profileIdCookieMaxAgeInSeconds = profileIdCookieMaxAgeInSeconds;
     }
 
-    public void setPrivacyService(PrivacyService privacyService) {
-        this.privacyService = privacyService;
-    }
-
-    public void setPersonalizationService(PersonalizationService personalizationService) {
-        this.personalizationService = personalizationService;
-    }
-
     public void setConfigSharingService(ConfigSharingService configSharingService) {
         this.configSharingService = configSharingService;
     }
 
-    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/wab/src/main/java/org/apache/unomi/web/EventsCollectorServlet.java b/wab/src/main/java/org/apache/unomi/web/EventsCollectorServlet.java
index 5bca92d..8268b76 100644
--- a/wab/src/main/java/org/apache/unomi/web/EventsCollectorServlet.java
+++ b/wab/src/main/java/org/apache/unomi/web/EventsCollectorServlet.java
@@ -17,14 +17,6 @@
 
 package org.apache.unomi.web;
 
-import com.fasterxml.jackson.core.JsonFactory;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.unomi.api.*;
-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.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -34,20 +26,11 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.Date;
-import java.util.UUID;
 
 public class EventsCollectorServlet extends HttpServlet {
     private static final long serialVersionUID = 2008054804885122957L;
     private static final Logger logger = LoggerFactory.getLogger(EventsCollectorServlet.class.getName());
 
-    private String profileIdCookieName = "context-profile-id";
-
-    private EventService eventService;
-    private ProfileService profileService;
-    private PrivacyService privacyService;
-
     @Override
     public void init(ServletConfig config) throws ServletException {
         super.init(config);
@@ -61,182 +44,7 @@
     }
 
     @Override
-    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-        try {
-            doEvent(req, resp);
-        } catch (Throwable t) { // Here in order to return generic message instead of the whole stack trace in case of not caught exception
-            logger.error("EventsCollectorServlet failed to execute get", t);
-            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal server error");
-        }
-    }
-
-    @Override
-    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-        try {
-            doEvent(req, resp);
-        } catch (Throwable t) { // Here in order to return generic message instead of the whole stack trace in case of not caught exception
-            logger.error("EventsCollectorServlet failed to execute post", t);
-            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal server error");
-        }
-    }
-
-    @Override
-    protected void doOptions(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
-//        logger.debug(HttpUtils.dumpRequestInfo(request));
-        try {
-            HttpUtils.setupCORSHeaders(request, response);
-            response.flushBuffer();
-        }
-        catch (Throwable t) { // Here in order to return generic message instead of the whole stack trace in case of not caught exception
-            logger.error("EventsCollectorServlet failed to execute doOptions request", t);
-            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal server error");
-        }
-    }
-
-    private void doEvent(HttpServletRequest request, HttpServletResponse response) throws IOException {
-        Date timestamp = new Date();
-        if (request.getParameter("timestamp") != null) {
-            try {
-                timestamp.setTime(Long.parseLong(request.getParameter("timestamp")));
-            } catch (NumberFormatException e) {
-                // catch to avoid logging the error with the timestamp value to avoid potential log injection
-                logger.error("Invalid timestamp parameter");
-                return;
-            }
-        }
-
-        HttpUtils.setupCORSHeaders(request, response);
-
-        String payload = HttpUtils.getPayload(request);
-        if (payload == null) {
-            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Check logs for more details");
-            logger.error("No event payload found for request, aborting !");
-            return;
-        }
-
-        ObjectMapper mapper = CustomObjectMapper.getObjectMapper();
-        JsonFactory factory = mapper.getFactory();
-        EventsCollectorRequest eventsCollectorRequest;
-        try {
-            eventsCollectorRequest = mapper.readValue(factory.createParser(payload), EventsCollectorRequest.class);
-        } catch (Exception e) {
-            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Check logs for more details");
-            logger.error("Cannot read payload. See debug level for more information");
-            if (logger.isDebugEnabled()) {
-                logger.debug("Cannot read payload: {}", payload, e);
-            }
-            return;
-        }
-        if (eventsCollectorRequest == null || eventsCollectorRequest.getEvents() == null) {
-            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Check logs for more details");
-            logger.error("No events found in payload");
-            return;
-        }
-
-        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.getSourceId()) && !event.getSourceId().equals("systemscope")) {
-                        scope = event.getSourceId();
-                        break;
-                    } else if (event.getSource() != null && StringUtils.isNotBlank(event.getSource().getScope()) && !event.getSource().getScope().equals("systemscope")) {
-                        scope = event.getSource().getScope();
-                        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) {
-                    response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Check logs for more details");
-                    logger.error("No valid profile found or persona found for profileId={}, aborting request !", session.getProfileId());
-                    return;
-                }
-            } 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) {
-                    response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Check logs for more details");
-                    logger.error("No valid profile found or persona found for profileId={}, aborting request !", session.getProfileId());
-                    return;
-                }
-            }
-        }
-
-        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) {
-            if (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();
-            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, errorMessage);
-            return;
-        }
-
-        response.setContentType("application/json");
-        PrintWriter responseWriter = response.getWriter();
-        responseWriter.append("{\"updated\":" + changes + "}");
-        responseWriter.flush();
-    }
-
-    public void setEventService(EventService eventService) {
-        this.eventService = eventService;
-    }
-
-    public void setProfileService(ProfileService profileService) {
-        this.profileService = profileService;
-    }
-
-    public void setPrivacyService(PrivacyService privacyService) {
-        this.privacyService = privacyService;
-    }
-
-    public void setProfileIdCookieName(String profileIdCookieName) {
-        this.profileIdCookieName = profileIdCookieName;
+    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+        HttpServletRequestForwardWrapper.forward(request, response);
     }
 }
diff --git a/wab/src/main/java/org/apache/unomi/web/HttpServletRequestForwardWrapper.java b/wab/src/main/java/org/apache/unomi/web/HttpServletRequestForwardWrapper.java
new file mode 100644
index 0000000..717df09
--- /dev/null
+++ b/wab/src/main/java/org/apache/unomi/web/HttpServletRequestForwardWrapper.java
@@ -0,0 +1,86 @@
+/*
+ * 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.web;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+
+/**
+ * Http wrapper that force the content type to be "application/json"
+ */
+class HttpServletRequestForwardWrapper extends HttpServletRequestWrapper {
+
+    private static final Logger logger = LoggerFactory.getLogger(HttpServletRequestForwardWrapper.class.getName());
+    private static final String JSON_CONTENT_TYPE = "application/json";
+
+    public HttpServletRequestForwardWrapper(HttpServletRequest request) {
+        super(request);
+    }
+
+    /**
+     * Forward servlet request to jax-rs endpoints. For a given path, forward to /cxs + path.
+     *
+     * @param request  initial request
+     * @param response initial response
+     */
+    public static void forward(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+        try {
+            HttpServletRequest requestWrapper = new HttpServletRequestForwardWrapper(request);
+            requestWrapper.getServletContext()
+                    .getContext("/cxs")
+                    .getRequestDispatcher("/cxs" + request.getRequestURI())
+                    .forward(requestWrapper, response);
+        } catch (Throwable t) { // Here in order to return generic message instead of the whole stack trace in case of not caught exception
+            logger.error("HttpServletRequestForwardWrapper failed to forward the request", t);
+            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal server error");
+        }
+    }
+
+    @Override
+    public String getHeader(String name) {
+        if ("Content-Type".equals(name) || "content-type".equals(name)) {
+            return JSON_CONTENT_TYPE;
+        }
+        return super.getHeader(name);
+    }
+
+    @Override
+    public Enumeration<String> getHeaders(String name) {
+        if ("Content-Type".equals(name) || "content-type".equals(name)) {
+            return Collections.enumeration(Collections.singleton(JSON_CONTENT_TYPE));
+        }
+        return super.getHeaders(name);
+    }
+
+    @Override
+    public String getContentType() {
+        return JSON_CONTENT_TYPE;
+    }
+
+    @Override
+    public String getCharacterEncoding() {
+        return "UTF-8";
+    }
+}
diff --git a/wab/src/main/java/org/apache/unomi/web/HttpUtils.java b/wab/src/main/java/org/apache/unomi/web/HttpUtils.java
index a6c34a3..dbe7eab 100644
--- a/wab/src/main/java/org/apache/unomi/web/HttpUtils.java
+++ b/wab/src/main/java/org/apache/unomi/web/HttpUtils.java
@@ -17,19 +17,10 @@
 
 package org.apache.unomi.web;
 
-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;
 
 public class HttpUtils {
 
@@ -50,116 +41,6 @@
             httpServletResponse.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
             httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
             httpServletResponse.setHeader("Access-Control-Allow-Methods", "OPTIONS, POST, GET");
-            // httpServletResponse.setHeader("Access-Control-Max-Age", "600");
-            // httpServletResponse.setHeader("Access-Control-Expose-Headers","Access-Control-Allow-Origin");
-            // httpServletResponse.flushBuffer();
         }
     }
-
-    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 String getBaseRequestURL(HttpServletRequest httpServletRequest) {
-        String baseRequestURL;
-        baseRequestURL = httpServletRequest.getScheme() + "://" + httpServletRequest.getServerName();
-        if (("http".equals(httpServletRequest.getScheme()) && (httpServletRequest.getServerPort() == 80)) ||
-                ("https".equals(httpServletRequest.getScheme()) && (httpServletRequest.getServerPort() == 443))) {
-            // normal case, don't add the port
-        } else {
-            baseRequestURL += ":" + httpServletRequest.getServerPort();
-        }
-        return baseRequestURL;
-    }
-
-    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/wab/src/main/java/org/apache/unomi/web/ServletCommon.java b/wab/src/main/java/org/apache/unomi/web/ServletCommon.java
deleted file mode 100644
index e8722c3..0000000
--- a/wab/src/main/java/org/apache/unomi/web/ServletCommon.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * 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.web;
-
-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;
-
-/**
- * @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.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/resources/OSGI-INF/blueprint/blueprint.xml b/wab/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index ccdd8ed..b4ea919 100644
--- a/wab/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/wab/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -41,11 +41,6 @@
 
 
     <bean id="contextServlet" class="org.apache.unomi.web.ContextServlet">
-        <property name="profileService" ref="profileService"/>
-        <property name="eventService" ref="eventService"/>
-        <property name="rulesService" ref="rulesService"/>
-        <property name="privacyService" ref="privacyService" />
-        <property name="personalizationService" ref="personalizationService"/>
         <property name="configSharingService" ref="configSharingService"/>
         <property name="profileIdCookieDomain" value="${web.contextserver.domain}" />
         <property name="profileIdCookieName" value="${web.contextserver.profileIdCookieName}"/>
@@ -68,12 +63,7 @@
         </service-properties>
     </service>
 
-    <bean id="eventsCollectorServlet" class="org.apache.unomi.web.EventsCollectorServlet">
-        <property name="profileService" ref="profileService"/>
-        <property name="eventService" ref="eventService"/>
-        <property name="privacyService" ref="privacyService" />
-        <property name="profileIdCookieName" value="${web.contextserver.profileIdCookieName}"/>
-    </bean>
+    <bean id="eventsCollectorServlet" class="org.apache.unomi.web.EventsCollectorServlet"/>
 
     <service id="eventsCollectorServletService" ref="eventsCollectorServlet">
         <interfaces>
@@ -87,10 +77,8 @@
     </service>
 
     <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>
 
     <service id="clientServletService" ref="clientServlet">