protected void processOutcomeXml(
      HttpServletRequest request,
      HttpServletResponse response,
      String lti_message_type,
      String sourcedid,
      IMSPOXRequest pox)
      throws java.io.IOException {
    // Things look good - time to process the grade
    boolean isRead = BasicLTIUtil.equals(lti_message_type, "readResultRequest");
    boolean isDelete = BasicLTIUtil.equals(lti_message_type, "deleteResultRequest");

    Map<String, String> bodyMap = pox.getBodyMap();
    String result_resultscore_textstring =
        bodyMap.get("/resultRecord/result/resultScore/textString");
    String result_resultdata_text = bodyMap.get("/resultRecord/result/resultData/text");
    String sourced_id = bodyMap.get("/resultRecord/result/sourcedId");
    // System.out.println("comment="+result_resultdata_text);
    // System.out.println("grade="+result_resultscore_textstring);

    if (BasicLTIUtil.isBlank(result_resultscore_textstring) && !isRead && !isDelete) {
      doErrorXML(request, response, pox, "outcomes.missing", "result_resultscore_textstring", null);
      return;
    }

    // Lets return an XML Response
    Map<String, Object> theMap = new TreeMap<String, Object>();
    String theGrade = null;
    boolean success = false;
    String message = null;
    Object retval = null;
    boolean strict = ServerConfigurationService.getBoolean(SakaiBLTIUtil.LTI_STRICT, false);

    try {
      Double dGrade;
      if (isRead) {
        retval = SakaiBLTIUtil.getGrade(sourcedid, request, ltiService);
        String sGrade = "";
        String comment = "";
        if (retval instanceof Map) {
          Map grade = (Map) retval;
          comment = (String) grade.get("comment");
          dGrade = (Double) grade.get("grade");
          if (dGrade != null) {
            sGrade = dGrade.toString();
          }
        } else {
          Object check = SakaiBLTIUtil.checkSourceDid(sourcedid, request, ltiService);
          if (check instanceof Boolean && ((Boolean) check)) {
            // Read fail with Good SourceDID is treated as empty
          } else {
            doErrorXML(request, response, pox, "outcomes.fail", (String) retval, null);
            return;
          }
        }

        theMap.put("/readResultResponse/result/sourcedId", sourced_id);
        theMap.put("/readResultResponse/result/resultScore/textString", sGrade);
        theMap.put("/readResultResponse/result/resultScore/language", "en");
        if (!strict) {
          theMap.put("/readResultResponse/result/resultData/text", comment);
        }
        message = "Result read";
      } else if (isDelete) {
        retval = SakaiBLTIUtil.deleteGrade(sourcedid, request, ltiService);
        if (retval instanceof String) {
          doErrorXML(request, response, pox, "outcomes.fail", (String) retval, null);
          return;
        }
        theMap.put("/deleteResultResponse", "");
        message = "Result deleted";
      } else {
        dGrade = new Double(result_resultscore_textstring);
        if (dGrade < 0.0 || dGrade > 1.0) {
          throw new Exception("Grade out of range");
        }
        dGrade = new Double(result_resultscore_textstring);
        retval =
            SakaiBLTIUtil.setGrade(sourcedid, request, ltiService, dGrade, result_resultdata_text);
        if (retval instanceof String) {
          doErrorXML(request, response, pox, "outcomes.fail", (String) retval, null);
          return;
        }
        theMap.put("/replaceResultResponse", "");
        message = "Result replaced";
      }

      success = true;
    } catch (Exception e) {
      doErrorXML(request, response, pox, "outcome.grade.fail", e.getMessage(), e);
    }

    if (!success) return;

    String output = null;
    String theXml = "";
    if (theMap.size() > 0) theXml = XMLMap.getXMLFragment(theMap, true);
    output = pox.getResponseSuccess(message, theXml);

    response.setContentType("application/xml");
    PrintWriter out = response.getWriter();
    out.println(output);
    M_log.debug(output);
  }
  protected void processOutcome(
      HttpServletRequest request,
      HttpServletResponse response,
      String lti_message_type,
      String sourcedid,
      Map<String, Object> theMap)
      throws java.io.IOException {
    // Things look good - time to process the grade
    boolean isRead = BasicLTIUtil.equals(lti_message_type, "basic-lis-readresult");
    boolean isDelete = BasicLTIUtil.equals(lti_message_type, "basic-lis-deleteresult");

    String result_resultscore_textstring = request.getParameter("result_resultscore_textstring");
    String result_resultdata_text = request.getParameter("result_resultdata_text");

    if (BasicLTIUtil.isBlank(result_resultscore_textstring) && !isRead) {
      doError(request, response, theMap, "outcomes.missing", "result_resultscore_textstring", null);
      return;
    }

    String theGrade = null;
    boolean success = false;
    Object retval = null;

    try {
      Double dGrade;
      if (isRead) {
        retval = SakaiBLTIUtil.getGrade(sourcedid, request, ltiService);
        if (retval instanceof Map) {
          Map grade = (Map) retval;
          dGrade = (Double) grade.get("grade");
          theMap.put("/message_response/result/resultscore/textstring", dGrade.toString());
          theMap.put("/message_response/result/resultdata/text", (String) grade.get("comment"));
        } else {
          // Read fail with Good SourceDID is treated as empty
          Object check = SakaiBLTIUtil.checkSourceDid(sourcedid, request, ltiService);
          if (check instanceof Boolean && ((Boolean) check)) {
            theMap.put("/message_response/result/resultscore/textstring", "");
            theMap.put("/message_response/result/resultdata/text", "");
          } else {
            doError(request, response, theMap, "outcome.fail", (String) retval, null);
            return;
          }
        }
      } else if (isDelete) {
        retval = SakaiBLTIUtil.deleteGrade(sourcedid, request, ltiService);
      } else {
        dGrade = new Double(result_resultscore_textstring);
        retval =
            SakaiBLTIUtil.setGrade(sourcedid, request, ltiService, dGrade, result_resultdata_text);
      }
      success = true;
      theMap.put("/message_response/statusinfo/codemajor", "Success");
      theMap.put("/message_response/statusinfo/severity", "Status");
      theMap.put("/message_response/statusinfo/codeminor", "fullsuccess");
    } catch (Exception e) {
      doError(request, response, theMap, "outcome.grade.fail", "", e);
    }

    if (!success) return;

    String theXml = XMLMap.getXML(theMap, true);
    PrintWriter out = response.getWriter();
    out.println(theXml);
  }
  @SuppressWarnings("unchecked")
  protected void doPostXml(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    String ipAddress = request.getRemoteAddr();

    M_log.debug("LTI POX Service request from IP=" + ipAddress);

    String allowOutcomes =
        ServerConfigurationService.getString(
            SakaiBLTIUtil.BASICLTI_OUTCOMES_ENABLED,
            SakaiBLTIUtil.BASICLTI_OUTCOMES_ENABLED_DEFAULT);
    if (!"true".equals(allowOutcomes)) allowOutcomes = null;

    if (allowOutcomes == null) {
      M_log.warn("LTI Services are disabled IP=" + ipAddress);
      response.setStatus(HttpServletResponse.SC_FORBIDDEN);
      return;
    }

    IMSPOXRequest pox = new IMSPOXRequest(request);
    if (!pox.valid) {
      doErrorXML(request, response, pox, "pox.invalid", pox.errorMessage, null);
      return;
    }

    // check lti_message_type
    String lti_message_type = pox.getOperation();

    String sourcedid = null;
    String message_type = null;
    if (M_log.isDebugEnabled()) M_log.debug("POST\n" + XMLMap.prettyPrint(pox.postBody));
    Map<String, String> bodyMap = pox.getBodyMap();
    if (("replaceResultRequest".equals(lti_message_type)
            || "readResultRequest".equals(lti_message_type)
            || "deleteResultRequest".equals(lti_message_type))
        && allowOutcomes != null) {
      sourcedid = bodyMap.get("/resultRecord/sourcedGUID/sourcedId");
      message_type = "basicoutcome";
    } else {
      String output = pox.getResponseUnsupported("Not supported " + lti_message_type);
      response.setContentType("application/xml");
      PrintWriter out = response.getWriter();
      out.println(output);
      return;
    }

    // No point continuing without a sourcedid
    if (BasicLTIUtil.isBlank(sourcedid)) {
      doErrorXML(request, response, pox, "outcomes.missing", "sourcedid", null);
      return;
    }

    // Handle the outcomes here using the new SakaiBLTIUtil code
    if (allowOutcomes != null && "basicoutcome".equals(message_type)) {
      processOutcomeXml(request, response, lti_message_type, sourcedid, pox);
      return;
    }

    // Truncate this to the maximum length to insure no cruft at the end
    if (sourcedid.length() > 2048) sourcedid = sourcedid.substring(0, 2048);

    // Attempt to parse the sourcedid, any failure is fatal
    String placement_id = null;
    String signature = null;
    String user_id = null;
    try {
      int pos = sourcedid.indexOf(":::");
      if (pos > 0) {
        signature = sourcedid.substring(0, pos);
        String dec2 = sourcedid.substring(pos + 3);
        pos = dec2.indexOf(":::");
        user_id = dec2.substring(0, pos);
        placement_id = dec2.substring(pos + 3);
      }
    } catch (Exception e) {
      // Log some detail for ourselves
      M_log.warn(
          "Unable to decrypt result_sourcedid IP=" + ipAddress + " Error=" + e.getMessage(), e);
      signature = null;
      placement_id = null;
      user_id = null;
    }

    // Send a more generic message back to the caller
    if (placement_id == null || user_id == null) {
      doErrorXML(
          request, response, pox, "outcomes.sourcedid", "missing user_id or placement_id", null);
      return;
    }

    M_log.debug("signature=" + signature);
    M_log.debug("user_id=" + user_id);
    M_log.debug("placement_id=" + placement_id);

    Properties pitch = SakaiBLTIUtil.getPropertiesFromPlacement(placement_id, ltiService);
    if (pitch == null) {
      M_log.debug("Error retrieving result_sourcedid information");
      doErrorXML(request, response, pox, "outcomes.sourcedid", "sourcedid", null);
      return;
    }

    String siteId = pitch.getProperty(LTIService.LTI_SITE_ID);
    Site site = null;
    try {
      site = SiteService.getSite(siteId);
    } catch (Exception e) {
      M_log.debug("Error retrieving result_sourcedid site: " + e.getLocalizedMessage(), e);
    }

    // Send a more generic message back to the caller
    if (site == null) {
      doErrorXML(request, response, pox, "outcomes.sourcedid", "sourcedid", null);
      return;
    }

    // Check the message signature using OAuth
    String oauth_consumer_key = pox.getOAuthConsumerKey();
    String oauth_secret = pitch.getProperty(LTIService.LTI_SECRET);
    M_log.debug("oauth_secret: " + oauth_secret);
    oauth_secret = SakaiBLTIUtil.decryptSecret(oauth_secret);
    M_log.debug("oauth_secret (decrypted): " + oauth_secret);

    String URL = SakaiBLTIUtil.getOurServletPath(request);
    pox.validateRequest(oauth_consumer_key, oauth_secret, request, URL);
    if (!pox.valid) {
      if (pox.base_string != null) {
        M_log.warn(pox.base_string);
      }
      doErrorXML(request, response, pox, "outcome.no.validate", oauth_consumer_key, null);
      return;
    }

    // Check the signature of the sourcedid to make sure it was not altered
    String placement_secret = pitch.getProperty(LTIService.LTI_PLACEMENTSECRET);

    // Send a generic message back to the caller
    if (placement_secret == null) {
      M_log.debug("placement_secret is null");
      doErrorXML(request, response, pox, "outcomes.sourcedid", "sourcedid", null);
      return;
    }

    String pre_hash = placement_secret + ":::" + user_id + ":::" + placement_id;
    String received_signature = LegacyShaUtil.sha256Hash(pre_hash);
    M_log.debug("Received signature=" + signature + " received=" + received_signature);
    boolean matched = signature.equals(received_signature);

    String old_placement_secret = pitch.getProperty(LTIService.LTI_OLDPLACEMENTSECRET);
    if (old_placement_secret != null && !matched) {
      pre_hash = placement_secret + ":::" + user_id + ":::" + placement_id;
      received_signature = LegacyShaUtil.sha256Hash(pre_hash);
      M_log.debug("Received signature II=" + signature + " received=" + received_signature);
      matched = signature.equals(received_signature);
    }

    // Send a message back to the caller
    if (!matched) {
      doErrorXML(request, response, pox, "outcomes.sourcedid", "sourcedid", null);
      return;
    }

    response.setContentType("application/xml");
    PrintWriter writer = response.getWriter();
    String desc = "Message received and validated operation=" + pox.getOperation();
    String output = pox.getResponseUnsupported(desc);
    writer.println(output);
  }
  @SuppressWarnings("unchecked")
  protected void doPostForm(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    String ipAddress = request.getRemoteAddr();

    M_log.debug("Basic LTI Service request from IP=" + ipAddress);

    String allowOutcomes =
        ServerConfigurationService.getString(
            SakaiBLTIUtil.BASICLTI_OUTCOMES_ENABLED,
            SakaiBLTIUtil.BASICLTI_OUTCOMES_ENABLED_DEFAULT);
    if (!"true".equals(allowOutcomes)) allowOutcomes = null;

    String allowSettings =
        ServerConfigurationService.getString(
            SakaiBLTIUtil.BASICLTI_SETTINGS_ENABLED,
            SakaiBLTIUtil.BASICLTI_SETTINGS_ENABLED_DEFAULT);
    if (!"true".equals(allowSettings)) allowSettings = null;

    String allowRoster =
        ServerConfigurationService.getString(
            SakaiBLTIUtil.BASICLTI_ROSTER_ENABLED, SakaiBLTIUtil.BASICLTI_ROSTER_ENABLED_DEFAULT);
    if (!"true".equals(allowRoster)) allowRoster = null;

    if (allowOutcomes == null && allowSettings == null && allowRoster == null) {
      M_log.warn("LTI Services are disabled IP=" + ipAddress);
      response.setStatus(HttpServletResponse.SC_FORBIDDEN);
      return;
    }

    // Lets return an XML Response
    Map<String, Object> theMap = new TreeMap<String, Object>();

    Map<String, String[]> params = (Map<String, String[]>) request.getParameterMap();
    for (Map.Entry<String, String[]> param : params.entrySet()) {
      M_log.debug(param.getKey() + ":" + param.getValue()[0]);
    }

    // check lti_message_type
    String lti_message_type = request.getParameter(BasicLTIConstants.LTI_MESSAGE_TYPE);
    theMap.put("/message_response/lti_message_type", lti_message_type);
    String sourcedid = null;
    String message_type = null;
    if (BasicLTIUtil.equals(lti_message_type, "basic-lis-replaceresult")
        || BasicLTIUtil.equals(lti_message_type, "basic-lis-createresult")
        || BasicLTIUtil.equals(lti_message_type, "basic-lis-updateresult")
        || BasicLTIUtil.equals(lti_message_type, "basic-lis-deleteresult")
        || BasicLTIUtil.equals(lti_message_type, "basic-lis-readresult")) {
      sourcedid = request.getParameter("sourcedid");
      if (allowOutcomes != null) message_type = "basicoutcome";
    } else if (BasicLTIUtil.equals(lti_message_type, "basic-lti-loadsetting")
        || BasicLTIUtil.equals(lti_message_type, "basic-lti-savesetting")
        || BasicLTIUtil.equals(lti_message_type, "basic-lti-deletesetting")) {
      sourcedid = request.getParameter("id");
      if (allowSettings != null) message_type = "toolsetting";
    } else if (BasicLTIUtil.equals(lti_message_type, "basic-lis-readmembershipsforcontext")) {
      sourcedid = request.getParameter("id");
      if (allowRoster != null) message_type = "roster";
    } else {
      doError(
          request,
          response,
          theMap,
          "outcomes.invalid",
          "lti_message_type=" + lti_message_type,
          null);
      return;
    }

    // If we have not gotten one of our allowed message types, stop now
    if (message_type == null) {
      doError(
          request,
          response,
          theMap,
          "outcomes.invalid",
          "lti_message_type=" + lti_message_type,
          null);
      return;
    }

    // Perform the Outcomee first because we use the SakaiBLTIUtil code for this
    if ("basicoutcome".equals(message_type)) {
      processOutcome(request, response, lti_message_type, sourcedid, theMap);
      return;
    }

    // No point continuing without a sourcedid
    if (BasicLTIUtil.isBlank(sourcedid)) {
      doError(request, response, theMap, "outcomes.missing", "sourcedid", null);
      return;
    }

    String lti_version = request.getParameter(BasicLTIConstants.LTI_VERSION);
    if (!BasicLTIUtil.equals(lti_version, "LTI-1p0")) {
      doError(request, response, theMap, "outcomes.invalid", "lti_version=" + lti_version, null);
      return;
    }

    String oauth_consumer_key = request.getParameter("oauth_consumer_key");
    if (BasicLTIUtil.isBlank(oauth_consumer_key)) {
      doError(request, response, theMap, "outcomes.missing", "oauth_consumer_key", null);
      return;
    }

    // Truncate this to the maximum length to insure no cruft at the end
    if (sourcedid.length() > 2048) sourcedid = sourcedid.substring(0, 2048);

    // Attempt to parse the sourcedid, any failure is fatal
    String placement_id = null;
    String signature = null;
    String user_id = null;
    try {
      int pos = sourcedid.indexOf(":::");
      if (pos > 0) {
        signature = sourcedid.substring(0, pos);
        String dec2 = sourcedid.substring(pos + 3);
        pos = dec2.indexOf(":::");
        user_id = dec2.substring(0, pos);
        placement_id = dec2.substring(pos + 3);
      }
    } catch (Exception e) {
      // Log some detail for ourselves
      M_log.warn(
          "Unable to decrypt result_sourcedid IP=" + ipAddress + " Error=" + e.getMessage(), e);
      signature = null;
      placement_id = null;
      user_id = null;
    }

    // Send a more generic message back to the caller
    if (placement_id == null || user_id == null) {
      doError(request, response, theMap, "outcomes.sourcedid", "sourcedid", null);
      return;
    }

    M_log.debug("signature=" + signature);
    M_log.debug("user_id=" + user_id);
    M_log.debug("placement_id=" + placement_id);

    Properties pitch = SakaiBLTIUtil.getPropertiesFromPlacement(placement_id, ltiService);
    if (pitch == null) {
      M_log.debug("Error retrieving result_sourcedid information");
      doError(request, response, theMap, "outcomes.sourcedid", "sourcedid", null);
      return;
    }

    String siteId = pitch.getProperty(LTIService.LTI_SITE_ID);
    Site site = null;
    try {
      site = SiteService.getSite(siteId);
    } catch (Exception e) {
      M_log.debug("Error retrieving result_sourcedid site: " + e.getLocalizedMessage(), e);
    }

    // Send a more generic message back to the caller
    if (site == null) {
      doError(request, response, theMap, "outcomes.sourcedid", "sourcedid", null);
      return;
    }

    // Check the message signature using OAuth
    String oauth_secret = pitch.getProperty(LTIService.LTI_SECRET);
    M_log.debug("oauth_secret: " + oauth_secret);
    oauth_secret = SakaiBLTIUtil.decryptSecret(oauth_secret);
    M_log.debug("oauth_secret (decrypted): " + oauth_secret);

    String URL = SakaiBLTIUtil.getOurServletPath(request);
    OAuthMessage oam = OAuthServlet.getMessage(request, URL);
    OAuthValidator oav = new SimpleOAuthValidator();
    OAuthConsumer cons =
        new OAuthConsumer(
            "about:blank#OAuth+CallBack+NotUsed", oauth_consumer_key, oauth_secret, null);

    OAuthAccessor acc = new OAuthAccessor(cons);

    String base_string = null;
    try {
      base_string = OAuthSignatureMethod.getBaseString(oam);
    } catch (Exception e) {
      M_log.error(e.getLocalizedMessage(), e);
      base_string = null;
    }

    try {
      oav.validateMessage(oam, acc);
    } catch (Exception e) {
      M_log.warn("Provider failed to validate message");
      M_log.warn(e.getLocalizedMessage(), e);
      if (base_string != null) {
        M_log.warn(base_string);
      }
      doError(request, response, theMap, "outcome.no.validate", oauth_consumer_key, null);
      return;
    }

    // Check the signature of the sourcedid to make sure it was not altered
    String placement_secret = pitch.getProperty(LTIService.LTI_PLACEMENTSECRET);

    // Send a generic message back to the caller
    if (placement_secret == null) {
      doError(request, response, theMap, "outcomes.sourcedid", "sourcedid", null);
      return;
    }

    String pre_hash = placement_secret + ":::" + user_id + ":::" + placement_id;
    String received_signature = LegacyShaUtil.sha256Hash(pre_hash);
    M_log.debug("Received signature=" + signature + " received=" + received_signature);
    boolean matched = signature.equals(received_signature);

    String old_placement_secret = pitch.getProperty(LTIService.LTI_OLDPLACEMENTSECRET);
    if (old_placement_secret != null && !matched) {
      pre_hash = placement_secret + ":::" + user_id + ":::" + placement_id;
      received_signature = LegacyShaUtil.sha256Hash(pre_hash);
      M_log.debug("Received signature II=" + signature + " received=" + received_signature);
      matched = signature.equals(received_signature);
    }

    // Send a message back to the caller
    if (!matched) {
      doError(request, response, theMap, "outcomes.sourcedid", "sourcedid", null);
      return;
    }

    // Perform the message-specific handling
    if ("toolsetting".equals(message_type))
      processSetting(
          request, response, lti_message_type, site, siteId, placement_id, pitch, user_id, theMap);

    if ("roster".equals(message_type))
      processRoster(
          request, response, lti_message_type, site, siteId, placement_id, pitch, user_id, theMap);
  }