content: /*
   *  Copyright (C) 2015, 2019 Stichting Akvo (Akvo Foundation)
   *
   *  This file is part of Akvo FLOW.
   *
   *  Akvo FLOW is free software: you can redistribute it and modify it under the terms of
   *  the GNU Affero General Public License (AGPL) as published by the Free Software Foundation,
   *  either version 3 of the License or any later version.
   *
   *  Akvo FLOW is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
   *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
   *  See the GNU Affero General Public License included below for more details.
   *
   *  The full license text can also be seen at <http://www.gnu.org/licenses/agpl.html>.
   */
  
  package org.akvo.flow.events;
  
  import java.io.IOException;
  import java.net.HttpURLConnection;
  import java.net.URL;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  
  import org.akvo.flow.rest.security.user.GaeUser;
  import org.akvo.flow.util.FlowJsonObjectWriter;
  
  import com.gallatinsystems.common.Constants;
  import com.gallatinsystems.survey.domain.SurveyGroup;
  import com.google.appengine.api.datastore.Entity;
  import com.google.appengine.api.datastore.Text;
  
  public class EventUtils {
  
      private static Logger log = Logger.getLogger(EventUtils.class.getName());
  
      public enum EventSourceType {
          USER, DEVICE, SENSOR, WEBFORM, API, UNKNOWN, SYSTEM
      };
  
      public enum EntityType {
          SURVEY_GROUP, FORM, QUESTION_GROUP, QUESTION, DATA_POINT, FORM_INSTANCE, ANSWER, DEVICE_FILE,
          USER, USER_AUTHORIZATION, USER_ROLE
      };
  
      // names of kinds in Google App Engine
      static public class Kind {
          public static final String SURVEY_GROUP = "SurveyGroup";
          public static final String FORM = "Survey";
          public static final String QUESTION_GROUP = "QuestionGroup";
          public static final String QUESTION = "Question";
          public static final String DATA_POINT = "SurveyedLocale";
          public static final String FORM_INSTANCE = "SurveyInstance";
          public static final String ANSWER = "QuestionAnswerStore";
          public static final String DEVICE_FILE = "DeviceFiles";
          public static final String USER = "User";
          public static final String USER_AUTHORIZATION = "UserAuthorization";
          public static final String USER_ROLE = "UserRole";
      }
  
      // How we name the actions
      public static class Action {
          public static final String SURVEY_GROUP = "surveyGroup";
          public static final String FORM = "form";
          public static final String QUESTION_GROUP = "questionGroup";
          public static final String QUESTION = "question";
          public static final String DATA_POINT = "dataPoint";
          public static final String FORM_INSTANCE = "formInstance";
          public static final String ANSWER = "answer";
          public static final String DEVICE_FILE = "deviceFile";
  
          public static final String USER = "user";
          public static final String USER_AUTHORIZATION = "userAuthorization";
          public static final String USER_ROLE = "userRole";
  
          public static final String DELETED = "Deleted";
          public static final String CREATED = "Created";
          public static final String UPDATED = "Updated";
  
          public static final String UNIFIED_LOG_NOTIFIED = "unifiedLogNotified";
  
      }
  
      static class Key {
          public static final String ID = "id";
          public static final String EMAIL = "email";
          public static final String DISPLAY_TEXT = "displayText";
          public static final String NAME = "name";
          public static final String DESCRIPTION = "description";
          public static final String TIMESTAMP = "timestamp";
          public static final String SOURCE = "source";
          public static final String TYPE = "type";
          public static final String ORDER = "order";
          public static final String LAT = "lat";
          public static final String LON = "lon";
          public static final String ORG_ID = "orgId";
          public static final String SURVEY_ID = "surveyId";
          public static final String QUESTION_TYPE = "questionType";
          public static final String ANSWER_TYPE = "answerType";
          public static final String PARENT_ID = "parentId";
          public static final String FORM_ID = "formId";
          public static final String QUESTION_GROUP_ID = "questionGroupId";
          public static final String QUESTION_ID = "questionId";
          public static final String FORM_INSTANCE_ID = "formInstanceId";
          public static final String ANSWER_ID = "answerId";
          public static final String DATA_POINT_ID = "dataPointId";
          public static final String SUBMITTER_NAME = "submitterName";
          public static final String COLLECTION_DATE = "collectionDate";
          public static final String FORM_VERSION = "formVersion";
          public static final String SURVEYAL_TIME = "surveyalTime";
          public static final String PUBLIC = "public";
          public static final String VALUE = "value";
          public static final String IDENTIFIER = "identifier";
          public static final String SURVEY_GROUP_TYPE = "surveyGroupType";
          public static final String APP_ID = "orgId";
          public static final String URL = "url";
          public static final String ITERATION = "iteration";
          public static final String STATUS = "status";
          public static final String VERSION = "version";
      }
  
      public static class Prop {
          public static final String SURVEY_INSTANCE_ID = "surveyInstanceId";
          public static final String TYPE = "type";
          public static final String VALUE = "value";
          public static final String VALUE_TEXT = "valueText";
          public static final String SURVEY_ID = "surveyId";
          public static final String SURVEYED_LOCALE_ID = "surveyedLocaleId";
          public static final String COLLECTION_DATE = "collectionDate";
          public static final String FORM_VERSION = "formVersion";
          public static final String SURVEYAL_TIME = "surveyalTime";
          public static final String IDENTIFIER = "identifier";
          public static final String LATITUDE = "latitude";
          public static final String LONGITUDE = "longitude";
          public static final String DISPLAY_NAME = "displayName";
          public static final String NAME = "name";
          public static final String PARENT_ID = "parentId";
          public static final String DESCRIPTION = "description";
          public static final String DESC = "desc";
          public static final String SURVEY_GROUP_ID = "surveyGroupId";
          public static final String ORDER = "order";
          public static final String TEXT = "text";
          public static final String QUESTION_ID = "questionID";
          public static final String QUESTION_IDENTIFIER = "questionId";
          public static final String QUESTION_GROUP_ID = "questionGroupId";
          public static final String PROJECT_TYPE = "projectType";
          public static final String LAST_UPDATE_DATE_TIME = "lastUpdateDateTime";
          public static final String CREATED_DATE_TIME = "createdDateTime";
          public static final String ALIAS = "alias";
          public static final String EVENT_NOTIFICATION = "eventNotification";
          public static final String ENABLE_CHANGE_EVENTS = "enableChangeEvents";
          public static final String ITERATION = "iteration";
          public static final String STATUS = "status";
          public static final String VERSION = "version";
      }
  
      public static final String SURVEY_GROUP_TYPE_SURVEY = "SURVEY";
      public static final String SURVEY_GROUP_TYPE_FOLDER = "FOLDER";
  
      public static class EventTypes {
          public final EntityType type;
          public final String action;
  
          public EventTypes(EntityType type, String action) {
              this.type = type;
              this.action = action;
          }
      }
  
      public static EventTypes getEventAndActionType(String kindName) {
          switch (kindName) {
              case Kind.ANSWER:
                  return new EventTypes(EntityType.ANSWER, Action.ANSWER);
              case Kind.FORM_INSTANCE:
                  return new EventTypes(EntityType.FORM_INSTANCE, Action.FORM_INSTANCE);
              case Kind.DATA_POINT:
                  return new EventTypes(EntityType.DATA_POINT, Action.DATA_POINT);
              case Kind.SURVEY_GROUP:
                  return new EventTypes(EntityType.SURVEY_GROUP, Action.SURVEY_GROUP);
              case Kind.FORM:
                  return new EventTypes(EntityType.FORM, Action.FORM);
              case Kind.QUESTION_GROUP:
                  return new EventTypes(EntityType.QUESTION_GROUP, Action.QUESTION_GROUP);
              case Kind.QUESTION:
                  return new EventTypes(EntityType.QUESTION, Action.QUESTION);
              case Kind.DEVICE_FILE:
                  return new EventTypes(EntityType.DEVICE_FILE, Action.DEVICE_FILE);
              case Kind.USER:
                  return new EventTypes(EntityType.USER, Action.USER);
              case Kind.USER_AUTHORIZATION:
                  return new EventTypes(EntityType.USER_AUTHORIZATION, Action.USER_AUTHORIZATION);
              case Kind.USER_ROLE:
                  return new EventTypes(EntityType.USER_ROLE, Action.USER_ROLE);
          }
          return null;
      }
  
      private static Map<String, Object> addProperty(String key, Object val,
              Map<String, Object> data) {
          if (val != null) {
              data.put(key, val);
          }
          return data;
      }
  
      private static Map<String, Object> addProperty(String key, Object val,
              Object defaultVal, Map<String, Object> data) {
          if (val != null) {
              data.put(key, val);
          } else {
              data.put(key, defaultVal);
          }
          return data;
      }
  
      public static Map<String, Object> populateEntityProperties(EntityType type, Entity e,
              Map<String, Object> data) {
          switch (type) {
              case ANSWER:
                  addProperty(Key.FORM_INSTANCE_ID, e.getProperty(Prop.SURVEY_INSTANCE_ID), data);
                  addProperty(Key.ANSWER_TYPE, e.getProperty(Prop.TYPE), data);
                  addProperty(Key.QUESTION_ID,
                          Long.valueOf((String) e.getProperty(Prop.QUESTION_ID)), data);
  
                  Object value = e.getProperty(Prop.VALUE);
                  if (value == null) {
                      Object text = e.getProperty(Prop.VALUE_TEXT);
                      if (text != null && text instanceof Text) {
                          value = ((Text) text).getValue();
                      }
                  }
  
                  addProperty(Key.VALUE, value, data);
                  addProperty(Key.FORM_ID, e.getProperty(Prop.SURVEY_ID), data);
                  addProperty(Key.ITERATION, e.getProperty(Prop.ITERATION), data);
                  break;
              case FORM_INSTANCE:
                  addProperty(Key.FORM_ID, e.getProperty(Prop.SURVEY_ID), data);
                  addProperty(Key.DATA_POINT_ID, e.getProperty(Prop.SURVEYED_LOCALE_ID), data);
                  addProperty(Key.COLLECTION_DATE, e.getProperty(Prop.COLLECTION_DATE), data);
                  addProperty(Key.FORM_VERSION, e.getProperty(Prop.FORM_VERSION), data);
                  addProperty(Key.SURVEYAL_TIME, e.getProperty(Prop.SURVEYAL_TIME), data);
                  break;
              case DATA_POINT:
                  addProperty(Key.IDENTIFIER, e.getProperty(Prop.IDENTIFIER), data);
                  addProperty(Key.LAT, e.getProperty(Prop.LATITUDE), data);
                  addProperty(Key.LON, e.getProperty(Prop.LONGITUDE), data);
                  addProperty(Key.NAME, e.getProperty(Prop.DISPLAY_NAME), data);
                  addProperty(Key.SURVEY_ID, e.getProperty(Prop.SURVEY_GROUP_ID), data);
                  break;
              case SURVEY_GROUP:
                  addProperty(Key.NAME, e.getProperty(Prop.NAME), "<name missing>", data);
                  addProperty(Key.PARENT_ID, e.getProperty(Prop.PARENT_ID), data);
  
                  if (e.getProperty(Prop.PROJECT_TYPE).toString()
                          .equals(SurveyGroup.ProjectType.PROJECT.toString())) {
                      data.put(Key.SURVEY_GROUP_TYPE, SURVEY_GROUP_TYPE_SURVEY);
                  } else if (e.getProperty(Prop.PROJECT_TYPE).toString()
                          .equals(SurveyGroup.ProjectType.PROJECT_FOLDER.toString())) {
                      data.put(Key.SURVEY_GROUP_TYPE, SURVEY_GROUP_TYPE_FOLDER);
                  }
  
                  addProperty(Key.DESCRIPTION, e.getProperty(Prop.DESCRIPTION), data);
                  break;
              case FORM:
                  addProperty(Key.NAME, e.getProperty(Prop.NAME), data);
                  addProperty(Key.DESCRIPTION, e.getProperty(Prop.DESC), data);
                  addProperty(Key.SURVEY_ID, e.getProperty(Prop.SURVEY_GROUP_ID), data);
                  addProperty(Key.STATUS, e.getProperty(Prop.STATUS), data);
                  addProperty(Key.VERSION, e.getProperty(Prop.VERSION), data);
                  break;
              case QUESTION_GROUP:
                  addProperty(Key.NAME, e.getProperty(Prop.NAME), "<name missing>", data);
                  addProperty(Key.ORDER, e.getProperty(Prop.ORDER), data);
                  addProperty(Key.FORM_ID, e.getProperty(Prop.SURVEY_ID), data);
                  break;
              case QUESTION:
                  addProperty(Key.DISPLAY_TEXT, e.getProperty(Prop.TEXT), data);
                  addProperty(Key.IDENTIFIER, e.getProperty(Prop.QUESTION_IDENTIFIER), data);
                  addProperty(Key.QUESTION_GROUP_ID, e.getProperty(Prop.QUESTION_GROUP_ID),
                          data);
                  addProperty(Key.FORM_ID, e.getProperty(Prop.SURVEY_ID), data);
                  addProperty(Key.QUESTION_TYPE, e.getProperty(Prop.TYPE), data);
                  break;
              case DEVICE_FILE:
                  addProperty("uri", e.getProperty("URI"), data);
                  addProperty("checksum", e.getProperty("checksum"), data);
                  addProperty("phoneNumber", e.getProperty("phoneNumber"), data);
                  addProperty("imei", e.getProperty("imei"), data);
                  break;
              case USER:
                  addProperty("userName", e.getProperty("userName"), data);
                  addProperty("emailAddress", e.getProperty("emailAddress"), data);
                  addProperty("superAdmin", e.getProperty("superAdmin"), data);
                  addProperty("permissionList", e.getProperty("permissionList"), data);
                  addProperty("language", e.getProperty("language"), data);
                  break;
              case USER_AUTHORIZATION:
                  addProperty("securedObjectId", e.getProperty("securedObjectId"), data);
                  addProperty("userId", e.getProperty("userId"), data);
                  addProperty("roleId", e.getProperty("roleId"), data);
                  break;
              case USER_ROLE:
                  addProperty("permissions", e.getProperty("permissions"), data);
                  addProperty("name", e.getProperty("name"), data);
                  break;
          }
          return data;
      }
  
      public static Map<String, Object> newEvent(String orgId, String eventType,
              Map<String, Object> entity,
              Map<String, Object> context) throws AssertionError {
  
          assert orgId != null : "orgId is required";
          assert eventType != null : "eventType is required";
          assert entity != null : "entity is required";
          assert context != null : "context is required";
  
          Map<String, Object> result = new HashMap<String, Object>();
  
          result.put("orgId", orgId);
          result.put("eventType", eventType);
          result.put("entity", entity);
          result.put("context", context);
  
          return result;
      }
  
      public static Map<String, Object> newSource(Object principal) {
          Map<String, Object> source = new HashMap<String, Object>();
  
          if (principal instanceof String) {
              // Tasks related events get an "anonymousUser" as principal
              source.put(Key.TYPE, EventSourceType.SYSTEM);
          } else if (principal instanceof GaeUser) {
              GaeUser usr = (GaeUser) principal;
              source.put(Key.TYPE, EventSourceType.USER);
              source.put(Key.EMAIL, usr.getEmail());
              source.put(Key.ID, usr.getUserId());
          } else {
              log.log(Level.WARNING, "Unable to identify source from authentication principal: "
                      + principal.toString());
          }
  
          return source;
      }
  
      public static Map<String, Object> newContext(Date timestamp, Map<String, Object> source) {
          Map<String, Object> context = new HashMap<String, Object>();
          context.put(Key.TIMESTAMP, timestamp);
          context.put(Key.SOURCE, source);
          return context;
  
      }
  
      public static Map<String, Object> newEntity(EntityType type, Long id) {
          Map<String, Object> entity = new HashMap<String, Object>();
          entity.put(Key.TYPE, type);
          entity.put(Key.ID, id);
          return entity;
      }
  
      public static Entity createEventLogEntity(Map<String, Object> event, Date timestamp) throws IOException {
          Entity entity = new Entity("EventQueue");
          entity.setProperty("createdDateTime", timestamp);
          entity.setProperty("lastUpdateDateTime", timestamp);
  
          FlowJsonObjectWriter writer = new FlowJsonObjectWriter();
          String payload = writer.writeAsString(event);
  
          if (payload.length() > Constants.MAX_LENGTH) {
              entity.setProperty("payloadText", new Text(payload));
          } else {
              entity.setProperty("payload", payload);
          }
          return entity;
      }
      public static void sendEvents(String urlString, List<Map<String, Object>> events)
              throws IOException {
          URL url = new URL(urlString);
          HttpURLConnection connection = (HttpURLConnection) url
                  .openConnection();
  
          connection.setDoOutput(true);
          connection.setRequestMethod("POST");
          connection.setRequestProperty("Content-Type",
                  "application/json");
  
          FlowJsonObjectWriter writer = new FlowJsonObjectWriter();
          writer.writeValue(connection.getOutputStream(), events);
  
          System.out.println("    " + connection.getResponseCode());
  
          connection.disconnect();
      }
  }
  