Java tutorial
/** * Copyright 2014 Unicon (R) * 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 ltistarter.lti; import ltistarter.model.*; import ltistarter.repository.AllRepositories; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.transaction.annotation.Transactional; import javax.persistence.Query; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import java.util.*; import org.imsglobal.basiclti.BasicLTIConstants; import org.imsglobal.basiclti.BasicLTIUtil; import org.imsglobal.json.IMSJSONRequest; import org.imsglobal.lti2.objects.Service_offered; import org.imsglobal.lti2.objects.StandardServices; import org.imsglobal.lti2.objects.ToolConsumer; /** * LTI Request object holds all the details for a valid LTI request * (including data populated on the validated launch) * * This basically does everything in lti_db.php from tsugi (except the OAuth stuff, that is handled by spring security) * */ public class LTIRequest { final static Logger log = LoggerFactory.getLogger(LTIRequest.class); static final String LIS_PERSON_PREFIX = "lis_person_name_"; public static final String LTI_CONSUMER_KEY = "oauth_consumer_key"; public static final String LTI_CONTEXT_ID = "context_id"; public static final String LTI_CONTEXT_TITLE = "context_title"; public static final String LTI_CONTEXT_LABEL = "context_label"; public static final String LTI_LINK_ID = "resource_link_id"; public static final String LTI_LINK_TITLE = "resource_link_title"; public static final String LTI_LINK_DESC = "resource_link_description"; public static final String LTI_MESSAGE_TYPE = "lti_message_type"; public static final String LTI_PRES_LOCALE = "launch_presentation_locale"; public static final String LTI_PRES_TARGET = "launch_presentation_document_target"; public static final String LTI_PRES_WIDTH = "launch_presentation_width"; public static final String LTI_PRES_HEIGHT = "launch_presentation_height"; public static final String LTI_PRES_RETURN_URL = "launch_presentation_return_url"; public static final String LTI_SERVICE = "lis_outcome_service_url"; public static final String LTI_SOURCEDID = "lis_result_sourcedid"; public static final String LTI_TOOL_CONSUMER_CODE = "tool_consumer_info_product_family_code"; public static final String LTI_TOOL_CONSUMER_VERSION = "tool_consumer_info_version"; public static final String LTI_TOOL_CONSUMER_NAME = "tool_consumer_instance_name"; public static final String LTI_TOOL_CONSUMER_EMAIL = "tool_consumer_instance_contact_email"; public static final String LTI_USER_ID = "user_id"; public static final String LTI_USER_EMAIL = "lis_person_contact_email_primary"; public static final String LTI_USER_NAME_FULL = LIS_PERSON_PREFIX + "full"; public static final String LTI_USER_IMAGE_URL = "user_image"; public static final String LTI_USER_ROLES = "roles"; public static final String LTI_VERSION = "lti_version"; public static final String USER_ROLE_OVERRIDE = "role_override"; public static final String LTI_MESSAGE_TYPE_BASIC = "basic-lti-launch-request"; public static final String LTI_MESSAGE_TYPE_PROXY_REG = "ToolProxyReregistrationRequest"; public static final String LTI_VERSION_1P0 = "LTI-1p0"; public static final String LTI_VERSION_2P0 = "LTI-2p0"; HttpServletRequest httpServletRequest; // these are populated by the loadLTIDataFromDB operation LtiKeyEntity key; LtiContextEntity context; LtiLinkEntity link; LtiMembershipEntity membership; LtiUserEntity user; LtiServiceEntity service; LtiResultEntity result; //ProfileEntity profile; boolean loaded = false; boolean complete = false; boolean updated = false; int loadingUpdates = 0; // these are populated on construct String ltiContextId; String ltiContextTitle; String ltiContextLabel; String ltiConsumerKey; String ltiLinkId; String ltiLinkTitle; String ltiLinkDescription; Locale ltiPresLocale; String ltiPresTarget; int ltiPresWidth; int ltiPresHeight; String ltiPresReturnUrl; String ltiMessageType; String ltiServiceId; String ltiSourcedid; String ltiToolConsumerCode; String ltiToolConsumerVersion; String ltiToolConsumerName; String ltiToolConsumerEmail; String ltiUserId; String ltiUserEmail; String ltiUserDisplayName; String ltiUserImageUrl; String rawUserRoles; Set<String> ltiUserRoles; int userRoleNumber; String rawUserRolesOverride; String ltiVersion; /** * @param request an http servlet request * @throws IllegalStateException if this is not an LTI request */ public LTIRequest(HttpServletRequest request) { assert request != null : "cannot make an LtiRequest without a request"; this.httpServletRequest = request; // extract the typical LTI data from the request if (!isLTIRequest(request)) { throw new IllegalStateException("Request is not an LTI request"); } processRequestParameters(request); } /** * @param request an http servlet request * @param repos the repos accessor which will be used to load DB data (if possible) for this request * @param update if true then update (or insert) the DB records for this request (else skip DB updating) * @throws IllegalStateException if this is not an LTI request */ public LTIRequest(HttpServletRequest request, AllRepositories repos, boolean update) { this(request); assert repos != null : "AllRepositories cannot be null"; this.loadLTIDataFromDB(repos); if (update) { this.updateLTIDataInDB(repos); } } /** * @param paramName the request parameter name * @return the value of the parameter OR null if there is none */ public String getParam(String paramName) { String value = null; if (this.httpServletRequest != null && paramName != null) { value = StringUtils.trimToNull(this.httpServletRequest.getParameter(paramName)); } return value; } /** * Processes all the parameters in this request into populated internal variables in the LTI Request * * @param request an http servlet request * @return true if this is a complete LTI request (includes key, context, link, user) OR false otherwise */ public boolean processRequestParameters(HttpServletRequest request) { if (request != null && this.httpServletRequest != request) { this.httpServletRequest = request; } assert this.httpServletRequest != null; ltiMessageType = getParam(LTI_MESSAGE_TYPE); ltiVersion = getParam(LTI_VERSION); // These 4 really need to be populated for this LTI request to make any sense... ltiConsumerKey = getParam(LTI_CONSUMER_KEY); ltiContextId = getParam(LTI_CONTEXT_ID); ltiLinkId = getParam(LTI_LINK_ID); ltiUserId = getParam(LTI_USER_ID); complete = checkCompleteLTIRequest(false); // OPTIONAL fields below ltiServiceId = getParam(LTI_SERVICE); ltiSourcedid = getParam(LTI_SOURCEDID); ltiUserEmail = getParam(LTI_USER_EMAIL); ltiUserImageUrl = getParam(LTI_USER_IMAGE_URL); ltiLinkTitle = getParam(LTI_LINK_TITLE); ltiLinkDescription = getParam(LTI_LINK_DESC); ltiContextTitle = getParam(LTI_CONTEXT_TITLE); ltiContextLabel = getParam(LTI_CONTEXT_LABEL); String localeStr = getParam(LTI_PRES_LOCALE); if (localeStr == null) { ltiPresLocale = Locale.getDefault(); } else { ltiPresLocale = Locale.forLanguageTag(localeStr); } ltiPresTarget = getParam(LTI_PRES_TARGET); ltiPresWidth = NumberUtils.toInt(getParam(LTI_PRES_WIDTH), 0); ltiPresHeight = NumberUtils.toInt(getParam(LTI_PRES_HEIGHT), 0); ltiPresReturnUrl = getParam(LTI_PRES_RETURN_URL); ltiToolConsumerCode = getParam(LTI_TOOL_CONSUMER_CODE); ltiToolConsumerVersion = getParam(LTI_TOOL_CONSUMER_VERSION); ltiToolConsumerName = getParam(LTI_TOOL_CONSUMER_NAME); ltiToolConsumerEmail = getParam(LTI_TOOL_CONSUMER_EMAIL); rawUserRoles = getParam(LTI_USER_ROLES); userRoleNumber = makeUserRoleNum(rawUserRoles); String[] splitRoles = StringUtils.split(StringUtils.trimToEmpty(rawUserRoles), ","); ltiUserRoles = new HashSet<>(Arrays.asList(splitRoles)); // user displayName requires some special processing if (getParam(LTI_USER_NAME_FULL) != null) { ltiUserDisplayName = getParam(LTI_USER_NAME_FULL); } else if (getParam(LIS_PERSON_PREFIX + "given") != null && getParam(LIS_PERSON_PREFIX + "family") != null) { ltiUserDisplayName = getParam(LIS_PERSON_PREFIX + "given") + " " + getParam(LIS_PERSON_PREFIX + "family"); } else if (getParam(LIS_PERSON_PREFIX + "given") != null) { ltiUserDisplayName = getParam(LIS_PERSON_PREFIX + "given"); } else if (getParam(LIS_PERSON_PREFIX + "family") != null) { ltiUserDisplayName = getParam(LIS_PERSON_PREFIX + "family"); } return complete; } /** * Loads up the data which is referenced in this LTI request (assuming it can be found in the DB) * * @param repos the repos accessor used to load the data (must be set) * @return true if any data was loaded OR false if none could be loaded (because no matching data was found or the input keys are not set) */ @Transactional public boolean loadLTIDataFromDB(AllRepositories repos) { assert repos != null; loaded = false; if (ltiConsumerKey == null) { // don't even attempt this without a key, it's pointless log.info("LTIload: No key to load results for"); return false; } boolean includesService = (ltiServiceId != null); boolean includesSourcedid = (ltiSourcedid != null); StringBuilder sb = new StringBuilder(); sb.append("SELECT k, c, l, m, u"); //k.keyId, k.keyKey, k.secret, c.contextId, c.title AS contextTitle, l.linkId, l.title AS linkTitle, u.userId, u.displayName AS userDisplayName, u.email AS userEmail, u.subscribe, u.userSha256, m.membershipId, m.role, m.roleOverride"); // 15 if (includesService) { sb.append(", s"); //, s.serviceId, s.serviceKey AS service"); // 2 } if (includesSourcedid) { sb.append(", r"); //, r.resultId, r.sourcedid, r.grade"); // 3 } /* if (includeProfile) { sb.append(", p"); //", p.profileId, p.displayName AS profileDisplayName, p.email AS profileEmail, p.subscribe AS profileSubscribe"); // 4 }*/ sb.append(" FROM LtiKeyEntity k " + "LEFT JOIN k.contexts c ON c.contextSha256 = :context " + // LtiContextEntity "LEFT JOIN c.links l ON l.linkSha256 = :link " + // LtiLinkEntity "LEFT JOIN c.memberships m " + // LtiMembershipEntity "LEFT JOIN m.user u ON u.userSha256 = :user " // LtiUserEntity ); if (includesService) { sb.append(" LEFT JOIN k.services s ON s.serviceSha256 = :service"); // LtiServiceEntity } if (includesSourcedid) { sb.append(" LEFT JOIN u.results r ON r.sourcedidSha256 = :sourcedid"); // LtiResultEntity } /* if (includeProfile) { sb.append(" LEFT JOIN u.profile p"); // ProfileEntity }*/ sb.append(" WHERE k.keySha256 = :key AND (m IS NULL OR (m.context = c AND m.user = u))"); /* if (includeProfile) { sb.append(" AND (u IS NULL OR u.profile = p)"); }*/ String sql = sb.toString(); Query q = repos.entityManager.createQuery(sql); q.setMaxResults(1); q.setParameter("key", BaseEntity.makeSHA256(ltiConsumerKey)); q.setParameter("context", BaseEntity.makeSHA256(ltiContextId)); q.setParameter("link", BaseEntity.makeSHA256(ltiLinkId)); q.setParameter("user", BaseEntity.makeSHA256(ltiUserId)); if (includesService) { q.setParameter("service", BaseEntity.makeSHA256(ltiServiceId)); } if (includesSourcedid) { q.setParameter("sourcedid", BaseEntity.makeSHA256(ltiSourcedid)); } @SuppressWarnings("unchecked") List<Object[]> rows = q.getResultList(); if (rows == null || rows.isEmpty()) { log.info("LTIload: No results found for key=" + ltiConsumerKey); } else { // k, c, l, m, u, s, r Object[] row = rows.get(0); if (row.length > 0) key = (LtiKeyEntity) row[0]; if (row.length > 1) context = (LtiContextEntity) row[1]; if (row.length > 2) link = (LtiLinkEntity) row[2]; if (row.length > 3) membership = (LtiMembershipEntity) row[3]; if (row.length > 4) user = (LtiUserEntity) row[4]; if (includesService && includesSourcedid) { if (row.length > 5) service = (LtiServiceEntity) row[5]; if (row.length > 6) result = (LtiResultEntity) row[6]; } else if (includesService) { if (row.length > 5) service = (LtiServiceEntity) row[5]; } else if (includesSourcedid) { if (row.length > 5) result = (LtiResultEntity) row[5]; } // handle SPECIAL post lookup processing // If there is an appropriate role override variable, we use that role if (membership != null && membership.getRoleOverride() != null) { int roleOverrideNum = membership.getRoleOverride(); if (roleOverrideNum > userRoleNumber) { userRoleNumber = roleOverrideNum; } } // check if the loading resulted in a complete set of LTI data checkCompleteLTIRequest(true); loaded = true; log.info("LTIload: loaded data for key=" + ltiConsumerKey + " and context=" + ltiContextId + ", complete=" + complete); } return loaded; } /** * Attempts to insert or update the various LTI launch data (or other data that is part of this request) * * @param repos the repos accessor used to load the data (must be set) * @return the number of changes (inserts or updates) that occur */ @Transactional public int updateLTIDataInDB(AllRepositories repos) { assert repos != null : "access to the repos is required"; assert loaded : "Data must be loaded before it can be updated"; assert key != null : "Key data must not be null to update data"; repos.entityManager.merge(key); // reconnect the key object for this transaction int inserts = 0; int updates = 0; if (context == null && ltiContextId != null) { LtiContextEntity newContext = new LtiContextEntity(ltiContextId, key, ltiContextTitle, null); context = repos.contexts.save(newContext); inserts++; log.info("LTIupdate: Inserted context id=" + ltiContextId); } else if (context != null) { repos.entityManager.merge(context); // reconnect object for this transaction ltiContextId = context.getContextKey(); log.info("LTIupdate: Reconnected existing context id=" + ltiContextId); } if (link == null && ltiLinkId != null) { LtiLinkEntity newLink = new LtiLinkEntity(ltiLinkId, context, ltiLinkTitle); link = repos.links.save(newLink); inserts++; log.info("LTIupdate: Inserted link id=" + ltiLinkId); } else if (link != null) { repos.entityManager.merge(link); // reconnect object for this transaction ltiLinkId = link.getLinkKey(); log.info("LTIupdate: Reconnected existing link id=" + ltiLinkId); } if (user == null && ltiUserId != null) { LtiUserEntity newUser = new LtiUserEntity(ltiUserId, null); newUser.setDisplayName(ltiUserDisplayName); newUser.setEmail(ltiUserEmail); user = repos.users.save(newUser); inserts++; log.info("LTIupdate: Inserted user id=" + ltiUserId); } else if (user != null) { repos.entityManager.merge(user); // reconnect object for this transaction ltiUserId = user.getUserKey(); ltiUserDisplayName = user.getDisplayName(); ltiUserEmail = user.getEmail(); log.info("LTIupdate: Reconnected existing user id=" + ltiUserId); } if (membership == null && context != null && user != null) { int roleNum = makeUserRoleNum(rawUserRoles); // NOTE: do not use userRoleNumber here, it may have been overridden LtiMembershipEntity newMember = new LtiMembershipEntity(context, user, roleNum); membership = repos.members.save(newMember); inserts++; log.info("LTIupdate: Inserted membership id=" + newMember.getMembershipId() + ", role=" + newMember.getRole() + ", user=" + ltiUserId + ", context=" + ltiContextId); } else if (membership != null) { repos.entityManager.merge(membership); // reconnect object for this transaction ltiUserId = user.getUserKey(); ltiContextId = context.getContextKey(); log.info("LTIupdate: Reconnected existing membership id=" + membership.getMembershipId()); } // We need to handle the case where the service URL changes but we already have a sourcedid boolean serviceCreated = false; if (service == null && ltiServiceId != null && ltiSourcedid != null) { LtiServiceEntity newService = new LtiServiceEntity(ltiServiceId, key, null); service = repos.services.save(newService); inserts++; serviceCreated = true; log.info("LTIupdate: Inserted service id=" + ltiServiceId); } else if (service != null) { repos.entityManager.merge(service); // reconnect object for this transaction ltiServiceId = service.getServiceKey(); log.info("LTIupdate: Reconnected existing service id=" + ltiServiceId); } // If we just created a new service entry but we already had a result entry, update it if (serviceCreated && result != null && ltiServiceId != null && ltiSourcedid != null) { repos.entityManager.merge(result); // reconnect object for this transaction result.setSourcedid(ltiSourcedid); repos.results.save(result); inserts++; log.info("LTIupdate: Updated existing result id=" + result.getResultId() + ", sourcedid=" + ltiSourcedid); } // If we don't have a result but do have a service - link them together if (result == null && service != null && user != null && link != null && ltiServiceId != null && ltiSourcedid != null) { LtiResultEntity newResult = new LtiResultEntity(ltiSourcedid, user, link, null, null); result = repos.results.save(newResult); inserts++; log.info("LTIupdate: Inserted result id=" + result.getResultId()); } else if (result != null) { repos.entityManager.merge(result); // reconnect object for this transaction ltiSourcedid = result.getSourcedid(); } // If we don't have a result and do not have a service - just store the result (prep for LTI 2.0) if (result == null && service == null && user != null && link != null && ltiSourcedid != null) { LtiResultEntity newResult = new LtiResultEntity(ltiSourcedid, user, link, null, null); result = repos.results.save(newResult); inserts++; log.info("LTIupdate: Inserted LTI 2 result id=" + result.getResultId() + ", sourcedid=" + ltiSourcedid); } // Here we handle updates to sourcedid if (result != null && ltiSourcedid != null && !ltiSourcedid.equals(result.getSourcedid())) { result.setSourcedid(ltiSourcedid); result = repos.results.save(result); updates++; log.info("LTIupdate: Updated result (id=" + result.getResultId() + ") sourcedid=" + ltiSourcedid); } // Next we handle updates to context_title, link_title, user_displayname, user_email, or role if (ltiContextTitle != null && context != null && !ltiContextTitle.equals(context.getTitle())) { context.setTitle(ltiContextTitle); context = repos.contexts.save(context); updates++; log.info("LTIupdate: Updated context (id=" + context.getContextId() + ") title=" + ltiContextTitle); } if (ltiLinkTitle != null && link != null && !ltiLinkTitle.equals(link.getTitle())) { link.setTitle(ltiLinkTitle); link = repos.links.save(link); updates++; log.info("LTIupdate: Updated link (id=" + link.getLinkKey() + ") title=" + ltiLinkTitle); } boolean userChanged = false; if (ltiUserDisplayName != null && user != null && !ltiUserDisplayName.equals(user.getDisplayName())) { user.setDisplayName(ltiUserDisplayName); } if (ltiUserEmail != null && user != null && !ltiUserEmail.equals(user.getEmail())) { user.setEmail(ltiUserEmail); } if (userChanged) { user = repos.users.save(user); updates++; log.info("LTIupdate: Updated user (id=" + user.getUserKey() + ") name=" + ltiUserDisplayName + ", email=" + ltiUserEmail); } if (rawUserRoles != null && userRoleNumber != membership.getRole()) { membership.setRole(userRoleNumber); membership = repos.members.save(membership); updates++; log.info("LTIupdate: Updated membership (id=" + membership.getMembershipId() + ", user=" + ltiUserId + ", context=" + ltiContextId + ") roles=" + rawUserRoles + ", role=" + userRoleNumber); } // need to recheck and see if we are complete now checkCompleteLTIRequest(true); loadingUpdates = inserts + updates; updated = true; log.info("LTIupdate: changes=" + loadingUpdates + ", inserts=" + inserts + ", updates=" + updates); return loadingUpdates; } /** * Checks if this LTI request object has a complete set of required LTI data, * also sets the #complete variable appropriately * * @param objects if true then check for complete objects, else just check for complete request params * @return true if complete */ private boolean checkCompleteLTIRequest(boolean objects) { if (objects && key != null && context != null && link != null && user != null) { complete = true; } else if (!objects && ltiConsumerKey != null && ltiContextId != null && ltiLinkId != null && ltiUserId != null) { complete = true; } else { complete = false; } return complete; } public boolean isRoleAdministrator() { return (rawUserRoles != null && userRoleNumber >= 2); } public boolean isRoleInstructor() { return (rawUserRoles != null && userRoleNumber >= 1); } public boolean isRoleLearner() { return (rawUserRoles != null && StringUtils.containsIgnoreCase(rawUserRoles, "learner")); } // STATICS /** * @param request the incoming request * @return true if this is a valid LTI request */ public static boolean isLTIRequest(ServletRequest request) { boolean valid = false; String ltiVersion = StringUtils.trimToNull(request.getParameter(LTI_VERSION)); String ltiMessageType = StringUtils.trimToNull(request.getParameter(LTI_MESSAGE_TYPE)); if (ltiMessageType != null && ltiVersion != null) { boolean goodMessageType = LTI_MESSAGE_TYPE_BASIC.equals(ltiMessageType) || LTI_MESSAGE_TYPE_PROXY_REG.equals(ltiMessageType); boolean goodLTIVersion = LTI_VERSION_1P0.equals(ltiVersion) || LTI_VERSION_2P0.equals(ltiVersion); valid = goodMessageType && goodLTIVersion; } // resource_link_id is also required return valid; } /** * Creates an LTI composite key which can be used to identify a user session consistently * * @param request the incoming request * @param sessionSalt the salt (defaults to a big random string) * @return the composite string (md5) */ public static String makeLTICompositeKey(HttpServletRequest request, String sessionSalt) { if (StringUtils.isBlank(sessionSalt)) { sessionSalt = "A7k254A0itEuQ9ndKJuZ"; } String composite = sessionSalt + "::" + request.getParameter(LTI_CONSUMER_KEY) + "::" + request.getParameter(LTI_CONTEXT_ID) + "::" + request.getParameter(LTI_LINK_ID) + "::" + request.getParameter(LTI_USER_ID) + "::" + (System.currentTimeMillis() / 1800) + request.getHeader("User-Agent") + "::" + request.getContextPath(); return DigestUtils.md5Hex(composite); } /** * @param rawUserRoles the raw roles string (this could also only be part of the string assuming it is the highest one) * @return the number that represents the role (higher is more access) */ public static int makeUserRoleNum(String rawUserRoles) { int roleNum = 0; if (rawUserRoles != null) { String lcRUR = rawUserRoles.toLowerCase(); if (lcRUR.contains("administrator")) { roleNum = 2; } else if (lcRUR.contains("instructor")) { roleNum = 1; } } return roleNum; } // GETTERS public HttpServletRequest getHttpServletRequest() { return httpServletRequest; } public String getLtiContextId() { return ltiContextId; } public String getLtiConsumerKey() { return ltiConsumerKey; } public String getLtiLinkId() { return ltiLinkId; } public String getLtiMessageType() { return ltiMessageType; } public String getLtiServiceId() { return ltiServiceId; } public String getLtiSourcedid() { return ltiSourcedid; } public String getLtiUserId() { return ltiUserId; } public String getLtiVersion() { return ltiVersion; } public LtiKeyEntity getKey() { return key; } public LtiContextEntity getContext() { return context; } public LtiLinkEntity getLink() { return link; } public LtiMembershipEntity getMembership() { return membership; } public LtiUserEntity getUser() { return user; } public LtiServiceEntity getService() { return service; } public LtiResultEntity getResult() { return result; } public String getLtiContextTitle() { return ltiContextTitle; } public String getLtiContextLabel() { return ltiContextLabel; } public String getLtiLinkTitle() { return ltiLinkTitle; } public String getLtiLinkDescription() { return ltiLinkDescription; } public Locale getLtiPresLocale() { return ltiPresLocale; } public String getLtiPresTarget() { return ltiPresTarget; } public int getLtiPresWidth() { return ltiPresWidth; } public int getLtiPresHeight() { return ltiPresHeight; } public String getLtiPresReturnUrl() { return ltiPresReturnUrl; } public String getLtiToolConsumerCode() { return ltiToolConsumerCode; } public String getLtiToolConsumerVersion() { return ltiToolConsumerVersion; } public String getLtiToolConsumerName() { return ltiToolConsumerName; } public String getLtiToolConsumerEmail() { return ltiToolConsumerEmail; } public String getLtiUserEmail() { return ltiUserEmail; } public String getLtiUserDisplayName() { return ltiUserDisplayName; } public String getLtiUserImageUrl() { return ltiUserImageUrl; } public String getRawUserRoles() { return rawUserRoles; } public Set<String> getLtiUserRoles() { return ltiUserRoles; } public int getUserRoleNumber() { return userRoleNumber; } public int getLoadingUpdates() { return loadingUpdates; } public boolean isLoaded() { return loaded; } public boolean isComplete() { return complete; } public boolean isUpdated() { return updated; } }