Index: lams_build/3rdParty.userlibraries =================================================================== diff -u -rae4e479f1a2faa602f74d22096f14db9232d97e1 -r700058382334580a4a183013cfaad01e0b0ca51a --- lams_build/3rdParty.userlibraries (.../3rdParty.userlibraries) (revision ae4e479f1a2faa602f74d22096f14db9232d97e1) +++ lams_build/3rdParty.userlibraries (.../3rdParty.userlibraries) (revision 700058382334580a4a183013cfaad01e0b0ca51a) @@ -11,6 +11,10 @@ + + + + Index: lams_common/conf/hibernate/mappings/org/lamsfoundation/lams/integration/ExtServerOrgMap.hbm.xml =================================================================== diff -u -rd27ed028b0e16c263776418b7bce22099fed4eed -r700058382334580a4a183013cfaad01e0b0ca51a --- lams_common/conf/hibernate/mappings/org/lamsfoundation/lams/integration/ExtServerOrgMap.hbm.xml (.../ExtServerOrgMap.hbm.xml) (revision d27ed028b0e16c263776418b7bce22099fed4eed) +++ lams_common/conf/hibernate/mappings/org/lamsfoundation/lams/integration/ExtServerOrgMap.hbm.xml (.../ExtServerOrgMap.hbm.xml) (revision 700058382334580a4a183013cfaad01e0b0ca51a) @@ -64,6 +64,14 @@ column="serverdesc" length="65535" /> + + getAllExtServerOrgMaps(); + + /** + * @return all available tool consumers + */ + List getAllToolConsumers(); void saveExtServerOrgMap(ExtServerOrgMap map); @@ -143,6 +172,24 @@ void saveExtServerToolAdapterMap(ExtServerToolAdapterMap map); void deleteExtServerToolAdapterMap(ExtServerToolAdapterMap map); + + /** + * Creates new ExtServerLessonMap object. Method is suitable for creating lessons via integration servers. + * + * @param lessonId + * @param extServer + */ + void createExtServerLessonMap(Long lessonId, ExtServerOrgMap extServer); + + /** + * Creates new ExtServerLessonMap object. Method is suitable for creating lessons via LTI tool consumers as long as + * they provide resourceLinkId as a parameter and not lessonId. + * + * @param lessonId + * @param resourceLinkId resource_link_id parameter sent by LTI tool consumer + * @param extServer + */ + void createExtServerLessonMap(Long lessonId, String resourceLinkId, ExtServerOrgMap extServer); /** * Checks whether the lesson was created from extServer and returns lessonFinishCallbackUrl if it's not blank. Index: lams_common/src/java/org/lamsfoundation/lams/integration/service/IntegrationService.java =================================================================== diff -u -r445a4dce899e983de37328df3219fbed8ab78f9d -r700058382334580a4a183013cfaad01e0b0ca51a --- lams_common/src/java/org/lamsfoundation/lams/integration/service/IntegrationService.java (.../IntegrationService.java) (revision 445a4dce899e983de37328df3219fbed8ab78f9d) +++ lams_common/src/java/org/lamsfoundation/lams/integration/service/IntegrationService.java (.../IntegrationService.java) (revision 700058382334580a4a183013cfaad01e0b0ca51a) @@ -33,6 +33,7 @@ import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder; +import java.security.GeneralSecurityException; import java.text.ParseException; import java.util.ArrayList; import java.util.Date; @@ -44,6 +45,9 @@ import org.apache.log4j.Logger; import org.apache.tomcat.util.json.JSONArray; import org.apache.tomcat.util.json.JSONObject; +import org.imsglobal.pox.IMSPOXRequest; +import org.lamsfoundation.lams.gradebook.GradebookUserLesson; +import org.lamsfoundation.lams.gradebook.service.IGradebookService; import org.lamsfoundation.lams.integration.ExtCourseClassMap; import org.lamsfoundation.lams.integration.ExtServerLessonMap; import org.lamsfoundation.lams.integration.ExtServerOrgMap; @@ -57,6 +61,7 @@ import org.lamsfoundation.lams.integration.util.LoginRequestDispatcher; import org.lamsfoundation.lams.lesson.Lesson; import org.lamsfoundation.lams.lesson.service.ILessonService; +import org.lamsfoundation.lams.tool.service.ILamsCoreToolService; import org.lamsfoundation.lams.usermanagement.AuthenticationMethod; import org.lamsfoundation.lams.usermanagement.Organisation; import org.lamsfoundation.lams.usermanagement.OrganisationState; @@ -72,6 +77,8 @@ import org.lamsfoundation.lams.util.ValidationUtil; import org.lamsfoundation.lams.util.WebUtil; +import oauth.signpost.exception.OAuthException; + /** *

* View Source @@ -83,24 +90,35 @@ private static Logger log = Logger.getLogger(IntegrationService.class); + private IGradebookService gradebookService; private IUserManagementService service; private ILessonService lessonService; + private ILamsCoreToolService toolService; + /** + * Returns integration server or LTI tool consumer by its human-entered server key/server id. + * + * @param serverId server key/server id + * @param isIntegrationServer true in case this is an integration server, false - LTI tool consumer + * @return + */ @Override public ExtServerOrgMap getExtServerOrgMap(String serverId) { - List list = service.findByProperty(ExtServerOrgMap.class, "serverid", serverId); + Map properties = new HashMap(); + properties.put("serverid", serverId); + List list = service.findByProperties(ExtServerOrgMap.class, properties); if (list == null || list.size() == 0) { return null; } else { - return (ExtServerOrgMap) list.get(0); + return list.get(0); } } @Override - public ExtCourseClassMap getExtCourseClassMap(Integer extServerOrgMapId, String extCourseId) { + public ExtCourseClassMap getExtCourseClassMap(Integer sid, String extCourseId) { Map properties = new HashMap(); properties.put("courseid", extCourseId); - properties.put("extServerOrgMap.sid", extServerOrgMapId); + properties.put("extServerOrgMap.sid", sid); List list = service.findByProperties(ExtCourseClassMap.class, properties); if (list == null || list.size() == 0) { @@ -500,9 +518,18 @@ } @Override - public List getAllExtServerOrgMaps() { - return service.findAll(ExtServerOrgMap.class); + public List getAllExtServerOrgMaps() { + Map properties = new HashMap(); + properties.put("serverTypeId", ExtServerOrgMap.INTEGRATION_SERVER_TYPE); + return service.findByProperties(ExtServerOrgMap.class, properties); } + + @Override + public List getAllToolConsumers() { + Map properties = new HashMap(); + properties.put("serverTypeId", ExtServerOrgMap.LTI_CONSUMER_SERVER_TYPE); + return service.findByProperties(ExtServerOrgMap.class, properties); + } @Override @SuppressWarnings("unchecked") @@ -547,9 +574,16 @@ return (ExtServerOrgMap) service.findById(ExtServerOrgMap.class, sid); } + @Override public void createExtServerLessonMap(Long lessonId, ExtServerOrgMap extServer) { + createExtServerLessonMap(lessonId, null, extServer);; + } + + @Override + public void createExtServerLessonMap(Long lessonId, String resourceLinkId, ExtServerOrgMap extServer) { ExtServerLessonMap map = new ExtServerLessonMap(); map.setLessonId(lessonId); + map.setResourceLinkId(resourceLinkId); map.setExtServer(extServer); service.save(map); } @@ -567,23 +601,71 @@ // checks whether the lesson was created from extServer and whether it has lessonFinishCallbackUrl setting if (extServerLesson != null && StringUtils.isNotBlank(extServerLesson.getExtServer().getLessonFinishUrl())) { - ExtServerOrgMap serverMap = extServerLesson.getExtServer(); + ExtServerOrgMap server = extServerLesson.getExtServer(); - ExtUserUseridMap extUserUseridMap = getExtUserUseridMapByUserId(serverMap, user.getUserId()); - if (extUserUseridMap != null) { - String extUsername = extUserUseridMap.getExtUsername(); + ExtUserUseridMap extUser = getExtUserUseridMapByUserId(server, user.getUserId()); + if (extUser != null) { + String extUsername = extUser.getExtUsername(); - // construct real lessonFinishCallbackUrl - lessonFinishCallbackUrl = serverMap.getLessonFinishUrl(); - String timestamp = Long.toString(new Date().getTime()); - String hash = hash(serverMap, extUsername, timestamp); - String encodedExtUsername = URLEncoder.encode(extUsername, "UTF8"); + //return URL in case of integration server + if (server.getServerTypeId().equals(ExtServerOrgMap.INTEGRATION_SERVER_TYPE)) { + // construct real lessonFinishCallbackUrl + lessonFinishCallbackUrl = server.getLessonFinishUrl(); + String timestamp = Long.toString(new Date().getTime()); + String hash = hash(server, extUsername, timestamp); + String encodedExtUsername = URLEncoder.encode(extUsername, "UTF8"); - // set the values for the parameters - lessonFinishCallbackUrl = lessonFinishCallbackUrl.replaceAll("%username%", encodedExtUsername) - .replaceAll("%lessonid%", lessonId.toString()).replaceAll("%timestamp%", timestamp) - .replaceAll("%hash%", hash); - log.debug(lessonFinishCallbackUrl); + // set the values for the parameters + lessonFinishCallbackUrl = lessonFinishCallbackUrl.replaceAll("%username%", encodedExtUsername) + .replaceAll("%lessonid%", lessonId.toString()).replaceAll("%timestamp%", timestamp) + .replaceAll("%hash%", hash); + log.debug(lessonFinishCallbackUrl); + + // in case of LTI Tool Consumer - create a new thread to report score back to LMS (in order to do this task in parallel not to slow down later work) + } else { + + // calculate lesson's MaxPossibleMark + Long lessonMaxPossibleMark = toolService.getLessonMaxPossibleMark(lesson); + GradebookUserLesson gradebookUserLesson = gradebookService.getGradebookUserLesson(lessonId, + user.getUserId()); + Double userTotalMark = (gradebookUserLesson == null) || (gradebookUserLesson.getMark() == null) + ? null : gradebookUserLesson.getMark(); + + final String lessonFinishUrl = server.getLessonFinishUrl(); + if (userTotalMark != null && StringUtils.isNotBlank(lessonFinishUrl)) { + + Double score = lessonMaxPossibleMark.equals(0L) ? 0 : userTotalMark / lessonMaxPossibleMark; + final String scoreStr = (userTotalMark == null) || lessonMaxPossibleMark.equals(0L) ? "" + : score.toString(); + + final String serverKey = server.getServerid(); + final String serverSecret = server.getServerkey(); + final String tcGradebookId = extUser.getTcGradebookId(); + final ExtUserUseridMap extUserFinal = extUser; + + Thread preaddLearnersMonitorsThread = new Thread(new Runnable() { + @Override + public void run() { + //send Request directly + try { + IMSPOXRequest.sendReplaceResult(lessonFinishUrl, serverKey, serverSecret, + tcGradebookId, scoreStr); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (OAuthException e) { + throw new RuntimeException(e); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + log.debug("Score (" + scoreStr + ") posted to Tool Consumer (serverKey:" + serverKey + + "). extUsername:" + extUserFinal.getExtUsername()); + } + }, "LAMS_sendScoresLTI_thread"); + preaddLearnersMonitorsThread.start(); + + } + } + } } } @@ -743,8 +825,19 @@ properties.put("extServerOrgMap.sid", sid); properties.put("organisation.organisationId", lesson.getOrganisation().getOrganisationId()); List list = service.findByProperties(ExtCourseClassMap.class, properties); - return list == null || list.isEmpty() ? null : list.get(0); + + return (list == null || list.isEmpty()) ? null : list.get(0); } + + @Override + public ExtServerLessonMap getLtiConsumerLesson(String serverId, String resourceLinkId) { + Map properties = new HashMap(); + properties.put("extServer.serverid", serverId); + properties.put("resourceLinkId", resourceLinkId); + List list = service.findByProperties(ExtServerLessonMap.class, properties); + + return (list == null || list.isEmpty()) ? null : list.get(0); + } private ExtServerLessonMap getExtServerLessonMap(Long lessonId) { List list = service.findByProperty(ExtServerLessonMap.class, "lessonId", lessonId); @@ -787,4 +880,13 @@ public ILessonService getLessonService() { return lessonService; } + + public void setGradebookService(IGradebookService gradebookService) { + this.gradebookService = gradebookService; + } + + public void setToolService(ILamsCoreToolService toolService) { + this.toolService = toolService; + } + } \ No newline at end of file Index: lams_common/src/java/org/lamsfoundation/lams/integration/util/LtiUtils.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/integration/util/LtiUtils.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/integration/util/LtiUtils.java (revision 700058382334580a4a183013cfaad01e0b0ca51a) @@ -0,0 +1,79 @@ +package org.lamsfoundation.lams.integration.util; + +import java.util.LinkedList; +import java.util.List; + +public class LtiUtils { + + public static final String OAUTH_CONSUMER_KEY = "oauth_consumer_key"; + + /** + * Return true if the user is an administrator. + * {Method added by LAMS} + * + * @return true if the user has a role of administrator + */ + public static boolean isAdmin(String roles) { + List rolesToSearchFor = new LinkedList(); + rolesToSearchFor.add("urn:lti:role:ims/lis/Administrator"); + rolesToSearchFor.add("urn:lti:sysrole:ims/lis/SysAdmin"); + rolesToSearchFor.add("urn:lti:sysrole:ims/lis/Administrator"); + rolesToSearchFor.add("urn:lti:instrole:ims/lis/Administrator"); + + return hasRole(roles, rolesToSearchFor); + } + + /** + * Return true if the user is staff. + * {Method added by LAMS} + * + * @return true if the user has a role of instructor, contentdeveloper or teachingassistant + */ + public static boolean isStaff(String roles) { + List rolesToSearchFor = new LinkedList(); + rolesToSearchFor.add("urn:lti:role:ims/lis/Instructor"); + rolesToSearchFor.add("urn:lti:role:ims/lis/ContentDeveloper"); + rolesToSearchFor.add("urn:lti:role:ims/lis/TeachingAssistant"); + + return hasRole(roles, rolesToSearchFor); + } + + /** + * Return true if the user is a learner. + * {Method added by LAMS} + * + * @return true if the user has a role of learner + */ + public static boolean isLearner(String roles) { + List rolesToSearchFor = new LinkedList(); + rolesToSearchFor.add("urn:lti:role:ims/lis/Learner"); + + return hasRole(roles, rolesToSearchFor); + } + + /* + * Check whether the user has a specified role name. + * {Method added by LAMS} + * + * @param role + * Name of role + * + * @return true if the user has the specified role + */ + private static boolean hasRole(String roles, List rolesToSearchFor) { + String[] roleArray = roles.split(","); + + boolean hasRole = false; + for (String role : roleArray) { + for (String roleToSearchFor : rolesToSearchFor) { + if (role.equals(roleToSearchFor)) { + hasRole = true; + break; + } + } + } + + return hasRole; + } + +} Index: lams_common/src/java/org/lamsfoundation/lams/integrationContext.xml =================================================================== diff -u -rd27ed028b0e16c263776418b7bce22099fed4eed -r700058382334580a4a183013cfaad01e0b0ca51a --- lams_common/src/java/org/lamsfoundation/lams/integrationContext.xml (.../integrationContext.xml) (revision d27ed028b0e16c263776418b7bce22099fed4eed) +++ lams_common/src/java/org/lamsfoundation/lams/integrationContext.xml (.../integrationContext.xml) (revision 700058382334580a4a183013cfaad01e0b0ca51a) @@ -2,8 +2,10 @@ - + + + Index: lams_common/src/java/org/lamsfoundation/lams/tool/service/ILamsCoreToolService.java =================================================================== diff -u -rde44e2e970afac102177634d2e9106919f65f773 -r700058382334580a4a183013cfaad01e0b0ca51a --- lams_common/src/java/org/lamsfoundation/lams/tool/service/ILamsCoreToolService.java (.../ILamsCoreToolService.java) (revision de44e2e970afac102177634d2e9106919f65f773) +++ lams_common/src/java/org/lamsfoundation/lams/tool/service/ILamsCoreToolService.java (.../ILamsCoreToolService.java) (revision 700058382334580a4a183013cfaad01e0b0ca51a) @@ -281,6 +281,14 @@ * @return activity's max possible mark if available, null otherwise */ Long getActivityMaxPossibleMark(ToolActivity activity); + + /** + * Calculates lesson's maximum possible mark by adding up all activities max marks. + * + * @param lesson + * @return + */ + Long getLessonMaxPossibleMark(Lesson lesson); /** * Update the tool session data. Index: lams_common/src/java/org/lamsfoundation/lams/tool/service/LamsCoreToolService.java =================================================================== diff -u -rde44e2e970afac102177634d2e9106919f65f773 -r700058382334580a4a183013cfaad01e0b0ca51a --- lams_common/src/java/org/lamsfoundation/lams/tool/service/LamsCoreToolService.java (.../LamsCoreToolService.java) (revision de44e2e970afac102177634d2e9106919f65f773) +++ lams_common/src/java/org/lamsfoundation/lams/tool/service/LamsCoreToolService.java (.../LamsCoreToolService.java) (revision 700058382334580a4a183013cfaad01e0b0ca51a) @@ -32,6 +32,7 @@ import org.lamsfoundation.lams.learningdesign.Activity; import org.lamsfoundation.lams.learningdesign.ActivityEvaluation; import org.lamsfoundation.lams.learningdesign.ToolActivity; +import org.lamsfoundation.lams.learningdesign.dao.IActivityDAO; import org.lamsfoundation.lams.lesson.Lesson; import org.lamsfoundation.lams.tool.SystemTool; import org.lamsfoundation.lams.tool.Tool; @@ -74,6 +75,7 @@ // Instance variables // --------------------------------------------------------------------- private ApplicationContext context; + private IActivityDAO activityDAO; private IToolSessionDAO toolSessionDAO; private ISystemToolDAO systemToolDAO; private ToolContentIDGenerator contentIDGenerator; @@ -87,6 +89,10 @@ public void setApplicationContext(ApplicationContext context) throws BeansException { this.context = context; } + + public void setActivityDAO(IActivityDAO activityDAO) { + this.activityDAO = activityDAO; + } /** * @param toolSessionDAO @@ -559,7 +565,52 @@ } return null; } + + @Override + public Long getLessonMaxPossibleMark(Lesson lesson) { + // calculate lesson's MaxPossibleMark + Set activities = getLessonActivities(lesson); + Long lessonMaxPossibleMark = 0L; + for (ToolActivity activity : activities) { + Long activityMaxPossibleMark = getActivityMaxPossibleMark(activity); + if (activityMaxPossibleMark != null) { + lessonMaxPossibleMark += activityMaxPossibleMark; + } + } + return lessonMaxPossibleMark; + } + + /** + * Returns lesson tool activities. It works almost the same as lesson.getLearningDesign().getActivities() except it + * solves problem with first activity unable to cast to ToolActivity. + */ + @SuppressWarnings("unchecked") + private Set getLessonActivities(Lesson lesson) { + Set activities = new TreeSet(); + Set toolActivities = new TreeSet(); + /* + * Hibernate CGLIB is failing to load the first activity in the sequence as a ToolActivity for some mysterious + * reason Causes a ClassCastException when you try to cast it, even if it is a ToolActivity. + * + * THIS IS A HACK to retrieve the first tool activity manually so it can be cast as a ToolActivity - if it is + * one + */ + Activity firstActivity = activityDAO + .getActivityByActivityId(lesson.getLearningDesign().getFirstActivity().getActivityId()); + activities.add(firstActivity); + activities.addAll(lesson.getLearningDesign().getActivities()); + + for (Activity activity : activities) { + if (activity instanceof ToolActivity) { + ToolActivity toolActivity = (ToolActivity) activity; + toolActivities.add(toolActivity); + } + } + + return toolActivities; + } + @Override public void updateToolSession(ToolSession toolSession) { toolSessionDAO.updateToolSession(toolSession); Index: lams_common/src/java/org/lamsfoundation/lams/toolApplicationContext.xml =================================================================== diff -u -rde44e2e970afac102177634d2e9106919f65f773 -r700058382334580a4a183013cfaad01e0b0ca51a --- lams_common/src/java/org/lamsfoundation/lams/toolApplicationContext.xml (.../toolApplicationContext.xml) (revision de44e2e970afac102177634d2e9106919f65f773) +++ lams_common/src/java/org/lamsfoundation/lams/toolApplicationContext.xml (.../toolApplicationContext.xml) (revision 700058382334580a4a183013cfaad01e0b0ca51a) @@ -45,6 +45,7 @@ +