/**
   * This method renders the given request URI using the Sling request processing and stores the
   * result at the request's location relative to the cache root folder. The request is processed in
   * the context of the given user's session.
   *
   * @param uri The request URI including selectors and extensions
   * @param configCacheRoot The cache root folder
   * @param admin The admin session used to store the result in the cache
   * @param session The user's session
   * @return <code>true</code> if the cache was updated
   */
  protected boolean renderResource(
      String uri, String configCacheRoot, Session admin, Session session)
      throws RepositoryException, ServletException, IOException {
    String cachePath = configCacheRoot + getTargetPath(uri);

    ResourceResolver resolver = null;
    try {
      resolver = createResolver(session.getUserID());

      // render resource
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      HttpServletRequest request = createRequest(uri);
      HttpServletResponse response = requestResponseFactory.createResponse(out);

      slingServlet.processRequest(request, response, resolver);
      response.getWriter().flush();

      // compare md5 checksum with cache
      String md5 = requestResponseFactory.getMD5(response);
      String md5Path = cachePath + "/" + JcrConstants.JCR_CONTENT + "/" + MD5_HASH_PROPERTY;

      if (!admin.propertyExists(md5Path) || !admin.getProperty(md5Path).getString().equals(md5)) {
        log.info("MD5 hash missing or not equal, updating content sync cache: {}", cachePath);

        JcrUtil.createPath(cachePath, "sling:Folder", "nt:file", admin, false);

        Node cacheContentNode =
            JcrUtil.createPath(cachePath + "/jcr:content", "nt:resource", admin);
        if (needsUtf8Encoding(response)) {
          cacheContentNode.setProperty(
              JcrConstants.JCR_DATA,
              admin
                  .getValueFactory()
                  .createBinary(
                      IOUtils.toInputStream(out.toString(response.getCharacterEncoding()))));
        } else {
          cacheContentNode.setProperty(
              JcrConstants.JCR_DATA,
              admin.getValueFactory().createBinary(new ByteArrayInputStream(out.toByteArray())));
        }
        cacheContentNode.setProperty(JcrConstants.JCR_LASTMODIFIED, Calendar.getInstance());
        if (response.getContentType() != null) {
          cacheContentNode.setProperty(JcrConstants.JCR_MIMETYPE, response.getContentType());
        }
        if (response.getCharacterEncoding() != null) {
          cacheContentNode.setProperty(JcrConstants.JCR_ENCODING, response.getCharacterEncoding());
        }

        cacheContentNode.addMixin(NT_MD5_HASH);
        cacheContentNode.setProperty(MD5_HASH_PROPERTY, md5);

        admin.save();

        return true;
      } else {
        log.info("Skipping update of content sync cache: {}", uri);

        return false;
      }
    } catch (LoginException e) {
      log.error("Creating resource resolver for resource rendering failed: ", e);
      return false;
    } finally {
      if (resolver != null) {
        resolver.close();
      }

      if (admin.hasPendingChanges()) {
        admin.refresh(false);
      }
    }
  }
 /**
  * Creates a GET request for the given uri. This method can be overridden to provide a customized
  * request object, e.g. with added parameters.
  *
  * @see com.day.cq.contentsync.handler.util.RequestResponseFactory
  * @param uri The uri
  * @return The request object
  */
 protected HttpServletRequest createRequest(String uri) {
   return requestResponseFactory.createRequest("GET", uri);
 }