Java tutorial
/********************************************************************************** * $URL: $ * $Id: $ *********************************************************************************** * * Author: Eric Jeney, jeney@rutgers.edu * * Copyright (c) 2010 Rutgers, the State University of New Jersey * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * **********************************************************************************/ package org.sakaiproject.lessonbuildertool.model; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Map; import java.util.HashMap; import java.sql.Connection; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.criterion.DetachedCriteria; import org.hibernate.criterion.Restrictions; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.HibernateException; import org.hibernate.Transaction; import org.springframework.orm.hibernate3.HibernateCallback; import org.sakaiproject.authz.api.SecurityService; import org.sakaiproject.authz.cover.AuthzGroupService; import org.sakaiproject.db.api.SqlReader; import org.sakaiproject.db.api.SqlService; import org.sakaiproject.event.cover.EventTrackingService; import org.sakaiproject.lessonbuildertool.SimplePage; import org.sakaiproject.lessonbuildertool.SimplePageComment; import org.sakaiproject.lessonbuildertool.SimplePageCommentImpl; import org.sakaiproject.lessonbuildertool.SimplePageGroup; import org.sakaiproject.lessonbuildertool.SimplePageGroupImpl; import org.sakaiproject.lessonbuildertool.SimplePageImpl; import org.sakaiproject.lessonbuildertool.SimplePageItem; import org.sakaiproject.lessonbuildertool.SimplePageItemImpl; import org.sakaiproject.lessonbuildertool.SimplePageItemAttributeImpl; import org.sakaiproject.lessonbuildertool.SimplePageLogEntry; import org.sakaiproject.lessonbuildertool.SimplePageLogEntryImpl; import org.sakaiproject.lessonbuildertool.SimplePageQuestionAnswer; import org.sakaiproject.lessonbuildertool.SimplePageQuestionAnswerImpl; import org.sakaiproject.lessonbuildertool.SimplePageQuestionResponse; import org.sakaiproject.lessonbuildertool.SimplePageQuestionResponseImpl; import org.sakaiproject.lessonbuildertool.SimplePageQuestionResponseTotals; import org.sakaiproject.lessonbuildertool.SimplePageQuestionResponseTotalsImpl; import org.sakaiproject.lessonbuildertool.SimpleStudentPage; import org.sakaiproject.lessonbuildertool.SimpleStudentPageImpl; import org.sakaiproject.lessonbuildertool.SimplePagePeerEval; import org.sakaiproject.lessonbuildertool.SimplePagePeerEvalImpl; import org.sakaiproject.lessonbuildertool.SimplePagePeerEvalResult; import org.sakaiproject.lessonbuildertool.SimplePagePeerEvalResultImpl; import org.sakaiproject.lessonbuildertool.SimplePageProperty; import org.sakaiproject.lessonbuildertool.SimplePagePropertyImpl; import org.sakaiproject.tool.api.ToolManager; import org.sakaiproject.user.cover.UserDirectoryService; import org.springframework.dao.DataAccessException; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import org.springframework.orm.hibernate3.HibernateTemplate; import org.json.simple.JSONObject; import org.json.simple.JSONValue; import org.json.simple.JSONArray; public class SimplePageToolDaoImpl extends HibernateDaoSupport implements SimplePageToolDao { private static Log log = LogFactory.getLog(SimplePageToolDaoImpl.class); private ToolManager toolManager; private SecurityService securityService; private SqlService sqlService; private static String SITE_UPD = "site.upd"; // part of HibernateDaoSupport; this is the only context in which it is OK // to modify the template configuration protected void initDao() throws Exception { super.initDao(); getHibernateTemplate().setCacheQueries(true); log.info("initDao template " + getHibernateTemplate()); SimplePageItemImpl.setSimplePageToolDao(this); } // the permissions model here is preliminary. I'm not convinced that all the code in // upper layers checks where it should, so the Dao is supplying an extra layer of // protection. As far as I can tell, any database change should be done by // someone with update privs, except that add or update to the log is done on // behalf of normal people. I've checked all the code that does save or update for // log entries and it looks OK. public HibernateTemplate getDaoHibernateTemplate() { return getHibernateTemplate(); } public boolean canEditPage() { String ref = null; // no placement, startup testing, should be an advisor in place try { ref = "/site/" + toolManager.getCurrentPlacement().getContext(); } catch (java.lang.NullPointerException ignore) { ref = ""; } return securityService.unlock(SimplePage.PERMISSION_LESSONBUILDER_UPDATE, ref); } public boolean canEditPage(long pageId) { boolean canEdit = canEditPage(); // forced comments have a pageid of -1, because they are associated with // more than one page. But the student can't edit them anyway, so fail it // also, top-level fake items have pageid of 0 if (!canEdit && pageId != -1L && pageId != 0L) { SimplePage page = getPage(pageId); String owner = page.getOwner(); String group = page.getGroup(); if (group != null) group = "/site/" + page.getSiteId() + "/group/" + group; String currentUser = UserDirectoryService.getCurrentUser().getId(); if (currentUser != null) { if (group == null && currentUser.equals(owner)) canEdit = true; else if (group != null && AuthzGroupService.getUserRole(currentUser, group) != null) canEdit = true; } } return canEdit; } public boolean canEditPage(SimplePage page) { boolean canEdit = canEditPage(); // forced comments have a pageid of -1, because they are associated with // more than one page. But the student can't edit them anyway, so fail it if (!canEdit && page != null) { String owner = page.getOwner(); String group = page.getGroup(); if (group != null) group = "/site/" + page.getSiteId() + "/group/" + group; String currentUser = UserDirectoryService.getCurrentUser().getId(); if (currentUser != null) { if (group == null && currentUser.equals(owner)) canEdit = true; else if (group != null && AuthzGroupService.getUserRole(currentUser, group) != null) canEdit = true; } } return canEdit; } public void setSecurityService(SecurityService service) { securityService = service; } public void setSqlService(SqlService service) { sqlService = service; } public void setToolManager(ToolManager service) { toolManager = service; } public List<SimplePageItem> findItemsOnPage(long pageId) { DetachedCriteria d = DetachedCriteria.forClass(SimplePageItem.class).add(Restrictions.eq("pageId", pageId)); List<SimplePageItem> list = getHibernateTemplate().findByCriteria(d); Collections.sort(list, new Comparator<SimplePageItem>() { public int compare(SimplePageItem a, SimplePageItem b) { return Integer.valueOf(a.getSequence()).compareTo(b.getSequence()); } }); return list; } public void flush() { getHibernateTemplate().flush(); } public void clear() { getHibernateTemplate().clear(); } public List<SimplePageItem> findItemsInSite(String siteId) { Object[] fields = new Object[1]; fields[0] = siteId; List<String> ids = sqlService.dbRead( "select b.id from lesson_builder_pages a,lesson_builder_items b,SAKAI_SITE_PAGE c where a.siteId = ? and a.parent is null and a.pageId = b.sakaiId and b.type = 2 and b.pageId = 0 and a.toolId = c.PAGE_ID order by c.SITE_ORDER", fields, null); List<SimplePageItem> result = new ArrayList<SimplePageItem>(); if (result != null) { for (String id : ids) { SimplePageItem i = findItem(new Long(id)); result.add(i); } } return result; } public List<SimplePageItem> findDummyItemsInSite(String siteId) { Object[] fields = new Object[1]; fields[0] = siteId; List<String> ids = sqlService.dbRead( "select b.id from lesson_builder_pages a,lesson_builder_items b where a.siteId = ? and a.pageId = b.pageId and b.sakaiId = '/dummy'", fields, null); List<SimplePageItem> result = new ArrayList<SimplePageItem>(); if (result != null) { for (String id : ids) { SimplePageItem i = findItem(new Long(id)); result.add(i); } } return result; } // warning: only the id and html fields will be filled out // we had serious performance issues with an earlier version, so I'm doing it without hibernate public List<SimplePageItem> findTextItemsInSite(String siteId) { Object[] fields = new Object[1]; fields[0] = siteId; List<SimplePageItem> items = sqlService.dbRead( "select b.id,b.html from lesson_builder_pages a,lesson_builder_items b where a.siteId = ? and a.pageId = b.pageId and b.type = 5", fields, new SqlReader() { public Object readSqlResultRecord(ResultSet result) { try { SimplePageItem item = new SimplePageItemImpl(); item.setId(result.getLong(1)); item.setHtml(result.getString(2)); return item; } catch (SQLException e) { log.warn("findTextItemsInSite: " + e); return null; } } }); return items; } // because they don't come from hibernate, you can't use update on them. // we need to be able to update the HTML field public PageData findMostRecentlyVisitedPage(final String userId, final String toolId) { Object[] fields = new Object[4]; fields[0] = userId; fields[1] = toolId; fields[2] = userId; fields[3] = toolId; List<PageData> rv = sqlService.dbRead( "select a.itemId, a.id, b.sakaiId, b.name from lesson_builder_log a, lesson_builder_items b where a.userId=? and a.toolId=? and a.lastViewed = (select max(lastViewed) from lesson_builder_log where userId=? and toolId = ?) and a.itemId = b.id", fields, new SqlReader() { public Object readSqlResultRecord(ResultSet result) { try { PageData ret = new PageData(); ret.itemId = result.getLong(1); ret.pageId = result.getLong(3); ret.name = result.getString(4); return ret; } catch (SQLException e) { log.warn("findMostRecentlyVisitedPage: " + toolId + " : " + e); return null; } } }); if (rv != null && rv.size() > 0) return rv.get(0); else return null; } public SimplePageItem findItem(long id) { DetachedCriteria d = DetachedCriteria.forClass(SimplePageItem.class).add(Restrictions.eq("id", id)); List<SimplePageItem> list = getHibernateTemplate().findByCriteria(d); if (list != null && list.size() > 0) { return list.get(0); } else { return null; } } public SimplePageProperty findProperty(String attribute) { DetachedCriteria d = DetachedCriteria.forClass(SimplePageProperty.class) .add(Restrictions.eq("attribute", attribute)); List<SimplePageProperty> list = null; try { list = getHibernateTemplate().findByCriteria(d); } catch (org.hibernate.ObjectNotFoundException e) { return null; } if (list != null && list.size() > 0) { return list.get(0); } else { return null; } } public SimplePageProperty makeProperty(String attribute, String value) { return new SimplePagePropertyImpl(attribute, value); } public List<SimplePageComment> findComments(long commentWidgetId) { DetachedCriteria d = DetachedCriteria.forClass(SimplePageComment.class) .add(Restrictions.eq("itemId", commentWidgetId)); List<SimplePageComment> list = getHibernateTemplate().findByCriteria(d); return list; } public List<SimplePageComment> findCommentsOnItems(List<Long> commentItemIds) { if (commentItemIds == null || commentItemIds.size() == 0) return new ArrayList<SimplePageComment>(); DetachedCriteria d = DetachedCriteria.forClass(SimplePageComment.class) .add(Restrictions.in("itemId", commentItemIds)); List<SimplePageComment> list = getHibernateTemplate().findByCriteria(d); return list; } public List<SimplePageComment> findCommentsOnItemsByAuthor(List<Long> commentItemIds, String author) { if (commentItemIds == null || commentItemIds.size() == 0) return new ArrayList<SimplePageComment>(); DetachedCriteria d = DetachedCriteria.forClass(SimplePageComment.class) .add(Restrictions.in("itemId", commentItemIds)).add(Restrictions.eq("author", author)); List<SimplePageComment> list = getHibernateTemplate().findByCriteria(d); return list; } public List<SimplePageComment> findCommentsOnItemByAuthor(long commentWidgetId, String author) { DetachedCriteria d = DetachedCriteria.forClass(SimplePageComment.class) .add(Restrictions.eq("itemId", commentWidgetId)).add(Restrictions.eq("author", author)); List<SimplePageComment> list = getHibernateTemplate().findByCriteria(d); return list; } public List<SimplePageComment> findCommentsOnPageByAuthor(long pageId, String author) { DetachedCriteria d = DetachedCriteria.forClass(SimplePageComment.class) .add(Restrictions.eq("pageId", pageId)).add(Restrictions.eq("author", author)); List<SimplePageComment> list = getHibernateTemplate().findByCriteria(d); return list; } public SimplePageComment findCommentById(long commentId) { DetachedCriteria d = DetachedCriteria.forClass(SimplePageComment.class) .add(Restrictions.eq("id", commentId)); List<SimplePageComment> list = getHibernateTemplate().findByCriteria(d); if (list.size() > 0) { return list.get(0); } else { return null; } } public SimplePageComment findCommentByUUID(String commentUUID) { DetachedCriteria d = DetachedCriteria.forClass(SimplePageComment.class) .add(Restrictions.eq("UUID", commentUUID)); List<SimplePageComment> list = getHibernateTemplate().findByCriteria(d); if (list.size() > 0) { return list.get(0); } else { return null; } } public SimplePageItem findCommentsToolBySakaiId(String sakaiId) { DetachedCriteria d = DetachedCriteria.forClass(SimplePageItem.class) .add(Restrictions.eq("sakaiId", sakaiId)); List<SimplePageItem> list = getHibernateTemplate().findByCriteria(d); // We loop through and check type here in-case something else has the same // sakaiId, and to prevent creating a new index for something that probably // doesn't really need it. There shouldn't be more than a couple of matches // with different types. for (SimplePageItem item : list) { if (item.getType() == SimplePageItem.COMMENTS) { return item; } } return null; } public List<SimplePageItem> findItemsBySakaiId(String sakaiId) { DetachedCriteria d = DetachedCriteria.forClass(SimplePageItem.class) .add(Restrictions.eq("sakaiId", sakaiId)); return getHibernateTemplate().findByCriteria(d); } // find the student's page. In theory we keep them from doing a second page. With // group pages that means students in more than one group can only do one. So return the first // Different versions if item is controlled by group or not. That lets us use simple // hibernate queries and maximum caching public SimpleStudentPage findStudentPage(long itemId, String owner) { DetachedCriteria d = DetachedCriteria.forClass(SimpleStudentPage.class) .add(Restrictions.eq("itemId", itemId)).add(Restrictions.eq("owner", owner)) .add(Restrictions.eq("deleted", false)); List<SimpleStudentPage> list = getHibernateTemplate().findByCriteria(d); if (list.size() > 0) { return list.get(0); } else { return null; } } // groups is set of groups to search. // null groups means there are no permitted groups, so the answer is obviously null public SimpleStudentPage findStudentPage(long itemId, Collection<String> groups) { if (groups == null || groups.size() == 0) // no possible groups, so no result return null; DetachedCriteria d = DetachedCriteria.forClass(SimpleStudentPage.class) .add(Restrictions.eq("itemId", itemId)).add(Restrictions.in("group", groups)) .add(Restrictions.eq("deleted", false)); List<SimpleStudentPage> list = getHibernateTemplate().findByCriteria(d); if (list.size() > 0) { return list.get(0); } else { return null; } } public SimpleStudentPage findStudentPage(long id) { DetachedCriteria d = DetachedCriteria.forClass(SimpleStudentPage.class).add(Restrictions.eq("id", id)); List<SimpleStudentPage> list = getHibernateTemplate().findByCriteria(d); if (list.size() > 0) { return list.get(0); } else { return null; } } public SimpleStudentPage findStudentPageByPageId(long pageId) { DetachedCriteria d = DetachedCriteria.forClass(SimpleStudentPage.class) .add(Restrictions.eq("pageId", pageId)); List<SimpleStudentPage> list = getHibernateTemplate().findByCriteria(d); if (list.size() > 0) { return list.get(0); } else { return null; } } public List<SimpleStudentPage> findStudentPages(long itemId) { DetachedCriteria d = DetachedCriteria.forClass(SimpleStudentPage.class) .add(Restrictions.eq("itemId", itemId)); List<SimpleStudentPage> list = getHibernateTemplate().findByCriteria(d); return list; } public SimplePageItem findItemFromStudentPage(long pageId) { DetachedCriteria d = DetachedCriteria.forClass(SimpleStudentPage.class) .add(Restrictions.eq("pageId", pageId)); List<SimpleStudentPage> list = getHibernateTemplate().findByCriteria(d); if (list.size() > 0) { return findItem(list.get(0).getItemId()); } else { return null; } } public SimplePageItem findTopLevelPageItemBySakaiId(String id) { DetachedCriteria d = DetachedCriteria.forClass(SimplePageItem.class).add(Restrictions.eq("sakaiId", id)) .add(Restrictions.eq("pageId", 0L)).add(Restrictions.eq("type", SimplePageItem.PAGE)); List<SimplePageItem> list = getHibernateTemplate().findByCriteria(d); if (list == null || list.size() < 1) return null; return list.get(0); } public List<SimplePageItem> findPageItemsBySakaiId(String id) { DetachedCriteria d = DetachedCriteria.forClass(SimplePageItem.class).add(Restrictions.eq("sakaiId", id)) .add(Restrictions.eq("type", SimplePageItem.PAGE)); List<SimplePageItem> list = getHibernateTemplate().findByCriteria(d); return list; } public List findControlledResourcesBySakaiId(String id, String siteId) { Object[] fields = new Object[2]; fields[0] = id; fields[1] = siteId; List ids = sqlService.dbRead( "select a.id from lesson_builder_items a, lesson_builder_pages b where a.sakaiId = ? and ( a.type=1 or a.type=7) and a.prerequisite = 1 and a.pageId = b.pageId and b.siteId = ?", fields, null); return ids; } public SimplePageItem findNextPageItemOnPage(long pageId, int sequence) { DetachedCriteria d = DetachedCriteria.forClass(SimplePageItem.class).add(Restrictions.eq("pageId", pageId)) .add(Restrictions.eq("sequence", sequence + 1)).add(Restrictions.eq("type", SimplePageItem.PAGE)); List<SimplePageItem> list = getHibernateTemplate().findByCriteria(d); if (list == null || list.size() < 1) return null; return list.get(0); } public SimplePageItem findNextItemOnPage(long pageId, int sequence) { DetachedCriteria d = DetachedCriteria.forClass(SimplePageItem.class).add(Restrictions.eq("pageId", pageId)) .add(Restrictions.eq("sequence", sequence + 1)); List<SimplePageItem> list = getHibernateTemplate().findByCriteria(d); if (list == null || list.size() < 1) return null; return list.get(0); } public List<SimplePageQuestionAnswer> findAnswerChoices(SimplePageItem question) { List<SimplePageQuestionAnswer> ret = new ArrayList<SimplePageQuestionAnswer>(); // find new id number, max + 1 List answers = (List) question.getJsonAttribute("answers"); if (answers == null) return ret; for (Object a : answers) { Map answer = (Map) a; SimplePageQuestionAnswer newAnswer = new SimplePageQuestionAnswerImpl((Long) answer.get("id"), (String) answer.get("text"), (Boolean) answer.get("correct")); ret.add(newAnswer); } return ret; } public boolean hasCorrectAnswer(SimplePageItem question) { // find new id number, max + 1 List answers = (List) question.getJsonAttribute("answers"); if (answers == null) return false; for (Object a : answers) { Map answer = (Map) a; if ((Boolean) answer.get("correct")) return true; } return false; } public SimplePageQuestionAnswer findAnswerChoice(SimplePageItem question, long answerId) { // find new id number, max + 1 List answers = (List) question.getJsonAttribute("answers"); if (answers == null) return null; for (Object a : answers) { Map answer = (Map) a; if (answerId == (Long) answer.get("id")) { SimplePageQuestionAnswer newAnswer = new SimplePageQuestionAnswerImpl(answerId, (String) answer.get("text"), (Boolean) answer.get("correct")); return newAnswer; } } return null; } public SimplePageQuestionResponse findQuestionResponse(long questionId, String userId) { DetachedCriteria d = DetachedCriteria.forClass(SimplePageQuestionResponse.class) .add(Restrictions.eq("questionId", questionId)).add(Restrictions.eq("userId", userId)); List<SimplePageQuestionResponse> list = getHibernateTemplate().findByCriteria(d); if (list != null && list.size() > 0) { return list.get(0); } else { return null; } } public SimplePageQuestionResponse findQuestionResponse(long responseId) { DetachedCriteria d = DetachedCriteria.forClass(SimplePageQuestionResponse.class) .add(Restrictions.eq("id", responseId)); List<SimplePageQuestionResponse> list = getHibernateTemplate().findByCriteria(d); if (list != null && list.size() > 0) { return list.get(0); } else { return null; } } public List<SimplePageQuestionResponse> findQuestionResponses(long questionId) { DetachedCriteria d = DetachedCriteria.forClass(SimplePageQuestionResponse.class) .add(Restrictions.eq("questionId", questionId)); List<SimplePageQuestionResponse> list = getHibernateTemplate().findByCriteria(d); return list; } public void getCause(Throwable t, List<String> elist) { while (t.getCause() != null) { t = t.getCause(); } log.warn("error saving or updating: " + t.toString()); elist.add(t.getLocalizedMessage()); } public boolean saveItem(Object o, List<String> elist, String nowriteerr, boolean requiresEditPermission) { /* * This checks a lot of conditions: * 1) If o is SimplePageItem or SimplePage, it makes sure it gets the right page and checks the * permissions on it. * 2) If it's a log entry or question response, it lets it go. * 3) If requiresEditPermission is set to false, it lets it go. * * Essentially, if any of those say that the edit is fine, it won't throw the error. */ if (requiresEditPermission && !(o instanceof SimplePageItem && canEditPage(((SimplePageItem) o).getPageId())) && !(o instanceof SimplePage && canEditPage((SimplePage) o)) && !(o instanceof SimplePageLogEntry || o instanceof SimplePageQuestionResponse) && !(o instanceof SimplePageGroup)) { elist.add(nowriteerr); return false; } try { getHibernateTemplate().save(o); if (o instanceof SimplePageItem) { SimplePageItem i = (SimplePageItem) o; EventTrackingService.post(EventTrackingService.newEvent("lessonbuilder.create", "/lessonbuilder/item/" + i.getId(), true)); } else if (o instanceof SimplePage) { SimplePage i = (SimplePage) o; EventTrackingService.post(EventTrackingService.newEvent("lessonbuilder.create", "/lessonbuilder/page/" + i.getPageId(), true)); } if (o instanceof SimplePageItem || o instanceof SimplePage) { updateStudentPage(o); } return true; } catch (org.springframework.dao.DataIntegrityViolationException e) { getCause(e, elist); return false; } catch (org.hibernate.exception.DataException e) { getCause(e, elist); return false; } catch (DataAccessException e) { getCause(e, elist); return false; } } // for use within copytransfer. We don't need to do permissions, and it probably // doesn't make sense to log every item created public boolean quickSaveItem(Object o) { try { Object id = getHibernateTemplate().save(o); return true; } catch (DataAccessException e) { e.printStackTrace(); log.warn("Hibernate could not save: " + e.toString()); return false; } } public boolean deleteItem(Object o) { /* * If o is SimplePageItem or SimplePage, it makes sure it gets the right page and checks the * permissions on it. If the item isn't SimplePageItem or SimplePage, it lets it go. * * Essentially, if any of those say that the edit is fine, it won't throw the error. */ if (!(o instanceof SimplePageItem && canEditPage(((SimplePageItem) o).getPageId())) && !(o instanceof SimplePage && canEditPage((SimplePage) o)) && (o instanceof SimplePage || o instanceof SimplePageItem)) { return false; } if (o instanceof SimplePageItem) { SimplePageItem i = (SimplePageItem) o; EventTrackingService.post(EventTrackingService.newEvent("lessonbuilder.delete", "/lessonbuilder/item/" + i.getId(), true)); } else if (o instanceof SimplePage) { SimplePage i = (SimplePage) o; EventTrackingService.post(EventTrackingService.newEvent("lessonbuilder.delete", "/lessonbuilder/page/" + i.getPageId(), true)); } else if (o instanceof SimplePageComment) { SimplePageComment i = (SimplePageComment) o; EventTrackingService.post(EventTrackingService.newEvent("lessonbuilder.delete", "/lessonbuilder/comment/" + i.getId(), true)); } try { getHibernateTemplate().delete(o); return true; } catch (DataAccessException e) { try { /* If we have multiple objects of the same item, you must merge them * before deleting. If the first delete fails, we merge and try again. */ getHibernateTemplate().delete(getHibernateTemplate().merge(o)); return true; } catch (DataAccessException ex) { ex.printStackTrace(); log.warn("Hibernate could not delete: " + e.toString()); return false; } } } public boolean update(Object o, List<String> elist, String nowriteerr, boolean requiresEditPermission) { /* * This checks a lot of conditions: * 1) If o is SimplePageItem or SimplePage, it makes sure it gets the right page and checks the * permissions on it. * 2) If it's a log entry, it lets it go. * 3) If requiresEditPermission is set to false, it lets it go. * * Essentially, if any of those say that the edit is fine, it won't throw the error. */ if (requiresEditPermission && !(o instanceof SimplePageItem && canEditPage(((SimplePageItem) o).getPageId())) && !(o instanceof SimplePage && canEditPage((SimplePage) o)) && !(o instanceof SimplePageLogEntry || o instanceof SimplePageQuestionResponse) && !(o instanceof SimplePageGroup)) { elist.add(nowriteerr); return false; } if (o instanceof SimplePageItem) { SimplePageItem i = (SimplePageItem) o; EventTrackingService.post(EventTrackingService.newEvent("lessonbuilder.update", "/lessonbuilder/item/" + i.getId(), true)); } else if (o instanceof SimplePage) { SimplePage i = (SimplePage) o; EventTrackingService.post(EventTrackingService.newEvent("lessonbuilder.update", "/lessonbuilder/page/" + i.getPageId(), true)); } try { if (!(o instanceof SimplePageLogEntry)) { getHibernateTemplate().merge(o); } else { // Updating seems to always update the timestamp on the log correctly, // while merging doesn't always get it right. However, it's possible that // update will fail, so we do both, in order of preference. try { getHibernateTemplate().update(o); } catch (DataAccessException ex) { log.warn("Wasn't able to update log entry, timing might be a bit off."); getHibernateTemplate().merge(o); } } if (o instanceof SimplePageItem || o instanceof SimplePage) { updateStudentPage(o); } return true; } catch (org.springframework.dao.DataIntegrityViolationException e) { getCause(e, elist); return false; } catch (org.hibernate.exception.DataException e) { getCause(e, elist); return false; } catch (DataAccessException e) { getCause(e, elist); return false; } } // ditto for update public boolean quickUpdate(Object o) { try { getHibernateTemplate().merge(o); return true; } catch (DataAccessException e) { e.printStackTrace(); return false; } } public Long getTopLevelPageId(String toolId) { DetachedCriteria d = DetachedCriteria.forClass(SimplePage.class).add(Restrictions.eq("toolId", toolId)) .add(Restrictions.isNull("parent")); List list = getHibernateTemplate().findByCriteria(d); if (list.size() > 1) { log.warn("Problem finding which page we should be on. Doing the best we can."); } if (list != null && list.size() > 0) { return ((SimplePage) list.get(0)).getPageId(); } else { return null; } } public SimplePage getPage(long pageId) { DetachedCriteria d = DetachedCriteria.forClass(SimplePage.class).add(Restrictions.eq("pageId", pageId)); List l = getHibernateTemplate().findByCriteria(d); if (l != null && l.size() > 0) { return (SimplePage) l.get(0); } else { return null; } } public List<SimplePage> getSitePages(String siteId) { DetachedCriteria d = DetachedCriteria.forClass(SimplePage.class).add(Restrictions.eq("siteId", siteId)) .add(Restrictions.isNull("owner")); List<SimplePage> l = getHibernateTemplate().findByCriteria(d); if (l != null && l.size() > 0) { return l; } else { return null; } } public SimplePageLogEntry getLogEntry(String userId, long itemId, Long studentPageId) { if (studentPageId.equals(-1L)) studentPageId = null; DetachedCriteria d = DetachedCriteria.forClass(SimplePageLogEntry.class) .add(Restrictions.eq("userId", userId)).add(Restrictions.eq("itemId", itemId)); if (studentPageId != null) { d.add(Restrictions.eq("studentPageId", studentPageId)); } else { d.add(Restrictions.isNull("studentPageId")); } List l = getHibernateTemplate().findByCriteria(d); if (l != null && l.size() > 0) { return (SimplePageLogEntry) l.get(0); } else { return null; } } // owner not currently used. would need group as well public boolean isPageVisited(long pageId, String userId, String owner) { // if this is a student page, it's most likely the top level, so do that query first if (owner != null) { Object[] fields = new Object[3]; fields[0] = pageId; fields[1] = pageId; fields[2] = userId; List<String> ones = sqlService.dbRead( "select 1 from lesson_builder_student_pages a, lesson_builder_log b where a.pageId=? and a.itemId = b.itemId and b.studentPageId=? and b.userId=?", fields, null); if (ones != null && ones.size() > 0) return true; } Object[] fields = new Object[2]; fields[0] = Long.toString(pageId); fields[1] = userId; List<String> ones = sqlService.dbRead( "select 1 from lesson_builder_items a, lesson_builder_log b where a.sakaiId=? and a.type=2 and a.id=b.itemId and b.userId=?", fields, null); if (ones != null && ones.size() > 0) return true; else return false; } public List<SimplePageLogEntry> getStudentPageLogEntries(long itemId, String userId) { DetachedCriteria d = DetachedCriteria.forClass(SimplePageLogEntry.class) .add(Restrictions.eq("userId", userId)).add(Restrictions.eq("itemId", itemId)) .add(Restrictions.isNotNull("studentPageId")); List<SimplePageLogEntry> entries = getHibernateTemplate().findByCriteria(d); return entries; } public List<String> findUserWithCompletePages(Long itemId) { Object[] fields = new Object[1]; fields[0] = itemId; List<String> users = sqlService.dbRead( "select a.userId from lesson_builder_log a where a.itemId = ? and a.complete = true", fields, null); return users; } public SimplePageGroup findGroup(String itemId) { DetachedCriteria d = DetachedCriteria.forClass(SimplePageGroup.class) .add(Restrictions.eq("itemId", itemId)); List l = getHibernateTemplate().findByCriteria(d); if (l != null && l.size() > 0) { return (SimplePageGroup) l.get(0); } else { return null; } } public SimplePage makePage(String toolId, String siteId, String title, Long parent, Long topParent) { return new SimplePageImpl(toolId, siteId, title, parent, topParent); } public SimplePageItem makeItem(long id, long pageId, int sequence, int type, String sakaiId, String name) { return new SimplePageItemImpl(id, pageId, sequence, type, sakaiId, name); } public SimplePageItem makeItem(long pageId, int sequence, int type, String sakaiId, String name) { return new SimplePageItemImpl(pageId, sequence, type, sakaiId, name); } public SimplePageGroup makeGroup(String itemId, String groupId, String groups, String siteId) { return new SimplePageGroupImpl(itemId, groupId, groups, siteId); } public SimplePageQuestionResponse makeQuestionResponse(String userId, long questionId) { return new SimplePageQuestionResponseImpl(userId, questionId); } public SimplePageLogEntry makeLogEntry(String userId, long itemId, Long studentPageId) { return new SimplePageLogEntryImpl(userId, itemId, studentPageId); } public SimplePageComment makeComment(long itemId, long pageId, String author, String comment, String UUID, boolean html) { return new SimplePageCommentImpl(itemId, pageId, author, comment, UUID, html); } public SimpleStudentPage makeStudentPage(long itemId, long pageId, String title, String author, String group) { return new SimpleStudentPageImpl(itemId, pageId, title, author, group); } // answer is stored as id [int], text, correct [boolean] public SimplePageQuestionAnswer makeQuestionAnswer(String text, boolean correct) { return new SimplePageQuestionAnswerImpl(0, text, correct); } // only implemented for existing questions, i.e. questions with id numbers public boolean deleteQuestionAnswer(SimplePageQuestionAnswer questionAnswer, SimplePageItem question) { if (!canEditPage(question.getPageId())) { log.warn( "User tried to edit question on page without edit permission. PageId: " + question.getPageId()); return false; } // getId returns long, so this can never be null Long delid = questionAnswer.getId(); // find new id number, max + 1 // JSON uses Long for integer values List answers = (List) question.getJsonAttribute("answers"); Long max = -1L; if (answers == null) return false; for (Object a : answers) { Map answer = (Map) a; if (delid.equals(answer.get("id"))) { answers.remove(a); return true; } } return false; } // methods above are historical. I leave them for completeness. THere are the ones actually used: public void clearQuestionAnswers(SimplePageItem question) { question.setJsonAttribute("answers", null); } public Long maxQuestionAnswer(SimplePageItem question) { Long max = 0L; List answers = (List) question.getJsonAttribute("answers"); if (answers == null) return max; for (Object a : answers) { Map answer = (Map) a; Long i = (Long) answer.get("id"); if (i > max) max = i; } return max; } public Long addQuestionAnswer(SimplePageItem question, Long id, String text, Boolean isCorrect) { // no need to check security. that happens when item is saved List answers = (List) question.getJsonAttribute("answers"); if (answers == null) { answers = new JSONArray(); question.setJsonAttribute("answers", answers); if (id <= 0L) id = 1L; } else if (id <= 0L) { Long max = 0L; for (Object a : answers) { Map answer = (Map) a; Long i = (Long) answer.get("id"); if (i > max) max = i; } id = max + 1; } // create and add the json form of the answer Map newAnswer = new JSONObject(); newAnswer.put("id", id); newAnswer.put("text", text); newAnswer.put("correct", isCorrect); answers.add(newAnswer); return id; } public void clearPeerEvalRows(SimplePageItem question) { question.setJsonAttribute("rows", null); } public Long maxPeerEvalRow(SimplePageItem question) { Long max = 0L; List rows = (List) question.getJsonAttribute("rows"); if (rows == null) return max; for (Object a : rows) { Map row = (Map) a; Long i = (Long) row.get("id"); if (i > max) max = i; } return max; } public void addPeerEvalRow(SimplePageItem question, Long id, String text) { // no need to check security. that happens when item is saved List rows = (List) question.getJsonAttribute("rows"); if (rows == null) { rows = new JSONArray(); question.setJsonAttribute("rows", rows); if (id <= 0L) id = 1L; } else if (id <= 0L) { Long max = 0L; for (Object r : rows) { Map row = (Map) r; Long i = (Long) row.get("id"); if (i > max) max = i; } id = max + 1; } Map newRow = new JSONObject(); newRow.put("id", id); newRow.put("rowText", text); rows.add(newRow); } public SimplePageItem copyItem(SimplePageItem old) { SimplePageItem item = new SimplePageItemImpl(); item.setPageId(old.getPageId()); item.setSequence(old.getSequence()); item.setType(old.getType()); item.setSakaiId(old.getSakaiId()); item.setName(old.getName()); item.setHtml(old.getHtml()); item.setDescription(old.getDescription()); item.setHeight(old.getHeight()); item.setWidth(old.getWidth()); item.setAlt(old.getAlt()); item.setNextPage(old.getNextPage()); item.setFormat(old.getFormat()); item.setRequired(old.isRequired()); item.setAlternate(old.isAlternate()); item.setPrerequisite(old.isPrerequisite()); item.setSubrequirement(old.getSubrequirement()); item.setRequirementText(old.getRequirementText()); item.setSameWindow(old.isSameWindow()); item.setAnonymous(old.isAnonymous()); item.setShowComments(old.getShowComments()); item.setForcedCommentsAnonymous(old.getForcedCommentsAnonymous()); item.setAttributeString(old.getAttributeString()); // copy via json item.setShowPeerEval(old.getShowPeerEval()); // Map<String, SimplePageItemAttributeImpl> attrs = ((SimplePageItemImpl)old).getAttributes(); // if (attrs != null) { // Collection<SimplePageItemAttributeImpl> attributes = attrs.values(); // if (attributes.size() > 0) { // for (SimplePageItemAttributeImpl attr: attributes) { // item.setAttribute(attr.getAttr(), attr.getValue()); //} // } // } return item; } // phase 2 of copy after save, we need item number here public SimplePageItem copyItem2(SimplePageItem old, SimplePageItem item) { syncQRTotals(item); return item; } private void updateStudentPage(Object o) { SimplePage page; if (o instanceof SimplePageItem) { SimplePageItem item = (SimplePageItem) o; page = getPage(item.getPageId()); } else if (o instanceof SimplePage) { page = (SimplePage) o; } else { return; } if (page != null && page.getTopParent() != null) { SimpleStudentPage studentPage = findStudentPage(page.getTopParent()); if (studentPage != null) { studentPage.setLastUpdated(new Date()); quickUpdate(studentPage); } } } public Map JSONParse(String s) { return (JSONObject) JSONValue.parse(s); } public Map newJSONObject() { return new JSONObject(); } public List newJSONArray() { return new JSONArray(); } public SimplePageQuestionResponseTotals makeQRTotals(long qid, long rid) { return new SimplePageQuestionResponseTotalsImpl(qid, rid); } public List<SimplePageQuestionResponseTotals> findQRTotals(long questionId) { DetachedCriteria d = DetachedCriteria.forClass(SimplePageQuestionResponseTotals.class) .add(Restrictions.eq("questionId", questionId)); List<SimplePageQuestionResponseTotals> list = getHibernateTemplate().findByCriteria(d); return list; } public void incrementQRCount(long questionId, long responseId) { Object[] fields = new Object[2]; fields[0] = questionId; fields[1] = responseId; sqlService.dbWrite( "update lesson_builder_qr_totals set respcount = respcount + 1 where questionId = ? and responseId = ?", fields); } public void syncQRTotals(SimplePageItem item) { if (item.getType() != SimplePageItem.QUESTION || !"multipleChoice".equals(item.getAttribute("questionType"))) return; Map<Long, SimplePageQuestionResponseTotals> oldTotals = new HashMap<Long, SimplePageQuestionResponseTotals>(); List<SimplePageQuestionResponseTotals> oldQrTotals = findQRTotals(item.getId()); for (SimplePageQuestionResponseTotals total : oldQrTotals) oldTotals.put(total.getResponseId(), total); for (SimplePageQuestionAnswer answer : findAnswerChoices(item)) { Long id = answer.getId(); if (oldTotals.get(id) != null) oldTotals.remove(id); // in both old and new, done with it else { // in new but not old, add it SimplePageQuestionResponseTotals total = makeQRTotals(item.getId(), id); quickSaveItem(total); } } // entries that were in old list but not new one, remove them for (Long rid : oldTotals.keySet()) { deleteItem(oldTotals.get(rid)); } } public SimplePagePeerEval findPeerEval(long itemId) { SimplePageItem item = findItem(itemId); List rows = (List) item.getJsonAttribute("rows"); if (rows == null) return null; for (Object a : rows) { Map row = (Map) a; } return null; } public List<SimplePagePeerEvalResult> findPeerEvalResult(long pageId, String userId, String gradee) //List<String> ids = sqlService.dbRead("select b.id from lesson_builder_pages a,lesson_builder_items b where a.siteId = ? and a.pageId = b.pageId and b.sakaiId = '/dummy'", fields, null); { DetachedCriteria d = DetachedCriteria.forClass(SimplePagePeerEvalResult.class) .add(Restrictions.eq("pageId", pageId)).add(Restrictions.eq("grader", userId)) .add(Restrictions.eq("gradee", gradee)); List<SimplePagePeerEvalResult> list = getHibernateTemplate().findByCriteria(d); List<SimplePagePeerEvalResult> newList = new ArrayList<SimplePagePeerEvalResult>(); for (SimplePagePeerEvalResult eval : list) { if (eval.getSelected()) newList.add(eval); } return newList; } public SimplePagePeerEvalResult makePeerEvalResult(long pageId, String gradee, String grader, String rowText, int columnValue) { return new SimplePagePeerEvalResultImpl(pageId, gradee, grader, rowText, columnValue); } public List<SimplePagePeerEvalResult> findPeerEvalResultByOwner(long pageId, String pageOwner) { DetachedCriteria d = DetachedCriteria.forClass(SimplePagePeerEvalResult.class) .add(Restrictions.eq("pageId", pageId)).add(Restrictions.eq("gradee", pageOwner)); List<SimplePagePeerEvalResult> list = getHibernateTemplate().findByCriteria(d); List<SimplePagePeerEvalResult> newList = new ArrayList<SimplePagePeerEvalResult>(); for (SimplePagePeerEvalResult eval : list) { if (eval.getSelected()) newList.add(eval); } return newList; } public List<SimplePageItem> findGradebookItems(final String gradebookUid) { String hql = "select item from org.sakaiproject.lessonbuildertool.SimplePageItem item, org.sakaiproject.lessonbuildertool.SimplePage page where item.pageId = page.pageId and page.siteId = :site and (item.gradebookId is not null or item.altGradebook is not null)"; return getHibernateTemplate().findByNamedParam(hql, "site", gradebookUid); } public List<SimplePage> findGradebookPages(final String gradebookUid) { String hql = "select page from org.sakaiproject.lessonbuildertool.SimplePage page where page.siteId = :site and (page.gradebookPoints is not null)"; return getHibernateTemplate().findByNamedParam(hql, "site", gradebookUid); } // items in lesson_builder_groups for specified site, map of itemId to groups public Map<String, String> getExternalAssigns(String siteId) { DetachedCriteria d = DetachedCriteria.forClass(SimplePageGroup.class) .add(Restrictions.eq("siteId", siteId)); List<SimplePageGroup> list = getHibernateTemplate().findByCriteria(d); Map<String, String> ret = new HashMap<String, String>(); for (SimplePageGroup group : list) ret.put(group.getItemId(), group.getGroups()); return ret; } // return 1 if we found something, 0 if not // this is only going to find something once per site, typically. // so it's best to optimize for the normal case that nothing is there // initialy this called // dbWriteCount("delete from SAKAI_SITE_PROPERTY where SITE_ID=? and NAME='lessonbuilder-needsfixup'", fields, null, null, false); // but that doesn't exist in 2.8. public int clearNeedsFixup(String siteId) { Object[] fields = new Object[1]; fields[0] = siteId; List<String> needsList = sqlService.dbRead( "select VALUE from SAKAI_SITE_PROPERTY where SITE_ID=? and NAME='lessonbuilder-needsfixup'", fields, null); // normal case -- no flag if (needsList == null || needsList.size() == 0) { return 0; } // there is a flag, do something more carefully avoiding race conditions // There is a possible timing issue if someone copies data into the site after the // last test. If so, we'll get it next time someone uses the site. // we need to be provably sure that if the flag is set, this code returns 1 exactly once. // I believe that is the case. int retval = 0; Connection conn = null; boolean wasCommit = true; try { conn = sqlService.borrowConnection(); needsList = sqlService.dbRead(conn, "select VALUE from SAKAI_SITE_PROPERTY where SITE_ID=? and NAME='lessonbuilder-needsfixup' for update", fields, null); wasCommit = conn.getAutoCommit(); conn.setAutoCommit(false); if (needsList != null && needsList.size() > 0) { retval = 1; try { retval = Integer.parseInt(needsList.get(0)); } catch (Exception ignore) { } sqlService.dbWrite(conn, "delete from SAKAI_SITE_PROPERTY where SITE_ID=? and NAME='lessonbuilder-needsfixup'", fields); } conn.commit(); // I don't think we need to handle errrors explicitly. They will result in // returning 0, which is about the best we can do } catch (Exception e) { } finally { if (conn != null) { try { conn.setAutoCommit(wasCommit); } catch (Exception e) { System.out.println("transact: (setAutoCommit): " + e); } sqlService.returnConnection(conn); } } return retval; } public void setNeedsGroupFixup(String siteId, final int value) { // I'm doing this in hibernate because the table is cached and I don't want // hibernate's caceh to be out of date final String property = "groupfixup " + siteId; getHibernateTemplate().flush(); Session session = getSessionFactory().openSession(); Transaction tx = session.getTransaction(); try { tx = session.beginTransaction(); Query query = session.createQuery("from SimplePagePropertyImpl as prop where prop.attribute = :attr"); query.setString("attr", property); SimplePageProperty prop = (SimplePageProperty) query.uniqueResult(); if (prop != null) { int oldValue = 0; try { oldValue = Integer.parseInt(prop.getValue()); } catch (Exception e) { } ; if (value > oldValue) prop.setValue(Integer.toString(value)); } else { prop = makeProperty(property, Integer.toString(value)); } session.saveOrUpdate(prop); tx.commit(); } catch (Exception e) { if (tx != null) tx.rollback(); } finally { session.close(); } } // for efficiency, we first check a cached reference. On multiple systems // this may be out of date, but the most common case is that the instructor copies the site and // then tries it on the same server. If not, the fixup can be delayed 10 min. I thikn that's OK // really don't want to add one db query per page display for this stupid thing public int clearNeedsGroupFixup(String siteId) { String property = "groupfixup " + siteId; DetachedCriteria d = DetachedCriteria.forClass(SimplePageProperty.class) .add(Restrictions.eq("attribute", property)); List<SimplePageProperty> list = getHibernateTemplate().findByCriteria(d); if (list == null || list.size() == 0) return 0; // there is a flag, do something more carefully avoiding race conditions // There is a possible timing issue if someone copies data into the site after the // last test. If so, we'll get it next time someone uses the site. // we need to be provably sure that if the flag is set, this code returns 1 exactly once. // I believe that is the case. getHibernateTemplate().flush(); int retval = 0; Session session = getSessionFactory().openSession(); Transaction tx = session.getTransaction(); try { tx = session.beginTransaction(); Query query = session.createQuery("from SimplePagePropertyImpl as prop where prop.attribute = :attr"); query.setString("attr", property); SimplePageProperty prop = (SimplePageProperty) query.uniqueResult(); // if it's there, remember current value and delete if (prop != null) { try { retval = Integer.parseInt(prop.getValue()); } catch (Exception e) { } ; session.delete(prop); } tx.commit(); } catch (Exception e) { if (tx != null) tx.rollback(); } finally { session.close(); } return retval; } public Map<String, String> getObjectMap(String siteId) { Object[] fields = new Object[1]; fields[0] = siteId; final Map<String, String> objectMap = new HashMap<String, String>(); sqlService.dbRead( "select a.sakaiId,a.alt from lesson_builder_items a, lesson_builder_pages b where a.pageId=b.pageId and b.siteId=? and a.type in (3,4,8)", fields, new SqlReader() { public Object readSqlResultRecord(ResultSet result) { try { String newObject = result.getString(1); String oldObject = result.getString(2); if (oldObject != null && oldObject.length() > 0 && !oldObject.startsWith("sam_core")) { int i = oldObject.indexOf("/"); if (i >= 0) i = oldObject.indexOf("/", i + 1); if (i >= 0) oldObject = oldObject.substring(0, i); oldObject = "/" + oldObject; objectMap.put(oldObject, newObject); } // also put new object in map if (!newObject.startsWith("/sam_core")) objectMap.put(newObject, ""); return null; } catch (SQLException e) { log.warn("findTextItemsInSite: " + e); return null; } } }); return objectMap; } }