/** * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * <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 the * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> * <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> * Initial code contributed and copyrighted by<br> * frentix GmbH, http://www.frentix.com * <p> */ package org.olat.course.nodes.bc; import static org.olat.restapi.security.RestSecurityHelper.getUserRequest; import static org.olat.restapi.security.RestSecurityHelper.isAuthor; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.modules.bc.vfs.OlatNamedContainerImpl; import org.olat.core.commons.services.notifications.NotificationsManager; import org.olat.core.commons.services.notifications.Subscriber; import org.olat.core.gui.UserRequest; import org.olat.core.id.IdentityEnvironment; import org.olat.core.util.StringHelper; import org.olat.core.util.nodes.INode; import org.olat.core.util.tree.Visitor; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.callbacks.VFSSecurityCallback; import org.olat.core.util.vfs.restapi.VFSWebservice; import org.olat.course.ICourse; import org.olat.course.condition.Condition; import org.olat.course.nodes.BCCourseNode; import org.olat.course.nodes.CourseNode; import org.olat.course.run.userview.CourseTreeVisitor; import org.olat.course.run.userview.VisibleTreeFilter; import org.olat.modules.ModuleConfiguration; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; import org.olat.resource.accesscontrol.ACService; import org.olat.resource.accesscontrol.AccessResult; import org.olat.restapi.repository.course.AbstractCourseNodeWebService; import org.olat.restapi.repository.course.CourseWebService; import org.olat.restapi.repository.course.CoursesWebService; import org.olat.restapi.support.vo.FolderVO; import org.olat.restapi.support.vo.FolderVOes; /** * * Description:<br> * * <P> * Initial Date: 6 févr. 2012 <br> * * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com */ @Path("repo/courses/{courseId}/elements/folder") public class BCWebService extends AbstractCourseNodeWebService { /** * Retrieves metadata of the course node * @response.representation.200.qname {http://www.example.com}folderVOes * @response.representation.200.mediaType application/xml, application/json * @response.representation.200.doc The course node metadatas * @response.representation.200.example {@link org.olat.restapi.support.vo.Examples#SAMPLE_FOLDERVOes} * @response.representation.401.doc The roles of the authenticated user are not sufficient * @response.representation.404.doc The course or parentNode not found * @param courseId The course resourceable's id * @param nodeId The node's id * @param httpRequest The HTTP request * @return The persisted structure element (fully populated) */ @GET @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response getFolders(@PathParam("courseId") Long courseId, @Context HttpServletRequest httpRequest) { final ICourse course = CoursesWebService.loadCourse(courseId); if(course == null) { return Response.serverError().status(Status.NOT_FOUND).build(); } else if (!CourseWebService.isCourseAccessible(course, false, httpRequest)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } final UserRequest ureq = getUserRequest(httpRequest); RepositoryEntry entry = RepositoryManager.getInstance().lookupRepositoryEntry(course, true); ACService acManager = CoreSpringFactory.getImpl(ACService.class); AccessResult result = acManager.isAccessible(entry, ureq.getIdentity(), false); if(!result.isAccessible()) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } final Set<String> subscribed = new HashSet<String>(); NotificationsManager man = NotificationsManager.getInstance(); List<String> notiTypes = Collections.singletonList("FolderModule"); List<Subscriber> subs = man.getSubscribers(ureq.getIdentity(), notiTypes); for(Subscriber sub:subs) { Long courseKey = sub.getPublisher().getResId(); if(courseId.equals(courseKey)) { subscribed.add(sub.getPublisher().getSubidentifier()); break; } } final List<FolderVO> folderVOs = new ArrayList<FolderVO>(); new CourseTreeVisitor(course, ureq.getUserSession().getIdentityEnvironment()).visit(new Visitor() { @Override public void visit(INode node) { if(node instanceof BCCourseNode) { BCCourseNode bcNode = (BCCourseNode)node; FolderVO folder = createFolderVO(ureq.getUserSession().getIdentityEnvironment(), course, bcNode, subscribed); folderVOs.add(folder); } } }, new VisibleTreeFilter()); FolderVOes voes = new FolderVOes(); voes.setFolders(folderVOs.toArray(new FolderVO[folderVOs.size()])); voes.setTotalCount(folderVOs.size()); return Response.ok(voes).build(); } /** * This attaches a Folder Element onto a given course. The element will be * inserted underneath the supplied parentNodeId. * @response.representation.mediaType application/x-www-form-urlencoded * @response.representation.200.qname {http://www.example.com}courseNodeVO * @response.representation.200.mediaType application/xml, application/json * @response.representation.200.doc The folder node metadatas * @response.representation.200.example {@link org.olat.restapi.support.vo.Examples#SAMPLE_COURSENODEVO} * @response.representation.401.doc The roles of the authenticated user are not sufficient * @response.representation.404.doc The course or parentNode not found * @param courseId The course resourceable id * @param parentNodeId The node's id which will be the parent of this folder * @param position The node's position relative to its sibling nodes (optional) * @param shortTitle The node short title * @param longTitle The node long title * @param objectives The node learning objectives * @param visibilityExpertRules The rules to view the node (optional) * @param downloadExpertRules The rules to download files (optional) * @param uploadExpertRules The rules to upload files (optional) * @param request The HTTP request * @return The persisted folder element (fully populated) */ @PUT @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response attachFolder(@PathParam("courseId") Long courseId, @QueryParam("parentNodeId") String parentNodeId, @QueryParam("position") Integer position, @QueryParam("shortTitle") @DefaultValue("undefined") String shortTitle, @QueryParam("longTitle") @DefaultValue("undefined") String longTitle, @QueryParam("objectives") @DefaultValue("undefined") String objectives, @QueryParam("visibilityExpertRules") String visibilityExpertRules, @QueryParam("downloadExpertRules") String downloadExpertRules, @QueryParam("uploadExpertRules") String uploadExpertRules, @Context HttpServletRequest request) { FolderCustomConfig config = new FolderCustomConfig(downloadExpertRules, uploadExpertRules); return attach(courseId, parentNodeId, "bc", position, shortTitle, longTitle, objectives, visibilityExpertRules, null, config, request); } /** * This attaches a Folder Element onto a given course. The element will be * inserted underneath the supplied parentNodeId. * @response.representation.mediaType application/x-www-form-urlencoded * @response.representation.200.qname {http://www.example.com}courseNodeVO * @response.representation.200.mediaType application/xml, application/json * @response.representation.200.doc The folder node metadatas * @response.representation.200.example {@link org.olat.restapi.support.vo.Examples#SAMPLE_COURSENODEVO} * @response.representation.401.doc The roles of the authenticated user are not sufficient * @response.representation.404.doc The course or parentNode not found * @param courseId The course resourceable's id * @param parentNodeId The node's id which will be the parent of this folder * @param position The node's position relative to its sibling nodes (optional) * @param shortTitle The node short title * @param longTitle The node long title * @param objectives The node learning objectives * @param visibilityExpertRules The rules to view the node (optional) * @param downloadExpertRules The rules to download files (optional) * @param uploadExpertRules The rules to upload files (optional) * @param request The HTTP request * @return The persisted folder element (fully populated) */ @POST @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response attachFolderPost(@PathParam("courseId") Long courseId, @FormParam("parentNodeId") String parentNodeId, @FormParam("position") Integer position, @FormParam("shortTitle") @DefaultValue("undefined") String shortTitle, @FormParam("longTitle") @DefaultValue("undefined") String longTitle, @FormParam("objectives") @DefaultValue("undefined") String objectives, @FormParam("visibilityExpertRules") String visibilityExpertRules, @FormParam("downloadExpertRules") String downloadExpertRules, @FormParam("uploadExpertRules") String uploadExpertRules, @Context HttpServletRequest request) { return attachFolder(courseId, parentNodeId, position, shortTitle, longTitle, objectives, visibilityExpertRules, downloadExpertRules, uploadExpertRules, request); } /** * This updates a Folder Element onto a given course. * @response.representation.mediaType application/x-www-form-urlencoded * @response.representation.200.qname {http://www.example.com}courseNodeVO * @response.representation.200.mediaType application/xml, application/json * @response.representation.200.doc The folder node metadatas * @response.representation.200.example {@link org.olat.restapi.support.vo.Examples#SAMPLE_COURSENODEVO} * @response.representation.401.doc The roles of the authenticated user are not sufficient * @response.representation.404.doc The course or parentNode not found * @param courseId The course resourceable's id * @param nodeId The node's id of this folder * @param shortTitle The node short title * @param longTitle The node long title * @param objectives The node learning objectives * @param visibilityExpertRules The rules to view the node (optional) * @param downloadExpertRules The rules to download files (optional) * @param uploadExpertRules The rules to upload files (optional) * @param request The HTTP request * @return The persisted folder element (fully populated) */ @POST @Path("{nodeId}") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) //fxdiff FXOLAT-122: course management public Response updateFolder(@PathParam("courseId") Long courseId, @PathParam("nodeId") String nodeId, @FormParam("shortTitle") @DefaultValue("undefined") String shortTitle, @FormParam("longTitle") @DefaultValue("undefined") String longTitle, @FormParam("objectives") @DefaultValue("undefined") String objectives, @FormParam("visibilityExpertRules") String visibilityExpertRules, @FormParam("downloadExpertRules") String downloadExpertRules, @FormParam("uploadExpertRules") String uploadExpertRules, @Context HttpServletRequest request) { FolderCustomConfig config = new FolderCustomConfig(downloadExpertRules, uploadExpertRules); return update(courseId, nodeId, shortTitle, longTitle, objectives, visibilityExpertRules, null, config, request); } /** * Retrieves metadata of the course node * @response.representation.200.qname {http://www.example.com}folderVO * @response.representation.200.mediaType application/xml, application/json * @response.representation.200.doc The course node metadatas * @response.representation.200.example {@link org.olat.restapi.support.vo.Examples#SAMPLE_FOLDERVO} * @response.representation.401.doc The roles of the authenticated user are not sufficient * @response.representation.404.doc The course or parentNode not found * @param courseId The course resourceable's id * @param nodeId The node's id * @param httpRequest The HTTP request * @return The persisted structure element (fully populated) */ @GET @Path("{nodeId}") @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response getFolder(@PathParam("courseId") Long courseId, @PathParam("nodeId") String nodeId, @Context HttpServletRequest httpRequest) { ICourse course = CoursesWebService.loadCourse(courseId); if(course == null) { return Response.serverError().status(Status.NOT_FOUND).build(); } else if (!CourseWebService.isCourseAccessible(course, false, httpRequest)) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } CourseNode courseNode = course.getRunStructure().getNode(nodeId); if(courseNode == null || !(courseNode instanceof BCCourseNode)) { return Response.serverError().status(Status.NOT_FOUND).build(); } UserRequest ureq = getUserRequest(httpRequest); boolean accessible = (new CourseTreeVisitor(course, ureq.getUserSession().getIdentityEnvironment())).isAccessible(courseNode, new VisibleTreeFilter()); if(accessible) { Set<String> subscribed = new HashSet<String>(); NotificationsManager man = NotificationsManager.getInstance(); List<String> notiTypes = Collections.singletonList("FolderModule"); List<Subscriber> subs = man.getSubscribers(ureq.getIdentity(), notiTypes); for(Subscriber sub:subs) { Long courseKey = sub.getPublisher().getResId(); if(courseId.equals(courseKey)) { subscribed.add(sub.getPublisher().getSubidentifier()); } } FolderVO folderVo = createFolderVO(ureq.getUserSession().getIdentityEnvironment(), course, (BCCourseNode)courseNode, subscribed); return Response.ok(folderVo).build(); } else { return Response.serverError().status(Status.UNAUTHORIZED).build(); } } /** * Return the FX implementation to manage a folder. * @param courseId * @param nodeId * @param request * @return */ @Path("{nodeId}/files") public VFSWebservice getVFSWebService(@PathParam("courseId") Long courseId, @PathParam("nodeId") String nodeId, @Context HttpServletRequest request) { boolean author = isAuthor(request); ICourse course = CoursesWebService.loadCourse(courseId); if(course == null) { throw new WebApplicationException( Response.serverError().status(Status.NOT_FOUND).build()); } else if (!author && !CourseWebService.isCourseAccessible(course, false, request)) { throw new WebApplicationException( Response.serverError().status(Status.UNAUTHORIZED).build()); } CourseNode node; if(author) { node = course.getEditorTreeModel().getCourseNode(nodeId); } else { node = course.getRunStructure().getNode(nodeId); } if(node == null) { throw new WebApplicationException( Response.serverError().status(Status.NOT_FOUND).build()); } else if(!(node instanceof BCCourseNode)) { throw new WebApplicationException(Response.serverError().status(Status.NOT_ACCEPTABLE).build()); } BCCourseNode bcNode = (BCCourseNode)node; UserRequest ureq = getUserRequest(request); VFSContainer container = BCCourseNode.getSecurisedNodeFolderContainer(bcNode, course.getCourseEnvironment(), ureq.getUserSession().getIdentityEnvironment()); return new VFSWebservice(container); } public class FolderCustomConfig implements CustomConfigDelegate { private final String downloadExpertRules; private final String uploadExpertRules; public FolderCustomConfig(String downloadExpertRules, String uploadExpertRules) { this.downloadExpertRules = downloadExpertRules; this.uploadExpertRules = uploadExpertRules; } @Override public boolean isValid() { return true; } @Override public void configure(ICourse course, CourseNode newNode, ModuleConfiguration moduleConfig) { BCCourseNode bcCourseNode = (BCCourseNode)newNode; if(StringHelper.containsNonWhitespace(downloadExpertRules)) { Condition downloadCond = createExpertCondition("downloaders", downloadExpertRules); bcCourseNode.setPreConditionDownloaders(downloadCond); } if(StringHelper.containsNonWhitespace(uploadExpertRules)) { Condition uploadCond = createExpertCondition("uploaders", uploadExpertRules); //fxdiff: RESTAPI bug fix bcCourseNode.setPreConditionUploaders(uploadCond); } } } public static FolderVO createFolderVO(IdentityEnvironment ienv, ICourse course, BCCourseNode bcNode, Collection<String> subscribed) { OlatNamedContainerImpl container = BCCourseNode.getSecurisedNodeFolderContainer(bcNode, course.getCourseEnvironment(), ienv); VFSSecurityCallback secCallback = container.getLocalSecurityCallback(); FolderVO folderVo = new FolderVO(); folderVo.setName(course.getCourseTitle()); folderVo.setDetailsName(bcNode.getShortTitle()); if(subscribed != null && subscribed.contains(bcNode.getIdent())) { folderVo.setSubscribed(true); } else { folderVo.setSubscribed(false); } folderVo.setCourseKey(course.getResourceableId()); folderVo.setCourseNodeId(bcNode.getIdent()); folderVo.setWrite(secCallback.canWrite()); folderVo.setRead(secCallback.canRead()); folderVo.setDelete(secCallback.canDelete()); folderVo.setList(secCallback.canList()); return folderVo; } }