/**
   * Processes a velocity template so that variable references are replaced by the same properties
   * in the property provider and request.
   *
   * @param request the request.
   * @param queryTemplate the query template.
   * @param propertyProviderName
   * @return A processed query template
   * @throws MissingParameterException
   */
  protected Query processQuery(SlingHttpServletRequest request, Node queryNode)
      throws RepositoryException, MissingParameterException, JSONException {
    String propertyProviderName = null;
    if (queryNode.hasProperty(SAKAI_PROPERTY_PROVIDER)) {
      propertyProviderName = queryNode.getProperty(SAKAI_PROPERTY_PROVIDER).getString();
    }
    Map<String, String> propertiesMap = loadProperties(request, propertyProviderName, queryNode);

    String queryTemplate = queryNode.getProperty(SAKAI_QUERY_TEMPLATE).getString();

    // process the query string before checking for missing terms to a) give processors a
    // chance to set things and b) catch any missing terms added by the processors.
    String queryString = templateService.evaluateTemplate(propertiesMap, queryTemplate);

    // expand home directory references to full path; eg. ~user => a:user
    queryString = expandHomeDirectory(queryString);

    // append the user principals to the query string
    queryString = addUserPrincipals(request, queryString);

    // check for any missing terms & process the query template
    Collection<String> missingTerms = templateService.missingTerms(propertiesMap, queryTemplate);
    if (!missingTerms.isEmpty()) {
      throw new MissingParameterException(
          "Your request is missing parameters for the template: "
              + StringUtils.join(missingTerms, ", "));
    }

    // collect query options
    JSONObject queryOptions = accumulateQueryOptions(queryNode);

    // process the options as templates and check for missing params
    Map<String, String> options = processOptions(propertiesMap, queryOptions);

    // check the resource type and set the query type appropriately
    // default to using solr for queries
    javax.jcr.Property resourceType = queryNode.getProperty("sling:resourceType");
    Query query = null;
    if ("sakai/sparse-search".equals(resourceType.getString())) {
      query = new Query(Type.SPARSE, queryString, options);
    } else {
      query = new Query(Type.SOLR, queryString, options);
    }
    return query;
  }
  /**
   * @param propertiesMap
   * @param queryOptions
   * @return
   * @throws JSONException
   * @throws MissingParameterException
   */
  private Map<String, String> processOptions(
      Map<String, String> propertiesMap, JSONObject queryOptions)
      throws JSONException, MissingParameterException {
    Collection<String> missingTerms;
    Map<String, String> options = Maps.newHashMap();
    if (queryOptions != null) {
      Iterator<String> keys = queryOptions.keys();
      while (keys.hasNext()) {
        String key = keys.next();
        String val = queryOptions.getString(key);
        missingTerms = templateService.missingTerms(propertiesMap, val);
        if (!missingTerms.isEmpty()) {
          throw new MissingParameterException(
              "Your request is missing parameters for the template: "
                  + StringUtils.join(missingTerms, ", "));
        }

        String processedVal = templateService.evaluateTemplate(propertiesMap, val);
        options.put(key, processedVal);
      }
    }
    return options;
  }
  private MultiPartEmail constructMessage(
      Content contentNode,
      List<String> recipients,
      javax.jcr.Session session,
      org.sakaiproject.nakamura.api.lite.Session sparseSession)
      throws EmailDeliveryException, StorageClientException, AccessDeniedException,
          PathNotFoundException, RepositoryException {
    MultiPartEmail email = new MultiPartEmail();
    // TODO: the SAKAI_TO may make no sense in an email context
    // and there does not appear to be any distinction between Bcc and To in java mail.

    Set<String> toRecipients = new HashSet<String>();
    Set<String> bccRecipients = new HashSet<String>();
    for (String r : recipients) {
      bccRecipients.add(convertToEmail(r.trim(), sparseSession));
    }

    if (contentNode.hasProperty(MessageConstants.PROP_SAKAI_TO)) {
      String[] tor =
          StringUtils.split((String) contentNode.getProperty(MessageConstants.PROP_SAKAI_TO), ',');
      for (String r : tor) {
        r = convertToEmail(r.trim(), sparseSession);
        if (bccRecipients.contains(r)) {
          toRecipients.add(r);
          bccRecipients.remove(r);
        }
      }
    }
    for (String r : toRecipients) {
      try {
        email.addTo(convertToEmail(r, sparseSession));
      } catch (EmailException e) {
        throw new EmailDeliveryException(
            "Invalid To Address [" + r + "], message is being dropped :" + e.getMessage(), e);
      }
    }
    for (String r : bccRecipients) {
      try {
        email.addBcc(convertToEmail(r, sparseSession));
      } catch (EmailException e) {
        throw new EmailDeliveryException(
            "Invalid Bcc Address [" + r + "], message is being dropped :" + e.getMessage(), e);
      }
    }

    if (contentNode.hasProperty(MessageConstants.PROP_SAKAI_FROM)) {
      String from = (String) contentNode.getProperty(MessageConstants.PROP_SAKAI_FROM);
      try {
        email.setFrom(convertToEmail(from, sparseSession));
      } catch (EmailException e) {
        throw new EmailDeliveryException(
            "Invalid From Address [" + from + "], message is being dropped :" + e.getMessage(), e);
      }
    } else {
      throw new EmailDeliveryException("Must provide a 'from' address.");
    }

    if (contentNode.hasProperty(MessageConstants.PROP_SAKAI_BODY)) {
      String messageBody = (String) contentNode.getProperty(MessageConstants.PROP_SAKAI_BODY);
      // if this message has a template, use it
      LOGGER.debug(
          "Checking for sakai:templatePath and sakai:templateParams properties on the outgoing message's node.");
      if (contentNode.hasProperty(MessageConstants.PROP_TEMPLATE_PATH)
          && contentNode.hasProperty(MessageConstants.PROP_TEMPLATE_PARAMS)) {
        Map<String, String> parameters =
            getTemplateProperties(
                (String) contentNode.getProperty(MessageConstants.PROP_TEMPLATE_PARAMS));
        String templatePath = (String) contentNode.getProperty(MessageConstants.PROP_TEMPLATE_PATH);
        LOGGER.debug("Got the path '{0}' to the template for this outgoing message.", templatePath);
        Node templateNode = session.getNode(templatePath);
        if (templateNode.hasProperty("sakai:template")) {
          String template = templateNode.getProperty("sakai:template").getString();
          LOGGER.debug("Pulled the template body from the template node: {0}", template);
          messageBody = templateService.evaluateTemplate(parameters, template);
          LOGGER.debug("Performed parameter substitution in the template: {0}", messageBody);
        }
      } else {
        LOGGER.debug(
            "Message node '{0}' does not have sakai:templatePath and sakai:templateParams properties",
            contentNode.getPath());
      }
      try {
        email.setMsg(messageBody);
      } catch (EmailException e) {
        throw new EmailDeliveryException(
            "Invalid Message Body, message is being dropped :" + e.getMessage(), e);
      }
    }

    if (contentNode.hasProperty(MessageConstants.PROP_SAKAI_SUBJECT)) {
      email.setSubject((String) contentNode.getProperty(MessageConstants.PROP_SAKAI_SUBJECT));
    }

    ContentManager contentManager = sparseSession.getContentManager();
    for (String streamId : contentNode.listStreams()) {
      String description = null;
      if (contentNode.hasProperty(
          StorageClientUtils.getAltField(
              MessageConstants.PROP_SAKAI_ATTACHMENT_DESCRIPTION, streamId))) {
        description =
            (String)
                contentNode.getProperty(
                    StorageClientUtils.getAltField(
                        MessageConstants.PROP_SAKAI_ATTACHMENT_DESCRIPTION, streamId));
      }
      LiteEmailDataSource ds = new LiteEmailDataSource(contentManager, contentNode, streamId);
      try {
        email.attach(ds, streamId, description);
      } catch (EmailException e) {
        throw new EmailDeliveryException(
            "Invalid Attachment [" + streamId + "] message is being dropped :" + e.getMessage(), e);
      }
    }
    return email;
  }