blob: ace630b3f5bddc403385d5579186be020226126a [file] [log] [blame]
/*
* 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.service.impl;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
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.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.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;
import java.util.stream.Collectors;
@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
private ConfigSharingService configSharingService;
@Reference
private PrivacyService privacyService;
@Reference
private EventService eventService;
@Reference
private ProfileService profileService;
@Reference
SchemaService schemaService;
@Override
public String getProfileIdCookieValue(HttpServletRequest httpServletRequest) {
String cookieProfileId = null;
Cookie[] cookies = httpServletRequest.getCookies();
if (cookies != null) {
final Object profileIdCookieName = configSharingService.getProperty("profileIdCookieName");
for (Cookie cookie : cookies) {
if (profileIdCookieName.equals(cookie.getName())) {
String profileIdJSON = JsonNodeFactory.instance.objectNode().put("profileId", cookie.getValue()).toString();
if (!schemaService.isValid(profileIdJSON, "https://unomi.apache.org/schemas/json/rest/requestIds/1-0-0")) {
throw new InvalidRequestException("Invalid profile ID format in cookie", "Invalid received data");
}
cookieProfileId = cookie.getValue();
}
}
}
return cookieProfileId;
}
@Override
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
Session session = new Session(sessionId, sessionProfile, timestamp, scope);
eventsRequestContext.setSession(session);
eventsRequestContext.setNewSession(true);
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);
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());
// execute provided events if any
if (events != null && !(eventsRequestContext.getProfile() instanceof Persona)) {
// set Total items on context
eventsRequestContext.setTotalItems(events.size());
for (Event event : events) {
eventsRequestContext.setProcessedItems(eventsRequestContext.getProcessedItems() + 1);
if (event.getEventType() != null) {
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(), 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 (eventsRequestContext.getProfile().isAnonymousProfile()) {
// Do not keep track of profile in event
eventToSend.setProfileId(null);
}
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 ((eventsRequestContext.getChanges() & EventService.PROFILE_UPDATED) == EventService.PROFILE_UPDATED) {
eventsRequestContext.setProfile(eventToSend.getProfile());
}
if (eventsRequestContext.isNewSession()) {
eventsRequestContext.getSession().getOriginEventIds().add(eventToSend.getItemId());
eventsRequestContext.getSession().getOriginEventTypes().add(eventToSend.getEventType());
}
if ((eventsRequestContext.getChanges() & EventService.ERROR) == EventService.ERROR) {
//Don't count the event that failed
eventsRequestContext.setProcessedItems(eventsRequestContext.getProcessedItems() - 1);
logger.error("Error processing events. Total number of processed events: {}/{}", eventsRequestContext.getProcessedItems(), eventsRequestContext.getTotalItems());
break;
}
}
}
}
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;
}
}