All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.tsugi.basiclti.ContentItem Maven / Gradle / Ivy

The newest version!
/*
 * $URL$
 * $Id$
 *
 * Copyright (c) 2015- Charles R. Severance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package org.tsugi.basiclti;

import java.util.Enumeration;
import java.util.Iterator;
import java.util.Properties;

import java.net.URLEncoder;

import javax.servlet.http.HttpServletRequest;

import net.oauth.OAuthAccessor;
import net.oauth.OAuthConsumer;
import net.oauth.OAuthMessage;
import net.oauth.OAuthValidator;
import net.oauth.SimpleOAuthValidator;
import net.oauth.server.OAuthServlet;
import net.oauth.signature.OAuthSignatureMethod;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;

import static org.tsugi.basiclti.BasicLTIUtil.getArray;
import static org.tsugi.basiclti.BasicLTIUtil.getObject;
import static org.tsugi.basiclti.BasicLTIUtil.getString;
import static org.tsugi.basiclti.BasicLTIUtil.getDouble;

// https://www.imsglobal.org/specs/lticiv1p0/specification
/* {
  "@context" : [
    "http://purl.imsglobal.org/ctx/lti/v1/ContentItem",
    {
      "lineItem" : "http://purl.imsglobal.org/ctx/lis/v2/LineItem",
      "res" : "http://purl.imsglobal.org/ctx/lis/v2p1/Result#"
    }
  ],
  "@graph" : [
    { "@type" : "LtiLinkItem",
      "mediaType" : "application/vnd.ims.lti.v1.ltilink",
      "title" : "Chapter 12 quiz",
      "lineItem" : {
        "@type" : "LineItem",
        "label" : "Chapter 12 quiz",
        "reportingMethod" : "res:totalScore",
        "assignedActivity" : {
          "@id" : "http://toolprovider.example.com/assessment/66400",
          "activity_id" : "a-9334df-33"
        },
        "scoreConstraints" : {
          "@type" : "NumericLimits",
          "normalMaximum" : 100,
          "extraCreditMaximum" : 10,
          "totalMaximum" : 110
        }
      },
      { "@type" : "FileItem",
        "url" : "http://www.imsglobal.org/xsd/qti/qtiv2p1/imsqti_v2p1.xsd",
        "copyAdvice" : "true",
        "expiresAt" : "2014-03-05T00:00:00Z",
        "mediaType" : "application/xml",
        "title" : "QTI v2.1 Specification Information Model",
        "placementAdvice" : {
          "windowTarget" : "_blank"
        }
      }
  ]
} */

public class ContentItem {

	public static final String ACCEPT_MEDIA_TYPES = "accept_media_types";
	public static String MEDIA_LTILINKITEM = "application/vnd.ims.lti.v1.ltilink";
	public static String MEDIA_ALL = "*/*";

	public static String ACCEPT_MULTIPLE = "accept_multiple";

	// http://www.iana.org/assignments/media-types/media-types.xhtml
	public static final String MEDIA_CC_1_1 = "application/vnd.ims.imsccv1p1";
	public static final String MEDIA_CC_1_2 = "application/vnd.ims.imsccv1p2";
	public static final String MEDIA_CC_1_3 = "application/vnd.ims.imsccv1p3";
	public static final String MEDIA_CC = MEDIA_CC_1_1+","+MEDIA_CC_1_2+","+MEDIA_CC_1_3;

	public static final String TYPE_LTILINKITEM = "LtiLinkItem";
	public static final String TYPE_CONTENTITEM = "ContentItem";
	public static final String TYPE_FILEITEM = "FileItem";
	public static final String TYPE_IMPORTITEM = "ImportItem";

	public static final String TITLE = "title";
	public static final String TEXT = "text";
	public static final String URL = "url";
	public static final String LINEITEM = "lineItem";
	public static final String SCORE_CONSTRAINTS = "scoreConstraints";
	public static final String SCORE_CONSTRAINTS_NORMAL_MAXIMUM = "normalMaximum";
	public static final String SCORE_CONSTRAINTS_EXTRA_CREDIT_MAXIMUM = "extraCreditMaximum";
	public static final String SCORE_CONSTRAINTS_TOTAL_MAXIMUM = "totalMaximum";
	public static final String CUSTOM = "custom";
	public static final String ICON = "icon";
	public static final String CONTENT_ITEMS = "content_items";
	public static final String NO_CONTENT_ITEMS = "Missing content_items= parameter from ContentItem return";
	public static final String BAD_CONTENT_MESSAGE = "CONTENT_ITEMS is wrong type ";
	public static final String NO_DATA_MESSAGE = "Missing data= parameter from ContentItem return";
	public static final String NO_GRAPH_MESSAGE = "A content_item must include a @graph";
	public static final String BAD_DATA_MESSAGE = "data= parameter is wrong type ";

	HttpServletRequest servletRequest = null;

	private JSONObject contentItem = null;

	private JSONArray graph = null;

	private Properties dataProps = new Properties();

	private String errorMessage = null;

	private String base_string = null;

	/**
	 * We check for the fields essential for the class to operate here.
	 */
	public ContentItem(HttpServletRequest req)
	{
		this.servletRequest = req;

		String contentItems = req.getParameter(CONTENT_ITEMS);
		if ( StringUtils.isEmpty(contentItems) ) {
			throw new java.lang.RuntimeException(NO_CONTENT_ITEMS);
		}

		Object cit = JSONValue.parse(contentItems);
		if ( cit != null && cit instanceof JSONObject ) {
			contentItem = (JSONObject) cit;
		} else {
			throw new java.lang.RuntimeException(BAD_CONTENT_MESSAGE + cit.getClass().getName());
		}

		String returnedData = req.getParameter("data");
		if ( StringUtils.isEmpty(returnedData) ) {
			throw new java.lang.RuntimeException(NO_DATA_MESSAGE);
		}

		returnedData = StringEscapeUtils.unescapeJson(returnedData);

		Object dat = JSONValue.parse(returnedData);
		JSONObject dataJson = null;
		if ( dat != null && dat instanceof JSONObject ) {
			dataJson = (JSONObject) dat;
		} else {
			throw new java.lang.RuntimeException("data= parameter is wrong type "+dat.getClass().getName());
		}

		Iterator it = dataJson.keySet().iterator();
		while (it.hasNext()) {
			String key = (String) it.next();
			Object value = dataJson.get(key);
			if ( value == null || ! (value instanceof String) ) continue;
			dataProps.setProperty(key, (String) value);
		}

		graph = getArray(contentItem,BasicLTIConstants.GRAPH);
		if ( graph == null ) {
			throw new java.lang.RuntimeException(NO_GRAPH_MESSAGE);
		}
	}

	/**
	 * Validate the incoming request
	 *
	 * @param URL The URL of the incoming request.  If this in null, the URL is taken
	 * from the request object.  Sometimes the request object can be misleading when 
	 * sitting behind a load balancer or some kind of proxy.
	 */
	public boolean validate(String oauth_consumer_key, String oauth_secret, String URL)
	{
		String req_oauth_consumer_key = servletRequest.getParameter("oauth_consumer_key");
		if ( req_oauth_consumer_key == null || req_oauth_consumer_key.length() < 1 ) {
			errorMessage = "Missing oauth_consumer_key from incoming request";
			return true;
		}
		if ( ! req_oauth_consumer_key.equals(oauth_consumer_key) ) {
			errorMessage = "Mis-match of oauth_consumer_key from incoming request";
			return false;
		}

		// A URL of null is OK
		OAuthMessage oam = OAuthServlet.getMessage(servletRequest, 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);
		base_string = null;
		try {
			base_string = OAuthSignatureMethod.getBaseString(oam);
		} catch (Exception e) {
			base_string = null;
		}

		try {
			oav.validateMessage(oam, acc);
		} catch (Exception e) {
			errorMessage = e.getLocalizedMessage();
			return false;
		}
		return true;
	}

	/**
	 * Return a string
	 */
	public String toString()
	{
		return contentItem.toString();
	}

	/**
	 * Retrieve the parsed tool_proxy
	 */
	public JSONObject getContentItem()
	{
		return contentItem;
	}

	/**
	 * Retrieve the @graph from the content item
	 */
	public JSONArray getGraph()
	{
		return graph;
	}

	/**
	 * Retrieve a particular type from the graph
	 * 
	 * @param String messageType - Which item type you are looking for
	 */
	public JSONObject getItemOfType(String itemType)
	{
		for ( Object i : graph ) {
			if ( ! (i instanceof JSONObject) ) continue;
			JSONObject item = (JSONObject) i;
			String type = getString(item, BasicLTIConstants.TYPE);
			if ( type == null ) continue;
			if ( type.equals(itemType) ) return item;
		}
		return null;
	}

	/**
	 * Get the data properties from the incoming request
	 */
	public Properties getDataProperties()
	{
		return dataProps;
	}

	/**
	 * Return the base string
	 */
	public String getBaseString()
	{
		return base_string;
	}

	/**
	 * Return the error message
	 */
	public String getErrorMessage()
	{
		return errorMessage;
	}

	/**
	 * Build up a ContentItem launch URL from a base url, return url and extra data
	 */
	public static String buildLaunch(String contentLaunch, String contentReturn, Properties contentData)
	{
		StringBuffer sb = new StringBuffer(contentLaunch);
		if ( contentLaunch.indexOf("?") > 1 ) {
			sb.append("&");
		} else {
			sb.append("?");
		}
		sb.append("contentReturn=");
		sb.append(URLEncoder.encode(contentReturn));
		if ( contentData == null ) return sb.toString();

		Enumeration en = contentData.keys();
		while (en.hasMoreElements()) {
			String key = (String) en.nextElement();
			String value = contentData.getProperty(key);
			if ( value == null ) continue;

			sb.append("&");
			sb.append(URLEncoder.encode(key));
			sb.append("=");
			sb.append(URLEncoder.encode(value));
		}
		return sb.toString();
	}

	/**
	 * Get the maximum score from a lineItem
	 *
	 * This logic is to produce the DeepLink equivalent of maxScore from a Content Item
	 */
	public static Double getScoreMaximum(JSONObject lineItem) {
        if ( lineItem == null ) return null;
        JSONObject scoreConstraints = getObject(lineItem, SCORE_CONSTRAINTS);
        if ( scoreConstraints == null ) return null;
		Double normalMaximum = getDouble(scoreConstraints, SCORE_CONSTRAINTS_NORMAL_MAXIMUM);
		Double totalMaximum = getDouble(scoreConstraints, SCORE_CONSTRAINTS_TOTAL_MAXIMUM);

		if ( totalMaximum == null ) return normalMaximum;
		if ( normalMaximum == null ) return totalMaximum;
		if ( normalMaximum > totalMaximum ) return normalMaximum;
		return totalMaximum;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy