public void serve(
        CallContext context,
        CmisService service,
        String repositoryId,
        HttpServletRequest request,
        HttpServletResponse response)
        throws Exception {
      assert context != null;
      assert service != null;
      assert repositoryId != null;
      assert request != null;
      assert response != null;

      // get parameters
      String statement = null;
      Boolean searchAllVersions = null;
      Boolean includeAllowableActions = null;
      IncludeRelationships includeRelationships = null;
      String renditionFilter = null;
      BigInteger maxItems = null;
      BigInteger skipCount = null;

      int statusCode = 0;

      if (Dispatcher.METHOD_POST.equals(request.getMethod())) {
        // POST -> read from stream

        QueryTypeImpl queryType = null;
        XMLStreamReader parser = null;
        try {
          parser = XMLUtils.createParser(request.getInputStream());
          XMLUtils.findNextStartElemenet(parser);
          queryType = XMLConverter.convertQuery(parser);
        } catch (XMLStreamException e) {
          throw new CmisInvalidArgumentException("Invalid query request!", e);
        } finally {
          if (parser != null) {
            try {
              parser.close();
            } catch (XMLStreamException e2) {
              // ignore
            }
          }
        }

        statement = queryType.getStatement();
        searchAllVersions = queryType.getSearchAllVersions();
        includeAllowableActions = queryType.getIncludeAllowableActions();
        includeRelationships = queryType.getIncludeRelationships();
        renditionFilter = queryType.getRenditionFilter();
        maxItems = queryType.getMaxItems();
        skipCount = queryType.getSkipCount();

        statusCode = HttpServletResponse.SC_CREATED;
      } else if (Dispatcher.METHOD_GET.equals(request.getMethod())) {
        // GET -> parameters
        statement = getStringParameter(request, Constants.PARAM_Q);
        searchAllVersions = getBooleanParameter(request, Constants.PARAM_SEARCH_ALL_VERSIONS);
        includeAllowableActions = getBooleanParameter(request, Constants.PARAM_ALLOWABLE_ACTIONS);
        includeRelationships =
            getEnumParameter(request, Constants.PARAM_RELATIONSHIPS, IncludeRelationships.class);
        // since CMIS 1.1
        renditionFilter = getStringParameter(request, Constants.PARAM_RENDITION_FILTER);
        maxItems = getBigIntegerParameter(request, Constants.PARAM_MAX_ITEMS);
        skipCount = getBigIntegerParameter(request, Constants.PARAM_SKIP_COUNT);

        statusCode = HttpServletResponse.SC_OK;
      } else {
        throw new CmisRuntimeException("Invalid HTTP method!");
      }

      // execute
      if (stopBeforeService(service)) {
        return;
      }

      ObjectList results =
          service.query(
              repositoryId,
              statement,
              searchAllVersions,
              includeAllowableActions,
              includeRelationships,
              renditionFilter,
              maxItems,
              skipCount,
              null);

      if (stopAfterService(service)) {
        return;
      }

      if (results == null) {
        throw new CmisRuntimeException("Results are null!");
      }

      // set headers
      UrlBuilder baseUrl = compileBaseUrl(request, repositoryId);

      UrlBuilder pagingUrl = compileUrlBuilder(baseUrl, RESOURCE_QUERY, null);
      pagingUrl.addParameter(Constants.PARAM_Q, statement);
      pagingUrl.addParameter(Constants.PARAM_SEARCH_ALL_VERSIONS, searchAllVersions);
      pagingUrl.addParameter(Constants.PARAM_ALLOWABLE_ACTIONS, includeAllowableActions);
      pagingUrl.addParameter(Constants.PARAM_RELATIONSHIPS, includeRelationships);

      UrlBuilder location = new UrlBuilder(pagingUrl);
      location.addParameter(Constants.PARAM_MAX_ITEMS, maxItems);
      location.addParameter(Constants.PARAM_SKIP_COUNT, skipCount);

      response.setStatus(statusCode);
      response.setContentType(Constants.MEDIATYPE_FEED);

      // The Content-Location header is optional (CMIS specification
      // 3.7.2.1).
      // Since it can cause problems with long query statements it is
      // deactivated.
      // response.setHeader("Content-Location", location.toString());

      // The Location header is not optional (CMIS specification 3.7.2.1).
      response.setHeader("Location", location.toString());

      // write XML
      AtomFeed feed = new AtomFeed();
      feed.startDocument(response.getOutputStream(), getNamespaces(service));
      feed.startFeed(true);

      // write basic Atom feed elements
      GregorianCalendar now = new GregorianCalendar();
      feed.writeFeedElements("query", null, "", "Query", now, null, results.getNumItems());

      // write links
      feed.writeServiceLink(baseUrl.toString(), repositoryId);

      feed.writePagingLinks(
          pagingUrl, maxItems, skipCount, results.getNumItems(), results.hasMoreItems(), PAGE_SIZE);

      CmisVersion cmisVersion = context.getCmisVersion();
      if (results.getObjects() != null) {
        AtomEntry entry = new AtomEntry(feed.getWriter());
        int idCounter = 0;
        for (ObjectData result : results.getObjects()) {
          if (result == null) {
            continue;
          }
          idCounter++;
          writeQueryResultEntry(entry, result, "id-" + idCounter, now, cmisVersion);
        }
      }

      // write extensions
      feed.writeExtensions(results);

      // we are done
      feed.endFeed();
      feed.endDocument();
    }
    public void serve(
        CallContext context,
        CmisService service,
        String repositoryId,
        HttpServletRequest request,
        HttpServletResponse response)
        throws Exception {
      assert context != null;
      assert service != null;
      assert repositoryId != null;
      assert request != null;
      assert response != null;

      // get parameters
      String changeLogToken = getStringParameter(request, Constants.PARAM_CHANGE_LOG_TOKEN);
      Boolean includeProperties = getBooleanParameter(request, Constants.PARAM_PROPERTIES);
      String filter = getStringParameter(request, Constants.PARAM_FILTER);
      Boolean includePolicyIds = getBooleanParameter(request, Constants.PARAM_POLICY_IDS);
      Boolean includeAcl = getBooleanParameter(request, Constants.PARAM_ACL);
      BigInteger maxItems = getBigIntegerParameter(request, Constants.PARAM_MAX_ITEMS);

      // execute
      if (stopBeforeService(service)) {
        return;
      }

      Holder<String> changeLogTokenHolder = new Holder<String>(changeLogToken);
      ObjectList changes =
          service.getContentChanges(
              repositoryId,
              changeLogTokenHolder,
              includeProperties,
              filter,
              includePolicyIds,
              includeAcl,
              maxItems,
              null);

      if (stopAfterService(service)) {
        return;
      }

      if (changes == null) {
        throw new CmisRuntimeException("Changes are null!");
      }

      // set headers
      response.setStatus(HttpServletResponse.SC_OK);
      response.setContentType(Constants.MEDIATYPE_FEED);

      // write XML
      AtomFeed feed = new AtomFeed();
      feed.startDocument(response.getOutputStream(), getNamespaces(service));
      feed.startFeed(true);

      // write basic Atom feed elements
      GregorianCalendar now = new GregorianCalendar();
      feed.writeFeedElements(
          "contentChanges", null, "", "Content Change", now, null, changes.getNumItems());

      // write links
      UrlBuilder baseUrl = compileBaseUrl(request, repositoryId);

      feed.writeServiceLink(baseUrl.toString(), repositoryId);

      UrlBuilder selfLink = compileUrlBuilder(baseUrl, RESOURCE_CHANGES, null);
      selfLink.addParameter(Constants.PARAM_CHANGE_LOG_TOKEN, changeLogToken);
      selfLink.addParameter(Constants.PARAM_PROPERTIES, includeProperties);
      selfLink.addParameter(Constants.PARAM_FILTER, filter);
      selfLink.addParameter(Constants.PARAM_POLICY_IDS, includePolicyIds);
      selfLink.addParameter(Constants.PARAM_ACL, includeAcl);
      selfLink.addParameter(Constants.PARAM_MAX_ITEMS, maxItems);
      feed.writeSelfLink(selfLink.toString(), null);

      if (changeLogTokenHolder.getValue() != null) {
        if (Boolean.TRUE.equals(changes.hasMoreItems())) {
          UrlBuilder nextLink = compileUrlBuilder(baseUrl, RESOURCE_CHANGES, null);
          nextLink.addParameter(Constants.PARAM_CHANGE_LOG_TOKEN, changeLogTokenHolder.getValue());
          nextLink.addParameter(Constants.PARAM_PROPERTIES, includeProperties);
          nextLink.addParameter(Constants.PARAM_FILTER, filter);
          nextLink.addParameter(Constants.PARAM_POLICY_IDS, includePolicyIds);
          nextLink.addParameter(Constants.PARAM_ACL, includeAcl);
          nextLink.addParameter(Constants.PARAM_MAX_ITEMS, maxItems);
          feed.writeNextLink(nextLink.toString());
        }

        // The CMIS spec says that the AtomPub binding doesn't provide
        // the change log token. We are doing it anyway.
        XMLStreamWriter writer = feed.getWriter();
        writer.writeStartElement(
            XMLConstants.PREFIX_APACHE_CHEMISTY,
            "changeLogToken",
            XMLConstants.NAMESPACE_APACHE_CHEMISTRY);
        writer.writeNamespace(
            XMLConstants.PREFIX_APACHE_CHEMISTY, XMLConstants.NAMESPACE_APACHE_CHEMISTRY);
        writer.writeCharacters(changeLogTokenHolder.getValue());
        writer.writeEndElement();
      }

      // write entries
      if (changes.getObjects() != null) {
        AtomEntry entry = new AtomEntry(feed.getWriter());
        for (ObjectData object : changes.getObjects()) {
          if (object == null) {
            continue;
          }
          writeContentChangesObjectEntry(
              service,
              entry,
              object,
              null,
              repositoryId,
              null,
              null,
              baseUrl,
              false,
              context.getCmisVersion());
        }
      }

      // write extensions
      feed.writeExtensions(changes);

      // we are done
      feed.endFeed();
      feed.endDocument();
    }