private static TimelineEntity maskFields(TimelineEntity entity, EnumSet<Field> fields) {
   // Conceal the fields that are not going to be exposed
   TimelineEntity entityToReturn = new TimelineEntity();
   entityToReturn.setEntityId(entity.getEntityId());
   entityToReturn.setEntityType(entity.getEntityType());
   entityToReturn.setStartTime(entity.getStartTime());
   // Deep copy
   if (fields.contains(Field.EVENTS)) {
     entityToReturn.addEvents(entity.getEvents());
   } else if (fields.contains(Field.LAST_EVENT_ONLY)) {
     entityToReturn.addEvent(entity.getEvents().get(0));
   } else {
     entityToReturn.setEvents(null);
   }
   if (fields.contains(Field.RELATED_ENTITIES)) {
     entityToReturn.addRelatedEntities(entity.getRelatedEntities());
   } else {
     entityToReturn.setRelatedEntities(null);
   }
   if (fields.contains(Field.PRIMARY_FILTERS)) {
     entityToReturn.addPrimaryFilters(entity.getPrimaryFilters());
   } else {
     entityToReturn.setPrimaryFilters(null);
   }
   if (fields.contains(Field.OTHER_INFO)) {
     entityToReturn.addOtherInfo(entity.getOtherInfo());
   } else {
     entityToReturn.setOtherInfo(null);
   }
   return entityToReturn;
 }
  @Override
  public TimelineEntities getEntities(
      String entityType,
      Long limit,
      Long windowStart,
      Long windowEnd,
      String fromId,
      Long fromTs,
      NameValuePair primaryFilter,
      Collection<NameValuePair> secondaryFilters,
      EnumSet<Field> fields) {
    if (limit == null) {
      limit = DEFAULT_LIMIT;
    }
    if (windowStart == null) {
      windowStart = Long.MIN_VALUE;
    }
    if (windowEnd == null) {
      windowEnd = Long.MAX_VALUE;
    }
    if (fields == null) {
      fields = EnumSet.allOf(Field.class);
    }

    Iterator<TimelineEntity> entityIterator = null;
    if (fromId != null) {
      TimelineEntity firstEntity = entities.get(new EntityIdentifier(fromId, entityType));
      if (firstEntity == null) {
        return new TimelineEntities();
      } else {
        entityIterator =
            new TreeSet<TimelineEntity>(entities.values()).tailSet(firstEntity, true).iterator();
      }
    }
    if (entityIterator == null) {
      entityIterator = new PriorityQueue<TimelineEntity>(entities.values()).iterator();
    }

    List<TimelineEntity> entitiesSelected = new ArrayList<TimelineEntity>();
    while (entityIterator.hasNext()) {
      TimelineEntity entity = entityIterator.next();
      if (entitiesSelected.size() >= limit) {
        break;
      }
      if (!entity.getEntityType().equals(entityType)) {
        continue;
      }
      if (entity.getStartTime() <= windowStart) {
        continue;
      }
      if (entity.getStartTime() > windowEnd) {
        continue;
      }
      if (fromTs != null
          && entityInsertTimes.get(
                  new EntityIdentifier(entity.getEntityId(), entity.getEntityType()))
              > fromTs) {
        continue;
      }
      if (primaryFilter != null && !matchPrimaryFilter(entity.getPrimaryFilters(), primaryFilter)) {
        continue;
      }
      if (secondaryFilters != null) { // AND logic
        boolean flag = true;
        for (NameValuePair secondaryFilter : secondaryFilters) {
          if (secondaryFilter != null
              && !matchPrimaryFilter(entity.getPrimaryFilters(), secondaryFilter)
              && !matchFilter(entity.getOtherInfo(), secondaryFilter)) {
            flag = false;
            break;
          }
        }
        if (!flag) {
          continue;
        }
      }
      entitiesSelected.add(entity);
    }
    List<TimelineEntity> entitiesToReturn = new ArrayList<TimelineEntity>();
    for (TimelineEntity entitySelected : entitiesSelected) {
      entitiesToReturn.add(maskFields(entitySelected, fields));
    }
    Collections.sort(entitiesToReturn);
    TimelineEntities entitiesWrapper = new TimelineEntities();
    entitiesWrapper.setEntities(entitiesToReturn);
    return entitiesWrapper;
  }
 @Override
 public TimelinePutResponse put(TimelineEntities data) {
   TimelinePutResponse response = new TimelinePutResponse();
   for (TimelineEntity entity : data.getEntities()) {
     EntityIdentifier entityId =
         new EntityIdentifier(entity.getEntityId(), entity.getEntityType());
     // store entity info in memory
     TimelineEntity existingEntity = entities.get(entityId);
     if (existingEntity == null) {
       existingEntity = new TimelineEntity();
       existingEntity.setEntityId(entity.getEntityId());
       existingEntity.setEntityType(entity.getEntityType());
       existingEntity.setStartTime(entity.getStartTime());
       entities.put(entityId, existingEntity);
       entityInsertTimes.put(entityId, System.currentTimeMillis());
     }
     if (entity.getEvents() != null) {
       if (existingEntity.getEvents() == null) {
         existingEntity.setEvents(entity.getEvents());
       } else {
         existingEntity.addEvents(entity.getEvents());
       }
       Collections.sort(existingEntity.getEvents());
     }
     // check startTime
     if (existingEntity.getStartTime() == null) {
       if (existingEntity.getEvents() == null || existingEntity.getEvents().isEmpty()) {
         TimelinePutError error = new TimelinePutError();
         error.setEntityId(entityId.getId());
         error.setEntityType(entityId.getType());
         error.setErrorCode(TimelinePutError.NO_START_TIME);
         response.addError(error);
         entities.remove(entityId);
         entityInsertTimes.remove(entityId);
         continue;
       } else {
         Long min = Long.MAX_VALUE;
         for (TimelineEvent e : entity.getEvents()) {
           if (min > e.getTimestamp()) {
             min = e.getTimestamp();
           }
         }
         existingEntity.setStartTime(min);
       }
     }
     if (entity.getPrimaryFilters() != null) {
       if (existingEntity.getPrimaryFilters() == null) {
         existingEntity.setPrimaryFilters(new HashMap<String, Set<Object>>());
       }
       for (Entry<String, Set<Object>> pf : entity.getPrimaryFilters().entrySet()) {
         for (Object pfo : pf.getValue()) {
           existingEntity.addPrimaryFilter(pf.getKey(), maybeConvert(pfo));
         }
       }
     }
     if (entity.getOtherInfo() != null) {
       if (existingEntity.getOtherInfo() == null) {
         existingEntity.setOtherInfo(new HashMap<String, Object>());
       }
       for (Entry<String, Object> info : entity.getOtherInfo().entrySet()) {
         existingEntity.addOtherInfo(info.getKey(), maybeConvert(info.getValue()));
       }
     }
     // relate it to other entities
     if (entity.getRelatedEntities() == null) {
       continue;
     }
     for (Map.Entry<String, Set<String>> partRelatedEntities :
         entity.getRelatedEntities().entrySet()) {
       if (partRelatedEntities == null) {
         continue;
       }
       for (String idStr : partRelatedEntities.getValue()) {
         EntityIdentifier relatedEntityId =
             new EntityIdentifier(idStr, partRelatedEntities.getKey());
         TimelineEntity relatedEntity = entities.get(relatedEntityId);
         if (relatedEntity != null) {
           relatedEntity.addRelatedEntity(
               existingEntity.getEntityType(), existingEntity.getEntityId());
         } else {
           relatedEntity = new TimelineEntity();
           relatedEntity.setEntityId(relatedEntityId.getId());
           relatedEntity.setEntityType(relatedEntityId.getType());
           relatedEntity.setStartTime(existingEntity.getStartTime());
           relatedEntity.addRelatedEntity(
               existingEntity.getEntityType(), existingEntity.getEntityId());
           entities.put(relatedEntityId, relatedEntity);
           entityInsertTimes.put(relatedEntityId, System.currentTimeMillis());
         }
       }
     }
   }
   return response;
 }