Index: lams_bb_integration/WEB-INF/web.xml =================================================================== diff -u -r45cb68e6656a9b0cf23271124292469a499632e9 -r54843a5ae3288a977cdfa75fd43afc9c67831624 --- lams_bb_integration/WEB-INF/web.xml (.../web.xml) (revision 45cb68e6656a9b0cf23271124292469a499632e9) +++ lams_bb_integration/WEB-INF/web.xml (.../web.xml) (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -2,63 +2,63 @@ ConfigServlet - org.lamsfoundation.ld.integration.blackboard.ConfigServlet + org.lamsfoundation.bb.integration.servlet.ConfigServlet UserDataServlet - org.lamsfoundation.ld.integration.blackboard.UserDataServlet + org.lamsfoundation.bb.integration.servlet.UserDataServlet GroupDataServlet - org.lamsfoundation.ld.integration.blackboard.GroupDataServlet + org.lamsfoundation.bb.integration.servlet.GroupDataServlet LamsLearningDesignServlet - org.lamsfoundation.ld.integration.blackboard.LamsLearningDesignServlet + org.lamsfoundation.bb.integration.servlet.LamsLearningDesignServlet LamsLearningDesignDeleteServlet - org.lamsfoundation.ld.integration.blackboard.LamsLearningDesignDeleteServlet + org.lamsfoundation.bb.integration.servlet.LamsLearningDesignDeleteServlet LinkToolsServlet - org.lamsfoundation.ld.integration.blackboard.LinkToolsServlet + org.lamsfoundation.bb.integration.servlet.LinkToolsServlet GradebookServlet - org.lamsfoundation.ld.integration.blackboard.GradebookServlet + org.lamsfoundation.bb.integration.servlet.GradebookServlet GradebookSyncServlet - org.lamsfoundation.ld.integration.blackboard.GradebookSyncServlet + org.lamsfoundation.bb.integration.servlet.GradebookSyncServlet GradebookSyncFixServlet - org.lamsfoundation.ld.integration.blackboard.GradebookSyncFixServlet + org.lamsfoundation.bb.integration.servlet.GradebookSyncFixServlet LessonManagerServlet - org.lamsfoundation.ld.integration.blackboard.LessonManagerServlet + org.lamsfoundation.bb.integration.servlet.LessonManagerServlet LearnerMonitorServlet - org.lamsfoundation.ld.integration.blackboard.LearnerMonitorServlet + org.lamsfoundation.bb.integration.servlet.LearnerMonitorServlet OpenLamsPageServlet - org.lamsfoundation.ld.integration.blackboard.OpenLamsPageServlet + org.lamsfoundation.bb.integration.servlet.OpenLamsPageServlet RenderDesignImageServlet - org.lamsfoundation.ld.integration.blackboard.RenderDesignImageServlet + org.lamsfoundation.bb.integration.servlet.RenderDesignImageServlet StartLessonAjaxServlet - org.lamsfoundation.ld.integration.blackboard.StartLessonAjaxServlet + org.lamsfoundation.bb.integration.servlet.StartLessonAjaxServlet UpdateServerUrlServlet - org.lamsfoundation.ld.integration.blackboard.UpdateServerUrlServlet + org.lamsfoundation.bb.integration.servlet.UpdateServerUrlServlet Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/Constants.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/Constants.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/Constants.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,62 @@ +/**************************************************************** + * Copyright (C) 2007 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ +package org.lamsfoundation.bb.integration; + +/** + * Constants used for blackboard integration + * + * @author Luke Foxton + */ +public class Constants { + + public static final String PARAM_USER_ID = "uid"; + public static final String PARAM_SERVER_ID = "sid"; + public static final String PARAM_TIMESTAMP = "ts"; + public static final String PARAM_HASH = "hash"; + // public static final String PARAM_URL = "url"; + public static final String PARAM_METHOD = "method"; + public static final String PARAM_LESSON_ID = "lsId"; + public static final String PARAM_LEARNING_DESIGN_ID = "ldid"; + public static final String PARAM_COURSE_ID = "course_id"; + public static final String PARAM_FOLDER_ID = "folderId"; + public static final String PARAM_IS_USER_DETAILS_REQUIRED = "isUserDetailsRequired"; + + public static final String SERVLET_LOGIN_REQUEST = "/lams/LoginRequest"; + public static final String SERVLET_ACTION_REQUEST = "/LamsActionRequest"; + + public static final String URLDECODER_CODING = "US-ASCII"; + + public static final String METHOD_AUTHOR = "author"; + public static final String METHOD_MONITOR = "monitor"; + public static final String METHOD_LEARNER = "learner"; + + public static final String GRADEBOOK_LINEITEM_TYPE = "LAMS grades"; + public static final int GRADEBOOK_POINTS_POSSIBLE = 100; + + // XML format constnats + public static final String ELEM_FOLDER = "Folder"; + public static final String ELEM_LEARNING_DESIGN = "LearningDesign"; + public static final String ATTR_NAME = "name"; + public static final String ATTR_RESOURCE_ID = "resourceId"; + +} Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/dto/LearnerProgressDTO.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/dto/LearnerProgressDTO.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/dto/LearnerProgressDTO.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,114 @@ +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + + +package org.lamsfoundation.bb.integration.dto; + +public class LearnerProgressDTO { + + private Long lessonId; + private String lessonName; + private String userName; + private String lastName; + private String firstName; + private Integer learnerId; + private int activityCount; + private int attemptedActivities; + private int activitiesCompleted; + private boolean lessonComplete; + + public LearnerProgressDTO(int activityCount, int attemptedActivities, int activitiesCompleted, + boolean lessonComplete) { + this.lessonId = lessonId; + this.lessonName = lessonName; + this.userName = userName; + this.lastName = lastName; + this.firstName = firstName; + this.learnerId = learnerId; + this.activityCount = activityCount; + this.attemptedActivities = attemptedActivities; + this.activitiesCompleted = activitiesCompleted; + this.lessonComplete = lessonComplete; + } + + /** + * @return Returns the learnerId. + */ + public Integer getLearnerId() { + return learnerId; + } + + /** + * @return Returns the lessonId. + */ + public Long getLessonId() { + return lessonId; + } + + /** + * @return Returns the lessonName. + */ + public String getLessonName() { + return lessonName; + } + + /** + * @return Returns the userName. + */ + public String getUserName() { + return userName; + } + + /** + * @return Returns the attemptedActivities. + */ + public int getAttemptedActivities() { + return attemptedActivities; + } + + /** + * @return Returns the activitiesCompleted. + */ + public int getActivitiesCompleted() { + return activitiesCompleted; + } + + /** + * @return Returns the activityCount. + */ + public int getActivityCount() { + return activityCount; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public boolean getLessonComplete() { + return lessonComplete; + } + +} Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/ConfigServlet.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/ConfigServlet.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/ConfigServlet.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,125 @@ +package org.lamsfoundation.bb.integration.servlet; + +import java.io.IOException; +import java.text.ParseException; +import java.util.Properties; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.parsers.ParserConfigurationException; + +import org.lamsfoundation.bb.integration.util.LamsPluginUtil; +import org.xml.sax.SAXException; + +import blackboard.base.InitializationException; +import blackboard.data.ValidationException; +import blackboard.persist.PersistenceException; +import blackboard.platform.BbServiceException; +import blackboard.platform.plugin.PlugInException; +import blackboard.platform.plugin.PlugInUtil; + +/** + * Handles displaying and modification of the LAMS BB plugin's config settings. + */ +@SuppressWarnings("serial") +public class ConfigServlet extends HttpServlet{ + + public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + process(request, response); + } + + public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + process(request, response); + } + + private void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + // SECURITY! + // Authorise current user for System Admin (automatic redirect) + try { + if (!PlugInUtil.authorizeForSystemAdmin(request, response)) + return; + } catch (PlugInException e) { + throw new RuntimeException(e); + } + + try { + String method = request.getParameter("method"); + if (method.equals("showConfigSettings")) { + showConfigSettings(request, response); + + } else if (method.equals("saveConfigSettings")) { + saveConfigSettings(request, response); + } + + } catch (InitializationException e) { + throw new ServletException(e); + } catch (BbServiceException e) { + throw new ServletException(e); + } catch (PersistenceException e) { + throw new ServletException(e); + } catch (ParseException e) { + throw new ServletException(e); + } catch (ValidationException e) { + throw new ServletException(e); + } catch (ParserConfigurationException e) { + throw new ServletException(e); + } catch (SAXException e) { + throw new ServletException(e); + } + } + + /** + * Show /admin/config.jsp page. + */ + private void showConfigSettings(HttpServletRequest request, HttpServletResponse response) + throws InitializationException, BbServiceException, PersistenceException, IOException, ServletException { + + // Get the LAMS2 Building Block properties from Blackboard (if set) + Properties properties = LamsPluginUtil.getProperties(); + + String lamsServerUrl = properties.getProperty(LamsPluginUtil.PROP_LAMS_URL, "http://"); + request.setAttribute("lamsServerUrl", lamsServerUrl); + + String lamsServerId = properties.getProperty(LamsPluginUtil.PROP_LAMS_SERVER_ID, ""); + request.setAttribute("lamsServerId", lamsServerId); + + String secretKey = properties.getProperty(LamsPluginUtil.PROP_LAMS_SECRET_KEY, ""); + request.setAttribute("secretKey", secretKey); + + String lamsServerTimeRefreshInterval = properties.getProperty(LamsPluginUtil.PROP_LAMS_SERVER_TIME_REFRESH_INTERVAL); + request.setAttribute("lamsServerTimeRefreshInterval", lamsServerTimeRefreshInterval); + + request.getRequestDispatcher("/admin/config.jsp").forward(request, response); + } + + /** + * Saves modified configuration settings. + */ + private void saveConfigSettings(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException, PersistenceException, ParseException, ValidationException, + ParserConfigurationException, SAXException { + + // Get the properties object + Properties properties = LamsPluginUtil.getProperties(); + + // Get the LAMS2 Building Block properties from the request + String lamsServerUrl = request.getParameter("lamsServerUrl"); + String lamsServerId = request.getParameter("lamsServerId"); + String lamsSecretKey = request.getParameter("lamsSecretKey"); + String lamsServerTimeRefreshInterval = request.getParameter("lamsServerTimeRefreshInterval"); + + // Save the properties to Blackboard + properties.setProperty(LamsPluginUtil.PROP_LAMS_URL, lamsServerUrl); + properties.setProperty(LamsPluginUtil.PROP_LAMS_SECRET_KEY, lamsSecretKey); + properties.setProperty(LamsPluginUtil.PROP_LAMS_SERVER_ID, lamsServerId); + properties.setProperty(LamsPluginUtil.PROP_LAMS_SERVER_TIME_REFRESH_INTERVAL, lamsServerTimeRefreshInterval); + + // Persist the properties object + LamsPluginUtil.setProperties(properties); + + request.getRequestDispatcher("/admin/configSuccess.jsp").forward(request, response); + } +} Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/GradebookServlet.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/GradebookServlet.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/GradebookServlet.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,260 @@ +/** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * http://www.gnu.org/licenses/gpl.txt + */ +package org.lamsfoundation.bb.integration.servlet; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.net.ConnectException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.lamsfoundation.bb.integration.Constants; +import org.lamsfoundation.bb.integration.util.LamsPluginUtil; +import org.lamsfoundation.bb.integration.util.LamsSecurityUtil; +import org.lamsfoundation.bb.integration.util.LineitemUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +import blackboard.base.InitializationException; +import blackboard.data.ValidationException; +import blackboard.data.course.CourseMembership; +import blackboard.data.gradebook.Lineitem; +import blackboard.data.gradebook.Score; +import blackboard.data.user.User; +import blackboard.persist.Id; +import blackboard.persist.KeyNotFoundException; +import blackboard.persist.PersistenceException; +import blackboard.persist.course.CourseMembershipDbLoader; +import blackboard.persist.gradebook.ScoreDbLoader; +import blackboard.persist.user.UserDbLoader; +import blackboard.platform.BbServiceException; +import blackboard.platform.BbServiceManager; +import blackboard.platform.context.ContextManager; + +/** + * Deals with Blackboard Grade Center. + */ +public class GradebookServlet extends HttpServlet { + + private static final long serialVersionUID = -3587062723412672084L; + private static Logger logger = LoggerFactory.getLogger(GradebookServlet.class); + + /** + * Receives call from Lams ab lesson completion. After that get the latest marks for this user in this lesson and stores it in DB. + */ + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + ContextManager ctxMgr = null; + + try { + // get Blackboard context + ctxMgr = (ContextManager) BbServiceManager.lookupService(ContextManager.class); + UserDbLoader userLoader = UserDbLoader.Default.getInstance(); + CourseMembershipDbLoader courseMembershipLoader = CourseMembershipDbLoader.Default.getInstance(); + ScoreDbLoader scoreLoader = ScoreDbLoader.Default.getInstance(); + + // get Parameter values + String userName = request.getParameter(Constants.PARAM_USER_ID); + String timeStamp = request.getParameter(Constants.PARAM_TIMESTAMP); + String hash = request.getParameter(Constants.PARAM_HASH); + String lamsLessonIdParam = request.getParameter(Constants.PARAM_LESSON_ID); + + // check parameters + if (userName == null || timeStamp == null || hash == null || lamsLessonIdParam == null) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "missing expected parameters"); + return; + } + + //check user rights + String secretKey = LamsPluginUtil.getServerSecretKey(); + String serverId = LamsPluginUtil.getServerId(); + if (!LamsSecurityUtil.sha1( + timeStamp.toLowerCase() + userName.toLowerCase() + serverId.toLowerCase() + + secretKey.toLowerCase()).equals(hash)) { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "authentication failed"); + return; + } + + // get user list, but no role info since there are no course info + User user = userLoader.loadByUserName(userName); + if (user == null) { + throw new ServletException("User not found with userName:" + userName); + } + Id userId = user.getId(); + + //allow lessonComplete.jsp on LAMS side to make an Ajax call to this servlet + String serverUrlWithLamsWord = LamsPluginUtil.getServerUrl(); + URI uri = new URI(serverUrlWithLamsWord); + //strip out '/lams/' from the end of the URL + String serverUrl = serverUrlWithLamsWord.lastIndexOf(uri.getPath()) == -1 ? serverUrlWithLamsWord + : serverUrlWithLamsWord.substring(0, serverUrlWithLamsWord.lastIndexOf(uri.getPath())); + response.addHeader("Access-Control-Allow-Origin", serverUrl); + + Lineitem lineitem = LineitemUtil.getLineitem(userId, lamsLessonIdParam, false); + //notifying LAMS that servlet finished its work without exceptions but the score wasn't saved + if (lineitem == null) { + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + out.write("No Lineitem object found"); + return; + } + // do not remove the following line: it's required to instantiate the object + logger.info("Record score for " + lineitem.getName() + " lesson. It now has " + lineitem.getScores().size() + + " scores."); + + String getLamsMarkURL = LamsPluginUtil.getServerUrl() + "/services/xml/LessonManager?" + + LamsSecurityUtil.generateAuthenticateParameters(userName) + + "&method=gradebookMarksUser" + + "&lsId=" + lamsLessonIdParam + + "&outputsUser=" + URLEncoder.encode(userName, "UTF8"); + URL url = new URL(getLamsMarkURL); + URLConnection conn = url.openConnection(); + if (!(conn instanceof HttpURLConnection)) { + throw new RuntimeException("Unable to open connection to: " + getLamsMarkURL); + } + HttpURLConnection httpConn = (HttpURLConnection) conn; + if (httpConn.getResponseCode() != HttpURLConnection.HTTP_OK) { + String errorMsg = "HTTP Response Code: " + httpConn.getResponseCode() + ", HTTP Response Message: " + + httpConn.getResponseMessage(); + logger.error(errorMsg); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, errorMsg); + return; + } + + // InputStream is = url.openConnection().getInputStream(); + InputStream is = conn.getInputStream(); + // parse xml response + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document document = db.parse(is); + + Node lesson = document.getDocumentElement().getFirstChild(); + Node learnerResult = lesson.getFirstChild(); + + // store new score + CourseMembership courseMembership = courseMembershipLoader.loadByCourseAndUserId(lineitem.getCourseId(), + userId); + Score currentScore = null; + try { + currentScore = scoreLoader.loadByCourseMembershipIdAndLineitemId(courseMembership.getId(), + lineitem.getId()); + } catch (KeyNotFoundException c) { + currentScore = new Score(); + currentScore.setLineitemId(lineitem.getId()); + currentScore.setCourseMembershipId(courseMembership.getId()); + } + + //updates and persists currentScore in the DB + LineitemUtil.updateScoreBasedOnLamsResponse(lesson, learnerResult, currentScore); + + //notifying LAMS that score has been stored successfully + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + out.write("OK"); + + //the following paragraph is kept due to the Ernie's request to keep it for potential usage in the future +// NodeList activities = document.getDocumentElement().getFirstChild().getChildNodes(); +// +// float maxResult = 0; +// float userResult = 0; +// for (int i = 0; i < activities.getLength(); i++) { +// Node activity = activities.item(i); +// +// for (int j = 0; j < activity.getChildNodes().getLength(); j++) { +// +// Node toolOutput = activity.getChildNodes().item(j); +// String toolOutputName = toolOutput.getAttributes().getNamedItem("name").getNodeValue(); +// // The only numeric outputs we get from LAMS are for the MCQ and Assessment activities +// // learner.total.score = Assessment +// // learner.mark = MCQ +// if ("learner.mark".equals(toolOutputName) || "learner.total.score".equals(toolOutputName)) { +// String userResultStr = toolOutput.getAttributes().getNamedItem("output").getNodeValue(); +// String maxResultStr = toolOutput.getAttributes().getNamedItem("marksPossible").getNodeValue(); +// +// userResult += Float.parseFloat(userResultStr); +// maxResult += Float.parseFloat(maxResultStr); +// } +// } +// +// } + + } catch (MalformedURLException e) { + throw new ServletException("Unable to get LAMS learning designs, bad URL: " + + ", please check lams.properties", e); + } catch (IllegalStateException e) { + throw new ServletException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (ConnectException e) { + throw new ServletException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (UnsupportedEncodingException e) { + throw new ServletException(e); + } catch (IOException e) { + throw new ServletException(e); + } catch (ParserConfigurationException e) { + throw new ServletException(e); + } catch (SAXException e) { + throw new ServletException(e); + } catch (URISyntaxException e) { + throw new ServletException(e); + } catch (PersistenceException e) { + throw new ServletException(e); + } catch (ValidationException e) { + throw new ServletException(e); + } catch (InitializationException e) { + throw new ServletException(e); + } catch (BbServiceException e) { + throw new ServletException(e); + } finally { + // make sure context is released + if (ctxMgr != null) { + ctxMgr.releaseContext(); + } + } + + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + doGet(req, resp); + } + +} Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/GradebookSyncFixServlet.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/GradebookSyncFixServlet.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/GradebookSyncFixServlet.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,220 @@ +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + + +package org.lamsfoundation.bb.integration.servlet; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.net.ConnectException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.lamsfoundation.bb.integration.Constants; +import org.lamsfoundation.bb.integration.util.LamsBuildingBlockException; +import org.lamsfoundation.bb.integration.util.LamsPluginUtil; +import org.lamsfoundation.bb.integration.util.LamsSecurityUtil; +import org.lamsfoundation.bb.integration.util.LineitemUtil; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import blackboard.base.InitializationException; +import blackboard.data.course.CourseMembership; +import blackboard.data.gradebook.Lineitem; +import blackboard.data.gradebook.Score; +import blackboard.persist.Id; +import blackboard.persist.PersistenceException; +import blackboard.persist.course.CourseMembershipDbLoader; +import blackboard.persist.gradebook.ScoreDbLoader; +import blackboard.persist.gradebook.ScoreDbPersister; +import blackboard.platform.BbServiceException; +import blackboard.platform.BbServiceManager; +import blackboard.platform.context.Context; +import blackboard.platform.context.ContextManager; + +/** + * Fixes issues created by GradebookSyncServlet, namely, removes marks of users that had not completed the lesson. + */ +@SuppressWarnings("serial") +public class GradebookSyncFixServlet extends HttpServlet { + + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + ContextManager ctxMgr = null; + int numberDeletedScores = 0; + + try { + // get Blackboard context + ctxMgr = (ContextManager) BbServiceManager.lookupService(ContextManager.class); + Context ctx = ctxMgr.setContext(request); + CourseMembershipDbLoader courseMemLoader = CourseMembershipDbLoader.Default.getInstance(); + ScoreDbLoader scoreLoader = ScoreDbLoader.Default.getInstance(); + ScoreDbPersister scorePersister = ScoreDbPersister.Default.getInstance(); + + // get Parameter values + String lamsLessonIdParam = request.getParameter(Constants.PARAM_LESSON_ID); + // validate method parameter + if (lamsLessonIdParam == null) { + throw new RuntimeException("Requred parameters missing. lsid=" + lamsLessonIdParam); + } + + Lineitem lineitem = LineitemUtil.getLineitem(ctx.getUserId(), lamsLessonIdParam, true); + if (lineitem == null) { + throw new ServletException("Lineitem was not found for userId:" + ctx.getUserId() + " and lamsLessonId:" + lamsLessonIdParam); + } + + String username = ctx.getUser().getUserName(); + String serviceURL = LamsPluginUtil.getServerUrl() + "/services/xml/LessonManager?" + + LamsSecurityUtil.generateAuthenticateParameters(username) + + "&method=gradebookMarksLesson&lsId=" + lamsLessonIdParam; + + URL url = new URL(serviceURL); + URLConnection conn = url.openConnection(); + if (!(conn instanceof HttpURLConnection)) { + throw new RuntimeException("Unable to open connection to: " + serviceURL); + } + + HttpURLConnection httpConn = (HttpURLConnection) conn; + + if (httpConn.getResponseCode() != HttpURLConnection.HTTP_OK) { + String errorMsg = "HTTP Response Code: " + httpConn.getResponseCode() + ", HTTP Response Message: " + + httpConn.getResponseMessage(); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write(errorMsg); + return; + } + + InputStream is = conn.getInputStream(); + + // parse xml response + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document document = db.parse(is); + Node lesson = document.getDocumentElement().getFirstChild(); + NodeList learnerResults = lesson.getChildNodes(); + + //in order to reduce DB queries we get scores and courseMemberships all at once + List dbScores = scoreLoader.loadByLineitemId(lineitem.getId()); + List courseMemberships = courseMemLoader.loadByCourseId(lineitem.getCourseId(), null, true); + + //removes marks of users that had not completed the lesson + for (CourseMembership courseMembership: courseMemberships) { + Id courseMembershipId = courseMembership.getId(); + String userName = courseMembership.getUser().getUserName(); + + // find old score + Score currentScore = null; + for (Score dbScore : dbScores) { + if (dbScore.getCourseMembershipId().equals(courseMembershipId)) { + currentScore = dbScore; + break; + } + } + + // determine whether user had finished the lesson + boolean isUserHadFinishedLesson = false; + for (int i = 0; i < learnerResults.getLength(); i++) { + Node learnerResult = learnerResults.item(i); + + String extUsername = learnerResult.getAttributes().getNamedItem("extUsername").getNodeValue(); + + if (userName.equals(extUsername)) { + isUserHadFinishedLesson = true; + break; + } + } + + //remove marks of the user that had not completed the lesson. + if ((currentScore != null) && !isUserHadFinishedLesson) { + scorePersister.deleteById(currentScore.getId()); + + //calculate how many marks are removed + numberDeletedScores++; + } + } + + + } catch (LamsBuildingBlockException e) { + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + out.write("Exception was thrown: " + e.getMessage()); + return; + + } catch (MalformedURLException e) { + throw new ServletException("Unable to get LAMS learning designs, bad URL: " + + ", please check lams.properties", e); + } catch (IllegalStateException e) { + throw new ServletException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (ConnectException e) { + throw new ServletException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (UnsupportedEncodingException e) { + throw new ServletException(e); + } catch (IOException e) { + throw new ServletException(e); + } catch (ParserConfigurationException e) { + throw new ServletException(e); + } catch (SAXException e) { + throw new ServletException(e); + } catch (PersistenceException e) { + throw new ServletException(e); + } catch (InitializationException e) { + throw new ServletException(e); + } catch (BbServiceException e) { + throw new ServletException(e); + } finally { + // make sure context is released + if (ctxMgr != null) { + ctxMgr.releaseContext(); + } + } + + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + out.write("Complete! " + numberDeletedScores + " marks have been removed from Blackboard Gradecenter."); + + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + doGet(req, resp); + } + +} Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/GradebookSyncServlet.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/GradebookSyncServlet.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/GradebookSyncServlet.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,238 @@ +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + + +package org.lamsfoundation.bb.integration.servlet; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.net.ConnectException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.lamsfoundation.bb.integration.Constants; +import org.lamsfoundation.bb.integration.util.LamsBuildingBlockException; +import org.lamsfoundation.bb.integration.util.LamsPluginUtil; +import org.lamsfoundation.bb.integration.util.LamsSecurityUtil; +import org.lamsfoundation.bb.integration.util.LineitemUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import blackboard.base.InitializationException; +import blackboard.data.ValidationException; +import blackboard.data.course.CourseMembership; +import blackboard.data.gradebook.Lineitem; +import blackboard.data.gradebook.Score; +import blackboard.persist.Id; +import blackboard.persist.PersistenceException; +import blackboard.persist.course.CourseMembershipDbLoader; +import blackboard.persist.gradebook.ScoreDbLoader; +import blackboard.platform.BbServiceException; +import blackboard.platform.BbServiceManager; +import blackboard.platform.context.Context; +import blackboard.platform.context.ContextManager; +import blackboard.util.StringUtil; + +/** + * Monitor on BB side calls this servlet to syncronize lesson marks with LAMS Gradebook. + */ +public class GradebookSyncServlet extends HttpServlet { + + private static final long serialVersionUID = -3587062723412672084L; + private static Logger logger = LoggerFactory.getLogger(GradebookSyncServlet.class); + + /** + * Monitor on BB side calls this servlet to syncronize lesson marks with LAMS Gradebook. After that get the latest + * marks for this user in this lesson and stores it in DB. + */ + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + ContextManager ctxMgr = null; + int numberUpdatedScores = 0; + + try { + // get Blackboard context + ctxMgr = (ContextManager) BbServiceManager.lookupService(ContextManager.class); + Context ctx = ctxMgr.setContext(request); + CourseMembershipDbLoader courseMemLoader = CourseMembershipDbLoader.Default.getInstance(); + ScoreDbLoader scoreLoader = ScoreDbLoader.Default.getInstance(); + + // get Parameter values + String lamsLessonIdParam = request.getParameter(Constants.PARAM_LESSON_ID); + // validate method parameter + if (lamsLessonIdParam == null) { + throw new RuntimeException("Requred parameters missing. lsid=" + lamsLessonIdParam); + } + + Lineitem lineitem = LineitemUtil.getLineitem(ctx.getUserId(), lamsLessonIdParam, true); + if (lineitem == null) { + throw new ServletException("Lineitem was not found for userId:" + ctx.getUserId() + " and lamsLessonId:" + lamsLessonIdParam); + } + + String username = ctx.getUser().getUserName(); + String serviceURL = LamsPluginUtil.getServerUrl() + "/services/xml/LessonManager?" + + LamsSecurityUtil.generateAuthenticateParameters(username) + + "&method=gradebookMarksLesson&lsId=" + lamsLessonIdParam; + + URL url = new URL(serviceURL); + URLConnection conn = url.openConnection(); + if (!(conn instanceof HttpURLConnection)) { + throw new RuntimeException("Unable to open connection to: " + serviceURL); + } + + HttpURLConnection httpConn = (HttpURLConnection) conn; + + if (httpConn.getResponseCode() != HttpURLConnection.HTTP_OK) { + String errorMsg = "HTTP Response Code: " + httpConn.getResponseCode() + ", HTTP Response Message: " + + httpConn.getResponseMessage(); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write(errorMsg); + return; + } + + InputStream is = conn.getInputStream(); + + // parse xml response + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document document = db.parse(is); + Node lesson = document.getDocumentElement().getFirstChild(); + NodeList learnerResults = lesson.getChildNodes(); + + //in order to reduce DB queries we get scores and courseMemberships all at once + List dbScores = scoreLoader.loadByLineitemId(lineitem.getId()); + List courseMemberships = courseMemLoader.loadByCourseId(lineitem.getCourseId(), null, true); + + //update all marks + for (CourseMembership courseMembership: courseMemberships) { + Id courseMembershipId = courseMembership.getId(); + String userName = courseMembership.getUser().getUserName(); + + // find old score + Score currentScore = null; + for (Score dbScore : dbScores) { + if (dbScore.getCourseMembershipId().equals(courseMembershipId)) { + currentScore = dbScore; + break; + } + } + + //find new score + for (int i = 0; i < learnerResults.getLength(); i++) { + Node learnerResult = learnerResults.item(i); + + String extUsername = learnerResult.getAttributes().getNamedItem("extUsername").getNodeValue(); + + if (userName.equals(extUsername)) { + + //update old score + if (currentScore == null) { + currentScore = new Score(); + currentScore.setLineitemId(lineitem.getId()); + currentScore.setCourseMembershipId(courseMembershipId); + } + + //updates and persists currentScore in the DB + double gradebookMark = LineitemUtil.updateScoreBasedOnLamsResponse(lesson, learnerResult, + currentScore); + + //calculate how many marks updated + if (StringUtil.isEmpty(currentScore.getGrade()) + || !new Double(currentScore.getGrade()).equals(new Double(gradebookMark))) { + numberUpdatedScores++; + } + + break; + } + } + } + + + } catch (LamsBuildingBlockException e) { + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + out.write("Exception was thrown: " + e.getMessage()); + return; + + } catch (MalformedURLException e) { + throw new ServletException("Unable to get LAMS learning designs, bad URL: " + + ", please check lams.properties", e); + } catch (IllegalStateException e) { + throw new ServletException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (ConnectException e) { + throw new ServletException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (UnsupportedEncodingException e) { + throw new ServletException(e); + } catch (IOException e) { + throw new ServletException(e); + } catch (ParserConfigurationException e) { + throw new ServletException(e); + } catch (SAXException e) { + throw new ServletException(e); + } catch (PersistenceException e) { + throw new ServletException(e); + } catch (ValidationException e) { + throw new ServletException(e); + } catch (InitializationException e) { + throw new ServletException(e); + } catch (BbServiceException e) { + throw new ServletException(e); + } finally { + // make sure context is released + if (ctxMgr != null) { + ctxMgr.releaseContext(); + } + } + + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + out.write("Complete! " + numberUpdatedScores + " marks have been updated/added to Blackboard Gradecenter."); + + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + doGet(req, resp); + } + +} Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/GroupDataServlet.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/GroupDataServlet.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/GroupDataServlet.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,174 @@ +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + + +package org.lamsfoundation.bb.integration.servlet; + +import java.io.IOException; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.lamsfoundation.bb.integration.Constants; +import org.lamsfoundation.bb.integration.util.LamsPluginUtil; +import org.lamsfoundation.bb.integration.util.LamsSecurityUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import blackboard.data.course.Course; +import blackboard.data.course.Group; +import blackboard.data.user.User; +import blackboard.persist.BbPersistenceManager; +import blackboard.persist.Id; +import blackboard.persist.PersistenceException; +import blackboard.persist.course.CourseDbLoader; +import blackboard.persist.course.GroupDbLoader; +import blackboard.persist.user.UserDbLoader; +import blackboard.platform.persistence.PersistenceServiceFactory; + +/** + * Fetch groups of the specified course. Serves 2 different types of calls: 1-initial request for group names; + * 2-sequential request for selected group users. + * + * @author Andrey Balan + */ +public class GroupDataServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + private static Logger logger = LoggerFactory.getLogger(GroupDataServlet.class); + + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + try { + + // get Parameter values + String usernameParam = request.getParameter(Constants.PARAM_USER_ID); + String tsParam = request.getParameter(Constants.PARAM_TIMESTAMP); + String hashParam = request.getParameter(Constants.PARAM_HASH); + String courseIdParam = request.getParameter(Constants.PARAM_COURSE_ID); + String[] groupIdsParam = request.getParameterValues("extGroupIds"); + + // check paramaeters + if (usernameParam == null || tsParam == null || hashParam == null || courseIdParam == null) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "missing expected parameters"); + return; + } + + String secretKey = LamsPluginUtil.getServerSecretKey(); + String serverId = LamsPluginUtil.getServerId(); + + if (!LamsSecurityUtil.sha1( + tsParam.toLowerCase() + usernameParam.toLowerCase() + serverId.toLowerCase() + + secretKey.toLowerCase()).equals(hashParam)) { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "authentication failed"); + return; + } + + // get the persistence manager + BbPersistenceManager bbPm = PersistenceServiceFactory.getInstance().getDbPersistenceManager(); + CourseDbLoader cLoader = CourseDbLoader.Default.getInstance(); + GroupDbLoader groupLoader = (GroupDbLoader) bbPm.getLoader(GroupDbLoader.TYPE); + UserDbLoader userDbLoader = (UserDbLoader) bbPm.getLoader(UserDbLoader.TYPE); +// Id courseId = bbPm.generateId(Course.DATA_TYPE, courseIdParam); + + //create JSON objects to return + Course course = cLoader.loadByCourseId(courseIdParam); + List groups = groupLoader.loadAvailableByCourseId(course.getId()); + JsonArray jsonGroups = new JsonArray(); + + //in case groupIds is supplied - it means we need to provide groups along with all users + if (groupIdsParam != null && groupIdsParam.length > 0) { + + for (String groupIdParam : groupIdsParam) { + for (Group group : groups) { + //only groups with ids requested by LAMS should be processed + Id groupId = group.getId(); + if (!groupId.toExternalString().equals(groupIdParam)) { + continue; + } + + JsonObject jsonGroup = new JsonObject(); + jsonGroup.addProperty("groupId", groupId.toExternalString()); + jsonGroup.addProperty("groupName", group.getTitle()); + jsonGroups.add(jsonGroup); + + JsonArray jsonUsers = new JsonArray(); + jsonGroup.add("users", jsonUsers); + + List users = userDbLoader.loadByGroupId(groupId, null, true); + for (User user : users) { + JsonObject jsonUser = new JsonObject(); + jsonUsers.add(jsonUser); + + jsonUser.addProperty("userName", user.getUserName()); + + // The CSV list should be the format below + // ,<First name>,<Last name>,<Address>,<City>,<State>, + // <Postcode>,<Country>,<Day time number>,<Mobile number>, + // <Fax number>,<Email>,<Locale language>,<Locale country> + jsonUser.addProperty("1", user.getTitle()); + jsonUser.addProperty("2", user.getGivenName()); + jsonUser.addProperty("3", user.getFamilyName()); + jsonUser.addProperty("4", user.getStreet1() + user.getStreet2()); + jsonUser.addProperty("5", user.getCity()); + jsonUser.addProperty("6", user.getState()); + jsonUser.addProperty("7", user.getZipCode()); + jsonUser.addProperty("8", user.getCountry()); + jsonUser.addProperty("9", user.getHomePhone1()); + jsonUser.addProperty("10", user.getMobilePhone()); + jsonUser.addProperty("11", user.getBusinessFax()); + jsonUser.addProperty("12", user.getEmailAddress()); + String locale = user.getLocale(); + String localeLang = LamsSecurityUtil.getLanguage(locale); + String localeCountry = LamsSecurityUtil.getCountry(locale); + jsonUser.addProperty("13", localeLang); + jsonUser.addProperty("14", localeCountry); + } + } + } + } else { + for (Group group : groups) { + Id groupId = group.getId(); + JsonObject jsonGroup = new JsonObject(); + jsonGroup.addProperty("groupId", groupId.toExternalString()); + jsonGroup.addProperty("groupName", group.getTitle()); + jsonGroup.addProperty("groupSize", group.getGroupMemberships().size()); + jsonGroups.add(jsonGroup); + + } + } + + response.getWriter().write(jsonGroups.toString()); + + } catch (PersistenceException e) { + throw new ServletException("Failed to fetch course's groups", e); + } + } + +} + Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/LamsLearningDesignDeleteServlet.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/LamsLearningDesignDeleteServlet.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/LamsLearningDesignDeleteServlet.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,111 @@ +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + + +package org.lamsfoundation.bb.integration.servlet; + +import java.io.IOException; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.lamsfoundation.bb.integration.util.LamsPluginUtil; +import org.lamsfoundation.bb.integration.util.LamsSecurityUtil; + +import blackboard.base.InitializationException; +import blackboard.platform.BbServiceException; +import blackboard.platform.BbServiceManager; +import blackboard.platform.context.Context; +import blackboard.platform.context.ContextManager; + +/** + * Makes a call to LAMS server to delete a learning design. + */ +public class LamsLearningDesignDeleteServlet extends HttpServlet { + + private static final long serialVersionUID = -351131323404991332L; + + public void doGet(HttpServletRequest request, HttpServletResponse response) { + process(request, response); + } + + public void doPost(HttpServletRequest request, HttpServletResponse response) { + process(request, response); + } + + protected void process(HttpServletRequest request, HttpServletResponse response) { + String serverAddr = LamsPluginUtil.getServerUrl(); + String serverId = LamsPluginUtil.getServerId(); + + // If lams.properties could not be read, throw exception + if (serverAddr == null || serverId == null) { + throw new RuntimeException("lams.properties file could not be read. serverAddr:" + serverAddr + ", serverId:" + serverId); + } + + // get request parameters + String courseId = request.getParameter("course_id"); + + String strLearningDesignId = request.getParameter("sequence_id"); + if ( strLearningDesignId != null ) { + strLearningDesignId.trim(); + } + + // validate method parameter and associated parameters + if ( strLearningDesignId == null || strLearningDesignId.length() == 0 ) { + throw new RuntimeException("Required parameters missing. Add sequence_id for the id of the learning design to be deleted"); + } + + long learningDesignId = 0; + try { + learningDesignId = Long.parseLong(strLearningDesignId); + } catch ( Exception e ) { + throw new RuntimeException("Required parameters missing. Add sequence_id for the id of the learning design to be deleted",e); + } + + ContextManager ctxMgr = null; + Context ctx = null; + try { + // get Blackboard context + ctxMgr = (ContextManager) BbServiceManager.lookupService(ContextManager.class); + ctx = ctxMgr.setContext(request); + + String serverResponse = LamsSecurityUtil.deleteLearningDesigns(ctx, courseId, learningDesignId); + + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().print(serverResponse); + + } catch (InitializationException e) { + throw new RuntimeException(e); + } catch (BbServiceException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + // make sure context is released + if (ctxMgr != null) { + ctxMgr.releaseContext(); + } + } + } +} + Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/LamsLearningDesignServlet.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/LamsLearningDesignServlet.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/LamsLearningDesignServlet.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,112 @@ +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + + +package org.lamsfoundation.bb.integration.servlet; + +import java.io.IOException; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.lamsfoundation.bb.integration.Constants; +import org.lamsfoundation.bb.integration.util.LamsPluginUtil; +import org.lamsfoundation.bb.integration.util.LamsSecurityUtil; + +import blackboard.base.InitializationException; +import blackboard.platform.BbServiceException; +import blackboard.platform.BbServiceManager; +import blackboard.platform.context.Context; +import blackboard.platform.context.ContextManager; + +/** + * Makes a call to LAMS server to get learning designs and returns it. + */ +public class LamsLearningDesignServlet extends HttpServlet { + + private static final long serialVersionUID = -351131323404991332L; + + public void doGet(HttpServletRequest request, HttpServletResponse response) { + process(request, response); + } + + public void doPost(HttpServletRequest request, HttpServletResponse response) { + process(request, response); + } + + protected void process(HttpServletRequest request, HttpServletResponse response) { + String serverAddr = LamsPluginUtil.getServerUrl(); + String serverId = LamsPluginUtil.getServerId(); + + // If lams.properties could not be read, throw exception + if (serverAddr == null || serverId == null) { + throw new RuntimeException("lams.properties file could not be read. serverAddr:" + serverAddr + ", serverId:" + serverId); + } + + // get request parameters + String folderId = request.getParameter(Constants.PARAM_FOLDER_ID); + + //paging parameters of tablesorter - used in the LAMS Template Wizard + boolean usePaging = false; + String page = request.getParameter("page"); + String size = request.getParameter("size"); + if ( page != null && page.length()>0) { + usePaging = true; + if ( size == null || size.length()==0) + size="10"; + } + String sortName = request.getParameter("sortName"); + String sortDate = request.getParameter("sortDate"); + String search = request.getParameter("search"); + String type = request.getParameter("type"); + String username = request.getParameter("username"); // backup method to get user, when the Blackboard context does not have the user + + ContextManager ctxMgr = null; + Context ctx = null; + try { + // get Blackboard context + ctxMgr = (ContextManager) BbServiceManager.lookupService(ContextManager.class); + ctx = ctxMgr.setContext(request); + + String courseId = ctx.getCourse().getCourseId(); + String method = usePaging ? "getPagedHomeLearningDesignsJSON" : "getLearningDesignsJSON"; + String learningDesigns = LamsSecurityUtil.getLearningDesigns(ctx, username, courseId, folderId, method, type, search, page, size, sortName, sortDate); + + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().print(learningDesigns); + + } catch (InitializationException e) { + throw new RuntimeException(e); + } catch (BbServiceException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + // make sure context is released + if (ctxMgr != null) { + ctxMgr.releaseContext(); + } + } + } +} + Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/LearnerMonitorServlet.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/LearnerMonitorServlet.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/LearnerMonitorServlet.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,161 @@ +package org.lamsfoundation.bb.integration.servlet; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.lamsfoundation.bb.integration.dto.LearnerProgressDTO; +import org.lamsfoundation.bb.integration.util.LamsSecurityUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import blackboard.base.InitializationException; +import blackboard.data.content.Content; +import blackboard.data.content.CourseDocument; +import blackboard.data.course.Course; +import blackboard.data.course.CourseMembership; +import blackboard.persist.BbPersistenceManager; +import blackboard.persist.Container; +import blackboard.persist.Id; +import blackboard.persist.KeyNotFoundException; +import blackboard.persist.PersistenceException; +import blackboard.persist.PkId; +import blackboard.persist.content.ContentDbLoader; +import blackboard.persist.course.CourseMembershipDbLoader; +import blackboard.platform.BbServiceException; +import blackboard.platform.BbServiceManager; +import blackboard.platform.context.Context; +import blackboard.platform.context.ContextManager; +import blackboard.platform.persistence.PersistenceServiceFactory; +import blackboard.platform.plugin.PlugInException; +import blackboard.platform.plugin.PlugInUtil; + +/** + * + */ +public class LearnerMonitorServlet extends HttpServlet { + + private static final long serialVersionUID = -351131323404991332L; + private static Logger logger = LoggerFactory.getLogger(LearnerMonitorServlet.class); + + public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + + // Authorise current user for Course Access (automatic redirect) + try { + if (!PlugInUtil.authorizeForCourse(request, response)) + return; + } catch (PlugInException e) { + throw new RuntimeException(e); + } + + try { + BbPersistenceManager bbPm = PersistenceServiceFactory.getInstance().getDbPersistenceManager(); + CourseMembershipDbLoader sessionCourseMembershipLoader = CourseMembershipDbLoader.Default.getInstance(); + + String strLessonId = request.getParameter("lsid").trim(); + long lessonId = Long.parseLong(strLessonId); + String courseIdParam = request.getParameter("course_id"); + + // Get Course ID and User ID + Id course_id = bbPm.generateId(Course.DATA_TYPE, courseIdParam); + + // get Blackboard context + ContextManager ctxMgr = (ContextManager) BbServiceManager.lookupService(ContextManager.class); + Context ctx = ctxMgr.getContext(); + + Id userId = ctx.getUser().getId(); + + // Get the membership data to determine the User's Role + CourseMembership courseMembership = null; + CourseMembership.Role courseRole = null; + boolean isActive = false; + + try { + courseMembership = sessionCourseMembershipLoader.loadByCourseAndUserId(course_id, userId); + courseRole = courseMembership.getRole(); + isActive = courseMembership.getIsAvailable(); + } catch (KeyNotFoundException e) { + // There is no membership record. + e.printStackTrace(); + } catch (PersistenceException pe) { + // There is no membership record. + pe.printStackTrace(); + } + + // Is the User an Instructor of Teaching Assistant + boolean isInstructor = false; + if (CourseMembership.Role.INSTRUCTOR.equals(courseRole) + || CourseMembership.Role.TEACHING_ASSISTANT.equals(courseRole) + || CourseMembership.Role.COURSE_BUILDER.equals(courseRole)) { + isInstructor = true; + + } else if (!CourseMembership.Role.STUDENT.equals(courseRole)) { + // The user is not an Instructor, Teaching Assistant or Student - Access Denied + response.sendRedirect("notAllowed.jsp"); + } + + // Are they active in the course? If not let Blackboard handle the redirect + if (!isActive) { + PlugInUtil.sendAccessDeniedRedirect(request, response); + } + + //Get lessson's title and description + String title = ""; + String description = ""; + //contentId is available in versions after 1.2.3 + String contentIdParam = request.getParameter("content_id"); + if (contentIdParam != null) { + + Container bbContainer = bbPm.getContainer(); + Id contentId = new PkId(bbContainer, CourseDocument.DATA_TYPE, contentIdParam); + ContentDbLoader courseDocumentLoader = (ContentDbLoader) bbPm.getLoader(ContentDbLoader.TYPE); + Content courseDoc = (Content) courseDocumentLoader.loadById(contentId); + title = courseDoc.getTitle(); + description = courseDoc.getBody().getFormattedText(); + + } else { + + title = (request.getParameter("title") != null) ? request.getParameter("title") : "LAMS Options"; + description = request.getParameter("description"); + } + + //display learning design image + String strIsDisplayDesignImage = request.getParameter("isDisplayDesignImage"); + boolean isDisplayDesignImage = "true".equals(strIsDisplayDesignImage) ? true : false; + String learningDesignImageUrl = ""; + if (isDisplayDesignImage) { + String username = ctx.getUser().getUserName(); + learningDesignImageUrl = LamsSecurityUtil.generateRequestLearningDesignImage(username) + "&lsId=" + + lessonId; + } + + //prepare learnerProgressDto for displaying on jsp + LearnerProgressDTO learnerProgressDto = LamsSecurityUtil.getLearnerProgress(ctx, lessonId); + + request.setAttribute("title", title); + request.setAttribute("isInstructor", isInstructor); + request.setAttribute("description", description); + request.setAttribute("isDisplayDesignImage", isDisplayDesignImage); + request.setAttribute("learningDesignImageUrl", learningDesignImageUrl); + request.setAttribute("isLessonCompleted", learnerProgressDto.getLessonComplete()); + request.setAttribute("attemptedActivitiesCount", learnerProgressDto.getAttemptedActivities()); + request.setAttribute("activitiesCompletedCount", learnerProgressDto.getActivitiesCompleted()); + request.setAttribute("activitiesCount", learnerProgressDto.getActivityCount()); + + } catch (InitializationException e) { + throw new ServletException(e); + } catch (BbServiceException e) { + throw new ServletException(e); + } catch (PersistenceException e) { + throw new ServletException(e); + } + } + + public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + doGet(request, response); + } + +} Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/LessonManagerServlet.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/LessonManagerServlet.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/LessonManagerServlet.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,321 @@ +package org.lamsfoundation.bb.integration.servlet; +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.parsers.ParserConfigurationException; + +import org.lamsfoundation.bb.integration.util.BlackboardUtil; +import org.lamsfoundation.bb.integration.util.LamsPluginUtil; +import org.lamsfoundation.bb.integration.util.LamsSecurityUtil; +import org.lamsfoundation.bb.integration.util.LineitemUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.SAXException; + +import blackboard.base.FormattedText; +import blackboard.base.InitializationException; +import blackboard.data.ValidationException; +import blackboard.data.content.Content; +import blackboard.data.content.CourseDocument; +import blackboard.data.course.Course; +import blackboard.data.user.User; +import blackboard.persist.BbPersistenceManager; +import blackboard.persist.Container; +import blackboard.persist.Id; +import blackboard.persist.PersistenceException; +import blackboard.persist.PkId; +import blackboard.persist.content.ContentDbLoader; +import blackboard.persist.content.ContentDbPersister; +import blackboard.platform.BbServiceException; +import blackboard.platform.BbServiceManager; +import blackboard.platform.context.Context; +import blackboard.platform.context.ContextManager; +import blackboard.platform.persistence.PersistenceServiceFactory; +import blackboard.platform.plugin.PlugInException; +import blackboard.platform.plugin.PlugInUtil; +import blackboard.portal.data.ExtraInfo; +import blackboard.portal.data.PortalExtraInfo; +import blackboard.portal.servlet.PortalUtil; + +/** + * Shows startLesson page and modifyLesson pages, also handles subsequent start and modification of LAMS lessons. + */ +public class LessonManagerServlet extends HttpServlet { + + private static final long serialVersionUID = -351131323404991332L; + private static Logger logger = LoggerFactory.getLogger(LessonManagerServlet.class); + + public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + process(request, response); + } + + public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + process(request, response); + } + + private void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + // Authorise current user for Course Control Panel (automatic redirect) + try { + if (!PlugInUtil.authorizeForCourseControlPanel(request, response)) + return; + } catch (PlugInException e) { + throw new RuntimeException(e); + } + + ContextManager ctxMgr = null; + try { + // get Blackboard context + ctxMgr = (ContextManager) BbServiceManager.lookupService(ContextManager.class); + Context ctx = ctxMgr.getContext(); + + String method = request.getParameter("method"); + // -----------------------Start lesson functions --------------------------- + if (method.equals("showStartLessonPage")) { + showStartLessonPage(request, response, ctx); + + } else if (method.equals("start")) { + start(request, response, ctx); + + // -----------------------Modify lesson functions --------------------------- + } else if (method.equals("showModifyLessonPage")) { + showModifyLessonPage(request, response, ctx); + + } else if (method.equals("modify")) { + modify(request, response, ctx); + + // -----------------------Delete lesson functions --------------------------- + } else if (method.equals("delete")) { + delete(request, response, ctx); + } + + } catch (InitializationException e) { + throw new ServletException(e); + } catch (BbServiceException e) { + throw new ServletException(e); + } catch (PersistenceException e) { + throw new ServletException(e); + } catch (ParseException e) { + throw new ServletException(e); + } catch (ValidationException e) { + throw new ServletException(e); + } catch (ParserConfigurationException e) { + throw new ServletException(e); + } catch (SAXException e) { + throw new ServletException(e); + } + +// // important. make sure context is not released as otherwise <bbNG:genericPage> tag will throw an exception +// finally { +// if (ctxMgr != null) { +// ctxMgr.releaseContext(); +// } +// } + } + + /** + * Shows preparation page with available learning designs. Preview is available. + */ + private void showStartLessonPage(HttpServletRequest request, HttpServletResponse response, Context ctx) + throws InitializationException, BbServiceException, PersistenceException, IOException, ServletException { + + String lamsServerUrl = LamsPluginUtil.getServerUrl(); + request.setAttribute("lamsServerUrl", lamsServerUrl); + + // get all user accessible folders and LD descriptions as JSON + String learningDesigns = LamsSecurityUtil.getLearningDesigns(ctx, ctx.getCourse().getCourseId(), null); + request.setAttribute("learningDesigns", learningDesigns); + + request.getRequestDispatcher("/modules/create.jsp").forward(request, response); + } + + /** + * Starts preview lesson on LAMS server. Launches it. + */ + private void start(HttpServletRequest request, HttpServletResponse response, Context ctx) throws IOException, ServletException, PersistenceException, ParseException, ValidationException, ParserConfigurationException, SAXException { + + BbPersistenceManager bbPm = PersistenceServiceFactory.getInstance().getDbPersistenceManager(); + + //store newly created LAMS lesson + User user = ctx.getUser(); + BlackboardUtil.storeBlackboardContent(request, response, user); + + // constuct strReturnUrl + String courseIdStr = request.getParameter("course_id"); + String contentIdStr = request.getParameter("content_id"); + // internal Blackboard IDs for the course and parent content item + Id courseId = bbPm.generateId(Course.DATA_TYPE, courseIdStr); + Id folderId = bbPm.generateId(CourseDocument.DATA_TYPE, contentIdStr); + String returnUrl = PlugInUtil.getEditableContentReturnURL(folderId, courseId); + request.setAttribute("returnUrl", returnUrl); + + request.getRequestDispatcher("/modules/startLessonSuccess.jsp").forward(request, response); + } + + private void showModifyLessonPage(HttpServletRequest request, HttpServletResponse response, Context ctx) + throws InitializationException, BbServiceException, PersistenceException, IOException, ServletException { + + // retrive the LAMS lesson + BbPersistenceManager bbPm = PersistenceServiceFactory.getInstance().getDbPersistenceManager(); + Container bbContainer = bbPm.getContainer(); + Id contentId = new PkId(bbContainer, CourseDocument.DATA_TYPE, request.getParameter("content_id")); + ContentDbLoader courseDocumentLoader = (ContentDbLoader) bbPm.getLoader(ContentDbLoader.TYPE); + Content bbContent = (Content) courseDocumentLoader.loadById(contentId); + + // get LAMS lessons's properties + Calendar startDate = bbContent.getStartDate(); + Calendar endDate = bbContent.getEndDate(); + FormattedText description = bbContent.getBody(); + + request.setAttribute("bbContent", bbContent); + request.setAttribute("startDate", startDate); + request.setAttribute("endDate", endDate); + request.setAttribute("description", description); + + request.getRequestDispatcher("/modules/modify.jsp").forward(request, response); + } + + private void modify(HttpServletRequest request, HttpServletResponse response, Context ctx) + throws InitializationException, BbServiceException, PersistenceException, IOException, ValidationException, + ServletException, ParserConfigurationException, SAXException, ParseException { + + String _course_id = request.getParameter("course_id"); + String _content_id = request.getParameter("content_id"); + + // Retrieve the Db persistence manager from the persistence service + BbPersistenceManager bbPm = PersistenceServiceFactory.getInstance().getDbPersistenceManager(); + Container bbContainer = bbPm.getContainer(); + + // Internal Blackboard IDs for the course and parent content item + Id courseId = bbPm.generateId(Course.DATA_TYPE, _course_id); + Id contentId = new PkId( bbContainer, CourseDocument.DATA_TYPE, _content_id); + + // Load the content item + ContentDbLoader courseDocumentLoader = (ContentDbLoader) bbPm.getLoader( ContentDbLoader.TYPE ); + Content bbContent = (Content)courseDocumentLoader.loadById( contentId ); + + // Get the form parameters and convert into correct data types + // TODO: Use bb text area instead + String strTitle = request.getParameter("title").trim(); + String strDescription = request.getParameter("descriptiontext").trim(); + FormattedText description = new FormattedText(strDescription, FormattedText.Type.HTML); + + String strIsAvailable = request.getParameter("isAvailable"); + String strIsGradecenter = request.getParameter("isGradecenter"); + String strIsTracked = request.getParameter("isTracked"); + boolean isAvailable = strIsAvailable.equals("true")?true:false; + boolean isGradecenter = strIsGradecenter.equals("true")?true:false; + boolean isTracked = strIsTracked.equals("true")?true:false; + + String strStartDate = request.getParameter("lessonAvailability_start_datetime"); + String strEndDate = request.getParameter("lessonAvailability_end_datetime"); + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String strStartDateCheckbox = request.getParameter("lessonAvailability_start_checkbox"); + String strEndDateCheckbox = request.getParameter("lessonAvailability_end_checkbox"); + + //if teacher turned Gradecenter option ON (and it was OFF previously) - create lineitem + if (!bbContent.getIsDescribed() && isGradecenter) { + String username = ctx.getUser().getUserName(); + LineitemUtil.createLineitem(bbContent, username); + + //if teacher turned Gradecenter option OFF (and it was ON previously) - remove lineitem + } else if (bbContent.getIsDescribed() && !isGradecenter) { + LineitemUtil.removeLineitem(_content_id, _course_id); + + //change existing lineitem's name if lesson name has been changed + } else if (isGradecenter && !strTitle.equals(bbContent.getTitle())) { + LineitemUtil.changeLineitemName(_content_id, _course_id, strTitle); + } + + // Set LAMS content data in Blackboard + bbContent.setTitle(strTitle); + bbContent.setIsAvailable(isAvailable); + bbContent.setIsDescribed(isGradecenter);//isDescribed field is used for storing isGradecenter parameter + bbContent.setIsTracked(isTracked); + bbContent.setBody(description); + + // Set Availability Dates + // Clear the date (set to null) if the checkbox is unchecked + // Start Date + Calendar startDate = null; + if (strStartDateCheckbox != null && strStartDateCheckbox.equals("1")) { + startDate = Calendar.getInstance(); + startDate.setTime(formatter.parse(strStartDate)); + } + bbContent.setStartDate(startDate); + // End Date + Calendar endDate = null; + if (strEndDateCheckbox != null && (strEndDateCheckbox.equals("1"))) { + endDate = Calendar.getInstance(); + endDate.setTime(formatter.parse(strEndDate)); + } + bbContent.setEndDate(endDate); + + //Persist the Modified Lesson Object in Blackboard + ContentDbPersister persister= (ContentDbPersister) bbPm.getPersister( ContentDbPersister.TYPE ); + persister.persist( bbContent ); + + String strReturnUrl = PlugInUtil.getEditableContentReturnURL(bbContent.getParentId(), courseId); + request.setAttribute("strReturnUrl", strReturnUrl); + + request.getRequestDispatcher("/modules/modifyLessonSuccess.jsp").forward(request, response); + } + + private void delete(HttpServletRequest request, HttpServletResponse response, Context ctx) + throws InitializationException, BbServiceException, PersistenceException, IOException, ServletException, ParserConfigurationException, SAXException { + + //remove Lineitem object from Blackboard DB + String _content_id = request.getParameter("content_id"); + String _course_id = request.getParameter("course_id"); + LineitemUtil.removeLineitem(_content_id, _course_id); + + // remove internalContentId -> externalContentId key->value pair (it's used for GradebookServlet) + PortalExtraInfo pei = PortalUtil.loadPortalExtraInfo(null, null, "LamsStorage"); + ExtraInfo ei = pei.getExtraInfo(); + ei.clearEntry(_content_id); + PortalUtil.savePortalExtraInfo(pei); + + //remove lesson from LAMS server + BbPersistenceManager bbPm = PersistenceServiceFactory.getInstance().getDbPersistenceManager(); + Container bbContainer = bbPm.getContainer(); + ContentDbLoader courseDocumentLoader = ContentDbLoader.Default.getInstance(); + Id contentId = new PkId(bbContainer, CourseDocument.DATA_TYPE, _content_id); + Content bbContent = courseDocumentLoader.loadById(contentId); + String lsId = bbContent.getLinkRef(); + String userName = ctx.getUser().getUserName(); + Boolean isDeletedSuccessfully = LamsSecurityUtil.deleteLesson(userName, lsId); + + System.out.println("Lesson (bbContentId:" + _content_id + ") successfully deleted by userName:" + userName); + } + + + +} Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/LinkToolsServlet.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/LinkToolsServlet.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/LinkToolsServlet.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,603 @@ +package org.lamsfoundation.bb.integration.servlet; +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URLEncoder; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.parsers.ParserConfigurationException; + +import org.lamsfoundation.bb.integration.util.BlackboardUtil; +import org.lamsfoundation.bb.integration.util.LamsSecurityUtil; +import org.lamsfoundation.bb.integration.util.LamsServerException; +import org.lamsfoundation.bb.integration.util.LineitemUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.SAXException; + +import blackboard.base.FormattedText; +import blackboard.base.InitializationException; +import blackboard.data.ValidationException; +import blackboard.data.content.Content; +import blackboard.data.course.Course; +import blackboard.data.navigation.CourseToc; +import blackboard.data.user.User; +import blackboard.persist.BbPersistenceManager; +import blackboard.persist.Container; +import blackboard.persist.Id; +import blackboard.persist.PersistenceException; +import blackboard.persist.PkId; +import blackboard.persist.content.ContentDbLoader; +import blackboard.persist.content.ContentDbPersister; +import blackboard.persist.course.CourseDbLoader; +import blackboard.persist.navigation.CourseTocDbLoader; +import blackboard.platform.BbServiceException; +import blackboard.platform.BbServiceManager; +import blackboard.platform.context.Context; +import blackboard.platform.context.ContextManager; +import blackboard.platform.persistence.PersistenceServiceFactory; +import blackboard.platform.plugin.PlugInException; +import blackboard.platform.plugin.PlugInUtil; +import blackboard.util.StringUtil; + +/** + * + */ +public class LinkToolsServlet extends HttpServlet { + + private static final long serialVersionUID = -351131323404991332L; + private static Logger logger = LoggerFactory.getLogger(LinkToolsServlet.class); + + public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + process(request, response); + } + + public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + process(request, response); + } + + private void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + // Authorise current user for Course Control Panel (automatic redirect) + try { + if (!PlugInUtil.authorizeForCourseControlPanel(request, response)) + return; + } catch (PlugInException e) { + throw new RuntimeException(e); + } + + String method = request.getParameter("method"); + if (method.equals("openAdminLinkTool")) { + openAdminLinkTool(request, response); + + } else if (method.equals("openAuthorLinkTool")) { + openAuthorLinkTool(request, response); + + } else if (method.equals("openMonitorLinkTool")) { + openMonitorLinkTool(request, response); + + //Admin on BB side calls this servlet to clone old lesson that were copied to the new course. + } else if (method.equals("cloneLessons")) { + cloneLessons(request, response); + + //Admin on BB side calls this servlet to import old lesson that were copied to the new course. + } else if (method.equals("importLessons")) { + importLessons(request, response); + + //Admin on BB side calls this servlet to correct lineitems that have been screwed up while copying/importing courses. + } else if (method.equals("correctLineitems")) { + correctLineitems(request, response); + } + } + + private void openAdminLinkTool(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.getRequestDispatcher("/links/admin.jsp").forward(request, response); + } + + private void openAuthorLinkTool(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.getRequestDispatcher("/links/author.jsp").forward(request, response); + } + + private void openMonitorLinkTool(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + + BbPersistenceManager bbPm = PersistenceServiceFactory.getInstance().getDbPersistenceManager(); + Container bbContainer = bbPm.getContainer(); + Id courseId = new PkId(bbContainer, Course.DATA_TYPE, request.getParameter("course_id")); + + ContextManager ctxMgr = null; + String strOut = "[[]]"; + try { + // get Blackboard context + ctxMgr = (ContextManager) BbServiceManager.lookupService(ContextManager.class); + Context ctx = ctxMgr.getContext(); + ContentDbLoader cLoader = (ContentDbLoader) bbPm.getLoader(ContentDbLoader.TYPE); + CourseTocDbLoader ctLoader = (CourseTocDbLoader) bbPm.getLoader(CourseTocDbLoader.TYPE); + + Course course = ctx.getCourse(); + List<CourseToc> ctList = ctLoader.loadByCourseId(courseId); + CourseToc[] courseTocs = (CourseToc[]) ctList.toArray(new CourseToc[0]); + + int idx = 0; + StringBuilder strB = new StringBuilder(); + strB.append("[{type:'Text', label:'" + course.getTitle().replace("'", "\\'") + "', id:0, children:["); + for (int i = 0; i < courseTocs.length; i++) { + if (courseTocs[i].getTargetType().compareTo(CourseToc.Target.CONTENT) == 0) { + Content cont = cLoader.loadByTocId(courseTocs[i].getId()); + strB.append(getChild(cont, cLoader)); + idx = i; + break; + } + } + for (int i = idx + 1; i < courseTocs.length; i++) { + if (courseTocs[i].getTargetType().compareTo(CourseToc.Target.CONTENT) == 0) { + Content cont = cLoader.loadByTocId(courseTocs[i].getId()); + strB.append(", ").append(getChild(cont, cLoader)); + } + } + strB.append("]}]"); + strOut = strB.toString(); + } catch (InitializationException e) { + throw new ServletException(e); + } catch (BbServiceException e) { + throw new ServletException(e); + } catch (PersistenceException e) { + throw new ServletException(e); + } + + request.setAttribute("treeView", strOut); + request.getRequestDispatcher("/links/monitor.jsp").forward(request, response); + } + + private static String extractParameterValue(String url, String param) { + if (url != null && param != null) { + int quotationMarkIndex = url.indexOf("?"); + String queryPart = quotationMarkIndex > -1 ? url.substring(quotationMarkIndex + 1) : url; + String[] paramEntries = queryPart.split("&"); + for (String paramEntry : paramEntries) { + String[] paramEntrySplitted = paramEntry.split("="); + if ((paramEntrySplitted.length > 1) && param.equalsIgnoreCase(paramEntrySplitted[0])) { + return paramEntrySplitted[1]; + } + } + } + return null; + } + + private String getChild(Content f, ContentDbLoader cLoader) { + StringBuilder sb = new StringBuilder(); + try { + + if (f.getIsFolder()) { + + List<Content> cList = cLoader.loadChildren(f.getId()); + Content[] cArray = cList.toArray(new Content[0]); + //sort content by title + Arrays.sort(cArray, new Comparator<Content>() { + @Override + public int compare(Content o1, Content o2) { + if (o1 != null && o2 != null) { + return o1.getTitle().compareToIgnoreCase(o2.getTitle()); + } else if (o1 != null) + return 1; + else + return -1; + } + }); + + String title = f.getTitle(); + if (title.indexOf("'") != -1) { + title = title.replace("'", "\\'"); + } + sb.append("{type:'Text', label:'" + title + "', id:0"); + + if (cArray.length == 0) { + sb.append(", expanded:0, children:[{type:'HTML', html:'<i>null</i>', id:0}]}"); + return sb.toString(); + + } else { + sb.append(", children:["); + sb.append(getChild(cArray[0], cLoader)); + for (int i = 1; i < cArray.length; i++) { + sb.append(", ").append(getChild(cArray[i], cLoader)); + } + sb.append("]}"); + } + return sb.toString(); + + } else { + + if (f.getContentHandler().equals("resource/x-lams-lamscontent")) { + String strUrl = f.getUrl(); + String strId = extractParameterValue(strUrl, "lsid"); + String strTitle = f.getTitle().replace("'", "\\'"); + sb.append("{type:'Text', label:'" + strTitle + "', id:'" + strId + "'}"); + // return sb.toString(); + + } else if (f.getContentHandler().equals("resource/x-ntu-hdllams")) { + String strUrl = f.getUrl(); + String strId = "0"; + if (strUrl.indexOf("&seq_id=") != -1) { + int pos1 = strUrl.indexOf("&seq_id=") + 8; +// int pos2 = strUrl.indexOf("&", pos1); + strId = strUrl.substring(pos1); + } + String strTitle = f.getTitle().replace("'", "\\'"); + sb.append("{type:'Text', label:'" + strTitle + "', id:'" + strId + "'}"); + + } else { + // sb.append("{type:'HTML', html:'<i>null</i>', id:0}"); + } + return sb.toString(); + } + + } catch (Exception e) { + return sb.toString(); + } + } + + /** + * Admin on BB side calls this servlet to clone old lesson that were copied to the new course. + * + * @param request + * @param response + * @throws IOException + * @throws ServletException + */ + private void cloneLessons(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + String _course_id = request.getParameter("course_id"); + if (StringUtil.isEmpty(_course_id)) { + throw new RuntimeException("Required parameters are missing. courseId: " + _course_id); + } + + String newLessonIds = ""; + try { + newLessonIds = recreateLessonsAfterCourseCopy(_course_id); + } catch (IllegalStateException e) { + throw new ServletException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (PersistenceException e) { + throw new ServletException(e); + } catch (ValidationException e) { + throw new ServletException(e); + } catch (ParserConfigurationException e) { + throw new ServletException(e); + } catch (SAXException e) { + throw new ServletException(e); + } + + //prepare string to write out + int newLessonsCounts = newLessonIds.length() - newLessonIds.replace(",", "").length(); + String resultStr = "Complete! " + newLessonsCounts + " lessons have been cloned."; + //add all lessonIds (without the last comma) + if (newLessonsCounts > 0) { + resultStr += " Their updated lessonIds: " + newLessonIds.substring(0, newLessonIds.length() - 2); + } + + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + out.write(resultStr); + out.flush(); + out.close(); + } + + /** + * Recreates lessons after course has been copied. I.e. asks LAMS server to clone old lesson and then updates BB + * link with the newly created lesson Id. + * + * @param courseIdParam + * id of the course that has been copied + * @return + * @throws PersistenceException + * @throws ValidationException + * @throws IOException + * @throws ServletException + * @throws SAXException + * @throws ParserConfigurationException + */ + private static String recreateLessonsAfterCourseCopy(String _course_id) + throws PersistenceException, ValidationException, ServletException, IOException, ParserConfigurationException, SAXException { + String newLessonIds = ""; + + BbPersistenceManager bbPm = PersistenceServiceFactory.getInstance().getDbPersistenceManager(); + ContentDbPersister persister = ContentDbPersister.Default.getInstance(); + CourseDbLoader courseLoader = CourseDbLoader.Default.getInstance(); + + PkId courseId = (PkId) bbPm.generateId(Course.DATA_TYPE, _course_id); + Course course = courseLoader.loadById(courseId); + String courseIdStr = course.getCourseId(); + + logger.debug("Starting clonning course lessons (courseId=" + courseId + ")."); + + // find a teacher that will be assigned as lesson's author on LAMS side + User teacher = BlackboardUtil.getCourseTeacher(courseId); + + //find all lessons that should be updated + List<Content> lamsContents = BlackboardUtil.getLamsLessonsByCourse(courseId); + for (Content content : lamsContents) { + + String _content_id = content.getId().toExternalString(); + + String url = content.getUrl(); + String urlLessonId = getParameterValue(url, "lsid"); + String urlCourseId = getParameterValue(url, "course_id"); + String urlContentId = getParameterValue(url, "content_id"); + + //in case when both courseId and contentId don't coincide with the ones from URL - means lesson needs to be cloned + if (!urlCourseId.equals(_course_id) && !urlContentId.equals(_content_id)) { + + final Long newLessonId = LamsSecurityUtil.cloneLesson(teacher, courseIdStr, urlLessonId); + + // update lesson id + content.setLinkRef(Long.toString(newLessonId)); + + // update URL + url = replaceParameterValue(url, "lsid", Long.toString(newLessonId)); + url = replaceParameterValue(url, "course_id", _course_id); + url = replaceParameterValue(url, "content_id", _content_id); + content.setUrl(url); + + // persist updated content + persister.persist(content); + + //update lineitem details + LineitemUtil.updateLineitemLessonId(content, _course_id, newLessonId, teacher.getUserName()); + + logger.debug("Lesson (lessonId=" + urlLessonId + ") was successfully cloned to the one (lessonId=" + + newLessonId + ")."); + + newLessonIds += newLessonId + ", "; + } + + } + + return newLessonIds; + } + + /** + * Admin on BB side calls this servlet to import old lesson that were copied to the new course. + */ + private void importLessons(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String _course_id = request.getParameter("course_id"); + if (StringUtil.isEmpty(_course_id)) { + throw new RuntimeException("Required parameters are missing. courseId: " + _course_id); + } + + String newLessonIds = ""; + try { + BbPersistenceManager bbPm = PersistenceServiceFactory.getInstance().getDbPersistenceManager(); + ContentDbPersister persister = ContentDbPersister.Default.getInstance(); + CourseDbLoader courseLoader = CourseDbLoader.Default.getInstance(); + + PkId courseId = (PkId) bbPm.generateId(Course.DATA_TYPE, _course_id); + Course course = courseLoader.loadById(courseId); + String courseIdStr = course.getCourseId(); + + logger.debug("Starting importing course lessons (courseId=" + courseId + ")."); + + // find a teacher that will be assigned as lesson's author on LAMS side + User teacher = BlackboardUtil.getCourseTeacher(courseId); + + //find all lessons that should be updated + List<Content> lamsContents = BlackboardUtil.getLamsLessonsByCourse(courseId); + for (Content content : lamsContents) { + + String _content_id = content.getId().toExternalString(); + + String url = content.getUrl(); + String urlLessonId = getParameterValue(url, "lsid"); + String urlCourseId = getParameterValue(url, "course_id"); + String urlContentId = getParameterValue(url, "content_id"); + String urlLdId = getParameterValue(url, "ldid"); + + //in case when both courseId and contentId don't coincide with the ones from URL - means lesson needs to be imported + if (!urlCourseId.equals(_course_id) && !urlContentId.equals(_content_id)) { + + final Long newLdId = LamsSecurityUtil.importLearningDesign(teacher, courseIdStr, urlLessonId, + urlLdId); + + logger.debug("Lesson (lessonId=" + urlLessonId + + ") was successfully imported to the one (learningDesignId=" + newLdId + ")."); + + // Start the Lesson in LAMS (via Webservices) and get back the lesson ID + String title = content.getTitle(); + FormattedText descriptionFormatted = content.getBody(); + String description = URLEncoder.encode(descriptionFormatted.getText(), "UTF-8"); + final long newLessonId = LamsSecurityUtil.startLesson(teacher, courseIdStr, newLdId, title, + description, false); + + // update lesson id + content.setLinkRef(Long.toString(newLessonId)); + + // update URL + url = replaceParameterValue(url, "ldid", Long.toString(newLdId)); + url = replaceParameterValue(url, "lsid", Long.toString(newLessonId)); + url = replaceParameterValue(url, "course_id", _course_id); + url = replaceParameterValue(url, "content_id", _content_id); + content.setUrl(url); + + // persist updated content + persister.persist(content); + + //update lineitem details + LineitemUtil.updateLineitemLessonId(content, _course_id, newLessonId, teacher.getUserName()); + + logger.debug("Lesson (lessonId=" + urlLessonId + ") was successfully imported to the one (lessonId=" + + newLessonId + ")."); + + newLessonIds += newLessonId + ", "; + } + } + + } catch (LamsServerException e) { + //printing out error cause + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + out.write("Failed! " + e.getMessage()); + out.flush(); + out.close(); + return; + } catch (IllegalStateException e) { + throw new ServletException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (PersistenceException e) { + throw new ServletException(e); + } catch (ValidationException e) { + throw new ServletException(e); + } catch (ParserConfigurationException e) { + throw new ServletException(e); + } catch (SAXException e) { + throw new ServletException(e); + } + + //prepare string to write out + int newLessonsCounts = newLessonIds.length() - newLessonIds.replace(",", "").length(); + String resultStr = "Complete! " + newLessonsCounts + " lessons have been imported."; + //add all lessonIds (without the last comma) + if (newLessonsCounts > 0) { + resultStr += " Their updated lessonIds: " + newLessonIds.substring(0, newLessonIds.length()-2); + } + logger.debug(resultStr); + + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + out.write(resultStr); + out.flush(); + out.close(); + } + + /** + * Admin on BB side calls this servlet to correct lineitems that have been screwed up while copying/importing courses. + * + * @param request + * @param response + * @throws ServletException + * @throws IOException + */ + private void correctLineitems(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String _course_id = request.getParameter("course_id"); + if (StringUtil.isEmpty(_course_id)) { + throw new RuntimeException("Required parameters are missing. courseId: " + _course_id); + } + + try { + BbPersistenceManager bbPm = PersistenceServiceFactory.getInstance().getDbPersistenceManager(); + PkId courseId = (PkId) bbPm.generateId(Course.DATA_TYPE, _course_id); + + logger.debug("Starting clonning course lessons (courseId=" + courseId + ")."); + + // find a teacher that will be assigned as lesson's author on LAMS side + User teacher = BlackboardUtil.getCourseTeacher(courseId); + + //find all lessons that should be updated + List<Content> lamsContents = BlackboardUtil.getLamsLessonsByCourse(courseId); + for (Content content : lamsContents) { + + // update lesson id + String lessonId = content.getLinkRef(); + + //update lineitem details + LineitemUtil.updateLineitemLessonId(content, _course_id, Long.parseLong(lessonId), + teacher.getUserName()); + } + + } catch (IllegalStateException e) { + throw new ServletException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (NumberFormatException e) { + throw new ServletException(e); + } catch (PersistenceException e) { + throw new ServletException(e); + } catch (ValidationException e) { + throw new ServletException(e); + } catch (ParserConfigurationException e) { + throw new ServletException(e); + } catch (SAXException e) { + throw new ServletException(e); + } + + //prepare string to write out + String resultStr = "Complete! All lineiems have been corrected."; + logger.debug(resultStr); + + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + out.write(resultStr); + out.flush(); + out.close(); + } + + /* + * Returns param value, and empty string in case of there is no such param available + * + * @param url + * @param paramName + * @return + */ + private static String getParameterValue(String url, String paramName) { + String paramValue = ""; + + int quotationMarkIndex = url.indexOf("?"); + String queryPart = quotationMarkIndex > -1 ? url.substring(quotationMarkIndex + 1) : url; + String[] paramEntries = queryPart.split("&"); + for (String paramEntry : paramEntries) { + String[] paramEntrySplitted = paramEntry.split("="); + if ((paramEntrySplitted.length > 1) && paramName.equalsIgnoreCase(paramEntrySplitted[0])) { + paramValue = paramEntrySplitted[1]; + break; + } + } + + return paramValue; + } + + private static String replaceParameterValue(String url, String paramName, String newParamValue) { + String oldParamValue = ""; + + int quotationMarkIndex = url.indexOf("?"); + String queryPart = quotationMarkIndex > -1 ? url.substring(quotationMarkIndex + 1) : url; + String[] paramEntries = queryPart.split("&"); + for (String paramEntry : paramEntries) { + String[] paramEntrySplitted = paramEntry.split("="); + if ((paramEntrySplitted.length > 1) && paramName.equalsIgnoreCase(paramEntrySplitted[0])) { + oldParamValue = paramEntrySplitted[1]; + + return url.replaceFirst(paramName + "=" + oldParamValue, paramName + "=" + newParamValue); + } + } + + return url; + } + +} Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/OpenLamsPageServlet.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/OpenLamsPageServlet.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/OpenLamsPageServlet.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,246 @@ +package org.lamsfoundation.bb.integration.servlet; +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.lamsfoundation.bb.integration.util.LamsSecurityUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import blackboard.base.InitializationException; +import blackboard.data.course.Course; +import blackboard.data.course.CourseMembership; +import blackboard.data.user.User; +import blackboard.persist.BbPersistenceManager; +import blackboard.persist.Id; +import blackboard.persist.KeyNotFoundException; +import blackboard.persist.PersistenceException; +import blackboard.persist.course.CourseMembershipDbLoader; +import blackboard.platform.BbServiceException; +import blackboard.platform.BbServiceManager; +import blackboard.platform.context.Context; +import blackboard.platform.context.ContextManager; +import blackboard.platform.persistence.PersistenceServiceFactory; +import blackboard.platform.plugin.PlugInException; +import blackboard.platform.plugin.PlugInUtil; + +/** + * Launches LAMS pages: author, learner and monitor one. + */ +public class OpenLamsPageServlet extends HttpServlet { + + private static final long serialVersionUID = -351131323404991332L; + private static Logger logger = LoggerFactory.getLogger(OpenLamsPageServlet.class); + + public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + process(request, response); + } + + public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + process(request, response); + } + + private void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + ContextManager ctxMgr = null; + try { + // get Blackboard context + ctxMgr = (ContextManager) BbServiceManager.lookupService(ContextManager.class); + Context ctx = ctxMgr.getContext(); + + String method = request.getParameter("method"); + // -----------------------Assessment Author functions --------------------------- + if (method.equals("openAuthor")) { + openAuthor(request, response, ctx); + + } else if (method.equals("openPreview")) { + openPreview(request, response, ctx); + + } else if (method.equals("openLearner")) { + openLearner(request, response, ctx); + + } else if (method.equals("openMonitor")) { + openMonitor(request, response, ctx); + } + + } catch (InitializationException e) { + throw new ServletException(e); + } catch (BbServiceException e) { + throw new ServletException(e); + } catch (PersistenceException e) { + throw new ServletException(e); + } finally { + // make sure context is released + if (ctxMgr != null) { + ctxMgr.releaseContext(); + } + } + + } + + private void openAuthor(HttpServletRequest request, HttpServletResponse response, Context ctx) + throws InitializationException, BbServiceException, PersistenceException, IOException { + // Authorise current user for Course Control Panel (automatic redirect) + try { + if (!PlugInUtil.authorizeForCourseControlPanel(request, response)) + return; + } catch (PlugInException e) { + throw new RuntimeException(e); + } + + // construct Login Request URL for authoring LAMS Lessons + String authorUrl = LamsSecurityUtil.generateRequestURL(ctx, "author", null); + response.sendRedirect(authorUrl); + } + + /** + * Starts preview lesson on LAMS server. Launches it. + */ + private void openPreview(HttpServletRequest request, HttpServletResponse response, Context ctx) + throws InitializationException, BbServiceException, PersistenceException, IOException { + // Authorize current user for Course Control Panel (automatic redirect) + try { + if (!PlugInUtil.authorizeForCourseControlPanel(request, response)) + return; + } catch (PlugInException e) { + throw new RuntimeException(e); + } + + // Get the form parameters and convert into correct data types + String strTitle = request.getParameter("title").trim(); + String strLdId = request.getParameter("ldId").trim(); + long ldId = Long.parseLong(strLdId); + + // start lesson-preview in LAMS and get back the lesson ID + User user = ctx.getUser(); + Long lsId = LamsSecurityUtil.startLesson(user, "Previews", ldId, strTitle, "", true); + // error checking + if (lsId == -1) { + response.sendRedirect("lamsServerDown.jsp"); + System.exit(1); + } + + // redirect to preview lesson + String previewUrl = LamsSecurityUtil.generateRequestURL(ctx, "learnerStrictAuth", "" + lsId); + response.sendRedirect(previewUrl); + } + + private void openLearner(HttpServletRequest request, HttpServletResponse response, Context ctx) + throws InitializationException, BbServiceException, PersistenceException, IOException { + // Authorise current user for Course Access (automatic redirect) + try { + if (!PlugInUtil.authorizeForCourse(request, response)) + return; + } catch (PlugInException e) { + throw new RuntimeException(e); + } + + // Get Course ID and Session User ID + BbPersistenceManager bbPm = PersistenceServiceFactory.getInstance().getDbPersistenceManager(); + String course_idstr = request.getParameter("course_id"); + Id course_id = bbPm.generateId(Course.DATA_TYPE, course_idstr); + User sessionUser = ctx.getUser(); + Id sessionUserId = sessionUser.getId(); + // Get the membership data to determine the User's Role + CourseMembership courseMembership = null; + CourseMembership.Role courseRole = null; + CourseMembershipDbLoader sessionCourseMembershipLoader = (CourseMembershipDbLoader) bbPm + .getLoader(CourseMembershipDbLoader.TYPE); + try { + courseMembership = sessionCourseMembershipLoader.loadByCourseAndUserId(course_id, sessionUserId); + courseRole = courseMembership.getRole(); + } catch (KeyNotFoundException e) { + // There is no membership record. + e.printStackTrace(); + } catch (PersistenceException pe) { + // There is no membership record. + pe.printStackTrace(); + } + + // if the user is not an Instructor, Teaching Assistant or Student - Access Denied + if (!(courseRole.equals(CourseMembership.Role.INSTRUCTOR) + || courseRole.equals(CourseMembership.Role.TEACHING_ASSISTANT) + || courseRole.equals(CourseMembership.Role.COURSE_BUILDER) + || courseRole.equals(CourseMembership.Role.STUDENT))) { + response.sendRedirect("notAllowed.jsp"); + return; + } + + // Get the Login Request URL for authoring LAMS Lessons + String lsid = request.getParameter("lsid"); + String learnerUrl = LamsSecurityUtil.generateRequestURL(ctx, "learnerStrictAuth", lsid); + + response.sendRedirect(learnerUrl); + } + + private void openMonitor(HttpServletRequest request, HttpServletResponse response, Context ctx) + throws IOException, PersistenceException { + // Authorize current user for Course Control Panel (automatic redirect) + try { + if (!PlugInUtil.authorizeForCourseControlPanel(request, response)) + return; + } catch (PlugInException e) { + throw new RuntimeException(e); + } + + // Get Course ID and Session User ID + BbPersistenceManager bbPm = PersistenceServiceFactory.getInstance().getDbPersistenceManager(); + CourseMembershipDbLoader sessionCourseMembershipLoader = CourseMembershipDbLoader.Default.getInstance(); + String _course_id = request.getParameter("course_id"); + Id course_id = bbPm.generateId(Course.DATA_TYPE, _course_id); + User sessionUser = ctx.getUser(); + Id sessionUserId = sessionUser.getId(); + // Get the membership data to determine the User's Role + CourseMembership courseMembership = null; + CourseMembership.Role courseRole = null; + + try { + courseMembership = sessionCourseMembershipLoader.loadByCourseAndUserId(course_id, sessionUserId); + courseRole = courseMembership.getRole(); + } catch (KeyNotFoundException e) { + // There is no membership record. + e.printStackTrace(); + } catch (PersistenceException pe) { + // There is no membership record. + pe.printStackTrace(); + } + + // if the user is not an Instructor or Teaching Assistant - Access Denied + if (!(courseRole.equals(CourseMembership.Role.INSTRUCTOR) + || courseRole.equals(CourseMembership.Role.TEACHING_ASSISTANT) + || courseRole.equals(CourseMembership.Role.COURSE_BUILDER))) { + response.sendRedirect("notAllowed.jsp"); + } + + // construct Login Request URL for monitoring LAMS Lessons + String lsid = request.getParameter("lsid"); + String monitorUrl = LamsSecurityUtil.generateRequestURL(ctx, "monitor", lsid); + response.sendRedirect(monitorUrl); + } + +} Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/RenderDesignImageServlet.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/RenderDesignImageServlet.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/RenderDesignImageServlet.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,100 @@ +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + + +package org.lamsfoundation.bb.integration.servlet; + +import java.io.IOException; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.lamsfoundation.bb.integration.util.LamsSecurityUtil; + +import blackboard.base.InitializationException; +import blackboard.platform.BbServiceException; +import blackboard.platform.BbServiceManager; +import blackboard.platform.context.Context; +import blackboard.platform.context.ContextManager; + +/** + * Makes a call to LAMS server to get a learning design visual image (svg data) + * Looks for either sequence_id for the learning design (sequence) id. + */ +public class RenderDesignImageServlet extends HttpServlet { + + private static final long serialVersionUID = -351131323404991332L; + + public void doGet(HttpServletRequest request, HttpServletResponse response) { + process(request, response); + } + + public void doPost(HttpServletRequest request, HttpServletResponse response) { + process(request, response); + } + + public void process(HttpServletRequest request, HttpServletResponse response) { + + String strLearningDesignId = request.getParameter("sequence_id"); + if ( strLearningDesignId != null ) { + strLearningDesignId.trim(); + } + + // validate method parameter and associated parameters + if ( strLearningDesignId == null || strLearningDesignId.length() == 0 ) { + throw new RuntimeException("Requred parameters missing. Add sequence_id for the id of the learning design to be displayed"); + } + + long learningDesignId = 0; + try { + learningDesignId = Long.parseLong(strLearningDesignId); + } catch ( Exception e ) { + throw new RuntimeException("Requred parameters missing. Add sequence_id for the id of the learning design to be displayed",e); + } + + ContextManager ctxMgr = null; + Context ctx = null; + try { + // get Blackboard context + ctxMgr = (ContextManager) BbServiceManager.lookupService(ContextManager.class); + ctx = ctxMgr.setContext(request); + + String username = ctx.getUser().getUserName(); + String learningDesignImageUrl = LamsSecurityUtil.generateRequestLearningDesignImage(username) + "&ldId=" + learningDesignId; + response.sendRedirect(learningDesignImageUrl); + + } catch (InitializationException e) { + throw new RuntimeException(e); + } catch (BbServiceException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + // make sure context is released + if (ctxMgr != null) { + ctxMgr.releaseContext(); + } + } + } +} + Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/StartLessonAjaxServlet.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/StartLessonAjaxServlet.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/StartLessonAjaxServlet.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,113 @@ +package org.lamsfoundation.bb.integration.servlet; +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + +import java.io.IOException; +import java.text.ParseException; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.parsers.ParserConfigurationException; + +import org.lamsfoundation.bb.integration.util.BlackboardUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.SAXException; + +import blackboard.base.InitializationException; +import blackboard.data.ValidationException; +import blackboard.data.user.User; +import blackboard.persist.PersistenceException; +import blackboard.platform.BbServiceException; +import blackboard.platform.BbServiceManager; +import blackboard.platform.context.ContextManager; + +/** + * Starts a lesson, returning the BB Content Id in JSON. Based on start_lesson_proc but uses the username + * parameter as a basis for identifying the user. + * Return a server error rather than throw an exception as this will be consumed by AJAX call or the like. + */ +public class StartLessonAjaxServlet extends HttpServlet { + + private static final long serialVersionUID = -351131323404991332L; + private static Logger logger = LoggerFactory.getLogger(StartLessonAjaxServlet.class); + + public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { + process(request, response); + } + + public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { + process(request, response); + } + + protected void process(HttpServletRequest request, HttpServletResponse response) throws IOException { + + ContextManager ctxMgr = null; + try { + // get Blackboard context + ctxMgr = (ContextManager) BbServiceManager.lookupService(ContextManager.class); + + String courseIdStr = request.getParameter("course_id"); + String contentIdStr = request.getParameter("content_id"); + String strTitle = BlackboardUtil.getTrimmedString(request, "title"); + String strSequenceID = BlackboardUtil.getTrimmedString(request, "sequence_id"); + + if ( courseIdStr == null || contentIdStr == null || strSequenceID.length()==0 || strTitle.length() == 0) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, + "Unable to create error - parameter missing. course_id, content_id, sequence_id and title required"); + } else { + + String username = request.getParameter("username"); + User user = BlackboardUtil.loadUserFromDB(username); + + String bbContentId = BlackboardUtil.storeBlackboardContent(request, response, user); + + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().print("{\"content_id\":" + bbContentId + "}"); + } + + } catch (PersistenceException e) { + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to start lesson "+e.getMessage()); + } catch (ParseException e) { + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to start lesson "+e.getMessage()); + } catch (ValidationException e) { + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to start lesson "+e.getMessage()); + } catch (ParserConfigurationException e) { + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to start lesson "+e.getMessage()); + } catch (SAXException e) { + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to start lesson "+e.getMessage()); + } catch (InitializationException e) { + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to start lesson "+e.getMessage()); + } catch (BbServiceException e) { + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to start lesson "+e.getMessage()); + } finally { + // make sure context is released + if (ctxMgr != null) { + ctxMgr.releaseContext(); + } + } + + } + +} + Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/UpdateServerUrlServlet.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/UpdateServerUrlServlet.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/UpdateServerUrlServlet.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,126 @@ +/** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * http://www.gnu.org/licenses/gpl.txt + */ + +/* $$ */ +package org.lamsfoundation.bb.integration.servlet; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.lamsfoundation.bb.integration.Constants; +import org.lamsfoundation.bb.integration.util.BlackboardUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import blackboard.base.InitializationException; +import blackboard.data.ValidationException; +import blackboard.data.content.Content; +import blackboard.data.course.Course; +import blackboard.persist.BbPersistenceManager; +import blackboard.persist.Container; +import blackboard.persist.PersistenceException; +import blackboard.persist.PkId; +import blackboard.persist.content.ContentDbPersister; +import blackboard.platform.BbServiceException; +import blackboard.platform.BbServiceManager; +import blackboard.platform.context.ContextManager; +import blackboard.platform.persistence.PersistenceServiceFactory; +import blackboard.platform.plugin.PlugInException; +import blackboard.platform.plugin.PlugInUtil; + +/** + * Updates server urls for all LAMS lessons in the specified course. + */ +public class UpdateServerUrlServlet extends HttpServlet { + + private static final long serialVersionUID = 274843716397522792L; + private static Logger logger = LoggerFactory.getLogger(UpdateServerUrlServlet.class); + + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + // get Parameter values + String courseIdParam = request.getParameter(Constants.PARAM_COURSE_ID); + String oldUrlHost = request.getParameter("oldUrlHost"); + String newUrlHost = request.getParameter("newUrlHost"); + + // check parameters + if (courseIdParam == null || oldUrlHost == null || newUrlHost == null) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "missing expected parameters"); + return; + } + + ContextManager ctxMgr = null; + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + try { + // check permission + if (!PlugInUtil.authorizeForSystemAdmin(request, response)) { + return; + } + + // get Blackboard context + ctxMgr = (ContextManager) BbServiceManager.lookupService(ContextManager.class); + BbPersistenceManager bbPm = PersistenceServiceFactory.getInstance().getDbPersistenceManager(); + Container bbContainer = bbPm.getContainer(); + ContentDbPersister persister = ContentDbPersister.Default.getInstance(); + + PkId courseId = new PkId(bbContainer, Course.DATA_TYPE, courseIdParam); + + //find all lessons that should be updated + List<Content> lamsContents = BlackboardUtil.getLamsLessonsByCourse(courseId); + for (Content content : lamsContents) { + String oldUrl = content.getUrl(); + String newUrl = oldUrl.replaceFirst(oldUrlHost, newUrlHost); + content.setUrl(newUrl); + persister.persist(content); + + out.write("Old Url" + oldUrl + ". New url:" + newUrl + "\n\r"); + } + + } catch (PersistenceException e) { + throw new ServletException(e); + } catch (ValidationException e) { + throw new ServletException(e); + } catch (InitializationException e) { + throw new ServletException(e); + } catch (BbServiceException e) { + throw new ServletException(e); + } catch (PlugInException e) { + throw new ServletException(e); + } finally { + // make sure context is released + if (ctxMgr != null) { + ctxMgr.releaseContext(); + } + out.flush(); + out.close(); + } + + out.write("OK"); + } + +} Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/UserDataServlet.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/UserDataServlet.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/servlet/UserDataServlet.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,126 @@ +/** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * http://www.gnu.org/licenses/gpl.txt + */ +package org.lamsfoundation.bb.integration.servlet; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.lamsfoundation.bb.integration.Constants; +import org.lamsfoundation.bb.integration.util.CSVUtil; +import org.lamsfoundation.bb.integration.util.LamsPluginUtil; +import org.lamsfoundation.bb.integration.util.LamsSecurityUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import blackboard.data.user.User; +import blackboard.persist.PersistenceException; +import blackboard.persist.user.UserDbLoader; + +/** + * @author <a href="mailto:anthony.xiao@lamsinternational.com">Anthony Xiao</a> + */ +public class UserDataServlet extends HttpServlet { + + private static final long serialVersionUID = 2L; + private static Logger logger = LoggerFactory.getLogger(UserDataServlet.class); + + /** + * The doGet method of the servlet. <br> + * + * This method is called when a form has its tag value method equals to get. + * + * @param request + * the request send by the client to the server + * @param response + * the response send by the server to the client + * @throws ServletException + * if an error occurred + * @throws IOException + * if an error occurred + */ + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + // get Parameter values + String usernameParam = request.getParameter(Constants.PARAM_USER_ID); + String tsParam = request.getParameter(Constants.PARAM_TIMESTAMP); + String hashParam = request.getParameter(Constants.PARAM_HASH); + + // check paramaeters + if (usernameParam == null || tsParam == null || hashParam == null) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "missing expected parameters"); + return; + } + + String secretKey = LamsPluginUtil.getServerSecretKey(); + String serverId = LamsPluginUtil.getServerId(); + + if (!LamsSecurityUtil.sha1( + tsParam.toLowerCase() + usernameParam.toLowerCase() + serverId.toLowerCase() + secretKey.toLowerCase()) + .equals(hashParam)) { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "authentication failed"); + return; + } + + // get user list, but no role info since there are no course info + User user; + try { + UserDbLoader userLoader = UserDbLoader.Default.getInstance(); + user = userLoader.loadByUserName(usernameParam); + } catch (PersistenceException e) { + throw new ServletException(e); + } + + if (user == null) { + throw new ServletException("user not found"); + } + + // construct the address + String address = user.getStreet1() + (user.getStreet1().length() == 0 ? "" : " "); + address += user.getStreet2() + (address.length() == 0 ? "" : " "); + address += user.getState() + (address.length() == 0 ? "" : " "); + address += user.getCountry() + (address.length() == 0 ? "" : " "); + address += user.getZipCode(); + // String username = u.getUserName().replaceAll(); + + PrintWriter out = response.getWriter(); + + String locale = user.getLocale(); + String loc_lang = LamsSecurityUtil.getLanguage(locale); + String loc_cntry = LamsSecurityUtil.getCountry(locale); + + // The CSV list should be the format below + // <Title>,<First name>,<Last name>,<Address>,<City>,<State>, + // <Postcode>,<Country>,<Day time number>,<Mobile number>, + // <Fax number>,<Email>,<Locale language>,<Locale country> + String[] valList = { user.getTitle(), user.getGivenName(), user.getFamilyName(), + user.getStreet1() + user.getStreet2(), user.getCity(), user.getState(), user.getZipCode(), + user.getCountry(), user.getHomePhone1(), user.getMobilePhone(), user.getBusinessFax(), + user.getEmailAddress(), loc_lang, loc_cntry }; + + out.println(CSVUtil.write(valList)); + } + +} Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/util/BlackboardUtil.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/util/BlackboardUtil.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/util/BlackboardUtil.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,290 @@ +package org.lamsfoundation.bb.integration.util; + +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.parsers.ParserConfigurationException; + +import org.xml.sax.SAXException; + +import blackboard.base.FormattedText; +import blackboard.data.ValidationException; +import blackboard.data.content.Content; +import blackboard.data.content.CourseDocument; +import blackboard.data.course.Course; +import blackboard.data.course.CourseMembership; +import blackboard.data.navigation.CourseToc; +import blackboard.data.user.User; +import blackboard.persist.BbPersistenceManager; +import blackboard.persist.Id; +import blackboard.persist.KeyNotFoundException; +import blackboard.persist.PersistenceException; +import blackboard.persist.PkId; +import blackboard.persist.content.ContentDbLoader; +import blackboard.persist.content.ContentDbPersister; +import blackboard.persist.course.CourseDbLoader; +import blackboard.persist.course.CourseMembershipDbLoader; +import blackboard.persist.navigation.CourseTocDbLoader; +import blackboard.persist.user.UserDbLoader; +import blackboard.platform.persistence.PersistenceServiceFactory; +import blackboard.portal.data.ExtraInfo; +import blackboard.portal.data.PortalExtraInfo; +import blackboard.portal.servlet.PortalUtil; + +/** + * Set of utilities dealing with Blackboard data. + * + * @author Andrey Balan + */ +public class BlackboardUtil { + + /** + * Returns one random teacher from the specified course. + * + * @param courseId + * BB course id + * @return teacher + * @throws PersistenceException + */ + public static User getCourseTeacher(PkId courseId) throws PersistenceException { + // find the main teacher + CourseMembershipDbLoader courseMemLoader = CourseMembershipDbLoader.Default.getInstance(); + List<CourseMembership> monitorCourseMemberships = courseMemLoader.loadByCourseIdAndRole(courseId, + CourseMembership.Role.INSTRUCTOR, null, true); + if (monitorCourseMemberships.isEmpty()) { + List<CourseMembership> teachingAssistantCourseMemberships = courseMemLoader + .loadByCourseIdAndRole(courseId, CourseMembership.Role.TEACHING_ASSISTANT, null, true); + monitorCourseMemberships.addAll(teachingAssistantCourseMemberships); + if (monitorCourseMemberships.isEmpty()) { + List<CourseMembership> courseBuilderCourseMemberships = courseMemLoader + .loadByCourseIdAndRole(courseId, CourseMembership.Role.COURSE_BUILDER, null, true); + monitorCourseMemberships.addAll(courseBuilderCourseMemberships); + } + } + // validate teacher existence + if (monitorCourseMemberships.isEmpty()) { + throw new RuntimeException("There are no monitors in the course courseId=" + courseId); + } + User teacher = monitorCourseMemberships.get(0).getUser(); + + return teacher; + } + + /** + * Returns all LAMS lessons from the specified course. + * + * @param courseId + * BB course id + * @return list of LAMS lessons + * @throws PersistenceException + */ + public static List<Content> getLamsLessonsByCourse(PkId courseId) throws PersistenceException { + + ContentDbLoader contentLoader = ContentDbLoader.Default.getInstance(); + CourseTocDbLoader cTocDbLoader = CourseTocDbLoader.Default.getInstance(); + + // get a CourseTOC (Table of Contents) loader. We will need this to iterate through all of the "areas" + // within the course + List<CourseToc> courseTocs = cTocDbLoader.loadByCourseId(courseId); + + // iterate through the course TOC items + List<Content> lamsContents = new ArrayList<Content>(); + for (CourseToc courseToc : courseTocs) { + + // determine if the TOC item is of type "CONTENT" rather than applicaton, or something else + if ((courseToc.getTargetType() == CourseToc.Target.CONTENT) && (courseToc.getContentId() != Id.UNSET_ID)) { + // we have determined that the TOC item is content, next we need to load the content object and + // iterate through it + // load the content tree into an object "content" and iterate through it + List<Content> contents = contentLoader.loadListById(courseToc.getContentId()); + // iterate through the content items in this content object + for (Content content : contents) { + // only LAMS content + if ("resource/x-lams-lamscontent".equals(content.getContentHandler()) + || content.getContentHandler().equals("resource/x-ntu-hdllams")) { + lamsContents.add(content); + } + } + } + } + + return lamsContents; + } + + /** + * @throws ParseException + * @throws IOException + * @throws ValidationException + * @throws SAXException + * @throws ParserConfigurationException + */ + public static String storeBlackboardContent(HttpServletRequest request, HttpServletResponse response, User user) + throws PersistenceException, ParseException, IOException, ValidationException, ParserConfigurationException, SAXException { + + ContentDbLoader contentLoader = ContentDbLoader.Default.getInstance(); + + // Set the new LAMS Lesson Content Object + CourseDocument bbContent = new blackboard.data.content.CourseDocument(); + // Retrieve the Db persistence manager from the persistence service + BbPersistenceManager bbPm = PersistenceServiceFactory.getInstance().getDbPersistenceManager(); + + String _course_id = request.getParameter("course_id"); + String _content_id = request.getParameter("content_id"); + String strTitle = getTrimmedString(request, "title"); + String strSequenceID = getTrimmedString(request, "sequence_id"); + // TODO: Use bb text area instead + String strDescription = getTrimmedString(request, "descriptiontext"); + String strIsAvailable = request.getParameter("isAvailable"); + String strIsGradecenter = request.getParameter("isGradecenter"); + String strIsTracked = request.getParameter("isTracked"); + String isDisplayDesignImage = request.getParameter("isDisplayDesignImage"); + + // Internal Blackboard IDs for the course and parent content item + Id courseId = bbPm.generateId(Course.DATA_TYPE, _course_id); + Id folderId = bbPm.generateId(CourseDocument.DATA_TYPE, _content_id); + + FormattedText description = new FormattedText(strDescription, FormattedText.Type.HTML); + long ldId = Long.parseLong(strSequenceID); + + boolean isAvailable = (strIsAvailable == null || strIsAvailable.equals("true")) ? true : false; // default true + boolean isGradecenter = (strIsGradecenter != null && strIsGradecenter.equals("true")) ? true : false; // default false + boolean isTracked = (strIsTracked != null && strIsTracked.equals("true")) ? true : false; // default false + + // Set Availability Dates + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + // Start Date + String strStartDate = request.getParameter("lessonAvailability_start_datetime"); + if (strStartDate != null) { + Calendar startDate = Calendar.getInstance(); + startDate.setTime(formatter.parse(strStartDate)); + String strStartDateCheckbox = request.getParameter("lessonAvailability_start_checkbox"); + if (strStartDateCheckbox != null) { + if (strStartDateCheckbox.equals("1")) { + bbContent.setStartDate(startDate); + } + } + } + + // End Date + String strEndDate = request.getParameter("lessonAvailability_end_datetime"); + if (strEndDate != null) { + Calendar endDate = Calendar.getInstance(); + endDate.setTime(formatter.parse(strEndDate)); + String strEndDateCheckbox = request.getParameter("lessonAvailability_end_checkbox"); + if (strEndDateCheckbox != null) { + if (strEndDateCheckbox.equals("1")) { + bbContent.setEndDate(endDate); + } + } + } + + // Set the New LAMS Lesson content data (in Blackboard) + bbContent.setTitle(strTitle); + bbContent.setIsAvailable(isAvailable); + bbContent.setIsDescribed(isGradecenter);// isDescribed field is used for storing isGradecenter parameter + bbContent.setIsTracked(isTracked); + bbContent.setAllowGuests(false); + bbContent.setContentHandler(LamsPluginUtil.CONTENT_HANDLE); + + bbContent.setCourseId(courseId); + bbContent.setParentId(folderId); + + bbContent.setRenderType(Content.RenderType.URL); + bbContent.setBody(description); + + // assign LAMS lesson the last position number so it appears at the bottom of the list + List<Content> contents = contentLoader.loadListById(folderId); + int countContentsInsideFolder = contents.size(); + bbContent.setPosition(countContentsInsideFolder); + + //get course's courseId string that we need to provide LAMS with + CourseDbLoader courseLoader = CourseDbLoader.Default.getInstance(); + Course course = courseLoader.loadById(courseId); + String courseIdStr = course.getCourseId(); + + // Start the Lesson in LAMS (via Webservices) and capture the lesson ID + final long lamsLessonIdLong = LamsSecurityUtil.startLesson(user, courseIdStr, ldId, strTitle, strDescription, + false); + // error checking + if (lamsLessonIdLong == -1) { + response.sendRedirect("lamsServerDown.jsp"); + System.exit(1); + } + String lamsLessonId = Long.toString(lamsLessonIdLong); + bbContent.setLinkRef(lamsLessonId); + + // Persist the New Lesson Object in Blackboard + ContentDbPersister persister = (ContentDbPersister) bbPm.getPersister(ContentDbPersister.TYPE); + persister.persist(bbContent); + //same as "_" + bbContent.getId().getPk1() + "_" + bbContent.getId().getPk2() + String bbContentId = bbContent.getId().toExternalString(); + + // Build and set the content URL. Include new lesson id parameter + int bbport = request.getServerPort();// Add port to the url if the port is in the blackboard url. + String bbportstr = bbport != 0 ? ":" + bbport : ""; + String contentUrl = request.getScheme() + "://" + + request.getServerName() + bbportstr + request.getContextPath() + "/modules/learnermonitor.jsp" + + "?lsid=" + lamsLessonId + + "&course_id=" + request.getParameter("course_id") + + "&content_id=" + bbContentId + + "&ldid=" + ldId + + "&isDisplayDesignImage=" + isDisplayDesignImage; + bbContent.setUrl(contentUrl); + persister.persist(bbContent); + + // store internalContentId -> externalContentId. It's used for GradebookServlet + PortalExtraInfo pei = PortalUtil.loadPortalExtraInfo(null, null, "LamsStorage"); + ExtraInfo ei = pei.getExtraInfo(); + ei.setValue(bbContentId, lamsLessonId); + PortalUtil.savePortalExtraInfo(pei); + + // Create new Gradebook column for current lesson + if (isGradecenter) { + String userName = user.getUserName(); + LineitemUtil.createLineitem(bbContent, userName); + } + + // create a new thread to pre-add students and monitors to a lesson (in order to do this task in parallel not to + // slow down later work) + final User userFinal = user; + final Course courseFinal = course; + Thread preaddLearnersMonitorsThread = new Thread(new Runnable() { + @Override + public void run() { + LamsSecurityUtil.preaddLearnersMonitorsToLesson(userFinal, courseFinal, lamsLessonIdLong); + } + }, "LAMS_preaddLearnersMonitors_thread"); + preaddLearnersMonitorsThread.start(); + + return bbContentId; + } + + public static String getTrimmedString(HttpServletRequest request, String paramName) { + String value = request.getParameter(paramName); + return value != null ? value.trim() : ""; + } + + public static User loadUserFromDB(String username) { + User user = null; + try { + final UserDbLoader userDbLoader = UserDbLoader.Default.getInstance(); + user = userDbLoader.loadByUserName(username); + } catch (KeyNotFoundException e) { + throw new RuntimeException("No user details found in context or via username parameter. Unable access LAMS. "+e.getMessage()+" Username "+username,e); + } catch (PersistenceException e) { + throw new RuntimeException("No user details found in context or via username parameter. Unable access LAMS. "+e.getMessage()+" Username "+username,e); + } + if ( user == null ) { + throw new RuntimeException("No user details found in context or via username parameter. Unable access LAMS. Username "+username); + } + return user; + } + +} Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/util/CSVUtil.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/util/CSVUtil.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/util/CSVUtil.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,171 @@ +/**************************************************************** + * Copyright (C) 2007 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ +package org.lamsfoundation.bb.integration.util; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.regex.Pattern; + +/** + * CSVUtil Provides "Comma Seperated Value" writing and parsing. + * The two methods write() and parse() will perform writing to and parse from + * the CSV format + * + * @author <a href="mailto:lfoxton@melcoe.mq.edu.au">Luke Foxton</a> + */ +public class CSVUtil { + + private static final char QUOTE = '"'; + private static final char COMMA = ','; + + /* precompile the patterns to speed up the search */ + + // should we put quotes around a value? + private static final Pattern CONTAINS_NEWLINE = Pattern.compile(".*(\\n|\\r)+.*"); + + // should we put quotes around a value? + private static final Pattern CONTAINS_COMMA = Pattern.compile(".*(,)+.*"); + + // should we escape the quotes? + private static final Pattern CONTAINS_QUOTE = Pattern.compile("\""); + + // how should we wrap qoutes around comma or newline? + private static final String WRAP_QOUTE = "\"$0\""; + + // how should we escape the value if it has qoutes? + private static final String ESCAPE_QUOTE = "\"\""; + + // has this value been wrapped with quotes + private static final Pattern WRAPPED_QUOTE = Pattern.compile("^\"(.*((,|\\n|\\r)+).*)\"$"); + + // has this value been escaped by ESCAPE_QUOTE + private static final Pattern ESCAPED_QUOTE = Pattern.compile("\"\""); + + // how should we unescape the the ESCAPED_QUOTE? + private static final String UNWRAP_QOUTE = "$1"; + + // how should we unescape the the ESCAPED_COMMA? + private static final String UNESCAPE_QUOTE = "\""; + + /* + * NOTE: why are we using \\n|\\r in CONTAINS_NEWLINE and WRAPPED_QUOTE? + * javadoc says "." represents "Any character (may or may not match line terminators)" + * and we want to make sure terminiators such as newline (\n) gets matched as well + * if we dont match it then ,\n, will get written as ","\n"," + */ + + /** + * Writes a array of String into CSV format + * @param vals - The array of string to be written into CSV format + * @return + */ + public static String write(String[] vals){ + String str = ""; + int lastIndex = vals.length - 1; + for(int i=0; i<vals.length; i++){ + // str += vals[i].replaceAll("\"", "\"\"").replaceAll(".*,.*", "\"$0\"") + + // (i==lastIndex?"":","); //same as below but used compiled patterns + + //check for quotes then escape it + String tmp = CONTAINS_QUOTE.matcher(vals[i]).replaceAll(ESCAPE_QUOTE); + //System.out.println("trace: " + vals[i] + ", " + tmp); + + //check for comma then escape it + /** + * NOTE: the replaceAll method will not replace accross multiple line + * hence \n,\n will be replaced as \n","\n but "\n,\n" is expected + * therefore we put quotes around tmp without using replace + */ + // String wrapped = CONTAINS_COMMA.matcher(tmp).replaceAll("\"$0\""); + // String wrapped = CONTAINS_COMMA.matcher(tmp).replaceAll('"' + tmp + '"'); + String wrapped = CONTAINS_COMMA.matcher(tmp).find()?'"'+tmp+'"':tmp; + + //check if tmp has been wrapped because of commas found, + //if not wrapped then look look for newline and then wrap it + if(wrapped.equals(tmp)){ + wrapped = CONTAINS_COMMA.matcher(tmp).find()?'"'+tmp+'"':tmp; + } + + + str += wrapped + (i==lastIndex?"":","); //dont append comma to the last value + } + return str; + } + + /** + * Parse the CSV formatted string and return each value seperatly stored in an array + * + * The parse() method is design to parse 1 record only, each value in the record + * can contain , newline(\n) or carriage-return(\r). + * but parse() is not design to handle newline or carriage-return outside the quoted + * values. newline or carriage-return outside the quoted values signals a new record. + * + * If someone decide parse() need to handle multiple rows of record then we can extend + * the functionaliy of this method. + * + * @param str + * @return + */ + public static String[] parse(String str) throws ParseException{ + ArrayList<String> res = new ArrayList<String>(); + int startIndex = 0; + boolean openQuote = false; + + str += ","; //end the last value with comma, so last value can be found correctly + + for(int i=0; i<str.length(); i++){ + char ch = str.charAt(i); + + //match pairs of quote + if(ch==QUOTE){ + openQuote = !openQuote; + } + + //if comma is detected and is not inside quotes, then we have found a value + else if((ch==COMMA) && !openQuote){ + String val = str.substring(startIndex, i); + //System.out.println("trace: found - " + val); + + // check for escaped quote then unescape it + String tmp = ESCAPED_QUOTE.matcher(val).replaceAll(UNESCAPE_QUOTE); + + // check for wrapped quotes then unwrap it + res.add(WRAPPED_QUOTE.matcher(tmp).replaceAll(UNWRAP_QOUTE)); + //System.out.println("trace: " + val + " - " + tmp); + + //res.add(val.replaceAll("^\"((.|\\p{Space})+)\"$", "$1").replaceAll("\"\"", "\"")); + + startIndex = i + 1; + } + } + if(openQuote){ + throw new ParseException("Fail to find matching \" while parsing [" + str + "] ", startIndex); + } + + String [] resStr = new String[res.size()]; + res.toArray(resStr); + + + return resStr; + } +} Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/util/LamsBuildingBlockException.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/util/LamsBuildingBlockException.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/util/LamsBuildingBlockException.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,36 @@ +package org.lamsfoundation.bb.integration.util; + +/** + * Exception that originated in LAMS building block. + */ +public class LamsBuildingBlockException extends RuntimeException { + + private static final long serialVersionUID = -6836893578878806461L; + + public LamsBuildingBlockException() { + super(); + } + + /** + * @param message + */ + public LamsBuildingBlockException(String message) { + super(message); + } + + /** + * @param cause + */ + public LamsBuildingBlockException(Throwable cause) { + super(cause); + } + + /** + * @param message + * @param cause + */ + public LamsBuildingBlockException(String message, Throwable cause) { + super(message, cause); + } + +} Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/util/LamsPluginUtil.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/util/LamsPluginUtil.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/util/LamsPluginUtil.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,151 @@ +/**************************************************************** + * Copyright (C) 2007 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ +package org.lamsfoundation.bb.integration.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Properties; + +import blackboard.platform.plugin.PlugInException; +import blackboard.platform.plugin.PlugInUtil; + +/** + * + * This class basically manages the lams.properties file on the BB server This file contains the LAMS server URL, server + * ID and secret key These values allow the communication between the two servers + * + * @author <a href="mailto:lfoxton@melcoe.mq.edu.au">Luke Foxton</a> + */ +public class LamsPluginUtil { + + public static final String VENDOR_ID = "lams"; + public static final String PLUGIN_HANDLE = "lamscontent"; + public static final String CONTENT_HANDLE = "resource/x-lams-lamscontent"; + public static final String FILE_PROPERTIES = "lams.properties"; + + public static final String PROP_LAMS_SECRET_KEY = "LAMS_SERVER_SKEY"; + public static final String PROP_LAMS_SERVER_ID = "LAMS_SERVER_ID"; + public static final String PROP_LAMS_URL = "LAMS_SERVER_URL"; + public static final String PROP_LAMS_SERVER_TIME_REFRESH_INTERVAL = "LAMS_SERVER_TIME_REFRESH_INTERVAL"; + + private static Properties lamsProperties = null; + + /** + * Returns the properties file that contains the server name, key and connection URL + * + * @return The LAMS Properties file + * @throws PlugInException + * @throws FileNotFoundException + * @throws IOException + */ + public static Properties getProperties() { + if (lamsProperties != null) + return lamsProperties; + + // load LAMS Configuration File + try { + File configFile = new File(PlugInUtil.getConfigDirectory(VENDOR_ID, PLUGIN_HANDLE).getPath() + + File.separator + FILE_PROPERTIES); + Properties p = new Properties(); + + if (configFile.exists()) + p.load(new FileInputStream(configFile)); + else { + p.setProperty(PROP_LAMS_URL, ""); + p.setProperty(PROP_LAMS_SECRET_KEY, ""); + p.setProperty(PROP_LAMS_SERVER_ID, ""); + p.setProperty(PROP_LAMS_SERVER_TIME_REFRESH_INTERVAL, "24"); + } + + lamsProperties = p; + return p; + } catch (PlugInException e) { + throw new RuntimeException(e); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Save a Properties file as the LAMS properties file + * + * @param p + * @throws PlugInException + * @throws FileNotFoundException + * @throws IOException + */ + public static void setProperties(Properties p) { + try { + lamsProperties = p; + + FileOutputStream configFile = new FileOutputStream(PlugInUtil.getConfigDirectory(VENDOR_ID, PLUGIN_HANDLE) + .getPath() + File.separator + FILE_PROPERTIES); + p.store(configFile, "LAMS configuration"); + configFile.close(); + } catch (PlugInException e) { + throw new RuntimeException(e); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * + * @return the secret key from lams.properties + */ + public static String getServerSecretKey() { + return getProperties().getProperty(PROP_LAMS_SECRET_KEY); + } + + /** + * + * @return the secret url from lams.properties + */ + public static String getServerUrl() { + return getProperties().getProperty(PROP_LAMS_URL); + } + + /** + * + * @return the server id from lams.properties + */ + public static String getServerId() { + return getProperties().getProperty(PROP_LAMS_SERVER_ID); + } + + /** + * + * @return the LAMS server time refresh interval from lams.properties + */ + public static String getLamsServerTimeRefreshInterval() { + return getProperties().getProperty(PROP_LAMS_SERVER_TIME_REFRESH_INTERVAL); + } + +} Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/util/LamsSecurityUtil.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/util/LamsSecurityUtil.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/util/LamsSecurityUtil.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,1138 @@ +/**************************************************************** + * Copyright (C) 2007 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ +package org.lamsfoundation.bb.integration.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.net.ConnectException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.rmi.RemoteException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Date; +import java.util.List; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.commons.lang.StringUtils; +import org.lamsfoundation.bb.integration.dto.LearnerProgressDTO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.xml.sax.SAXException; + +import blackboard.data.course.Course; +import blackboard.data.course.CourseMembership; +import blackboard.data.user.User; +import blackboard.persist.BbPersistenceManager; +import blackboard.persist.Id; +import blackboard.persist.KeyNotFoundException; +import blackboard.persist.PersistenceException; +import blackboard.persist.course.CourseMembershipDbLoader; +import blackboard.platform.context.Context; +import blackboard.platform.persistence.PersistenceServiceFactory; +import blackboard.portal.data.ExtraInfo; +import blackboard.portal.data.PortalExtraInfo; +import blackboard.portal.servlet.PortalUtil; + +/** + * This class creates URLs, servlet calls and webservice calls for communication with LAMS + * + * @author <a href="mailto:lfoxton@melcoe.mq.edu.au">Luke Foxton</a> + */ +public class LamsSecurityUtil { + + private static Logger logger = LoggerFactory.getLogger(LamsSecurityUtil.class); + private static final String DUMMY_COURSE = "Previews"; + private static final String EXPORT_FOLDER_LAMS_SERVER = "/tmp/lams/"; + + /** + * Generates login requests to LAMS for author, monitor and learner + * + * @param ctx + * the blackboard contect, contains session data + * @param method + * the mehtod to request of LAMS "author", "monitor", "learnerStrictAuth" + * @param lsid + * lesson id. It is expected to be present in case of "monitor" and "learnerStrictAuth" + * @return a url pointing to the LAMS lesson, monitor, author session + * @throws IOException + * @throws PersistenceException + * @throws Exception + */ + public static String generateRequestURL(Context ctx, String method, String lsid) throws PersistenceException, IOException { + String serverAddr = LamsPluginUtil.getServerUrl(); + String serverId = LamsPluginUtil.getServerId(); + + // If lams.properties could not be read, throw exception + if (serverAddr == null || serverId == null) { + throw new RuntimeException("Configuration Exception " + serverAddr + ", " + serverId); + } + + String timestamp = getServerTime(); + String username = ctx.getUser().getUserName(); + String firstName = ctx.getUser().getGivenName(); + String lastName = ctx.getUser().getFamilyName(); + String email = ctx.getUser().getEmailAddress(); + String locale = ctx.getUser().getLocale(); + String country = getCountry(locale); + String lang = getLanguage(locale); + + // Even for authoring calls we still need a 'course' the user, role & organisation are all bound up together + // do to be authorised to use authoring you must be in an organisation. + String courseId = setupCourseId(ctx, null, true); + + String serverSecretKey = LamsPluginUtil.getServerSecretKey(); + + // in case of learnerStrictAuth we should also include lsid value when creating hash: [ts + uid + method + lsid + // + serverID + serverSecretKey] + // regular case: [ts + uid + method + serverID + serverSecretKey] + String plaintext = timestamp.toLowerCase().trim() + username.toLowerCase().trim() + method.toLowerCase().trim() + + ("learnerStrictAuth".equals(method) ? lsid.toLowerCase().trim() : "") + serverId.toLowerCase().trim() + + serverSecretKey.toLowerCase().trim(); + // generate authentication hash code to validate parameters + String hash = sha1(plaintext); + + String url; + try { + String course = courseId != null ? "&courseid=" + URLEncoder.encode(courseId, "UTF8") : ""; + url = serverAddr + "/LoginRequest?" + "&uid=" + URLEncoder.encode(username, "UTF8") + "&method=" + method + + "&ts=" + timestamp + "&sid=" + serverId + "&hash=" + hash + course + "&country=" + country + + "&lang=" + lang + "&firstName=" + URLEncoder.encode(firstName, "UTF-8") + "&lastName=" + + URLEncoder.encode(lastName, "UTF-8") + "&email=" + URLEncoder.encode(email, "UTF-8"); + + if ("learnerStrictAuth".equals(method) || "monitor".equals(method)) { + url += "&lsid=" + lsid; + } + + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + + logger.info("LAMS Req: " + url); + // System.out.println(url); + + return url; + } + + /** + * Generates default + * @throws UnsupportedEncodingException + */ + public static String generateAuthenticateParameters(String username) throws UnsupportedEncodingException { + String serverAddr = LamsPluginUtil.getServerUrl(); + String serverId = LamsPluginUtil.getServerId(); + + // If lams.properties could not be read, throw exception + if (serverAddr == null || serverId == null) { + throw new RuntimeException("Configuration Exception " + serverAddr + ", " + serverId); + } + + String timestamp = new Long(System.currentTimeMillis()).toString(); + String hash = generateAuthenticationHash(timestamp, username, serverId); + + String authenticateParameters = "&serverId=" + serverId + "&datetime=" + timestamp + "&hashValue=" + hash + + "&username=" + URLEncoder.encode(username, "UTF8"); + + return authenticateParameters; + } + + /** + * Generates url request to LAMS for LearningDesignImage. + * + * @param ctx + * the blackboard contect, contains session data + * @return a url pointing to the LAMS lesson, monitor, author session + * @throws UnsupportedEncodingException + */ + public static String generateRequestLearningDesignImage(String username) throws UnsupportedEncodingException { + String serverAddr = LamsPluginUtil.getServerUrl(); + + //$request = "$CFG->lamslesson_serverurl/services/LearningDesignSVG?serverId=" . $CFG->lamslesson_serverid . "&datetime=" . $datetime_encoded . "&hashValue=" . + //$hashvalue . "&username=" . $username . "&courseId=" . $courseid . "&courseName=" . urlencode($coursename) . "&mode=2&country=" . $country . "&lang=" . $lang . + //"&ldId=" . $ldid; + String url = serverAddr + "/services/LearningDesignSVG?" + generateAuthenticateParameters(username); + + logger.info("LAMS Req: " + url); + + return url; + } + + /** + * Gets a list of learning designs & workspace folders for the current user from LAMS. + * + * @param ctx + * the blackboard context, contains session data + * @param courseId + * blackboard courseid. We pass it as a parameter as ctx.getCourse().getCourseId() is null when called + * from LamsLearningDesignServlet. + * @param folderId folderId. It can be null and then LAMS returns default workspace folders. + * + * @return a string containing the LAMS workspace tree in tigra format + */ + public static String getLearningDesigns(Context ctx, String courseId, String folderId) { + return getLearningDesigns(ctx, null, courseId, folderId,"getLearningDesignsJSON",null,null,null,null,null,null); + } + + /** + * Gets a list of learning designs & workspace folders for the current user from LAMS or the user "usernameFromParam" + * + * @param ctx the blackboard context, contains session data + * @param usernameFromParam only used if there isn't a user in the context, due to how the servlet is called + * @param courseId blackboard course id. We pass it as a parameter as ctx.getCourse().getCourseId() is null when called + * from LamsLearningDesignServlet. + * @param folderId folderID in LAMS. It can be null and then LAMS returns default workspace folders. + * @param method which method to call on the LAMS end + * @param type used onlu for method = getLearningDesignsJSON, restricts by type + * @param page used only for method = getPagedHomeLearningDesignsJSON + * @param size used only for method = getPagedHomeLearningDesignsJSON + * @return a string containing the LAMS workspace tree in tigra format (method = getLearningDesignsJSON) or + * a string containing the learning designs in JSON (method = getPagedHomeLearningDesignsJSON) + */ + public static String getLearningDesigns(Context ctx, String usernameFromParam, String urlCourseId, String folderId, String method, String type, + String search, String page, String size, String sortName, String sortDate) { + + String serverAddr = LamsPluginUtil.getServerUrl(); + + String courseId = setupCourseId(ctx, urlCourseId, true); + String serverId = LamsPluginUtil.getServerId(); + + // If lams.properties could not be read, throw exception + if (serverAddr == null || serverId == null) { + throw new RuntimeException("lams.properties file could not be read. serverAddr:" + serverAddr + ", serverId:" + serverId); + } + + String timestamp = new Long(System.currentTimeMillis()).toString(); + + User user = ctx.getUser(); + if ( user == null ) + user = BlackboardUtil.loadUserFromDB(usernameFromParam); + + String username = user.getUserName(); + String firstName = user.getGivenName(); + String lastName = user.getFamilyName(); + String email = user.getEmailAddress(); + String hash = generateAuthenticationHash(timestamp, username, serverId); + + String locale = user.getLocale(); + String country = getCountry(locale); + String lang = getLanguage(locale); + + // the mode to call upon learning designs + final Integer MODE = 2; + + // TODO: Make locale settings work + String learningDesigns = ""; // empty + try { + + + String serviceURL = serverAddr + + "/services/xml/LearningDesignRepository?method="+method+"&datetime=" + + timestamp + "&username=" + URLEncoder.encode(username, "utf8") + "&serverId=" + + URLEncoder.encode(serverId, "utf8") + "&hashValue=" + hash + "&courseId=" + + URLEncoder.encode(courseId, "UTF8") + "&country=" + country + "&lang=" + lang + "&mode=" + MODE + + "&firstName=" + URLEncoder.encode(firstName, "UTF-8") + "&lastName=" + + URLEncoder.encode(lastName, "UTF-8") + "&email=" + URLEncoder.encode(email, "UTF-8"); + + if (folderId != null ) { + serviceURL += "&folderID=" + ( folderId.equalsIgnoreCase("home") ? "-1" : folderId); + } + + // The following parameter is only used for getLearningDesignsJSON + if ( type != null && type.length() > 0 ) { + serviceURL += "&type=" +type; + } + + // The following parameters are only used for getPagedLearningDesignsJSON + if (page != null ) { + serviceURL += "&page=" + page; + } + if (size != null ) { + serviceURL += "&size=" + size; + } + // sort by name, ascending = 1, descending = 0 + if (sortName != null ) { + serviceURL += "&sortName=" + sortName; + } + // sort by date, ascending = 1, descending = 0 + if (sortDate != null ) { + serviceURL += "&sortDate=" + sortDate; + } + // get all the designs that contain this string + if (search != null ) { + serviceURL += "&search=" + search; + } + + InputStream is = LamsSecurityUtil.callLamsServerPost(serviceURL); + + // Read/convert response to a String + StringWriter writer = new StringWriter(); + IOUtils.copy(is, writer, "UTF-8"); + learningDesigns = writer.toString(); + + } catch (MalformedURLException e) { + throw new RuntimeException("Unable to get LAMS learning designs, bad URL: '" + serverAddr + + "', please check lams.properties", e); + } catch (IllegalStateException e) { + throw new RuntimeException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (ConnectException e) { + throw new RuntimeException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return learningDesigns; + } + + /** + * Gets a list of learning designs & workspace folders for the current user from LAMS. + * + * @param ctx the blackboard context, contains session data + * @param courseId blackboard courseid. We pass it as a parameter as ctx.getCourse().getCourseId() is null when called + * from LamsLearningDesignServlet. + * @param ldId learning design to delete + * @return JSON response from server + */ + public static String deleteLearningDesigns(Context ctx, String urlCourseId, Long ldId) { + + String courseId = setupCourseId(ctx, urlCourseId, false); + + String serverAddr = LamsPluginUtil.getServerUrl(); + String serverId = LamsPluginUtil.getServerId(); + + // If lams.properties could not be read, throw exception + if (serverAddr == null || serverId == null) { + throw new RuntimeException("lams.properties file could not be read. serverAddr:" + serverAddr + ", serverId:" + serverId); + } + + String timestamp = new Long(System.currentTimeMillis()).toString(); + String username = ctx.getUser().getUserName(); + String firstName = ctx.getUser().getGivenName(); + String lastName = ctx.getUser().getFamilyName(); + String email = ctx.getUser().getEmailAddress(); + String hash = generateAuthenticationHash(timestamp, username, serverId); + + String locale = ctx.getUser().getLocale(); + String country = getCountry(locale); + String lang = getLanguage(locale); + + try { + + String serviceURL = serverAddr + + "/services/xml/LearningDesignRepository?method=deleteLearningDesignJSON&datetime=" + + timestamp + "&username=" + URLEncoder.encode(username, "utf8") + "&serverId=" + + URLEncoder.encode(serverId, "utf8") + "&hashValue=" + hash + "&courseId=" + + URLEncoder.encode(courseId, "UTF8") + "&country=" + country + "&lang=" + lang + + "&firstName=" + URLEncoder.encode(firstName, "UTF-8") + "&lastName=" + + URLEncoder.encode(lastName, "UTF-8") + "&email=" + URLEncoder.encode(email, "UTF-8") + + "&learningDesignID="+ldId; + + InputStream is = LamsSecurityUtil.callLamsServerPost(serviceURL); + + // Read/convert response to a String + StringWriter writer = new StringWriter(); + IOUtils.copy(is, writer, "UTF-8"); + return writer.toString(); + + } catch (MalformedURLException e) { + throw new RuntimeException("Unable to get LAMS learning designs, bad URL: '" + serverAddr + + "', please check lams.properties", e); + } catch (IllegalStateException e) { + throw new RuntimeException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (ConnectException e) { + throw new RuntimeException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static String setupCourseId(Context ctx, String urlCourseId, boolean allowUserDummyCourse) { + // can we pull the alphanumeric course id from the context, rather than the on passed in from the URL? If neither exist, use the dummy Preview course. + String courseId = null; + if (ctx.getCourse() != null) { + courseId = ctx.getCourse().getCourseId(); + } + if (courseId == null && urlCourseId != null && urlCourseId.length() > 0) { + courseId = urlCourseId; + } + if (courseId == null && allowUserDummyCourse) { + courseId = DUMMY_COURSE; + } + return courseId; + } + + /** + * Starts lessons in lams through a LAMS webservice. + * + * @param ctx + * the blackboard contect, contains session data + * @param usernameFromParam + * current user's username + * @param courseIdStr + * courseId + * @param ldId + * the learning design id for which you wish to start a lesson + * @param title + * the title of the lesson + * @param desc + * the description of the lesson + * + * @return the learning session id + */ + public static Long startLesson(User user, String courseId, long ldId, String title, String desc, boolean isPreview) { + + String serverId = LamsPluginUtil.getServerId(); + String serverAddr = LamsPluginUtil.getServerUrl(); + String serverSecretKey = LamsPluginUtil.getServerSecretKey(); + + String username = user.getUserName(); + String locale = user.getLocale(); + String country = getCountry(locale); + String lang = getLanguage(locale); + String method = (isPreview) ? "preview" : "start"; + + if (courseId == null || serverId == null || serverAddr == null || serverSecretKey == null) { + logger.info("Unable to start lesson, one or more lams configuration properties or the course id is null"); + throw new RuntimeException("Unable to start lesson, one or more lams configuration properties or the course id is null. courseId="+courseId); + } + + try { + String timestamp = new Long(System.currentTimeMillis()).toString(); + String hash = generateAuthenticationHash(timestamp, username, serverId); + String course = courseId != null ? "&courseId=" + URLEncoder.encode(courseId, "UTF8") : ""; + + String serviceURL = serverAddr + "/services/xml/LessonManager?" + "serverId=" + + URLEncoder.encode(serverId, "utf8") + "&datetime=" + timestamp + "&username=" + + URLEncoder.encode(username, "utf8") + "&hashValue=" + hash + course + + "&ldId=" + new Long(ldId).toString() + "&country=" + + country + "&lang=" + lang + "&method=" + method + "&title=" + + URLEncoder.encode(title, "utf8").trim() + "&desc=" + URLEncoder.encode(desc, "utf8").trim() + + "&enableNotifications=true"; + + logger.info("LAMS START LESSON Req: " + serviceURL); + + // parse xml response and get the lesson id + InputStream is = LamsSecurityUtil.callLamsServerPost(serviceURL); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document document = db.parse(is); + return Long.parseLong(document.getElementsByTagName("Lesson").item(0).getAttributes() + .getNamedItem("lessonId").getNodeValue()); + + } catch (MalformedURLException e) { + throw new RuntimeException("Unable to start LAMS lesson, bad URL: '" + serverAddr + + "', please check lams.properties", e); + } catch (IllegalStateException e) { + throw new RuntimeException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (RemoteException e) { + throw new RuntimeException("Unable to start LAMS lesson, RMI Remote Exception", e); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Unable to start LAMS lesson, Unsupported Encoding Exception", e); + } catch (ConnectException e) { + throw new RuntimeException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (IOException e) { + throw new RuntimeException("Unable to start LAMS lesson. " + e.getMessage() + + " Please contact your system administrator.", e); + } catch (ParserConfigurationException e) { + throw new RuntimeException("Unable to start LAMS lesson. Please contact your system administrator.", e); + } catch (SAXException e) { + throw new RuntimeException("Unable to start LAMS lesson. Please contact your system administrator.", e); + } + + } + + /** + * Deletes lesson on LAMS server through a LAMS webservice. + * + * @param ctx + * the blackboard contect, contains session data + * @param usernameFromParam + * current user's username + * @param lsId + * the lesson id to be deleted + * + * @return boolean whether lesson was successfully deleted + * @throws IOException + * @throws ParserConfigurationException + * @throws SAXException + */ + public static Boolean deleteLesson(String userName, String lsId) throws IOException, ParserConfigurationException, SAXException { + + String serverId = LamsPluginUtil.getServerId(); + String serverAddr = LamsPluginUtil.getServerUrl(); + String serverSecretKey = LamsPluginUtil.getServerSecretKey(); + + if (serverId == null || serverAddr == null || serverSecretKey == null) { + throw new RuntimeException("Unable to delete lesson. One or more LAMS configuration properties are null"); + } + + String timestamp = new Long(System.currentTimeMillis()).toString(); + String hash = generateAuthenticationHash(timestamp, userName, serverId); + + String serviceURL = serverAddr + "/services/xml/LessonManager?" + "serverId=" + + URLEncoder.encode(serverId, "utf8") + "&datetime=" + timestamp + "&username=" + + URLEncoder.encode(userName, "utf8") + "&hashValue=" + hash + "&method=removeLesson" + "&lsId=" + lsId; + + logger.info("LAMS DELETE LESSON Req: " + serviceURL); + + // parse xml response and get the lesson id + InputStream is = LamsSecurityUtil.callLamsServerPost(serviceURL); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document document = db.parse(is); + return Boolean.parseBoolean( + document.getElementsByTagName("Lesson").item(0).getAttributes().getNamedItem("deleted").getNodeValue()); + } + + /** + * Clones lessons in lams through a LAMS webservice using the lsID & courseId parameter. + * + * @param courseId + * courseId as a request parameter + * @param ldId + * the learning design id for which you wish to start a lesson + * + * @return lesson id of a cloned lesson + */ + public static Long cloneLesson(User teacher, String courseId, String lsId) { + + String serverId = LamsPluginUtil.getServerId(); + String serverAddr = LamsPluginUtil.getServerUrl(); + String serverSecretKey = LamsPluginUtil.getServerSecretKey(); + String username = teacher.getUserName(); + String locale = teacher.getLocale(); + String country = getCountry(locale); + String lang = getLanguage(locale); + + if (courseId == null || serverId == null || serverAddr == null || serverSecretKey == null) { + logger.info("Unable to clone lesson, one or more lams configuration properties or the course id is null"); + throw new RuntimeException("Unable to clone lesson, one or more lams configuration properties or the course id is null. courseId="+courseId); + } + + try { + String method = "clone"; + String timestamp = new Long(System.currentTimeMillis()).toString(); + String hash = generateAuthenticationHash(timestamp, username, serverId); + String serviceURL = serverAddr + "/services/xml/LessonManager?" + "serverId=" + + URLEncoder.encode(serverId, "utf8") + "&datetime=" + timestamp + "&username=" + + URLEncoder.encode(username, "utf8") + "&hashValue=" + hash + "&courseId=" + + URLEncoder.encode(courseId, "UTF8") + "&country=" + + country + "&lang=" + lang + "&lsId=" + lsId + "&method=" + method; + + logger.info("LAMS clone lesson request: " + serviceURL); + + // parse xml response and get the lesson id + InputStream is = LamsSecurityUtil.callLamsServerPost(serviceURL); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document document = db.parse(is); + return Long.parseLong(document.getElementsByTagName("Lesson").item(0).getAttributes() + .getNamedItem("lessonId").getNodeValue()); + + } catch (MalformedURLException e) { + throw new RuntimeException("Unable to clone LAMS lesson, bad URL: '" + serverAddr + + "', please check lams.properties", e); + } catch (IllegalStateException e) { + throw new RuntimeException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (RemoteException e) { + throw new RuntimeException("Unable to clone LAMS lesson, RMI Remote Exception", e); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Unable to clone LAMS lesson, Unsupported Encoding Exception", e); + } catch (ConnectException e) { + throw new RuntimeException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (IOException e) { + throw new RuntimeException( + "Unable to clone LAMS lesson. " + e.getMessage() + " Please contact your system administrator.", e); + } catch (ParserConfigurationException e) { + throw new RuntimeException( + "Unable to clone LAMS lesson. " + e.getMessage() + " Can't instantiate DocumentBuilder.", e); + } catch (SAXException e) { + throw new RuntimeException( + "Unable to clone LAMS lesson. " + e.getMessage() + " Can't parse LAMS results.", e); + } + + } + + /** + * Import learning design in LAMS from its temp folder. Then starting a lesson using this learning design. + * + * @param courseId + * courseId as a request parameter + * @param ldId + * the learning design id for which you wish to start a lesson + * + * @return lesson id of a cloned lesson + * @throws LamsServerException + */ + public static Long importLearningDesign(User teacher, String courseId, String lsId, String ldId) throws LamsServerException { + + String serverId = LamsPluginUtil.getServerId(); + String serverAddr = LamsPluginUtil.getServerUrl(); + String serverSecretKey = LamsPluginUtil.getServerSecretKey(); + String username = teacher.getUserName(); + String locale = teacher.getLocale(); + String country = getCountry(locale); + String lang = getLanguage(locale); + + if (courseId == null || serverId == null || serverAddr == null || serverSecretKey == null) { + logger.info("Unable to import lesson, one or more lams configuration properties or the course id is null"); + throw new RuntimeException("Unable to import lesson, one or more lams configuration properties or the course id is null. courseId="+courseId); + } + + //import a learning design + String filePath = EXPORT_FOLDER_LAMS_SERVER + lsId + "_" + ldId + ".zip"; + + try { + String filePathParam = URLEncoder.encode(filePath, "UTF-8"); + String timestamp = new Long(System.currentTimeMillis()).toString(); + String hash = generateAuthenticationHash(timestamp, username, serverId); + String serviceURL = serverAddr + "/services/xml/LessonManager?" + "serverId=" + + URLEncoder.encode(serverId, "utf8") + "&datetime=" + timestamp + "&username=" + + URLEncoder.encode(username, "utf8") + "&hashValue=" + hash + "&courseId=" + + URLEncoder.encode(courseId, "UTF8") + "&country=" + + country + "&lang=" + lang + "&method=import&customCSV=&filePath=" + filePathParam; + + logger.info("LAMS import lesson request: " + serviceURL); + + // parse xml response and get the ldid + InputStream is = LamsSecurityUtil.callLamsServerPost(serviceURL); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document document = db.parse(is); + return Long.parseLong(document.getElementsByTagName("Lesson").item(0).getAttributes() + .getNamedItem("ldId").getNodeValue()); + + } catch (MalformedURLException e) { + throw new LamsServerException("Unable to import LAMS lesson, bad URL: '" + serverAddr + + "', please check lams.properties. Tried to import file " + filePath, e); + } catch (IllegalStateException e) { + throw new LamsServerException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator. Tried to import file " + + filePath, + e); + } catch (RemoteException e) { + throw new LamsServerException( + "Unable to import LAMS lesson, RMI Remote Exception. Tried to import file " + filePath, e); + } catch (UnsupportedEncodingException e) { + throw new LamsServerException( + "Unable to import LAMS lesson, Unsupported Encoding Exception. Tried to import file " + filePath, + e); + } catch (ConnectException e) { + throw new LamsServerException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator. Tried to import file " + + filePath, + e); + } catch (IOException e) { + throw new LamsServerException("Unable to import LAMS lesson. " + e.getMessage() + + " Please contact your system administrator. Tried to import file " + filePath, e); + } catch (ParserConfigurationException e) { + throw new LamsServerException("Unable to import LAMS lesson. " + e.getMessage() + + " Can't instantiate DocumentBuilder. Tried to import file " + filePath, e); + } catch (SAXException e) { + throw new LamsServerException("Unable to import LAMS lesson. " + e.getMessage() + + " Can't parse LAMS results. Tried to import file " + filePath, e); + } + + } + + /** + * Pre-adding students and monitors to a lesson + * + * @param ctx + * the blackboard contect, contains session data + * @param lessonId + * the lesoon id that was just started + */ + public static void preaddLearnersMonitorsToLesson(User user, Course course, long lessonId) { + String serverId = LamsPluginUtil.getServerId(); + String serverAddr = LamsPluginUtil.getServerUrl(); + String serverSecretKey = LamsPluginUtil.getServerSecretKey(); + String username = user.getUserName(); + String locale = user.getLocale(); + String country = getCountry(locale); + String lang = getLanguage(locale); + + if (serverId == null || serverAddr == null || serverSecretKey == null) { + throw new RuntimeException("Unable to start lesson, one or more lams configuration properties is null"); + } + + try { + + /* + * Returns a list of learners and monitors in the given course or group. + */ + + String learnerIds = ""; + String firstNames = ""; + String lastNames = ""; + String emails = ""; + String monitorIds = ""; + final String DUMMY_VALUE = "-"; + + BbPersistenceManager bbPm = PersistenceServiceFactory.getInstance().getDbPersistenceManager(); + CourseMembershipDbLoader courseMemLoader = CourseMembershipDbLoader.Default.getInstance(); + + Id courseId = course.getId(); + List<CourseMembership> studentCourseMemberships = courseMemLoader.loadByCourseIdAndRole(courseId, + CourseMembership.Role.STUDENT, null, true); + for (CourseMembership courseMembership : studentCourseMemberships) { + String learnerId = escapeValue(courseMembership.getUser().getUserName()); + learnerIds += learnerId + ","; + + String firstName = escapeValue(courseMembership.getUser().getGivenName()); + firstNames += firstName + ","; + + String lastName = escapeValue(courseMembership.getUser().getFamilyName()); + lastNames += lastName + ","; + + String email = escapeValue(courseMembership.getUser().getEmailAddress()); + emails += email + ","; + } + + List<CourseMembership> monitorCourseMemberships = courseMemLoader.loadByCourseIdAndRole(courseId, + CourseMembership.Role.INSTRUCTOR, null, true); + List<CourseMembership> teachingAssistantCourseMemberships = courseMemLoader.loadByCourseIdAndRole(courseId, + CourseMembership.Role.TEACHING_ASSISTANT, null, true); + monitorCourseMemberships.addAll(teachingAssistantCourseMemberships); + List<CourseMembership> courseBuilderCourseMemberships = courseMemLoader.loadByCourseIdAndRole(courseId, + CourseMembership.Role.COURSE_BUILDER, null, true); + monitorCourseMemberships.addAll(courseBuilderCourseMemberships); + for (CourseMembership courseMembership : monitorCourseMemberships) { + String monitorId = escapeValue(courseMembership.getUser().getUserName()); + monitorIds += monitorId + ","; + + String firstName = escapeValue(courseMembership.getUser().getGivenName()); + firstNames += firstName + ","; + + String lastName = escapeValue(courseMembership.getUser().getFamilyName()); + lastNames += lastName + ","; + + String email = escapeValue(courseMembership.getUser().getEmailAddress()); + emails += email + ","; + } + + //no learners & no monitors - do nothing + if (learnerIds.isEmpty() && monitorIds.isEmpty()) { + return; + } + + // remove trailing comma + learnerIds = learnerIds.isEmpty() ? "" : learnerIds.substring(0, learnerIds.length() - 1); + firstNames = firstNames.isEmpty() ? "" : firstNames.substring(0, firstNames.length() - 1); + lastNames = lastNames.isEmpty() ? "" : lastNames.substring(0, lastNames.length() - 1); + emails = emails.isEmpty() ? "" : emails.substring(0, emails.length() - 1); + monitorIds = monitorIds.isEmpty() ? "" : monitorIds.substring(0, monitorIds.length() - 1); + + String timestamp = new Long(System.currentTimeMillis()).toString(); + String hash = generateAuthenticationHash(timestamp, username, serverId); + + String serviceURL = serverAddr + "/services/xml/LessonManager?" + "&serverId=" + + URLEncoder.encode(serverId, "utf8") + "&datetime=" + timestamp + "&username=" + + URLEncoder.encode(username, "utf8") + "&hashValue=" + hash + "&courseId=" + + URLEncoder.encode(course.getCourseId(), "utf8") + "&lsId=" + lessonId + "&country=" + country + "&lang=" + + lang + "&method=join" + "&firstNames=" + + firstNames + "&lastNames=" + lastNames + "&emails=" + emails; + if (!monitorIds.isEmpty()) { + serviceURL += "&monitorIds=" + monitorIds; + } + if (!learnerIds.isEmpty()) { + serviceURL += "&learnerIds=" + learnerIds; + } + + logger.info("LAMS Preadd users Req: " + serviceURL); + System.out.println("LAMS Preadd users Req: " + serviceURL); + + InputStream is = LamsSecurityUtil.callLamsServerPost(serviceURL); + + } catch (MalformedURLException e) { + throw new RuntimeException("Unable to preadd users to the lesson, bad URL: '" + serverAddr + + "', please check lams.properties", e); + } catch (IllegalStateException e) { + throw new RuntimeException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (RemoteException e) { + throw new RuntimeException("Unable to preadd users to the lesson, RMI Remote Exception", e); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Unable to preadd users to the lesson, Unsupported Encoding Exception", e); + } catch (ConnectException e) { + throw new RuntimeException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (IOException e) { + throw new RuntimeException("Unable to preadd users to the lesson. " + e.getMessage() + + " Please contact your system administrator.", e); + } catch (KeyNotFoundException e) { + throw new RuntimeException("Unable to preadd users to the lesson. " + e.getMessage() + + " Please contact your system administrator.", e); + } catch (PersistenceException e) { + throw new RuntimeException("Unable to preadd users to the lesson. " + e.getMessage() + + " Please contact your system administrator.", e); + } + + } + + /** + * Takes care about blank values. Besides, escapes CSV sensitive symbols (commas, quotes, etc) and then encodes it to be sent as a URL parameter. + * + * @param value + * @param CSV + * @return + * @throws UnsupportedEncodingException + */ + private static String escapeValue(String value) throws UnsupportedEncodingException { + final String DUMMY_VALUE = "-"; + + String notBlankValue = StringUtils.isBlank(value) ? DUMMY_VALUE : value; + String escapedCsv = StringEscapeUtils.escapeCsv(notBlankValue); + String encodedValue = URLEncoder.encode(escapedCsv, "utf8"); + + return encodedValue; + } + + /** + * getLearnerProgress in current lesson through a LAMS webservice + * + * @param ctx + * the blackboard contect, contains session data + * @param lsId + * the lesson id for which you wish to retrieve progress + * + * @return the learning session id + */ + public static LearnerProgressDTO getLearnerProgress(Context ctx, long lsId) { + String serverId = LamsPluginUtil.getServerId(); + String serverAddr = LamsPluginUtil.getServerUrl(); + String serverSecretKey = LamsPluginUtil.getServerSecretKey(); + String courseId = ctx.getCourse().getCourseId(); + + String username = ctx.getUser().getUserName(); + String firstName = ctx.getUser().getGivenName(); + String lastName = ctx.getUser().getFamilyName(); + String email = ctx.getUser().getEmailAddress(); + String locale = ctx.getUser().getLocale(); + String country = getCountry(locale); + String lang = getLanguage(locale); + + if (serverId == null || serverAddr == null || serverSecretKey == null) { + throw new RuntimeException("Unable to start lesson, one or more lams configuration properties is null"); + } + + try { + + String timestamp = new Long(System.currentTimeMillis()).toString(); + String hash = generateAuthenticationHash(timestamp, username, serverId); + + String serviceURL = serverAddr + "/services/xml/LessonManager?method=singleStudentProgress" + "&serverId=" + + URLEncoder.encode(serverId, "utf8") + "&datetime=" + timestamp + "&username=" + + URLEncoder.encode(username, "utf8") + "&hashValue=" + hash + "&courseId=" + + URLEncoder.encode(courseId, "utf8") + "&country=" + country + "&lang=" + lang + "&firstName=" + + URLEncoder.encode(firstName, "UTF-8") + "&lastName=" + URLEncoder.encode(lastName, "UTF-8") + + "&email=" + URLEncoder.encode(email, "UTF-8") + "&lsId=" + new Long(lsId).toString(); + + logger.info("Retirieving learner progress: " + serviceURL); + + // InputStream is = url.openConnection().getInputStream(); + InputStream is = LamsSecurityUtil.callLamsServerPost(serviceURL); + + // parse xml response + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document document = db.parse(is); + + // get the lesson id from the response + NamedNodeMap learnerProgress = document.getElementsByTagName("LearnerProgress").item(0).getAttributes(); + boolean lessonComplete = Boolean.parseBoolean(learnerProgress.getNamedItem("lessonComplete").getNodeValue()); + int activitiesCompleted = Integer.parseInt(learnerProgress.getNamedItem("activitiesCompleted").getNodeValue()); + int attemptedActivities = Integer.parseInt(learnerProgress.getNamedItem("attemptedActivities").getNodeValue()); + int activityCount = Integer.parseInt(learnerProgress.getNamedItem("activityCount").getNodeValue()); + + LearnerProgressDTO learnerProgressDto = new LearnerProgressDTO(activityCount, attemptedActivities, + activitiesCompleted, lessonComplete); + + return learnerProgressDto; + + } catch (MalformedURLException e) { + throw new RuntimeException("Unable to get LearnerProgress, bad URL: '" + serverAddr + + "', please check lams.properties", e); + } catch (IllegalStateException e) { + throw new RuntimeException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (RemoteException e) { + throw new RuntimeException("Unable to get LearnerProgress, RMI Remote Exception", e); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Unable to get LearnerProgress, Unsupported Encoding Exception", e); + } catch (ConnectException e) { + throw new RuntimeException( + "LAMS Server timeout, did not get a response from the LAMS server. Please contact your systems administrator", + e); + } catch (IOException e) { + throw new RuntimeException("Unable to get LearnerProgress. " + e.getMessage() + + " Please contact your system administrator.", e); + } catch (ParserConfigurationException e) { + throw new RuntimeException( + "Unable to get LearnerProgress. " + e.getMessage() + " Please contact your system administrator.", + e); + } catch (SAXException e) { + throw new RuntimeException( + "Unable to get LearnerProgress. " + e.getMessage() + " Please contact your system administrator.", + e); + } + + } + + /** + * Make a call to LAMS server. + * + * @param serviceURL + * @return resulted InputStream + * @throws IOException + */ + private static InputStream callLamsServer(String serviceURL) throws IOException { + URL url = new URL(serviceURL); + URLConnection conn = url.openConnection(); + if (!(conn instanceof HttpURLConnection)) { + throw new IOException("Unable to open connection to: " + serviceURL); + } + + HttpURLConnection httpConn = (HttpURLConnection) conn; + + if (httpConn.getResponseCode() != HttpURLConnection.HTTP_OK) { + throw new IOException("LAMS server responded with HTTP response code: " + httpConn.getResponseCode() + + ", HTTP response message: " + httpConn.getResponseMessage()); + } + + // InputStream is = url.openConnection().getInputStream(); + InputStream is = conn.getInputStream(); + + return is; + } + + + /** + * Make a call to LAMS server. + * + * @param serviceURL + * @return resulted InputStream + * @throws IOException + */ + private static InputStream callLamsServerPost(String serviceURL) throws IOException { + + String path; + String body; + + int bodyStart = serviceURL.indexOf('?'); + if ( bodyStart < 0 ) { + path = serviceURL; + body = ""; + } else { + path = serviceURL.substring(0,bodyStart); + body = serviceURL.substring(bodyStart+1); + } + + byte[] postData = body.getBytes("UTF-8"); + int postDataLength = postData.length; + + URL url = new URL(path); + URLConnection conn = url.openConnection(); + if (!(conn instanceof HttpURLConnection)) { + throw new IOException("Unable to open connection to: " + serviceURL); + } + + HttpURLConnection httpConn = (HttpURLConnection) conn; + conn.setDoOutput( true ); + httpConn.setRequestMethod("POST"); + conn.setRequestProperty( "Content-Type", "application/x-www-form-urlencoded"); + conn.setRequestProperty( "charset", "utf-8"); + conn.setRequestProperty( "Content-Length", Integer.toString( postDataLength )); + conn.setUseCaches( false ); + + conn.getOutputStream().write(postData); + + if (httpConn.getResponseCode() != HttpURLConnection.HTTP_OK) { + throw new IOException("LAMS server responded with HTTP response code: " + httpConn.getResponseCode() + + ", HTTP response message: " + httpConn.getResponseMessage()); + } + + InputStream is = conn.getInputStream(); + return is; + } + + public static String getServerTime() throws IOException, PersistenceException { + long now = (new Date()).getTime(); + + // get LamsServerTime from the storage + PortalExtraInfo pei = PortalUtil.loadPortalExtraInfo(null, null, "LamsServerTimeStorage"); + ExtraInfo ei = pei.getExtraInfo(); + + String lamsServerTimeDeltaStr = ei.getValue("LAMSServerTimeDelta"); + long lamsServerTimeDelta = (lamsServerTimeDeltaStr == null) ? -1 : Long.parseLong(lamsServerTimeDeltaStr); + + //check if it's time to update + String lastUpdateTimeStr = ei.getValue("lastUpdateTime"); + long lastUpdateTime = (lastUpdateTimeStr == null) ? -1 : Long.parseLong(lastUpdateTimeStr); + long lamsServerTime; + + long lamsServerTimeRefreshInterval = getLamsServerTimeRefreshInterval() * 60 * 60 * 1000; + if ((lamsServerTimeDeltaStr == null) || (lastUpdateTime + lamsServerTimeRefreshInterval < now)) { + + // refresh time from LAMS server + String serverAddr = LamsPluginUtil.getServerUrl(); + String serviceURL = serverAddr + "/services/getServerTime"; + logger.info("LAMS Get Server Time request: " + serviceURL); + InputStream is = LamsSecurityUtil.callLamsServer(serviceURL); + + StringWriter writer = new StringWriter(); + IOUtils.copy(is, writer, "UTF-8"); + + try { + lamsServerTime = Long.parseLong(writer.toString().trim()); + } catch (NumberFormatException e) { + throw new RuntimeException("LAMS server returned wrong time format on call to /service/getServerTime", + e); + } + lamsServerTimeDelta = now - lamsServerTime; + + // Store LAMSServerTime and lastUpdateTime to the storage + ei.setValue("LAMSServerTimeDelta", "" + lamsServerTimeDelta); + ei.setValue("lastUpdateTime", "" + now); + PortalUtil.savePortalExtraInfo(pei); + } else { + + //no need to refresh - use stored value + lamsServerTime = now - lamsServerTimeDelta; + } + + return "" + lamsServerTime; + } + + /** + * + * @return the LAMS server time refresh interval from lams.properties + */ + private static long getLamsServerTimeRefreshInterval() { + //set default value + long lamsServerTimeRefreshInterval = 24; + + try { + String lamsServerTimeRefreshIntervalStr = LamsPluginUtil.getLamsServerTimeRefreshInterval(); + lamsServerTimeRefreshInterval = Long.parseLong(lamsServerTimeRefreshIntervalStr); + } catch (NumberFormatException e) { + logger.warn("Wrong format of PROP_LAMS_SERVER_TIME_REFRESH_INTERVAL from lams.properties"); + } + + return lamsServerTimeRefreshInterval; + } + + // generate authentication hash code to validate parameters + private static String generateAuthenticationHash(String datetime, String login, String serverId) { + String serverSecretKey = LamsPluginUtil.getServerSecretKey(); + + String plaintext = datetime.toLowerCase().trim() + login.toLowerCase().trim() + serverId.toLowerCase().trim() + + serverSecretKey.toLowerCase().trim(); + + String hash = sha1(plaintext); + + return hash; + } + + /** + * The parameters are: uid - the username on the external system method - either author, monitor or learner ts - + * timestamp sid - serverID str is [ts + uid + method + serverID + serverSecretKey] (Note: all lower case) + * + * @param str + * The string to be hashed + * @return The hased string + */ + public static String sha1(String str) { + try { + MessageDigest md = MessageDigest.getInstance("SHA1"); + return new String(Hex.encodeHex(md.digest(str.getBytes()))); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + /** + * + * @param localeStr + * the full balckboard locale string + * @return the language + */ + public static String getLanguage(String localeStr) { + if (localeStr == null) + return "xx"; + String[] split = localeStr.split("_"); + return split[0]; + } + + /** + * + * @param localeStr + * the full balckboard locale string + * @return the country + */ + public static String getCountry(String localeStr) { + if (localeStr == null) + return "XX"; + String[] split = localeStr.split("_"); + + //default country set to AU + String country = split.length > 1 ? split[1] : "AU"; + return country; + } + +} Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/util/LamsServerException.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/util/LamsServerException.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/util/LamsServerException.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,34 @@ +package org.lamsfoundation.bb.integration.util; + +/** + * Exception that originated at LAMS server. + */ +public class LamsServerException extends Exception { + + public LamsServerException() { + super(); + } + + /** + * @param message + */ + public LamsServerException(String message) { + super(message); + } + + /** + * @param cause + */ + public LamsServerException(Throwable cause) { + super(cause); + } + + /** + * @param message + * @param cause + */ + public LamsServerException(String message, Throwable cause) { + super(message, cause); + } + +} Index: lams_bb_integration/src/org/lamsfoundation/bb/integration/util/LineitemUtil.java =================================================================== diff -u --- lams_bb_integration/src/org/lamsfoundation/bb/integration/util/LineitemUtil.java (revision 0) +++ lams_bb_integration/src/org/lamsfoundation/bb/integration/util/LineitemUtil.java (revision 54843a5ae3288a977cdfa75fd43afc9c67831624) @@ -0,0 +1,479 @@ +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + +package org.lamsfoundation.bb.integration.util; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.text.DecimalFormat; +import java.util.List; +import java.util.Set; + +import javax.servlet.ServletException; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.lamsfoundation.bb.integration.Constants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +import blackboard.data.ValidationException; +import blackboard.data.content.Content; +import blackboard.data.content.CourseDocument; +import blackboard.data.course.Course; +import blackboard.data.gradebook.Lineitem; +import blackboard.data.gradebook.Score; +import blackboard.data.gradebook.impl.OutcomeDefinition; +import blackboard.data.gradebook.impl.OutcomeDefinitionScale; +import blackboard.persist.BbPersistenceManager; +import blackboard.persist.Container; +import blackboard.persist.Id; +import blackboard.persist.PersistenceException; +import blackboard.persist.PkId; +import blackboard.persist.content.ContentDbLoader; +import blackboard.persist.course.CourseDbLoader; +import blackboard.persist.gradebook.LineitemDbLoader; +import blackboard.persist.gradebook.LineitemDbPersister; +import blackboard.persist.gradebook.ScoreDbPersister; +import blackboard.persist.gradebook.ext.OutcomeDefinitionScaleDbLoader; +import blackboard.persist.gradebook.ext.OutcomeDefinitionScaleDbPersister; +import blackboard.persist.gradebook.impl.OutcomeDefinitionDbPersister; +import blackboard.platform.persistence.PersistenceServiceFactory; +import blackboard.portal.data.ExtraInfo; +import blackboard.portal.data.PortalExtraInfo; +import blackboard.portal.servlet.PortalUtil; +import blackboard.util.StringUtil; + +public class LineitemUtil { + + private final static String LAMS_LINEITEM_STORAGE = "LamsLineitemStorage"; + + private static Logger logger = LoggerFactory.getLogger(LineitemUtil.class); + + @SuppressWarnings("deprecation") + public static void createLineitem(Content bbContent, String userName) + throws ValidationException, PersistenceException, IOException, ParserConfigurationException, SAXException { + LineitemDbPersister linePersister = LineitemDbPersister.Default.getInstance(); + OutcomeDefinitionDbPersister outcomeDefinitionPersister = OutcomeDefinitionDbPersister.Default.getInstance(); + OutcomeDefinitionScaleDbLoader outcomeDefinitionScaleLoader = OutcomeDefinitionScaleDbLoader.Default + .getInstance(); + OutcomeDefinitionScaleDbPersister uutcomeDefinitionScaleDbPersister = OutcomeDefinitionScaleDbPersister.Default + .getInstance(); + + String title = bbContent.getTitle(); + Id courseId = bbContent.getCourseId(); + String lamsLessonId = bbContent.getLinkRef(); + + // Create new Gradebook column for current bbContent + Lineitem lineitem = new Lineitem(); + lineitem.setCourseId(courseId); + lineitem.setName(title); + lineitem.setPointsPossible(Constants.GRADEBOOK_POINTS_POSSIBLE); + lineitem.setType(Constants.GRADEBOOK_LINEITEM_TYPE); + lineitem.setIsAvailable(true); + lineitem.setDateAdded(); + lineitem.setAssessmentLocation(Lineitem.AssessmentLocation.EXTERNAL); + lineitem.setAssessmentId(lamsLessonId, Lineitem.AssessmentLocation.EXTERNAL); + lineitem.validate(); + linePersister.persist(lineitem); + + OutcomeDefinition outcomeDefinition = lineitem.getOutcomeDefinition(); + outcomeDefinition.setCourseId(courseId); + outcomeDefinition.setPosition(1); + + boolean hasLessonScoreOutputs = LineitemUtil.hasLessonScoreOutputs(bbContent, userName); + OutcomeDefinitionScale outcomeDefinitionScale; + if (hasLessonScoreOutputs) { + outcomeDefinitionScale = outcomeDefinitionScaleLoader.loadByCourseIdAndTitle(courseId, + OutcomeDefinitionScale.SCORE); + outcomeDefinitionScale.setNumericScale(true); + outcomeDefinitionScale.setPercentageScale(true); + outcomeDefinition.setScorable(true); + } else { + outcomeDefinitionScale = outcomeDefinitionScaleLoader.loadByCourseIdAndTitle(courseId, + OutcomeDefinitionScale.COMPLETE_INCOMPLETE); + outcomeDefinitionScale.setNumericScale(false); + outcomeDefinition.setScorable(false); + } + uutcomeDefinitionScaleDbPersister.persist(outcomeDefinitionScale); + outcomeDefinition.setScale(outcomeDefinitionScale); + outcomeDefinitionPersister.persist(outcomeDefinition); + + updateLamsLineitemStorage(bbContent, lineitem); + } + + /* + * Checks whether lesson has scorable outputs (i.e. MCQ or Assessment activity). + * + * @param ctx + * the blackboard context, contains session data + * + * @return an url pointing to the LAMS lesson, monitor, author session + * + * @throws IOException + */ + private static boolean hasLessonScoreOutputs(Content bbContent, String username) throws IOException, ParserConfigurationException, SAXException { + + //at this moment bbContent contains already updated lessonId + String lessonId = bbContent.getLinkRef(); + + String serviceURL = LamsPluginUtil.getServerUrl() + "/services/xml/LessonManager?" + + LamsSecurityUtil.generateAuthenticateParameters(username) + + "&method=checkLessonForNumericToolOutputs&lsId=" + lessonId; + + URL url = new URL(serviceURL); + URLConnection conn = url.openConnection(); + if (!(conn instanceof HttpURLConnection)) { + throw new RuntimeException("Unable to open connection to: " + serviceURL); + } + + HttpURLConnection httpConn = (HttpURLConnection) conn; + + if (httpConn.getResponseCode() != HttpURLConnection.HTTP_OK) { + throw new RuntimeException("HTTP Response Code: " + httpConn.getResponseCode() + ", HTTP Response Message: " + + httpConn.getResponseMessage()); + } + + InputStream is = conn.getInputStream(); + + // parse xml response + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document document = db.parse(is); + Element lesson = document.getDocumentElement(); + boolean hasNumericToolOutput = Boolean + .parseBoolean(lesson.getAttributes().getNamedItem("hasNumericToolOutput").getNodeValue()); + + return hasNumericToolOutput; + } + + /** + * Removes lineitem. Throws exception if lineitem is not found. + * + * @param _content_id + * @param _course_id + * @throws PersistenceException + * @throws ServletException + */ + public static void removeLineitem(String _content_id, String _course_id) + throws PersistenceException, ServletException { + BbPersistenceManager bbPm = PersistenceServiceFactory.getInstance().getDbPersistenceManager(); + Container bbContainer = bbPm.getContainer(); + ContentDbLoader courseDocumentLoader = ContentDbLoader.Default.getInstance(); + LineitemDbPersister linePersister = LineitemDbPersister.Default.getInstance(); + + Id contentId = new PkId(bbContainer, CourseDocument.DATA_TYPE, _content_id); + Content bbContent = courseDocumentLoader.loadById(contentId); + //check isGradecenter option is ON and thus there should exist associated lineitem object + if (!bbContent.getIsDescribed()) { + return; + } + + Id lineitemId = getLineitem(_content_id, _course_id, true); + linePersister.deleteById(lineitemId); + + //Remove bbContentId -> lineitemid pair from the storage + PortalExtraInfo pei = PortalUtil.loadPortalExtraInfo(null, null, LAMS_LINEITEM_STORAGE); + ExtraInfo ei = pei.getExtraInfo(); + ei.clearEntry(_content_id); + PortalUtil.savePortalExtraInfo(pei); + } + + /** + * Changes lineitem's name. Throws exception if lineitem is not found. + * + * @param _content_id + * @param _course_id + * @param newLineitemName + * @throws PersistenceException + * @throws ServletException + * @throws ValidationException + */ + public static void changeLineitemName(String _content_id, String _course_id, String newLineitemName) + throws PersistenceException, ServletException, ValidationException { + LineitemDbLoader lineitemLoader = LineitemDbLoader.Default.getInstance(); + LineitemDbPersister linePersister = LineitemDbPersister.Default.getInstance(); + + Id lineitemId = getLineitem(_content_id, _course_id, true); + Lineitem lineitem = lineitemLoader.loadById(lineitemId); + lineitem.setName(newLineitemName); + linePersister.persist(lineitem); + } + + /** + * Changes lineitem's lamsLessonId. Throws exception if lineitem is not found. + * + * @param bbContentId + * @param courseIdStr + * @param newLineitemName + * @throws PersistenceException + * @throws ServletException + * @throws ValidationException + * @throws IOException + * @throws SAXException + * @throws ParserConfigurationException + */ + public static void updateLineitemLessonId(Content bbContent, String courseIdStr, Long newLamsLessonId, String userName) + throws PersistenceException, ServletException, ValidationException, IOException, ParserConfigurationException, SAXException { + LineitemDbLoader lineitemLoader = LineitemDbLoader.Default.getInstance(); + LineitemDbPersister linePersister = LineitemDbPersister.Default.getInstance(); + + String _content_id = bbContent.getId().toExternalString(); + + //update only in case grade center is ON + if (bbContent.getIsDescribed()) { + + Id lineitemId = getLineitem(_content_id, courseIdStr, false); + //in case admin forgot to check "Grade Center Columns and Settings" option on doing course copy/import + if (lineitemId == null) { + createLineitem(bbContent, userName); + + //in case he checked it and BB created Lineitem object, then just need to update it + } else { + Lineitem lineitem = lineitemLoader.loadById(lineitemId); + lineitem.setAssessmentId(Long.toString(newLamsLessonId), Lineitem.AssessmentLocation.EXTERNAL); + linePersister.persist(lineitem); + + updateLamsLineitemStorage(bbContent, lineitem); + } + + } + + // store internalContentId -> externalContentId. It's used for GradebookServlet. Store it just in case + PortalExtraInfo pei = PortalUtil.loadPortalExtraInfo(null, null, "LamsStorage"); + ExtraInfo ei = pei.getExtraInfo(); + ei.setValue(_content_id, Long.toString(newLamsLessonId)); + PortalUtil.savePortalExtraInfo(pei); + } + + /* Updates "LamsLineitemStorage" storage used for storing bbContentId->lineitem + * @param bbContent + * @param lineitem + * @throws PersistenceException + */ + private static void updateLamsLineitemStorage(Content bbContent, Lineitem lineitem) throws PersistenceException { + //get bbContent id + String _content_id = bbContent.getId().toExternalString(); + //get lineitem id + String _lineitem_id = lineitem.getId().toExternalString(); + + //Store lineitemid to the storage bbContentId -> lineitemid (this storage is available since 1.2.3 version) + PortalExtraInfo pei = PortalUtil.loadPortalExtraInfo(null, null, LAMS_LINEITEM_STORAGE); + ExtraInfo ei = pei.getExtraInfo(); + ei.setValue(_content_id, _lineitem_id); + PortalUtil.savePortalExtraInfo(pei); + } + + /* + * Finds lineitem by bbContentId and courseId. + * + * @throws ServletException + * + * @throws PersistenceException + */ + private static Id getLineitem(String bbContentId, String courseIdStr, boolean isThrowException) + throws ServletException, PersistenceException, LamsBuildingBlockException { + BbPersistenceManager bbPm = PersistenceServiceFactory.getInstance().getDbPersistenceManager(); + LineitemDbLoader lineitemLoader = LineitemDbLoader.Default.getInstance(); + + //get lineitemId from the storage (bbContentId -> lineitemId) + PortalExtraInfo pei = PortalUtil.loadPortalExtraInfo(null, null, LAMS_LINEITEM_STORAGE); + ExtraInfo ei = pei.getExtraInfo(); + String _lineitem_id = ei.getValue(bbContentId); + + // try to get lineitem from any course that user is participating in (for lineitems created in versions after 1.2 and before 1.2.3) + if (_lineitem_id == null) { + // get stored bbContentId -> lamsLessonId + PortalExtraInfo portalExtraInfo = PortalUtil.loadPortalExtraInfo(null, null, "LamsStorage"); + ExtraInfo extraInfo = portalExtraInfo.getExtraInfo(); + String lamsLessonId = extraInfo.getValue(bbContentId); + + if (lamsLessonId == null || "".equals(lamsLessonId)) { + if (isThrowException) { + throw new LamsBuildingBlockException("lamsLessonId corresponding to bbContentId= " + bbContentId + + " was not found in LamsStorage"); + } else { + return null; + } + } + + // search for appropriate lineitem + Id courseId = bbPm.generateId(Course.DATA_TYPE, courseIdStr); + List<Lineitem> lineitems = lineitemLoader.loadByCourseId(courseId); + + Lineitem lineitem = null; + for (Lineitem lineitemIter : lineitems) { + if (lineitemIter.getAssessmentId() != null && lineitemIter.getAssessmentId().equals(lamsLessonId)) { + lineitem = lineitemIter; + break; + } + } + + if (lineitem == null) { + if (isThrowException) { + throw new LamsBuildingBlockException( + "Lineitem that corresponds to bbContentId: " + bbContentId + " was not found"); + } else { + return null; + } + } + + // delete lineitem (can't delete it simply doing linePersister.deleteById(lineitem.getId()) due to BB9 bug) + _lineitem_id = lineitem.getId().toExternalString(); + } + + if (_lineitem_id == null) { + throw new LamsBuildingBlockException("Lineitem was not found for contentId:" + bbContentId + + ". This is despite the fact that isGradecenter option is ON."); + } + + Id lineitemId = bbPm.generateId(Lineitem.LINEITEM_DATA_TYPE, _lineitem_id.trim()); + return lineitemId; + } + + /** + * Finds lineitem by bbContentId, userId and lamsLessonId. + * + * @throws ServletException + * @throws PersistenceException + */ + public static Lineitem getLineitem(String bbContentId, Id userId, String lamsLessonIdParam) + throws ServletException, PersistenceException { + BbPersistenceManager bbPm = PersistenceServiceFactory.getInstance().getDbPersistenceManager(); + CourseDbLoader courseLoader = CourseDbLoader.Default.getInstance(); + LineitemDbLoader lineitemLoader = LineitemDbLoader.Default.getInstance(); + + // get lineitemId from the storage (bbContentId -> lineitemid) + PortalExtraInfo pei = PortalUtil.loadPortalExtraInfo(null, null, LAMS_LINEITEM_STORAGE); + ExtraInfo ei = pei.getExtraInfo(); + String lineitemIdStr = ei.getValue(bbContentId); + + if (lineitemIdStr != null) { + Id lineitemId = bbPm.generateId(Lineitem.LINEITEM_DATA_TYPE, lineitemIdStr.trim()); + return lineitemLoader.loadById(lineitemId); + + // try to get lineitem from any course that user is participating in (for lineitems created in versions after 1.2 and before 1.2.3) + } else { + + // search for appropriate lineitem + List<Course> userCourses = courseLoader.loadByUserId(userId); + for (Course userCourse : userCourses) { + List<Lineitem> lineitems = lineitemLoader.loadByCourseId(userCourse.getId()); + + for (Lineitem lineitem : lineitems) { + if (lineitem.getAssessmentId() != null && lineitem.getAssessmentId().equals(lamsLessonIdParam)) { + return lineitem; + } + } + } + } + + return null; + } + + /** + * Finds lineitem by userId and lamsLessonId. The same as above method but first finds bbContentId and also checks + * Grade center option is ON. + * + * @param isThrowException whether exception should be thrown in case Gradecenter option is OFF or it should return simple null + * @throws ServletException + * @throws PersistenceException + */ + public static Lineitem getLineitem(Id userId, String lamsLessonIdParam, boolean isThrowException) + throws ServletException, PersistenceException { + BbPersistenceManager bbPm = PersistenceServiceFactory.getInstance().getDbPersistenceManager(); + Container bbContainer = bbPm.getContainer(); + ContentDbLoader contentDbLoader = ContentDbLoader.Default.getInstance(); + + //Finds bbContentId from "LamsStorage" corresponding to specified lamsLessonId. + //Note: bbContentId can be null in case it's Chen Rui's BB, which doesn't have "LamsStorage". + PortalExtraInfo portalExtraInfo = PortalUtil.loadPortalExtraInfo(null, null, "LamsStorage"); + ExtraInfo extraInfo = portalExtraInfo.getExtraInfo(); + Set<String> bbContentIds = extraInfo.getKeys(); + String bbContentId = null; + for (String bbContentIdIter : bbContentIds) { + String lamsLessonId = extraInfo.getValue(bbContentIdIter); + if (lamsLessonIdParam.equals(lamsLessonId)) { + bbContentId = bbContentIdIter; + break; + } + } + + if (bbContentId != null) { + Id contentId = new PkId(bbContainer, CourseDocument.DATA_TYPE, bbContentId); + Content bbContent = contentDbLoader.loadById(contentId); + // check isGradecenter option is ON (bbContent.isDescribed field is used for storing isGradecenter parameter) + if (!bbContent.getIsDescribed()) { + if (isThrowException) { + throw new LamsBuildingBlockException("Operation failed due to lesson (lessonId=" + lamsLessonIdParam + + ", bbContentId=" + bbContentId + ") has gradecenter option switched OFF."); + } else { + return null; + } + } + } + + //get lineitem + return LineitemUtil.getLineitem(bbContentId == null ? "" : bbContentId, userId, lamsLessonIdParam); + } + + /** + * Updates and persists currentScore in the DB. + * + * @param lesson + * @param learnerResult + * @param currentScore provided score must be initialized (can't be null) + * @throws PersistenceException + * @throws ValidationException + */ + public static double updateScoreBasedOnLamsResponse(Node lesson, Node learnerResult, Score currentScore) + throws PersistenceException, ValidationException { + ScoreDbPersister scorePersister = ScoreDbPersister.Default.getInstance(); + + Long lessonMaxPossibleMark = new Long( + lesson.getAttributes().getNamedItem("lessonMaxPossibleMark").getNodeValue()); + String userTotalMarkStr = learnerResult.getAttributes().getNamedItem("userTotalMark").getNodeValue(); + + double newScore = StringUtil.isEmpty(userTotalMarkStr) ? 0 : new Double(userTotalMarkStr); + + //set score grade. if Lams supplies one (and lineitem will have score type) we set score; otherwise (and + // lineitme of type Complete/Incomplete) we set 0 + double gradebookMark = 0; + if (lessonMaxPossibleMark > 0) { + gradebookMark = (newScore / lessonMaxPossibleMark) * Constants.GRADEBOOK_POINTS_POSSIBLE; + } + + currentScore.setGrade(new DecimalFormat("##.##").format(gradebookMark)); + currentScore.validate(); + scorePersister.persist(currentScore); + + return gradebookMark; + } +} Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/Constants.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/dto/LearnerProgressDTO.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/servlet/ConfigServlet.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/servlet/GradebookServlet.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/servlet/GradebookSyncFixServlet.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/servlet/GradebookSyncServlet.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/servlet/GroupDataServlet.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/servlet/LamsLearningDesignDeleteServlet.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/servlet/LamsLearningDesignServlet.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/servlet/LearnerMonitorServlet.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/servlet/LessonManagerServlet.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/servlet/LinkToolsServlet.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/servlet/OpenLamsPageServlet.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/servlet/RenderDesignImageServlet.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/servlet/StartLessonAjaxServlet.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/servlet/UpdateServerUrlServlet.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/servlet/UserDataServlet.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/util/BlackboardUtil.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/util/CSVUtil.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/util/LamsBuildingBlockException.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/util/LamsPluginUtil.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/util/LamsSecurityUtil.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/util/LamsServerException.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 54843a5ae3288a977cdfa75fd43afc9c67831624 refers to a dead (removed) revision in file `lams_bb_integration/src/org/lamsfoundation/ld/integration/util/LineitemUtil.java'. Fisheye: No comparison available. Pass `N' to diff?