Return error to the client when exception is thrown from Elasticsearch impl (#202)

* Throw exception from elasticsearch impl and add option to return error to the client in this case

* read throwExcetions member from the elasticImpl class - classNotFpund exception

* merge glitch fix
diff --git a/itests/src/test/java/org/apache/unomi/itests/ProfileServiceIT.java b/itests/src/test/java/org/apache/unomi/itests/ProfileServiceIT.java
index db4e735..317c4ff 100644
--- a/itests/src/test/java/org/apache/unomi/itests/ProfileServiceIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/ProfileServiceIT.java
@@ -22,8 +22,10 @@
 import org.apache.unomi.persistence.spi.PersistenceService;
 import org.apache.unomi.api.services.DefinitionsService;
 import org.apache.unomi.api.PartialList;
+import org.apache.unomi.persistence.elasticsearch.*;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.Before;
@@ -112,4 +114,26 @@
         assertEquals(0, profiles.getList().size());
     }
 
+    // Relevant only when throwExceptions system property is true
+    @Test
+    public void testGetProfileWithWrongScrollerIdThrowException() throws InterruptedException {
+        ElasticSearchPersistenceServiceImpl esPersistenceService = (ElasticSearchPersistenceServiceImpl)persistenceService;
+        esPersistenceService.setThrowExceptions(true);
+
+        Query query = new Query();
+        query.setLimit(2);
+        query.setScrollTimeValidity("10m");
+        query.setScrollIdentifier("dummyScrollId");
+
+        try {
+            profileService.search(query, Profile.class);
+            fail("search method didn't throw when expected");
+        } catch (RuntimeException ex) {
+        }
+        finally {
+            esPersistenceService.setThrowExceptions(false);
+        }
+    }
+
+
 }
diff --git a/package/src/main/resources/etc/custom.system.properties b/package/src/main/resources/etc/custom.system.properties
index 2d681c2..138eb0e 100644
--- a/package/src/main/resources/etc/custom.system.properties
+++ b/package/src/main/resources/etc/custom.system.properties
@@ -126,6 +126,8 @@
 org.apache.unomi.elasticsearch.bulkProcessor.bulkSize=${env:UNOMI_ELASTICSEARCH_BULK_SIZE:-5MB}
 org.apache.unomi.elasticsearch.bulkProcessor.flushInterval=${env:UNOMI_ELASTICSEARCH_BULK_FLUSHINTERVAL:-5s}
 org.apache.unomi.elasticsearch.bulkProcessor.backoffPolicy=${env:UNOMI_ELASTICSEARCH_BULK_BACKOFFPOLICY:-exponential}
+# Errors
+org.apache.unomi.elasticsearch.throwExceptions=${env:UNOMI_ELASTICSEARCH_THROW_EXCEPTIONS:-false}
 # Authentication
 org.apache.unomi.elasticsearch.username=${env:UNOMI_ELASTICSEARCH_USERNAME:-}
 org.apache.unomi.elasticsearch.password=${env:UNOMI_ELASTICSEARCH_PASSWORD:-}
diff --git a/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java b/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
index 7be7322..df8fad4 100644
--- a/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
+++ b/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
@@ -180,6 +180,7 @@
     public static final String BULK_PROCESSOR_BACKOFF_POLICY = "bulkProcessor.backoffPolicy";
     public static final String INDEX_DATE_PREFIX = "date-";
     private static final Logger logger = LoggerFactory.getLogger(ElasticSearchPersistenceServiceImpl.class.getName());
+    private static boolean throwExceptions = false;
     private RestHighLevelClient client;
     private BulkProcessor bulkProcessor;
     private String elasticSearchAddresses;
@@ -402,6 +403,10 @@
     public void setAggQueryThrowOnMissingDocs(boolean aggQueryThrowOnMissingDocs) {
         this.aggQueryThrowOnMissingDocs = aggQueryThrowOnMissingDocs;
     }
+
+    public void setThrowExceptions(boolean throwExceptions) {
+        this.throwExceptions = throwExceptions;
+    }
     public void start() throws Exception {
 
         // on startup
@@ -2168,6 +2173,9 @@
             if (logError) {
                 logger.error("Error while executing in class loader", t);
             }
+            if (throwExceptions) {
+                throw new RuntimeException(t);
+            }
         }
 
         private void handleFatalStateError() {
diff --git a/persistence-elasticsearch/core/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/persistence-elasticsearch/core/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 48402f3..9f10ae2 100644
--- a/persistence-elasticsearch/core/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/persistence-elasticsearch/core/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -63,6 +63,7 @@
             <cm:property name="password" value="" />
             <cm:property name="sslEnable" value="false" />
             <cm:property name="sslTrustAllCertificates" value="false" />
+            <cm:property name="throwExceptions" value="false" />
         </cm:default-properties>
     </cm:property-placeholder>
 
@@ -136,6 +137,7 @@
         <property name="password" value="${es.password}" />
         <property name="sslEnable" value="${es.sslEnable}" />
         <property name="sslTrustAllCertificates" value="${es.sslTrustAllCertificates}" />
+        <property name="throwExceptions" value="${es.throwExceptions}" />
     </bean>
 
     <!-- We use a listener here because using the list directly for listening to proxies coming from the same bundle didn't seem to work -->
diff --git a/persistence-elasticsearch/core/src/main/resources/org.apache.unomi.persistence.elasticsearch.cfg b/persistence-elasticsearch/core/src/main/resources/org.apache.unomi.persistence.elasticsearch.cfg
index c0e7b46..147e99f 100644
--- a/persistence-elasticsearch/core/src/main/resources/org.apache.unomi.persistence.elasticsearch.cfg
+++ b/persistence-elasticsearch/core/src/main/resources/org.apache.unomi.persistence.elasticsearch.cfg
@@ -70,4 +70,7 @@
 username=${org.apache.unomi.elasticsearch.username:-}
 password=${org.apache.unomi.elasticsearch.password:-}
 sslEnable=${org.apache.unomi.elasticsearch.sslEnable:-false}
-sslTrustAllCertificates=${org.apache.unomi.elasticsearch.sslTrustAllCertificates:-false}
\ No newline at end of file
+sslTrustAllCertificates=${org.apache.unomi.elasticsearch.sslTrustAllCertificates:-false}
+
+# Errors
+throwExceptions=${org.apache.unomi.elasticsearch.throwExceptions:-false}
\ No newline at end of file
diff --git a/rest/src/main/java/org/apache/unomi/rest/RuntimeExceptionMapper.java b/rest/src/main/java/org/apache/unomi/rest/RuntimeExceptionMapper.java
new file mode 100644
index 0000000..00e1732
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/rest/RuntimeExceptionMapper.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.rest;
+
+import org.osgi.service.component.annotations.Component;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+import java.util.HashMap;
+
+@Provider
+@Component(service=ExceptionMapper.class)
+public class RuntimeExceptionMapper implements ExceptionMapper<RuntimeException> {
+
+    @Override
+    public Response toResponse(RuntimeException exception) {
+        HashMap<String, Object> body = new HashMap<>();
+        body.put("errorMessage", "internalServerError");
+        return Response.status(Response.Status.INTERNAL_SERVER_ERROR).header("Content-Type", MediaType.APPLICATION_JSON).entity(body).build();
+    }
+}
\ No newline at end of file
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 f385a53..59de0ce 100644
--- a/wab/src/main/java/org/apache/unomi/web/ClientServlet.java
+++ b/wab/src/main/java/org/apache/unomi/web/ClientServlet.java
@@ -67,26 +67,30 @@
 
     @Override
     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-        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);
+        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);
             }
-        } 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
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 ae163c2..82fe666 100644
--- a/wab/src/main/java/org/apache/unomi/web/ContextServlet.java
+++ b/wab/src/main/java/org/apache/unomi/web/ContextServlet.java
@@ -72,234 +72,239 @@
 
     @Override
     public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
-        final Date timestamp = new Date();
-        if (request.getParameter("timestamp") != null) {
-            timestamp.setTime(Long.parseLong(request.getParameter("timestamp")));
-        }
-
-        // 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.");
+        try {
+            final Date timestamp = new Date();
+            if (request.getParameter("timestamp") != null) {
+                timestamp.setTime(Long.parseLong(request.getParameter("timestamp")));
             }
-            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 with id=" + personaId);
-                profile = null;
-            } else {
-                profile = personaWithSessions.getPersona();
-                session = personaWithSessions.getLastSession();
-            }
-        }
+            // set up CORS headers as soon as possible so that errors are not misconstrued on the client for CORS errors
+            HttpUtils.setupCORSHeaders(request, response);
 
-        // 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 read payload " + stringPayload, e);
+            // 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;
             }
-            if (contextRequest.getSource() != null) {
-                scope = contextRequest.getSource().getScope();
+
+            // 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 with id=" + personaId);
+                    profile = null;
+                } else {
+                    profile = personaWithSessions.getPersona();
+                    session = personaWithSessions.getLastSession();
+                }
             }
-            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));
+            // 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 read payload " + stringPayload, e);
+                    return;
+                }
+                if (contextRequest.getSource() != null) {
+                    scope = contextRequest.getSource().getScope();
+                }
+                sessionId = contextRequest.getSessionId();
+                profileId = contextRequest.getProfileId();
             }
-            return;
-        }
 
-        int changes = EventService.NO_CHANGE;
-        if (profile == null) {
-            // Not a persona, resolve profile now
-            boolean profileCreated = false;
+            if (sessionId == null) {
+                sessionId = request.getParameter("sessionId");
+            }
 
-            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);
+            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 {
-                    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());
-                        }
+                    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();
                     }
+                }
 
-                    // 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())) {
+                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);
                         }
-                        session.setProfile(sessionProfile);
                     }
                 }
-            }
 
-            if (session == null || invalidateSession) {
-                sessionProfile = privacyService.isRequireAnonymousBrowsing(profile) ? privacyService.getAnonymousProfile(profile) : profile;
+                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);
+                    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);
                     }
-                    event.getAttributes().put(Event.HTTP_REQUEST_ATTRIBUTE, request);
-                    event.getAttributes().put(Event.HTTP_RESPONSE_ATTRIBUTE, response);
+                }
+
+                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={} session={} target={} timestamp={}",
-                                event.getEventType(), profile.getItemId(), session.getItemId(), event.getTarget(), timestamp);
+                        logger.debug("Received event {} for profile={} {} target={} timestamp={}", profileUpdated.getEventType(), profile.getItemId(),
+                                " session=" + (session != null ? session.getItemId() : null), profileUpdated.getTarget(), timestamp);
                     }
-                    changes |= eventService.send(event);
+                    changes |= eventService.send(profileUpdated);
                 }
             }
 
-            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 contextResponse = new ContextResponse();
             contextResponse.setProfileId(profile.getItemId());
-        }
-        if ((changes & EventService.SESSION_UPDATED) == EventService.SESSION_UPDATED && session != null) {
-            profileService.saveSession(session);
-            contextResponse.setSessionId(session.getItemId());
-        }
+            if (session != null) {
+                contextResponse.setSessionId(session.getItemId());
+            } else if (sessionId != null) {
+                contextResponse.setSessionId(sessionId);
+            }
 
-        if ((changes & EventService.ERROR) == EventService.ERROR) {
-            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-        }
+            if (contextRequest != null) {
+                Changes changesObject = handleRequest(contextRequest, session, profile, contextResponse, request, response, timestamp);
+                changes |= changesObject.getChangeType();
+                profile = changesObject.getProfile();
+            }
 
-        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");
-        }
+            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());
+            }
 
-        responseWriter.flush();
+            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");
+        }
     }
 
     private Changes checkMergedProfile(ServletResponse response, Profile profile, Session session) {
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 2c34659..313b25b 100644
--- a/wab/src/main/java/org/apache/unomi/web/EventsCollectorServlet.java
+++ b/wab/src/main/java/org/apache/unomi/web/EventsCollectorServlet.java
@@ -62,19 +62,35 @@
 
     @Override
     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-        doEvent(req, resp);
+        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 {
-        doEvent(req, resp);
+        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));
-        HttpUtils.setupCORSHeaders(request, response);
-        response.flushBuffer();
+        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 {