package org.rakam.plugin.user;
import com.facebook.presto.sql.tree.Expression;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.annotations.VisibleForTesting;
import org.rakam.collection.SchemaField;
import org.rakam.report.QueryExecution;
import org.rakam.report.QueryResult;
import org.rakam.server.http.annotations.ApiParam;
import org.rakam.util.RakamException;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
public abstract class AbstractUserService {
private final UserStorage storage;
public AbstractUserService(UserStorage storage) {
this.storage = storage;
}
public Object create(String project, Object id, ObjectNode properties) {
return storage.create(project, id, properties);
}
public List<Object> batchCreate(String project, List<User> users) {
return storage.batchCreate(project, users);
}
@VisibleForTesting
public void dropProject(String project) {
storage.dropProjectIfExists(project);
}
public void createProject(String project, boolean userIdIsNumeric) {
storage.createProjectIfNotExists(project, userIdIsNumeric);
}
public List<SchemaField> getMetadata(String project) {
return storage.getMetadata(project);
}
public CompletableFuture<QueryResult> searchUsers(String project, List<String> columns, Expression filterExpression, List<UserStorage.EventFilter> eventFilter, UserStorage.Sorting sorting, int limit, String offset) {
return storage.searchUsers(project, columns, filterExpression, eventFilter, sorting, limit, offset);
}
public void createSegment(String project, String name, String tableName, Expression filterExpression, List<UserStorage.EventFilter> eventFilter, Duration interval)
throws RakamException
{
if(filterExpression == null && (eventFilter == null || eventFilter.isEmpty())) {
throw new RakamException("At least one filter is required.", BAD_REQUEST);
}
storage.createSegment(project, name, tableName, filterExpression, eventFilter, interval);
}
public CompletableFuture<User> getUser(String project, Object user) {
return storage.getUser(project, user);
}
public void setUserProperties(String project, Object user, ObjectNode properties) {
storage.setUserProperties(project, user, properties);
}
public void setUserPropertiesOnce(String project, Object user, ObjectNode properties) {
storage.setUserPropertiesOnce(project, user, properties);
}
public abstract CompletableFuture<List<CollectionEvent>> getEvents(String project, String user, Optional<List<String>> properties, int limit, Instant beforeThisTime);
public void incrementProperty(String project, Object user, String property, double value) {
storage.incrementProperty(project, user, property, value);
}
public void unsetProperties(String project, Object user, List<String> properties) {
storage.unsetProperties(project, user, properties);
}
public abstract void merge(String project, Object user, Object anonymousId, Instant createdAt, Instant mergedAt);
public abstract QueryExecution preCalculate(String project, PreCalculateQuery query);
public void batch(String project, List<? extends ISingleUserBatchOperation> batchUserOperations) {
storage.batch(project, batchUserOperations);
}
public static class CollectionEvent {
public final String collection;
public final Map<String, Object> properties;
@JsonCreator
public CollectionEvent(@JsonProperty("collection") String collection,
@JsonProperty("properties") Map<String, Object> properties) {
this.properties = properties;
this.collection = collection;
}
}
public static class PreCalculateQuery {
public final String collection;
public final String dimension;
public PreCalculateQuery(@ApiParam(value = "collection", required = false) String collection,
@ApiParam(value = "dimension", required = false) String dimension) {
this.collection = collection;
this.dimension = dimension;
}
}
public static class PreCalculatedTable {
public final String name;
public final String tableName;
public PreCalculatedTable(String name, String tableName) {
this.name = name;
this.tableName = tableName;
}
}
public static class SingleUserBatchOperationRequest
{
public final Object id;
public final User.UserContext api;
public final List<SingleUserBatchOperations> data;
@JsonCreator
public SingleUserBatchOperationRequest(
@ApiParam("id") Object id,
@ApiParam("api") User.UserContext api,
@ApiParam("data") List<SingleUserBatchOperations> data)
{
this.id = id;
this.api = api;
this.data = data;
// non-static inner classes doesn't work with Jackson
// so we pass the outer variable manually.
data.forEach(op -> op.setUser(id));
}
public static class SingleUserBatchOperations
implements ISingleUserBatchOperation
{
public Object user;
@JsonProperty("set_properties") public final ObjectNode setProperties;
@JsonProperty("set_properties_once") public final ObjectNode setPropertiesOnce;
@JsonProperty("increment_properties") public final Map<String, Double> incrementProperties;
@JsonProperty("unset_properties") public final List<String> unsetProperties;
@JsonProperty("time") public final Long time;
@JsonCreator
public SingleUserBatchOperations(
@ApiParam(value = "set_properties", required = false) ObjectNode setProperties,
@ApiParam(value = "set_properties_once", required = false) ObjectNode setPropertiesOnce,
@ApiParam(value = "increment_properties", required = false) Map<String, Double> incrementProperties,
@ApiParam(value = "unset_properties", required = false) List<String> unsetProperties,
@ApiParam("time") Long time)
{
this.setProperties = setProperties;
this.setPropertiesOnce = setPropertiesOnce;
this.incrementProperties = incrementProperties;
this.unsetProperties = unsetProperties;
this.time = time;
}
@Override
public ObjectNode getSetProperties()
{
return setProperties;
}
@Override
public ObjectNode getSetPropertiesOnce()
{
return setPropertiesOnce;
}
@Override
public Map<String, Double> getIncrementProperties()
{
return incrementProperties;
}
@Override
public List<String> getUnsetProperties()
{
return unsetProperties;
}
@Override
public Long getTime()
{
return time;
}
@Override
public Object getUser() {
return user;
}
private void setUser(Object user) {
this.user = user;
}
}
}
public static class BatchUserOperationRequest
{
public final User.UserContext api;
public final List<BatchUserOperations> data;
@JsonCreator
public BatchUserOperationRequest(
@ApiParam("api") User.UserContext api,
@ApiParam("data") List<BatchUserOperations> data)
{
this.api = api;
this.data = data;
}
public static class BatchUserOperations implements ISingleUserBatchOperation
{
@JsonProperty("id") public Object id;
@JsonProperty(value = "set_properties") private final ObjectNode setProperties;
@JsonProperty(value = "set_properties_once") private final ObjectNode setPropertiesOnce;
@JsonProperty(value = "increment_properties") private final Map<String, Double> incrementProperties;
@JsonProperty(value = "unset_properties") private final List<String> unsetProperties;
@JsonProperty(value = "time") private Long time;
@JsonCreator
public BatchUserOperations(
@ApiParam("id") Object id,
@ApiParam(value = "set_properties", required = false) ObjectNode setProperties,
@ApiParam(value = "set_properties_once", required = false) ObjectNode setPropertiesOnce,
@ApiParam(value = "increment_properties", required = false) Map<String, Double> incrementProperties,
@ApiParam(value = "unset_properties", required = false) List<String> unsetProperties,
@ApiParam(value = "time", required = false) Long time)
{
this.id = id;
this.setProperties = setProperties;
this.setPropertiesOnce = setPropertiesOnce;
this.incrementProperties = incrementProperties;
this.unsetProperties = unsetProperties;
this.time = time;
}
@JsonIgnore
public Object getUser()
{
return id;
}
@Override
public ObjectNode getSetProperties()
{
return setProperties;
}
@Override
public ObjectNode getSetPropertiesOnce()
{
return setPropertiesOnce;
}
@Override
public Map<String, Double> getIncrementProperties()
{
return incrementProperties;
}
@Override
public List<String> getUnsetProperties()
{
return unsetProperties;
}
@Override
public Long getTime()
{
return time;
}
}
}
}