/** * OLAT - Online Learning and Training<br> * http://www.olat.org * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> * University of Zurich, Switzerland. * <hr> * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * This file has been modified by the OpenOLAT community. Changes are licensed * under the Apache 2.0 license as the original file. */ package org.olat.restapi.repository.course; import static org.olat.restapi.security.RestSecurityHelper.getIdentity; import static org.olat.restapi.security.RestSecurityHelper.isAuthor; import static org.olat.restapi.security.RestSecurityHelper.isAuthorEditor; import static org.olat.restapi.support.ObjectFactory.get; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.olat.core.gui.components.tree.TreeNode; import org.olat.core.id.Identity; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.StringHelper; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.coordinate.LockResult; import org.olat.course.CourseFactory; import org.olat.course.ICourse; import org.olat.course.condition.Condition; import org.olat.course.nodes.AbstractAccessableCourseNode; import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.CourseNodeConfiguration; import org.olat.course.nodes.CourseNodeFactory; import org.olat.course.tree.CourseEditorTreeNode; import org.olat.modules.ModuleConfiguration; import org.olat.restapi.support.vo.CourseNodeVO; public abstract class AbstractCourseNodeWebService { private static final OLog log = Tracing.createLoggerFor(AbstractCourseNodeWebService.class); private static final String CONDITION_ID_ACCESS = "accessability"; private static final String CONDITION_ID_VISIBILITY = "visibility"; private CourseEditSession openEditSession(ICourse course, Identity identity) { LockResult lock = CoordinatorManager.getInstance().getCoordinator().getLocker().acquireLock(course, identity, CourseFactory.COURSE_EDITOR_LOCK); if(lock.isSuccess()) { course = CourseFactory.openCourseEditSession(course.getResourceableId()); } return new CourseEditSession(course, lock); } //fxdiff FXOLAT-122: course management protected Response update(Long courseId, String nodeId, String shortTitle, String longTitle, String objectives, String visibilityExpertRules, String accessExpertRules, CustomConfigDelegate config, HttpServletRequest request) { return attach(courseId, null, nodeId, null, null, shortTitle, longTitle, objectives, visibilityExpertRules, accessExpertRules, config, request); } protected Response attach(Long courseId, String parentNodeId, String type, Integer position, String shortTitle, String longTitle, String objectives, String visibilityExpertRules, String accessExpertRules, CustomConfigDelegate config, HttpServletRequest request) { return attach(courseId, parentNodeId, null, type, position, shortTitle, longTitle, objectives, visibilityExpertRules, accessExpertRules, config, request); } protected Response attach(Long courseId, String parentNodeId, String nodeId, String type, Integer position, String shortTitle, String longTitle, String objectives, String visibilityExpertRules, String accessExpertRules, CustomConfigDelegate config, HttpServletRequest request) { if(!isAuthor(request)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } if(config != null && !config.isValid()) { return Response.serverError().status(Status.NOT_ACCEPTABLE).build(); } ICourse course = CoursesWebService.loadCourse(courseId); if(course == null) { return Response.serverError().status(Status.NOT_FOUND).build(); } else if (!isAuthorEditor(course, request)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } CourseEditSession editSession = null; try { editSession = openEditSession(course, getIdentity(request)); if(!editSession.canEdit()) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } CourseNodeVO node; if(nodeId != null) { node = updateCourseNode(nodeId, shortTitle, longTitle, objectives, visibilityExpertRules, accessExpertRules, config, editSession); } else { CourseNode parentNode = getParentNode(course, parentNodeId); if(parentNode == null) { return Response.serverError().status(Status.NOT_FOUND).build(); } node = createCourseNode(type, shortTitle, longTitle, objectives, visibilityExpertRules, accessExpertRules, config, editSession, parentNode, position); } return Response.ok(node).build(); } catch(Exception ex) { log.error("Error while adding an enrolment building block", ex); return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build(); } finally { saveAndCloseCourse(editSession); } } protected Response attachNodeConfig(Long courseId, String nodeId, FullConfigDelegate config, HttpServletRequest request) { if(!isAuthor(request)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } if(config == null || !config.isValid()) return Response.serverError().status(Status.CONFLICT).build(); ICourse course = CoursesWebService.loadCourse(courseId); if(course == null) { return Response.serverError().status(Status.NOT_FOUND).build(); } else if (!isAuthorEditor(course, request)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } CourseNode courseNode = getParentNode(course, nodeId); if(courseNode == null) { return Response.serverError().status(Status.NOT_FOUND).build(); } if(!config.isApplicable(course, courseNode)) return Response.serverError().status(Status.NOT_ACCEPTABLE).build(); CourseEditSession editSession = null; try { editSession = openEditSession(course, getIdentity(request)); if(!editSession.canEdit()) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } ModuleConfiguration moduleConfig = courseNode.getModuleConfiguration(); config.configure(course, courseNode, moduleConfig); return Response.ok().build(); } catch(Exception ex) { log.error("Error while adding an enrolment building block", ex); return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build(); } finally { saveAndCloseCourse(editSession); } } private CourseNodeVO createCourseNode(String type, String shortTitle, String longTitle, String learningObjectives, String visibilityExpertRules, String accessExpertRules, CustomConfigDelegate delegateConfig, CourseEditSession editSession, CourseNode parentNode, Integer position) { CourseNodeConfiguration newNodeConfig = CourseNodeFactory.getInstance().getCourseNodeConfiguration(type); CourseNode insertedNode = newNodeConfig.getInstance(); insertedNode.setShortTitle(shortTitle); insertedNode.setLongTitle(longTitle); insertedNode.setLearningObjectives(learningObjectives); insertedNode.setNoAccessExplanation("You don't have access"); if(StringHelper.containsNonWhitespace(visibilityExpertRules)) { Condition cond = this.createExpertCondition(CONDITION_ID_VISIBILITY, visibilityExpertRules); insertedNode.setPreConditionVisibility(cond); } if(StringHelper.containsNonWhitespace(accessExpertRules) && insertedNode instanceof AbstractAccessableCourseNode) { Condition cond = createExpertCondition(CONDITION_ID_ACCESS, accessExpertRules); ((AbstractAccessableCourseNode)insertedNode).setPreConditionAccess(cond); } ICourse course = editSession.getCourse(); if(delegateConfig != null) { ModuleConfiguration moduleConfig = insertedNode.getModuleConfiguration(); delegateConfig.configure(course, insertedNode, moduleConfig); } if (position == null || position.intValue() < 0) { course.getEditorTreeModel().addCourseNode(insertedNode, parentNode); } else { course.getEditorTreeModel().insertCourseNodeAt(insertedNode, parentNode, position); } CourseEditorTreeNode editorNode = course.getEditorTreeModel().getCourseEditorNodeContaining(insertedNode); CourseNodeVO vo = get(insertedNode); vo.setParentId(editorNode.getParent() == null ? null: editorNode.getParent().getIdent()); return vo; } //fxdiff FXOLAT-122: course management private CourseNodeVO updateCourseNode(String nodeId, String shortTitle, String longTitle, String learningObjectives, String visibilityExpertRules, String accessExpertRules, CustomConfigDelegate delegateConfig, CourseEditSession editSession) { ICourse course = editSession.getCourse(); TreeNode updateEditorNode = course.getEditorTreeModel().getNodeById(nodeId); CourseNode updatedNode = course.getEditorTreeModel().getCourseNode(nodeId); if(StringHelper.containsNonWhitespace(shortTitle)) { updatedNode.setShortTitle(shortTitle); } if(StringHelper.containsNonWhitespace(longTitle)) { updatedNode.setLongTitle(longTitle); } if(StringHelper.containsNonWhitespace(learningObjectives)) { updatedNode.setLearningObjectives(learningObjectives); } if(visibilityExpertRules != null) { Condition cond = createExpertCondition(CONDITION_ID_VISIBILITY, visibilityExpertRules); updatedNode.setPreConditionVisibility(cond); } if(StringHelper.containsNonWhitespace(accessExpertRules) && updatedNode instanceof AbstractAccessableCourseNode) { Condition cond = createExpertCondition(CONDITION_ID_ACCESS, accessExpertRules); ((AbstractAccessableCourseNode)updatedNode).setPreConditionAccess(cond); } if(delegateConfig != null) { ModuleConfiguration moduleConfig = updatedNode.getModuleConfiguration(); delegateConfig.configure(course, updatedNode, moduleConfig); } course.getEditorTreeModel().nodeConfigChanged(updateEditorNode); CourseEditorTreeNode editorNode = course.getEditorTreeModel().getCourseEditorNodeContaining(updatedNode); CourseNodeVO vo = get(updatedNode); vo.setParentId(editorNode.getParent() == null ? null: editorNode.getParent().getIdent()); return vo; } protected Condition createExpertCondition(String conditionId, String expertRules) { Condition cond = new Condition(); cond.setConditionExpression(expertRules); cond.setExpertMode(true); cond.setConditionId(conditionId); return cond; } protected CourseNode getParentNode(ICourse course, String parentNodeId) { if (parentNodeId == null) { return course.getRunStructure().getRootNode(); } else { return course.getEditorTreeModel().getCourseNode(parentNodeId); } } private void saveAndCloseCourse(CourseEditSession editSession) { if(editSession == null || !editSession.canEdit()) return; CourseFactory.saveCourseEditorTreeModel(editSession.getCourseId()); CourseFactory.fireModifyCourseEvent(editSession.getCourseId());//close the edit session too CoordinatorManager.getInstance().getCoordinator().getLocker().releaseLock(editSession.getLock()); } public interface CustomConfigDelegate { public boolean isValid(); public void configure(ICourse course, CourseNode newNode, ModuleConfiguration moduleConfig); } public interface FullConfigDelegate extends CustomConfigDelegate { public boolean isApplicable(ICourse course, CourseNode courseNode); } private class CourseEditSession { private final ICourse course; private final LockResult entry; public CourseEditSession(ICourse course, LockResult entry) { this.course = course; this.entry = entry; } public Long getCourseId() { return course.getResourceableId(); } public ICourse getCourse() { return course; } public LockResult getLock() { return entry; } public boolean canEdit() { return course != null && entry.isSuccess(); } } }