src/main/java/org/imsglobal/lti2/LTI2Util.java
@SuppressWarnings("unused")
	private static String parseToolProfileInternal(List<Properties> theTools, Properties info, JSONObject jsonObject)
	{
		Object o = null;
		JSONObject tool_profile = (JSONObject) jsonObject.get("tool_profile");
		if ( tool_profile == null  ) {
			return "JSON missing tool_profile";
		}
		JSONObject product_instance = (JSONObject) tool_profile.get("product_instance");
		if ( product_instance == null  ) {
			return "JSON missing product_instance";
		}

		String instance_guid = (String) product_instance.get("guid");
		if ( instance_guid == null  ) {
			return "JSON missing product_info / guid";
		}
		info.put("instance_guid",instance_guid);

		JSONObject product_info = (JSONObject) product_instance.get("product_info");
		if ( product_info == null  ) {
			return "JSON missing product_info";
		}

		// Look for required fields
		JSONObject product_name = product_info == null ? null : (JSONObject) product_info.get("product_name");
		String productTitle = product_name == null ? null : (String) product_name.get("default_value");
		JSONObject description = product_info == null ? null : (JSONObject) product_info.get("description");
		String productDescription = description == null ? null : (String) description.get("default_value");

		JSONObject product_family = product_info == null ? null : (JSONObject) product_info.get("product_family");
		String productCode = product_family == null ? null : (String) product_family.get("code");
		JSONObject product_vendor = product_family == null ? null : (JSONObject) product_family.get("vendor");
		description = product_vendor == null ? null : (JSONObject) product_vendor.get("description");
		String vendorDescription = description == null ? null : (String) description.get("default_value");
		String vendorCode = product_vendor == null ? null : (String) product_vendor.get("code");

		if ( productTitle == null || productDescription == null ) {
			return "JSON missing product_name or description ";
		}
		if ( productCode == null || vendorCode == null || vendorDescription == null ) {
			return "JSON missing product code, vendor code or description";
		}

		info.put("product_name", productTitle);
		info.put("description", productDescription);  // Backwards compatibility
		info.put("product_description", productDescription);
		info.put("product_code", productCode);
		info.put("vendor_code", vendorCode);
		info.put("vendor_description", vendorDescription);

		o = tool_profile.get("base_url_choice");
		if ( ! (o instanceof JSONArray)|| o == null  ) {
			return "JSON missing base_url_choices";
		}
		JSONArray base_url_choices = (JSONArray) o;

		String secure_base_url = null;
		String default_base_url = null;
		for ( Object i : base_url_choices ) {
			JSONObject url_choice = (JSONObject) i;
			secure_base_url = (String) url_choice.get("secure_base_url");
			default_base_url = (String) url_choice.get("default_base_url");
		}
		
		String launch_url = secure_base_url;
		if ( launch_url == null ) launch_url = default_base_url;
		if ( launch_url == null ) {
			return "Unable to determine launch URL";
		}

		o = (JSONArray) tool_profile.get("resource_handler");
		if ( ! (o instanceof JSONArray)|| o == null  ) {
			return "JSON missing resource_handlers";
		}
		JSONArray resource_handlers = (JSONArray) o;

		// Loop through resource handlers, read, and check for errors
		for(Object i : resource_handlers ) {
			JSONObject resource_handler = (JSONObject) i;
			JSONObject resource_type_json = (JSONObject) resource_handler.get("resource_type");
			String resource_type_code = (String) resource_type_json.get("code");
			if ( resource_type_code == null ) {
				return "JSON missing resource_type code";
			}
			o = (JSONArray) resource_handler.get("message");
			if ( ! (o instanceof JSONArray)|| o == null ) {
				return "JSON missing resource_handler / message";
			}
			JSONArray messages = (JSONArray) o;

			JSONObject titleObject = (JSONObject) resource_handler.get("name");
			String title = titleObject == null ? null : (String) titleObject.get("default_value");
			if ( title == null || titleObject == null ) {
				return "JSON missing resource_handler / name / default_value";
			}

			JSONObject buttonObject = (JSONObject) resource_handler.get("short_name");
			String button = buttonObject == null ? null : (String) buttonObject.get("default_value");
		
			JSONObject descObject = (JSONObject) resource_handler.get("description");
			String resourceDescription = descObject == null ? null : (String) descObject.get("default_value");

			String path = null;
			JSONArray parameter = null;
			JSONArray enabled_capability = null; 
			for ( Object m : messages ) {
				JSONObject message = (JSONObject) m;
				String message_type = (String) message.get("message_type");
				if ( ! "basic-lti-launch-request".equals(message_type) ) continue;
				if ( path != null ) {
					return "A resource_handler cannot have more than one basic-lti-launch-request message RT="+resource_type_code;
				}
				path = (String) message.get("path");
				if ( path == null ) {
					return "A basic-lti-launch-request message must have a path RT="+resource_type_code;
				} 
				o = (JSONArray) message.get("parameter");
				if ( ! (o instanceof JSONArray)) {
					return "Must be an array: parameter RT="+resource_type_code;
				}
				parameter = (JSONArray) o;

				o = (JSONArray) message.get("enabled_capability");
				if ( ! (o instanceof JSONArray)) {
					return "Must be an array: enabled_capability RT="+resource_type_code;
				}
				enabled_capability = (JSONArray) o;
			}

			// Ignore everything except launch handlers
			if ( path == null ) continue;

			// Check the URI
			String thisLaunch = launch_url;
			if ( ! thisLaunch.endsWith("/") && ! path.startsWith("/") ) thisLaunch = thisLaunch + "/";
			thisLaunch = thisLaunch + path;
			try {
				URL url = new URL(thisLaunch);
			} catch ( Exception e ) {
				return "Bad launch URL="+thisLaunch;
			}

			// Passed all the tests...  Lets keep it...
			Properties theTool = new Properties();

			theTool.put("resource_type", resource_type_code); // Backwards compatibility
			theTool.put("resource_type_code", resource_type_code);
			if ( title == null ) title = productTitle;
			if ( title != null ) theTool.put("title", title);
			if ( button != null ) theTool.put("button", button);
			if ( resourceDescription == null ) resourceDescription = productDescription;
			if ( resourceDescription != null ) theTool.put("description", resourceDescription);
			if ( parameter != null ) theTool.put("parameter", parameter.toString());
			if ( enabled_capability != null ) theTool.put("enabled_capability", enabled_capability.toString());
			theTool.put("launch", thisLaunch);
			theTools.add(theTool);
		}
		return null;  // All good
	}
@SuppressWarnings("unused")
	private static String parseToolProfileInternal(List<Properties> theTools, Properties info, JSONObject jsonObject)
	{
		Object o = null;
		JSONObject tool_profile = (JSONObject) jsonObject.get("tool_profile");
		if ( tool_profile == null  ) {
			return "JSON missing tool_profile";
		}
		JSONObject product_instance = (JSONObject) tool_profile.get("product_instance");
		if ( product_instance == null  ) {
			return "JSON missing product_instance";
		}

		String instance_guid = (String) product_instance.get("guid");
		if ( instance_guid == null  ) {
			return "JSON missing product_info / guid";
		}
		info.put("instance_guid",instance_guid);

		JSONObject product_info = (JSONObject) product_instance.get("product_info");
		if ( product_info == null  ) {
			return "JSON missing product_info";
		}

		// Look for required fields
		JSONObject product_name = product_info == null ? null : (JSONObject) product_info.get("product_name");
		String productTitle = product_name == null ? null : (String) product_name.get("default_value");
		JSONObject description = product_info == null ? null : (JSONObject) product_info.get("description");
		String productDescription = description == null ? null : (String) description.get("default_value");

		JSONObject product_family = product_info == null ? null : (JSONObject) product_info.get("product_family");
		String productCode = product_family == null ? null : (String) product_family.get("code");
		JSONObject product_vendor = product_family == null ? null : (JSONObject) product_family.get("vendor");
		description = product_vendor == null ? null : (JSONObject) product_vendor.get("description");
		String vendorDescription = description == null ? null : (String) description.get("default_value");
		String vendorCode = product_vendor == null ? null : (String) product_vendor.get("code");

		if ( productTitle == null || productDescription == null ) {
			return "JSON missing product_name or description ";
		}
		if ( productCode == null || vendorCode == null || vendorDescription == null ) {
			return "JSON missing product code, vendor code or description";
		}

		info.put("product_name", productTitle);
		info.put("description", productDescription);  // Backwards compatibility
		info.put("product_description", productDescription);
		info.put("product_code", productCode);
		info.put("vendor_code", vendorCode);
		info.put("vendor_description", vendorDescription);

		o = tool_profile.get("base_url_choice");
		if ( ! (o instanceof JSONArray)|| o == null  ) {
			return "JSON missing base_url_choices";
		}
		JSONArray base_url_choices = (JSONArray) o;

		String secure_base_url = null;
		String default_base_url = null;
		for ( Object i : base_url_choices ) {
			JSONObject url_choice = (JSONObject) i;
			secure_base_url = (String) url_choice.get("secure_base_url");
			default_base_url = (String) url_choice.get("default_base_url");
		}
		
		String launch_url = secure_base_url;
		if ( launch_url == null ) launch_url = default_base_url;
		if ( launch_url == null ) {
			return "Unable to determine launch URL";
		}

		o = (JSONArray) tool_profile.get("resource_handler");
		if ( ! (o instanceof JSONArray)|| o == null  ) {
			return "JSON missing resource_handlers";
		}
		JSONArray resource_handlers = (JSONArray) o;

		// Loop through resource handlers, read, and check for errors
		for(Object i : resource_handlers ) {
			JSONObject resource_handler = (JSONObject) i;
			JSONObject resource_type_json = (JSONObject) resource_handler.get("resource_type");
			String resource_type_code = (String) resource_type_json.get("code");
			if ( resource_type_code == null ) {
				return "JSON missing resource_type code";
			}
			o = (JSONArray) resource_handler.get("message");
			if ( ! (o instanceof JSONArray)|| o == null ) {
				return "JSON missing resource_handler / message";
			}
			JSONArray messages = (JSONArray) o;

			JSONObject titleObject = (JSONObject) resource_handler.get("name");
			String title = titleObject == null ? null : (String) titleObject.get("default_value");
			if ( title == null || titleObject == null ) {
				return "JSON missing resource_handler / name / default_value";
			}

			JSONObject buttonObject = (JSONObject) resource_handler.get("short_name");
			String button = buttonObject == null ? null : (String) buttonObject.get("default_value");
		
			JSONObject descObject = (JSONObject) resource_handler.get("description");
			String resourceDescription = descObject == null ? null : (String) descObject.get("default_value");

			String path = null;
			JSONArray parameter = null;
			JSONArray enabled_capability = null; 
			for ( Object m : messages ) {
				JSONObject message = (JSONObject) m;
				String message_type = (String) message.get("message_type");
				if ( ! "basic-lti-launch-request".equals(message_type) ) continue;
				if ( path != null ) {
					return "A resource_handler cannot have more than one basic-lti-launch-request message RT="+resource_type_code;
				}
				path = (String) message.get("path");
				if ( path == null ) {
					return "A basic-lti-launch-request message must have a path RT="+resource_type_code;
				} 
				o = (JSONArray) message.get("parameter");
				if ( ! (o instanceof JSONArray)) {
					return "Must be an array: parameter RT="+resource_type_code;
				}
				parameter = (JSONArray) o;

				o = (JSONArray) message.get("enabled_capability");
				if ( ! (o instanceof JSONArray)) {
					return "Must be an array: enabled_capability RT="+resource_type_code;
				}
				enabled_capability = (JSONArray) o;
			}

			// Ignore everything except launch handlers
			if ( path == null ) continue;

			// Check the URI
			String thisLaunch = launch_url;
			if ( ! thisLaunch.endsWith("/") && ! path.startsWith("/") ) thisLaunch = thisLaunch + "/";
			thisLaunch = thisLaunch + path;
			try {
				URL url = new URL(thisLaunch);
			} catch ( Exception e ) {
				return "Bad launch URL="+thisLaunch;
			}

			// Passed all the tests...  Lets keep it...
			Properties theTool = new Properties();

			theTool.put("resource_type", resource_type_code); // Backwards compatibility
			theTool.put("resource_type_code", resource_type_code);
			if ( title == null ) title = productTitle;
			if ( title != null ) theTool.put("title", title);
			if ( button != null ) theTool.put("button", button);
			if ( resourceDescription == null ) resourceDescription = productDescription;
			if ( resourceDescription != null ) theTool.put("description", resourceDescription);
			if ( parameter != null ) theTool.put("parameter", parameter.toString());
			if ( enabled_capability != null ) theTool.put("enabled_capability", enabled_capability.toString());
			theTool.put("launch", thisLaunch);
			theTools.add(theTool);
		}
		return null;  // All good
	}
Parse a provider profile with lots of error checking...