@Override
  public void handle(HttpServletRequest request, HttpServletResponse response, HandlerChain chain)
      throws Exception {
    long totalStartTime = System.nanoTime();
    final String query;
    final String filter = getRequiredParameter(request, "filter");
    final int offset = (int) getOptionalParameterLong(request, "offset", 0);
    final int size = (int) getOptionalParameterLong(request, "size", DEFAULT_RESULT_COUNT);
    final String conceptType = getOptionalParameter(request, "conceptType");
    final String getLeafNodes = getOptionalParameter(request, "leafNodes");
    final String relatedToVertexId = getOptionalParameter(request, "relatedToVertexId");
    if (relatedToVertexId == null) {
      query = getRequiredParameter(request, "q");
    } else {
      query = getOptionalParameter(request, "q");
    }

    long startTime = System.nanoTime();

    User user = getUser(request);
    Authorizations authorizations = getAuthorizations(request, user);
    String workspaceId = getActiveWorkspaceId(request);
    ModelUserContext modelUserContext =
        userRepository.getModelUserContext(authorizations, workspaceId);

    JSONArray filterJson = new JSONArray(filter);

    ontologyRepository.resolvePropertyIds(filterJson);

    graph.flush();

    LOGGER.debug("search %s\n%s", query, filterJson.toString(2));

    Query graphQuery;
    if (relatedToVertexId == null) {
      graphQuery = graph.query(query, authorizations);
    } else if (query == null || StringUtils.isBlank(query)) {
      graphQuery = graph.getVertex(relatedToVertexId, authorizations).query(authorizations);
    } else {
      graphQuery = graph.getVertex(relatedToVertexId, authorizations).query(query, authorizations);
    }

    for (int i = 0; i < filterJson.length(); i++) {
      JSONObject obj = filterJson.getJSONObject(i);
      if (obj.length() > 0) {
        updateQueryWithFilter(graphQuery, obj);
      }
    }

    if (conceptType != null) {
      Concept concept = ontologyRepository.getConceptByIRI(conceptType);
      if (getLeafNodes == null || !getLeafNodes.equals("false")) {
        List<Concept> leafNodeList = ontologyRepository.getAllLeafNodesByConcept(concept);
        if (leafNodeList.size() > 0) {
          String[] conceptIds = new String[leafNodeList.size()];
          int count = 0;
          for (Concept c : leafNodeList) {
            conceptIds[count] = c.getTitle();
            count++;
          }
          graphQuery.has(CONCEPT_TYPE.getPropertyName(), Compare.IN, conceptIds);
        }
      } else {
        graphQuery.has(CONCEPT_TYPE.getPropertyName(), conceptType);
      }
    }

    graphQuery.limit(size);
    graphQuery.skip(offset);
    Iterable<Vertex> searchResults;
    try {
      searchResults = graphQuery.vertices();
    } catch (SearchPhaseExecutionException ex) {
      respondWithBadRequest(response, "q", "Invalid Query");
      return;
    }

    Map<Object, Double> scores = null;
    if (searchResults instanceof IterableWithScores) {
      scores = ((IterableWithScores) searchResults).getScores();
    }

    long retrievalStartTime = System.nanoTime();
    List<JSONObject> verticesJsonList = new ArrayList<JSONObject>();
    for (Vertex vertex : searchResults) {
      JSONObject vertexJson = JsonSerializer.toJson(vertex, workspaceId);
      if (scores != null) {
        vertexJson.put("score", scores.get(vertex.getId()));
      }
      vertexJson.put(
          "detectedObjects",
          detectedObjectRepository.toJSON(vertex, modelUserContext, authorizations, workspaceId));
      verticesJsonList.add(vertexJson);
    }
    long retrievalEndTime = System.nanoTime();

    Collections.sort(
        verticesJsonList,
        new Comparator<JSONObject>() {
          @Override
          public int compare(JSONObject o1, JSONObject o2) {
            double score1 = o1.optDouble("score", 0.0);
            double score2 = o2.optDouble("score", 0.0);
            return -Double.compare(score1, score2);
          }
        });

    JSONArray verticesJson = new JSONArray();
    for (JSONObject vertexJson : verticesJsonList) {
      verticesJson.put(vertexJson);
    }

    long totalEndTime = System.nanoTime();

    JSONObject results = new JSONObject();
    results.put("vertices", verticesJson);
    results.put("nextOffset", offset + size);
    results.put("retrievalTime", retrievalEndTime - retrievalStartTime);
    results.put("totalTime", totalEndTime - totalStartTime);

    if (searchResults instanceof IterableWithTotalHits) {
      results.put("totalHits", ((IterableWithTotalHits) searchResults).getTotalHits());
    }
    if (searchResults instanceof IterableWithSearchTime) {
      results.put(
          "searchTime", ((IterableWithSearchTime) searchResults).getSearchTimeNanoSeconds());
    }

    long endTime = System.nanoTime();
    LOGGER.info(
        "Search for \"%s\" found %d vertices in %dms",
        query, verticesJsonList.size(), (endTime - startTime) / 1000 / 1000);

    respondWithJson(response, results);
  }