You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@unomi.apache.org by jk...@apache.org on 2022/08/05 14:18:59 UTC
[unomi] 01/01: UNOMI-522: event collector endpoint is now handling current visitor session and profile exactly like context.json endpoint
This is an automated email from the ASF dual-hosted git repository.
jkevan pushed a commit to branch eventCollectorSessionProfileHandling
in repository https://gitbox.apache.org/repos/asf/unomi.git
commit 3aa41e8c5ce596634c54161a28c9ce77564a54a4
Author: Kevan <ke...@jahia.com>
AuthorDate: Fri Aug 5 16:18:42 2022 +0200
UNOMI-522: event collector endpoint is now handling current visitor session and profile exactly like context.json endpoint
---
.../apache/unomi/api/EventsCollectorRequest.java | 21 ++
.../unomi/rest/endpoints/ContextJsonEndpoint.java | 286 +++------------------
.../rest/endpoints/EventsCollectorEndpoint.java | 121 ++-------
.../unomi/rest/service/RestServiceUtils.java | 62 ++++-
.../rest/service/impl/RestServiceUtilsImpl.java | 255 +++++++++++++++---
.../main/java/org/apache/unomi/utils/Changes.java | 57 ----
.../apache/unomi/utils/EventsRequestContext.java | 121 +++++++++
7 files changed, 484 insertions(+), 439 deletions(-)
diff --git a/api/src/main/java/org/apache/unomi/api/EventsCollectorRequest.java b/api/src/main/java/org/apache/unomi/api/EventsCollectorRequest.java
index 9eb4ee9ac..74f922eec 100644
--- a/api/src/main/java/org/apache/unomi/api/EventsCollectorRequest.java
+++ b/api/src/main/java/org/apache/unomi/api/EventsCollectorRequest.java
@@ -34,6 +34,9 @@ public class EventsCollectorRequest {
@Pattern(regexp = ValidationPattern.TEXT_VALID_CHARACTERS_PATTERN)
private String sessionId;
+ @Pattern(regexp = ValidationPattern.TEXT_VALID_CHARACTERS_PATTERN)
+ private String profileId;
+
/**
* Retrieves the events to be processed.
*
@@ -67,4 +70,22 @@ public class EventsCollectorRequest {
this.sessionId = sessionId;
}
+ /**
+ * Retrieve the profileId passed along with the request. All events will be processed with this profileId as a
+ * default
+ *
+ * @return the identifier for the profile
+ */
+ public String getProfileId() {
+ return profileId;
+ }
+
+ /**
+ * Sets the profileId in the request.
+ *
+ * @param profileId an unique identifier for the profile
+ */
+ public void setProfileId(String profileId) {
+ this.profileId = profileId;
+ }
}
diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java
index da3debb20..6207845f8 100644
--- a/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java
+++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java
@@ -20,7 +20,6 @@ package org.apache.unomi.rest.endpoints;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
-import org.apache.commons.lang3.StringUtils;
import org.apache.cxf.rs.security.cors.CrossOriginResourceSharing;
import org.apache.unomi.api.*;
import org.apache.unomi.api.conditions.Condition;
@@ -29,8 +28,7 @@ import org.apache.unomi.persistence.spi.CustomObjectMapper;
import org.apache.unomi.rest.exception.InvalidRequestException;
import org.apache.unomi.rest.service.RestServiceUtils;
import org.apache.unomi.schema.api.SchemaService;
-import org.apache.unomi.utils.Changes;
-import org.apache.unomi.utils.HttpUtils;
+import org.apache.unomi.utils.EventsRequestContext;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
@@ -38,8 +36,6 @@ import org.slf4j.LoggerFactory;
import javax.jws.WebService;
import javax.servlet.ServletContext;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.*;
@@ -53,7 +49,6 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.UUID;
@WebService
@Consumes(MediaType.APPLICATION_JSON)
@@ -63,9 +58,7 @@ import java.util.UUID;
public class ContextJsonEndpoint {
private static final Logger logger = LoggerFactory.getLogger(ContextJsonEndpoint.class.getName());
- private static final String DEFAULT_CLIENT_ID = "defaultClientId";
-
- private boolean sanitizeConditions = Boolean
+ private final boolean sanitizeConditions = Boolean
.parseBoolean(System.getProperty("org.apache.unomi.security.personalization.sanitizeConditions", "true"));
@Context
@@ -75,19 +68,13 @@ public class ContextJsonEndpoint {
@Context
HttpServletResponse response;
- @Reference
- private ProfileService profileService;
@Reference
private PrivacyService privacyService;
@Reference
- private EventService eventService;
- @Reference
private RulesService rulesService;
@Reference
private PersonalizationService personalizationService;
@Reference
- private ConfigSharingService configSharingService;
- @Reference
private RestServiceUtils restServiceUtils;
@Reference
private SchemaService schemaService;
@@ -147,10 +134,11 @@ public class ContextJsonEndpoint {
@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) {
+ @QueryParam("personaId") String personaId,
+ @QueryParam("sessionId") String sessionId,
+ @QueryParam("timestamp") Long timestampAsLong,
+ @QueryParam("invalidateProfile") boolean invalidateProfile,
+ @QueryParam("invalidateSession") boolean invalidateSession) {
// Schema validation
ObjectNode paramsAsJson = JsonNodeFactory.instance.objectNode();
@@ -159,233 +147,57 @@ public class ContextJsonEndpoint {
if (!schemaService.isValid(paramsAsJson.toString(), "https://unomi.apache.org/schemas/json/rest/requestIds/1-0-0")) {
throw new InvalidRequestException("Invalid parameter", "Invalid received data");
}
+
+ // Generate timestamp
Date timestamp = new Date();
if (timestampAsLong != null) {
timestamp = new Date(timestampAsLong);
}
- // Handle persona
- Profile profile = null;
- Session session = null;
+ // init ids
String profileId = null;
- 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();
- }
- }
-
String scope = null;
if (contextRequest != null) {
- if (contextRequest.getSource() != null) {
- scope = contextRequest.getSource().getScope();
- }
-
- if (contextRequest.getSessionId() != null) {
- sessionId = contextRequest.getSessionId();
- }
-
+ scope = contextRequest.getSource() != null ? contextRequest.getSource().getScope() : scope;
+ sessionId = contextRequest.getSessionId() != null ? contextRequest.getSessionId() : sessionId;
profileId = contextRequest.getProfileId();
}
- if (profileId == null) {
- // Get profile id from the cookie
- profileId = restServiceUtils.getProfileIdCookieValue(request);
- }
-
- if (profileId == null && sessionId == null && personaId == null) {
- logger.error(
- "Couldn't find profileId, sessionId or personaId in incoming request! Stopped processing request. See debug level for more information");
- if (logger.isDebugEnabled()) {
- logger.debug("Request dump: {}", HttpUtils.dumpRequestInfo(request));
- }
- throw new BadRequestException("Couldn't find profileId, sessionId or personaId in incoming request!");
- }
-
- int changes = EventService.NO_CHANGE;
-
- // Not a persona, resolve profile now
- boolean profileCreated = false;
-
- if (profile == null) {
- if (profileId == null || invalidateProfile) {
- // no profileId cookie was found or the profile has to be invalidated, we generate a new one and create the profile in the profile service
- profile = createNewProfile(null, timestamp);
- 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, timestamp);
- profileCreated = true;
- } else {
- Changes changesObject = checkMergedProfile(profile, session);
- changes |= changesObject.getChangeType();
- profile = changesObject.getProfile();
- }
- }
-
- Profile sessionProfile;
- if (StringUtils.isNotBlank(sessionId) && !invalidateSession) {
- session = profileService.loadSession(sessionId, timestamp);
- if (session != null) {
- sessionProfile = session.getProfile();
-
- boolean anonymousSessionProfile = sessionProfile.isAnonymousProfile();
- if (!profile.isAnonymousProfile() && !anonymousSessionProfile && !profile.getItemId()
- .equals(sessionProfile.getItemId())) {
- // Session user has been switched, profile id in cookie is not up to date
- // We must reload the profile with the session ID as some properties could be missing from the session profile
- // #personalIdentifier
- profile = profileService.load(sessionProfile.getItemId());
- }
-
- // Handle anonymous situation
- Boolean requireAnonymousBrowsing = privacyService.isRequireAnonymousBrowsing(profile);
- if (requireAnonymousBrowsing && anonymousSessionProfile) {
- // User wants to browse anonymously, anonymous profile is already set.
- } else if (requireAnonymousBrowsing && !anonymousSessionProfile) {
- // User wants to browse anonymously, update the sessionProfile to anonymous profile
- sessionProfile = privacyService.getAnonymousProfile(profile);
- session.setProfile(sessionProfile);
- changes |= EventService.SESSION_UPDATED;
- } else if (!requireAnonymousBrowsing && anonymousSessionProfile) {
- // User does not want to browse anonymously anymore, update the sessionProfile to real profile
- sessionProfile = profile;
- session.setProfile(sessionProfile);
- changes |= EventService.SESSION_UPDATED;
- } else if (!requireAnonymousBrowsing && !anonymousSessionProfile) {
- // User does not want to browse anonymously, use the real profile. Check that session contains the current profile.
- sessionProfile = profile;
- if (!session.getProfileId().equals(sessionProfile.getItemId())) {
- changes |= EventService.SESSION_UPDATED;
- }
- session.setProfile(sessionProfile);
- }
- }
- }
- if (session == null || invalidateSession) {
- sessionProfile = privacyService.isRequireAnonymousBrowsing(profile) ? privacyService.getAnonymousProfile(profile) : profile;
-
- if (StringUtils.isNotBlank(sessionId)) {
- // Only save session and send event if a session id was provided, otherwise keep transient session
- session = new Session(sessionId, sessionProfile, timestamp, scope);
- changes |= EventService.SESSION_UPDATED;
- Event event = new Event("sessionCreated", session, profile, scope, null, session, null, timestamp, false);
- 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);
- profileUpdated.getAttributes().put(Event.CLIENT_ID_ATTRIBUTE, DEFAULT_CLIENT_ID);
-
- 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);
- }
- }
+ // build public context, profile + session creation/anonymous etc ...
+ EventsRequestContext eventsRequestContext = restServiceUtils.initEventsRequest(scope, sessionId, profileId,
+ personaId, invalidateProfile, invalidateSession, request, response, timestamp);
+ // Build response
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();
+ eventsRequestContext = processContextRequest(contextRequest, contextResponse, eventsRequestContext);
}
- if ((changes & EventService.PROFILE_UPDATED) == EventService.PROFILE_UPDATED) {
- profileService.save(profile);
- contextResponse.setProfileId(profile.getItemId());
+ // finalize request, save profile and session if necessary and return profileId cookie in response
+ restServiceUtils.finalizeEventsRequest(eventsRequestContext, false);
- if (profileCreated) {
- String clientId = contextRequest != null && contextRequest.getClientId() != null ? contextRequest.getClientId() : DEFAULT_CLIENT_ID;
- String profileMasterId = profile.getMergedWith() != null ? profile.getMergedWith() : profile.getItemId();
- profileService.addAliasToProfile(profileMasterId, profile.getItemId(), clientId );
- }
- }
- if ((changes & EventService.SESSION_UPDATED) == EventService.SESSION_UPDATED && session != null) {
- profileService.saveSession(session);
- contextResponse.setSessionId(session.getItemId());
- }
-
- if ((changes & EventService.ERROR) == EventService.ERROR) {
- response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- }
- // Set profile cookie
- if (!(profile instanceof Persona)) {
- response.setHeader("Set-Cookie", HttpUtils.getProfileCookieString(profile, configSharingService, request.isSecure()));
+ contextResponse.setProfileId(eventsRequestContext.getProfile().getItemId());
+ if (eventsRequestContext.getSession() != null) {
+ contextResponse.setSessionId(eventsRequestContext.getSession().getItemId());
+ } else if (sessionId != null) {
+ contextResponse.setSessionId(sessionId);
}
return contextResponse;
}
- private Changes checkMergedProfile(Profile profile, Session session) {
- int changes = EventService.NO_CHANGE;
- if (profile.getMergedWith() != null && !privacyService.isRequireAnonymousBrowsing(profile) && !profile.isAnonymousProfile()) {
- Profile currentProfile = profile;
- String masterProfileId = profile.getMergedWith();
- Profile masterProfile = profileService.load(masterProfileId);
- if (masterProfile != null) {
- logger.info("Current profile {} was merged with profile {}, replacing profile in session", currentProfile.getItemId(),
- masterProfileId);
- profile = masterProfile;
- if (session != null) {
- session.setProfile(profile);
- changes = EventService.SESSION_UPDATED;
- }
- } else {
- logger.warn("Couldn't find merged profile {}, falling back to profile {}", masterProfileId, currentProfile.getItemId());
- profile.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) {
+ private EventsRequestContext processContextRequest(ContextRequest contextRequest, ContextResponse data, EventsRequestContext eventsRequestContext) {
- processOverrides(contextRequest, profile, session);
+ processOverrides(contextRequest, eventsRequestContext.getProfile(), eventsRequestContext.getSession());
- Changes changes = restServiceUtils.handleEvents(contextRequest.getEvents(), session, profile, request, response, timestamp);
- data.setProcessedEvents(changes.getProcessedItems());
+ eventsRequestContext = restServiceUtils.performEventsRequest(contextRequest.getEvents(), eventsRequestContext);
+ data.setProcessedEvents(eventsRequestContext.getProcessedItems());
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));
+ .put(personalizedContent.getId(), personalizationService.filter(eventsRequestContext.getProfile(), eventsRequestContext.getSession(), personalizedContent));
}
}
@@ -393,34 +205,31 @@ public class ContextJsonEndpoint {
if (personalizations != null) {
data.setPersonalizations(new HashMap<>());
for (PersonalizationService.PersonalizationRequest personalization : sanitizePersonalizations(personalizations)) {
- PersonalizationResult personalizationResult = personalizationService.personalizeList(profile, session, personalization);
- changes.setChangeType(changes.getChangeType() | personalizationResult.getChangeType());
- data.getPersonalizations()
- .put(personalization.getId(), personalizationResult.getContentIds());
+ PersonalizationResult personalizationResult = personalizationService.personalizeList(eventsRequestContext.getProfile(), eventsRequestContext.getSession(), personalization);
+ eventsRequestContext.addChanges(personalizationResult.getChangeType());
+ data.getPersonalizations().put(personalization.getId(), personalizationResult.getContentIds());
}
}
- profile = changes.getProfile();
-
if (contextRequest.isRequireSegments()) {
- data.setProfileSegments(profile.getSegments());
+ data.setProfileSegments(eventsRequestContext.getProfile().getSegments());
}
if (contextRequest.isRequireScores()) {
- data.setProfileScores(profile.getScores());
+ data.setProfileScores(eventsRequestContext.getProfile().getScores());
}
if (contextRequest.getRequiredProfileProperties() != null) {
- Map<String, Object> profileProperties = new HashMap<>(profile.getProperties());
+ Map<String, Object> profileProperties = new HashMap<>(eventsRequestContext.getProfile().getProperties());
if (!contextRequest.getRequiredProfileProperties().contains("*")) {
profileProperties.keySet().retainAll(contextRequest.getRequiredProfileProperties());
}
data.setProfileProperties(profileProperties);
}
- if (session != null) {
- data.setSessionId(session.getItemId());
+ if (eventsRequestContext.getSession() != null) {
+ data.setSessionId(eventsRequestContext.getSession().getItemId());
if (contextRequest.getRequiredSessionProperties() != null) {
- Map<String, Object> sessionProperties = new HashMap<>(session.getProperties());
+ Map<String, Object> sessionProperties = new HashMap<>(eventsRequestContext.getSession().getProperties());
if (!contextRequest.getRequiredSessionProperties().contains("*")) {
sessionProperties.keySet().retainAll(contextRequest.getRequiredSessionProperties());
}
@@ -428,16 +237,16 @@ public class ContextJsonEndpoint {
}
}
- if (!(profile instanceof Persona)) {
+ if (!(eventsRequestContext.getProfile() instanceof Persona)) {
data.setTrackedConditions(rulesService.getTrackedConditions(contextRequest.getSource()));
} else {
data.setTrackedConditions(Collections.emptySet());
}
- data.setAnonymousBrowsing(privacyService.isRequireAnonymousBrowsing(profile));
- data.setConsents(profile.getConsents());
+ data.setAnonymousBrowsing(privacyService.isRequireAnonymousBrowsing(eventsRequestContext.getProfile()));
+ data.setConsents(eventsRequestContext.getProfile().getConsents());
- return changes;
+ return eventsRequestContext;
}
/**
@@ -467,17 +276,6 @@ public class ContextJsonEndpoint {
}
}
- private Profile createNewProfile(String existingProfileId, Date timestamp) {
- Profile profile;
- String profileId = existingProfileId;
- if (profileId == null) {
- profileId = UUID.randomUUID().toString();
- }
- profile = new Profile(profileId);
- profile.setProperty("firstVisit", timestamp);
- return profile;
- }
-
public void destroy() {
logger.info("Context servlet shutdown.");
}
diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/EventsCollectorEndpoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/EventsCollectorEndpoint.java
index 8911402a2..f727b352f 100644
--- a/rest/src/main/java/org/apache/unomi/rest/endpoints/EventsCollectorEndpoint.java
+++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/EventsCollectorEndpoint.java
@@ -21,28 +21,16 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.cxf.rs.security.cors.CrossOriginResourceSharing;
import org.apache.unomi.api.Event;
import org.apache.unomi.api.EventsCollectorRequest;
-import org.apache.unomi.api.Persona;
-import org.apache.unomi.api.Profile;
-import org.apache.unomi.api.Session;
-import org.apache.unomi.api.services.ConfigSharingService;
-import org.apache.unomi.api.services.EventService;
-import org.apache.unomi.api.services.PrivacyService;
-import org.apache.unomi.api.services.ProfileService;
import org.apache.unomi.rest.exception.InvalidRequestException;
import org.apache.unomi.rest.models.EventCollectorResponse;
import org.apache.unomi.rest.service.RestServiceUtils;
-import org.apache.unomi.utils.Changes;
+import org.apache.unomi.utils.EventsRequestContext;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import javax.jws.WebService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import javax.validation.Valid;
-import javax.validation.constraints.NotNull;
-import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.OPTIONS;
@@ -54,7 +42,6 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.Date;
-import java.util.UUID;
@WebService
@Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
@@ -63,16 +50,7 @@ import java.util.UUID;
@Path("/")
@Component(service = EventsCollectorEndpoint.class, property = "osgi.jaxrs.resource=true")
public class EventsCollectorEndpoint {
- private static final Logger logger = LoggerFactory.getLogger(EventsCollectorEndpoint.class.getName());
- @Reference
- private EventService eventService;
- @Reference
- private ProfileService profileService;
- @Reference
- private PrivacyService privacyService;
- @Reference
- private ConfigSharingService configSharingService;
@Reference
private RestServiceUtils restServiceUtils;
@@ -114,87 +92,34 @@ public class EventsCollectorEndpoint {
if (sessionId == null) {
sessionId = request.getParameter("sessionId");
}
- Session session = null;
- if (sessionId != null) {
- session = profileService.loadSession(sessionId, timestamp);
- }
- Profile profile = null;
- if (session == null) {
- String scope = "systemscope";
- // Get the first available scope that is not equal to systemscope to create the session otherwise systemscope will be used
- for (Event event : eventsCollectorRequest.getEvents()) {
- if (StringUtils.isNotBlank(event.getEventType())) {
- if (StringUtils.isNotBlank(event.getScope()) && !event.getScope().equals("systemscope")) {
- scope = event.getScope();
- break;
- } else if (event.getSource() != null && StringUtils.isNotBlank(event.getSource().getScope()) && !event.getSource()
- .getScope().equals("systemscope")) {
- scope = event.getSource().getScope();
- break;
- }
- }
- }
- logger.debug("scope is now {}", scope);
- String cookieProfileId = restServiceUtils.getProfileIdCookieValue(request);
- 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();
- 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) {
- logger.error(errorMessage);
- throw new BadRequestException(errorMessage);
- }
- } else {
- // Session uses anonymous profile, try to find profile from cookie
- String cookieProfileId = restServiceUtils.getProfileIdCookieValue(request);
- if (StringUtils.isNotBlank(cookieProfileId)) {
- profile = profileService.load(cookieProfileId);
- }
- if (profile == null) {
- logger.error(errorMessage);
- throw new BadRequestException(errorMessage);
+ String profileId = eventsCollectorRequest.getProfileId();
+ // Get the first available scope that is not equal to systemscope otherwise systemscope will be used
+ String scope = "systemscope";
+ for (Event event : eventsCollectorRequest.getEvents()) {
+ if (StringUtils.isNotBlank(event.getEventType())) {
+ if (StringUtils.isNotBlank(event.getScope()) && !event.getScope().equals("systemscope")) {
+ scope = event.getScope();
+ break;
+ } else if (event.getSource() != null &&
+ StringUtils.isNotBlank(event.getSource().getScope()) &&
+ !event.getSource().getScope().equals("systemscope")) {
+ scope = event.getSource().getScope();
+ break;
}
}
}
- Changes changesObject = restServiceUtils
- .handleEvents(eventsCollectorRequest.getEvents(), session, profile, request, response, timestamp);
- int changes = changesObject.getChangeType();
- profile = changesObject.getProfile();
+ // build public context, profile + session creation/anonymous etc ...
+ EventsRequestContext eventsRequestContext = restServiceUtils.initEventsRequest(scope, sessionId, profileId,
+ null, false, false, request, response, timestamp);
- if ((changes & EventService.PROFILE_UPDATED) == EventService.PROFILE_UPDATED) {
- profileService.save(profile);
- }
- if ((changes & EventService.SESSION_UPDATED) == EventService.SESSION_UPDATED && session != null) {
- profileService.saveSession(session);
- }
- if ((changes & EventService.ERROR) == EventService.ERROR) {
- String errorMessage = "Error processing events. Total number of processed events: " + changesObject.getProcessedItems() + "/"
- + eventsCollectorRequest.getEvents().size();
- throw new BadRequestException(errorMessage);
- }
+ // process events
+ eventsRequestContext = restServiceUtils.performEventsRequest(eventsCollectorRequest.getEvents(), eventsRequestContext);
+
+ // finalize request
+ restServiceUtils.finalizeEventsRequest(eventsRequestContext, true);
- return new EventCollectorResponse(changes);
+ return new EventCollectorResponse(eventsRequestContext.getChanges());
}
}
diff --git a/rest/src/main/java/org/apache/unomi/rest/service/RestServiceUtils.java b/rest/src/main/java/org/apache/unomi/rest/service/RestServiceUtils.java
index e64db1485..0e6a9ba11 100644
--- a/rest/src/main/java/org/apache/unomi/rest/service/RestServiceUtils.java
+++ b/rest/src/main/java/org/apache/unomi/rest/service/RestServiceUtils.java
@@ -18,19 +18,65 @@
package org.apache.unomi.rest.service;
import org.apache.unomi.api.Event;
-import org.apache.unomi.api.Profile;
-import org.apache.unomi.api.Session;
-import org.apache.unomi.utils.Changes;
+import org.apache.unomi.utils.EventsRequestContext;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.List;
+/**
+ * Utility service for Public REST endpoints
+ */
public interface RestServiceUtils {
- String getProfileIdCookieValue(HttpServletRequest httpServletRequest);
+ /**
+ * This method is used to initialize the context for a request that would require executing events.
+ *
+ * It will load existing profile/session for current user or build new ones if necessary
+ * IT will also handle anonymous profile/session preferences in case there is specific ones.
+ * It will also handle persona in case it is provided
+ * And finally it will provide a contextual bean named: EventsRequestContext,
+ * that will contain all the required information for the next steps of the request, like: processing the events
+ *
+ * @param scope the current scope (mandatory, in case session need to be created)
+ * @param sessionId the current sessionId (mandatory)
+ * @param profileId the current profileId (optional in case profile doesn't exists yet for incoming visitor)
+ * @param personaId the current personaId (optional in case we don't want to apply persona on current request)
+ * @param invalidateProfile true in case we want to invalidate the current visitor profile, false otherwise
+ * @param invalidateSession true in case we want to invalidate the current visitor session, false otherwise
+ * @param request the current request
+ * @param response the current request response
+ * @param timestamp the current date, for timestamp the current visitor data
+ *
+ * @return the built EventsRequestContext
+ */
+ EventsRequestContext initEventsRequest(String scope, String sessionId, String profileId, String personaId,
+ boolean invalidateProfile, boolean invalidateSession,
+ HttpServletRequest request, HttpServletResponse response,
+ Date timestamp);
+
+ /**
+ * Execute the list of events using the dedicated eventsRequestContext
+ * @param events the list of events to he executed
+ * @param eventsRequestContext the current EventsRequestContext
+ * @return an updated version of the current eventsRequestContext
+ */
+ EventsRequestContext performEventsRequest(List<Event> events, EventsRequestContext eventsRequestContext);
- Changes handleEvents(List<Event> events, Session session, Profile profile, ServletRequest request, ServletResponse response,
- Date timestamp);
+ /**
+ * At the end of an events requests we want to save/update the profile and/or the session depending on the changes
+ * Also we want to return a cookie about current visitor profile ID
+ *
+ * @param eventsRequestContext the current EventsRequestContext
+ * @param crashOnError true if we want to throw an Exception in case of errors during events execution,
+ * false otherwise (otherwise, no exception, but just an error code directly returned to the HTTP response)
+ */
+ void finalizeEventsRequest(EventsRequestContext eventsRequestContext, boolean crashOnError);
+
+ /**
+ * Try to extract the current visitor profileId from the current request cookies.
+ * @param httpServletRequest the current HTTP request
+ * @return the profileId if found in the cookies, null otherwise
+ */
+ String getProfileIdCookieValue(HttpServletRequest httpServletRequest);
}
diff --git a/rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java b/rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java
index fae86daa2..75a98d1e1 100644
--- a/rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java
+++ b/rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java
@@ -17,32 +17,35 @@
package org.apache.unomi.rest.service.impl;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
-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.commons.lang3.StringUtils;
+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.rest.exception.InvalidRequestException;
import org.apache.unomi.rest.service.RestServiceUtils;
import org.apache.unomi.schema.api.SchemaService;
-import org.apache.unomi.utils.Changes;
+import org.apache.unomi.utils.HttpUtils;
+import org.apache.unomi.utils.EventsRequestContext;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
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 javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.BadRequestException;
import java.util.Date;
import java.util.List;
+import java.util.UUID;
@Component(service = RestServiceUtils.class)
public class RestServiceUtilsImpl implements RestServiceUtils {
+ private static final String DEFAULT_CLIENT_ID = "defaultClientId";
+
private static final Logger logger = LoggerFactory.getLogger(RestServiceUtilsImpl.class.getName());
@Reference
@@ -54,9 +57,13 @@ public class RestServiceUtilsImpl implements RestServiceUtils {
@Reference
private EventService eventService;
+ @Reference
+ private ProfileService profileService;
+
@Reference
SchemaService schemaService;
+ @Override
public String getProfileIdCookieValue(HttpServletRequest httpServletRequest) {
String cookieProfileId = null;
@@ -78,59 +85,243 @@ public class RestServiceUtilsImpl implements RestServiceUtils {
}
@Override
- public Changes handleEvents(List<Event> events, Session session, Profile profile, ServletRequest request, ServletResponse response,
- Date timestamp) {
- List<String> filteredEventTypes = privacyService.getFilteredEventTypes(profile);
+ public EventsRequestContext initEventsRequest(String scope, String sessionId, String profileId, String personaId,
+ boolean invalidateProfile, boolean invalidateSession,
+ HttpServletRequest request, HttpServletResponse response, Date timestamp) {
+
+ // Build context
+ EventsRequestContext eventsRequestContext = new EventsRequestContext(timestamp, null, null, request, response);
+
+ // Handle persona
+ if (personaId != null) {
+ PersonaWithSessions personaWithSessions = profileService.loadPersonaWithSessions(personaId);
+ if (personaWithSessions == null) {
+ logger.error("Couldn't find persona, please check your personaId parameter");
+ } else {
+ eventsRequestContext.setProfile(personaWithSessions.getPersona());
+ eventsRequestContext.setSession(personaWithSessions.getLastSession());
+ }
+ }
+
+ if (profileId == null) {
+ // Get profile id from the cookie
+ profileId = getProfileIdCookieValue(request);
+ }
+
+ if (profileId == null && sessionId == null && personaId == null) {
+ logger.error("Couldn't find profileId, sessionId or personaId in incoming request! Stopped processing request. See debug level for more information");
+ if (logger.isDebugEnabled()) {
+ logger.debug("Request dump: {}", HttpUtils.dumpRequestInfo(request));
+ }
+ throw new BadRequestException("Couldn't find profileId, sessionId or personaId in incoming request!");
+ }
+
+ boolean profileCreated = false;
+ if (eventsRequestContext.getProfile() == null) {
+ if (profileId == null || invalidateProfile) {
+ // no profileId cookie was found or the profile has to be invalidated, we generate a new one and create the profile in the profile service
+ eventsRequestContext.setProfile(createNewProfile(null, timestamp));
+ profileCreated = true;
+ } else {
+ eventsRequestContext.setProfile(profileService.load(profileId));
+ if (eventsRequestContext.getProfile() == 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.
+ eventsRequestContext.setProfile(createNewProfile(profileId, timestamp));
+ profileCreated = true;
+ }
+ }
+
+ // Try to recover existing session
+ Profile sessionProfile;
+ if (StringUtils.isNotBlank(sessionId) && !invalidateSession) {
+
+ eventsRequestContext.setSession(profileService.loadSession(sessionId, timestamp));
+ if (eventsRequestContext.getSession() != null) {
+
+ sessionProfile = eventsRequestContext.getSession().getProfile();
+ boolean anonymousSessionProfile = sessionProfile.isAnonymousProfile();
+ if (!eventsRequestContext.getProfile().isAnonymousProfile() &&
+ !anonymousSessionProfile &&
+ !eventsRequestContext.getProfile().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
+ eventsRequestContext.setProfile(profileService.load(sessionProfile.getItemId()));
+ }
+
+ // Handle anonymous situation
+ Boolean requireAnonymousBrowsing = privacyService.isRequireAnonymousBrowsing(eventsRequestContext.getProfile());
+ 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(eventsRequestContext.getProfile());
+ eventsRequestContext.getSession().setProfile(sessionProfile);
+ eventsRequestContext.addChanges(EventService.SESSION_UPDATED);
+ } else if (!requireAnonymousBrowsing && anonymousSessionProfile) {
+ // User does not want to browse anonymously anymore, update the sessionProfile to real profile
+ sessionProfile = eventsRequestContext.getProfile();
+ eventsRequestContext.getSession().setProfile(sessionProfile);
+ eventsRequestContext.addChanges(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 = eventsRequestContext.getProfile();
+ if (!eventsRequestContext.getSession().getProfileId().equals(sessionProfile.getItemId())) {
+ eventsRequestContext.addChanges(EventService.SESSION_UPDATED);
+ }
+ eventsRequestContext.getSession().setProfile(sessionProfile);
+ }
+ }
+ }
+
+ // Try to create new session
+ if (eventsRequestContext.getSession() == null || invalidateSession) {
+ sessionProfile = privacyService.isRequireAnonymousBrowsing(eventsRequestContext.getProfile()) ?
+ privacyService.getAnonymousProfile(eventsRequestContext.getProfile()) : eventsRequestContext.getProfile();
+
+ if (StringUtils.isNotBlank(sessionId)) {
+ // Only save session and send event if a session id was provided, otherwise keep transient session
+ eventsRequestContext.setSession(new Session(sessionId, sessionProfile, timestamp, scope));
+ eventsRequestContext.addChanges(EventService.SESSION_UPDATED);
+
+ Event event = new Event("sessionCreated", eventsRequestContext.getSession(), eventsRequestContext.getProfile(),
+ scope, null, eventsRequestContext.getSession(), null, timestamp, false);
+ 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(),
+ eventsRequestContext.getProfile().getItemId(), eventsRequestContext.getSession().getItemId(), event.getTarget(), timestamp);
+ }
+ eventsRequestContext.addChanges(eventService.send(event));
+ }
+ }
+
+ // Handle new profile creation
+ if (profileCreated) {
+ eventsRequestContext.addChanges(EventService.PROFILE_UPDATED);
- String thirdPartyId = eventService
- .authenticateThirdPartyServer(((HttpServletRequest) request).getHeader("X-Unomi-Peer"), request.getRemoteAddr());
+ Event profileUpdated = new Event("profileUpdated", eventsRequestContext.getSession(), eventsRequestContext.getProfile(),
+ scope, null, eventsRequestContext.getProfile(), timestamp);
+ profileUpdated.setPersistent(false);
+ profileUpdated.getAttributes().put(Event.HTTP_REQUEST_ATTRIBUTE, request);
+ profileUpdated.getAttributes().put(Event.HTTP_RESPONSE_ATTRIBUTE, response);
+ profileUpdated.getAttributes().put(Event.CLIENT_ID_ATTRIBUTE, DEFAULT_CLIENT_ID);
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Received event {} for profile={} {} target={} timestamp={}", profileUpdated.getEventType(),
+ eventsRequestContext.getProfile().getItemId(),
+ " session=" + (eventsRequestContext.getSession() != null ? eventsRequestContext.getSession().getItemId() : null),
+ profileUpdated.getTarget(), timestamp);
+ }
+ eventsRequestContext.addChanges(eventService.send(profileUpdated));
+ }
+ }
+
+ return eventsRequestContext;
+ }
+
+ @Override
+ public EventsRequestContext performEventsRequest(List<Event> events, EventsRequestContext eventsRequestContext) {
+ List<String> filteredEventTypes = privacyService.getFilteredEventTypes(eventsRequestContext.getProfile());
+ String thirdPartyId = eventService.authenticateThirdPartyServer(eventsRequestContext.getRequest().getHeader("X-Unomi-Peer"),
+ eventsRequestContext.getRequest().getRemoteAddr());
- int changes = EventService.NO_CHANGE;
// execute provided events if any
- int processedEventsCnt = 0;
- if (events != null && !(profile instanceof Persona)) {
+ if (events != null && !(eventsRequestContext.getProfile() instanceof Persona)) {
+ // set Total items on context
+ eventsRequestContext.setTotalItems(events.size());
+
for (Event event : events) {
- processedEventsCnt++;
+ eventsRequestContext.setProcessedItems(eventsRequestContext.getProcessedItems() + 1);
+
if (event.getEventType() != null) {
- Event eventToSend = new Event(event.getEventType(), session, profile, event.getScope(), event.getSource(),
- event.getTarget(), event.getProperties(), timestamp, event.isPersistent());
+ Event eventToSend = new Event(event.getEventType(), eventsRequestContext.getSession(), eventsRequestContext.getProfile(), event.getScope(), event.getSource(),
+ event.getTarget(), event.getProperties(), eventsRequestContext.getTimestamp(), event.isPersistent());
eventToSend.setFlattenedProperties(event.getFlattenedProperties());
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.getScope(),
- event.getSource(), event.getTarget(), event.getProperties(), timestamp, event.isPersistent());
+ eventToSend = new Event(event.getItemId(), event.getEventType(), eventsRequestContext.getSession(), eventsRequestContext.getProfile(), event.getScope(),
+ event.getSource(), event.getTarget(), event.getProperties(), eventsRequestContext.getTimestamp(), event.isPersistent());
eventToSend.setFlattenedProperties(event.getFlattenedProperties());
}
if (filteredEventTypes != null && filteredEventTypes.contains(event.getEventType())) {
logger.debug("Profile is filtering event type {}", event.getEventType());
continue;
}
- if (profile.isAnonymousProfile()) {
+ if (eventsRequestContext.getProfile().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);
+ eventToSend.getAttributes().put(Event.HTTP_REQUEST_ATTRIBUTE, eventsRequestContext.getRequest());
+ eventToSend.getAttributes().put(Event.HTTP_RESPONSE_ATTRIBUTE, eventsRequestContext.getResponse());
+ logger.debug("Received event " + event.getEventType() + " for profile=" + eventsRequestContext.getProfile().getItemId() + " session=" + (
+ eventsRequestContext.getSession() != null ? eventsRequestContext.getSession().getItemId() : null) +
+ " target=" + event.getTarget() + " timestamp=" + eventsRequestContext.getTimestamp());
+ eventsRequestContext.addChanges(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 ((eventsRequestContext.getChanges() & EventService.PROFILE_UPDATED) == EventService.PROFILE_UPDATED) {
+ eventsRequestContext.setProfile(eventToSend.getProfile());
}
- if ((changes & EventService.ERROR) == EventService.ERROR) {
+ if ((eventsRequestContext.getChanges() & 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());
+ eventsRequestContext.setProcessedItems(eventsRequestContext.getProcessedItems() - 1);
+ logger.error("Error processing events. Total number of processed events: {}/{}", eventsRequestContext.getProcessedItems(), eventsRequestContext.getTotalItems());
break;
}
}
}
}
- return new Changes(changes, processedEventsCnt, profile);
+
+ return eventsRequestContext;
+ }
+
+ @Override
+ public void finalizeEventsRequest(EventsRequestContext eventsRequestContext, boolean crashOnError) {
+ // in case of changes on profile, persist the profile
+ if ((eventsRequestContext.getChanges() & EventService.PROFILE_UPDATED) == EventService.PROFILE_UPDATED) {
+ profileService.save(eventsRequestContext.getProfile());
+ }
+
+ // in case of changes on session, persist the session
+ if ((eventsRequestContext.getChanges() & EventService.SESSION_UPDATED) == EventService.SESSION_UPDATED && eventsRequestContext.getSession() != null) {
+ profileService.saveSession(eventsRequestContext.getSession());
+ }
+
+ // In case of error, return an error message
+ if ((eventsRequestContext.getChanges() & EventService.ERROR) == EventService.ERROR) {
+ if (crashOnError) {
+ String errorMessage = "Error processing events. Total number of processed events: " + eventsRequestContext.getProcessedItems() + "/"
+ + eventsRequestContext.getTotalItems();
+ throw new BadRequestException(errorMessage);
+ } else {
+ eventsRequestContext.getResponse().setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ // Set profile cookie
+ if (!(eventsRequestContext.getProfile() instanceof Persona)) {
+ eventsRequestContext.getResponse().setHeader("Set-Cookie",
+ HttpUtils.getProfileCookieString(eventsRequestContext.getProfile(), configSharingService, eventsRequestContext.getRequest().isSecure()));
+ }
+ }
+
+ private Profile createNewProfile(String existingProfileId, Date timestamp) {
+ Profile profile;
+ String profileId = existingProfileId;
+ if (profileId == null) {
+ profileId = UUID.randomUUID().toString();
+ }
+ profile = new Profile(profileId);
+ profile.setProperty("firstVisit", timestamp);
+ return profile;
}
}
diff --git a/rest/src/main/java/org/apache/unomi/utils/Changes.java b/rest/src/main/java/org/apache/unomi/utils/Changes.java
deleted file mode 100644
index b3334307d..000000000
--- a/rest/src/main/java/org/apache/unomi/utils/Changes.java
+++ /dev/null
@@ -1,57 +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.utils;
-
-import org.apache.unomi.api.Profile;
-
-/**
- * This class is a simple object to get the updated profile without the need of reloading it
- *
- * @author dgaillard
- */
-public class Changes {
- private int changeType;
- private int processedItems;
- private Profile profile;
-
- public Changes(int changeType, Profile profile) {
- this(changeType,0,profile);
- }
-
- public Changes(int changeType, int processedItems, Profile profile) {
- this.changeType = changeType;
- this.processedItems = processedItems;
- this.profile = profile;
- }
-
- public int getChangeType() {
- return changeType;
- }
-
- public void setChangeType(int changeType) {
- this.changeType = changeType;
- }
-
- public int getProcessedItems() {
- return processedItems;
- }
-
- public Profile getProfile() {
- return profile;
- }
-}
diff --git a/rest/src/main/java/org/apache/unomi/utils/EventsRequestContext.java b/rest/src/main/java/org/apache/unomi/utils/EventsRequestContext.java
new file mode 100644
index 000000000..188f42da2
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/utils/EventsRequestContext.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.utils;
+
+import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.Session;
+import org.apache.unomi.api.services.EventService;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Date;
+
+/**
+ * This is a bean that maintain the current situation during a request that contains events to be processed.
+ * It's in charge to hold an up to date Session + Profile for the current request, but also the status of the events executions:
+ * - changes
+ * - number of events processed
+ */
+public class EventsRequestContext {
+
+ private Date timestamp;
+ private Profile profile;
+ private Session session;
+ private HttpServletRequest request;
+ private HttpServletResponse response;
+ private int changes;
+ private int totalItems;
+ private int processedItems;
+
+ private EventsRequestContext() {
+ }
+
+ public EventsRequestContext(Date timestamp, Profile profile, Session session, HttpServletRequest request, HttpServletResponse response) {
+ this.timestamp = timestamp;
+ this.profile = profile;
+ this.session = session;
+ this.request = request;
+ this.response = response;
+ this.changes = EventService.NO_CHANGE;
+ this.totalItems = 0;
+ this.processedItems = 0;
+ }
+
+ public Date getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(Date timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public Profile getProfile() {
+ return profile;
+ }
+
+ public void setProfile(Profile profile) {
+ this.profile = profile;
+ }
+
+ public Session getSession() {
+ return session;
+ }
+
+ public void setSession(Session session) {
+ this.session = session;
+ }
+
+ public int getChanges() {
+ return changes;
+ }
+
+ public void addChanges(int changes) {
+ this.changes |= changes;
+ }
+
+ public int getTotalItems() {
+ return totalItems;
+ }
+
+ public void setTotalItems(int totalItems) {
+ this.totalItems = totalItems;
+ }
+
+ public int getProcessedItems() {
+ return processedItems;
+ }
+
+ public void setProcessedItems(int processedItems) {
+ this.processedItems = processedItems;
+ }
+
+ public HttpServletRequest getRequest() {
+ return request;
+ }
+
+ public void setRequest(HttpServletRequest request) {
+ this.request = request;
+ }
+
+ public HttpServletResponse getResponse() {
+ return response;
+ }
+
+ public void setResponse(HttpServletResponse response) {
+ this.response = response;
+ }
+}