diff --git a/src/main/java/org/olat/course/CourseFactory.java b/src/main/java/org/olat/course/CourseFactory.java index 659cab77c5efa871a0f9828a80e75dfd22065090..9bacdc3668a72d09abedfb7ba1f6616601443202 100644 --- a/src/main/java/org/olat/course/CourseFactory.java +++ b/src/main/java/org/olat/course/CourseFactory.java @@ -587,6 +587,10 @@ public class CourseFactory extends BasicManager { * @param exportedCourseZIPFile */ public static RepositoryEntry deployCourseFromZIP(File exportedCourseZIPFile, int access) { + return deployCourseFromZIP(exportedCourseZIPFile, "administrator", null, access); + } + + public static RepositoryEntry deployCourseFromZIP(File exportedCourseZIPFile, String initialAuthor, String softKey, int access) { // create the course instance OLATResource newCourseResource = olatResourceManager.createOLATResourceInstance(CourseModule.class); ICourse course = CourseFactory.importCourseFromZip(newCourseResource, exportedCourseZIPFile); @@ -600,7 +604,9 @@ public class CourseFactory extends BasicManager { // create the repository entry RepositoryEntry re = repositoryManager.createRepositoryEntryInstance("administrator"); RepositoryEntryImportExport importExport = new RepositoryEntryImportExport(courseExportData); - String softKey = importExport.getSoftkey(); + if(!StringHelper.containsNonWhitespace(softKey)) { + softKey = importExport.getSoftkey(); + } RepositoryEntry existingEntry = repositoryManager.lookupRepositoryEntryBySoftkey(softKey, false); if (existingEntry != null) { Tracing.logInfo("RepositoryEntry with softkey " + softKey + " already exists. Course will not be deployed.", CourseFactory.class); diff --git a/src/main/java/org/olat/restapi/repository/RepositoryEntriesResource.java b/src/main/java/org/olat/restapi/repository/RepositoryEntriesResource.java index bb01debd2e5d00ff2e36ef01d93f604f710b1bd4..d401ae52c2d128f436c31cbc9e59bfd26c01e4ed 100644 --- a/src/main/java/org/olat/restapi/repository/RepositoryEntriesResource.java +++ b/src/main/java/org/olat/restapi/repository/RepositoryEntriesResource.java @@ -58,6 +58,7 @@ import org.olat.basesecurity.BaseSecurityManager; import org.olat.basesecurity.Constants; import org.olat.basesecurity.SecurityGroup; import org.olat.core.id.Identity; +import org.olat.core.id.OLATResourceable; import org.olat.core.id.Roles; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; @@ -316,66 +317,71 @@ public class RepositoryEntriesResource { try { FileResourceManager frm = FileResourceManager.getInstance(); FileResource newResource = frm.addFileResource(fResource, fResource.getName()); + return importResource(identity, newResource, resourcename, displayname, softkey); + } catch(Exception e) { + log.error("Fail to import a resource", e); + throw new WebApplicationException(e); + } + } + + public static RepositoryEntry importResource(Identity identity, OLATResourceable newResource, String resourcename, String displayname, + String softkey) { - RepositoryEntry addedEntry = RepositoryManager.getInstance().createRepositoryEntryInstance(identity.getName()); - addedEntry.setCanDownload(false); - addedEntry.setCanLaunch(true); - if(StringHelper.containsNonWhitespace(resourcename)) { - addedEntry.setResourcename(resourcename); - } - if(StringHelper.containsNonWhitespace(displayname)) { - addedEntry.setDisplayname(displayname); - } - if(StringHelper.containsNonWhitespace(softkey)) { - addedEntry.setSoftkey(softkey); - } - // Do set access for owner at the end, because unfinished course should be - // invisible - // addedEntry.setAccess(RepositoryEntry.ACC_OWNERS); - addedEntry.setAccess(0);// Access for nobody + RepositoryEntry addedEntry = RepositoryManager.getInstance().createRepositoryEntryInstance(identity.getName()); + addedEntry.setCanDownload(false); + addedEntry.setCanLaunch(true); + if(StringHelper.containsNonWhitespace(resourcename)) { + addedEntry.setResourcename(resourcename); + } + if(StringHelper.containsNonWhitespace(displayname)) { + addedEntry.setDisplayname(displayname); + } + if(StringHelper.containsNonWhitespace(softkey)) { + addedEntry.setSoftkey(softkey); + } + // Do set access for owner at the end, because unfinished course should be + // invisible + // addedEntry.setAccess(RepositoryEntry.ACC_OWNERS); + addedEntry.setAccess(0);// Access for nobody - // Set the resource on the repository entry and save the entry. - RepositoryManager rm = RepositoryManager.getInstance(); - OLATResource ores = OLATResourceManager.getInstance().findOrPersistResourceable(newResource); - addedEntry.setOlatResource(ores); + // Set the resource on the repository entry and save the entry. + RepositoryManager rm = RepositoryManager.getInstance(); + OLATResource ores = OLATResourceManager.getInstance().findOrPersistResourceable(newResource); + addedEntry.setOlatResource(ores); - BaseSecurity securityManager = BaseSecurityManager.getInstance(); - // create security group - SecurityGroup newGroup = securityManager.createAndPersistSecurityGroup(); - // member of this group may modify member's membership - securityManager.createAndPersistPolicy(newGroup, Constants.PERMISSION_ACCESS, newGroup); - // members of this group are always authors also - securityManager.createAndPersistPolicy(newGroup, Constants.PERMISSION_HASROLE, Constants.ORESOURCE_AUTHOR); + BaseSecurity securityManager = BaseSecurityManager.getInstance(); + // create security group + SecurityGroup newGroup = securityManager.createAndPersistSecurityGroup(); + // member of this group may modify member's membership + securityManager.createAndPersistPolicy(newGroup, Constants.PERMISSION_ACCESS, newGroup); + // members of this group are always authors also + securityManager.createAndPersistPolicy(newGroup, Constants.PERMISSION_HASROLE, Constants.ORESOURCE_AUTHOR); - securityManager.addIdentityToSecurityGroup(identity, newGroup); - addedEntry.setOwnerGroup(newGroup); - - //fxdiff VCRP-1,2: access control of resources - // security group for tutors / coaches - SecurityGroup tutorGroup = securityManager.createAndPersistSecurityGroup(); - // member of this group may modify member's membership - securityManager.createAndPersistPolicy(tutorGroup, Constants.PERMISSION_ACCESS, addedEntry.getOlatResource()); - // members of this group are always tutors also - securityManager.createAndPersistPolicy(tutorGroup, Constants.PERMISSION_HASROLE, Constants.ORESOURCE_TUTOR); - addedEntry.setTutorGroup(tutorGroup); - - // security group for participants - SecurityGroup participantGroup = securityManager.createAndPersistSecurityGroup(); - // member of this group may modify member's membership - securityManager.createAndPersistPolicy(participantGroup, Constants.PERMISSION_ACCESS, addedEntry.getOlatResource()); - // members of this group are always participants also - securityManager.createAndPersistPolicy(participantGroup, Constants.PERMISSION_HASROLE, Constants.ORESOURCE_PARTICIPANT); - addedEntry.setParticipantGroup(participantGroup); - - // Do set access for owner at the end, because unfinished course should be - // invisible - addedEntry.setAccess(RepositoryEntry.ACC_OWNERS); - rm.saveRepositoryEntry(addedEntry); - return addedEntry; - } catch (Exception e) { - log.error("Fail to import a resource", e); - throw new WebApplicationException(e); - } + securityManager.addIdentityToSecurityGroup(identity, newGroup); + addedEntry.setOwnerGroup(newGroup); + + //fxdiff VCRP-1,2: access control of resources + // security group for tutors / coaches + SecurityGroup tutorGroup = securityManager.createAndPersistSecurityGroup(); + // member of this group may modify member's membership + securityManager.createAndPersistPolicy(tutorGroup, Constants.PERMISSION_ACCESS, addedEntry.getOlatResource()); + // members of this group are always tutors also + securityManager.createAndPersistPolicy(tutorGroup, Constants.PERMISSION_HASROLE, Constants.ORESOURCE_TUTOR); + addedEntry.setTutorGroup(tutorGroup); + + // security group for participants + SecurityGroup participantGroup = securityManager.createAndPersistSecurityGroup(); + // member of this group may modify member's membership + securityManager.createAndPersistPolicy(participantGroup, Constants.PERMISSION_ACCESS, addedEntry.getOlatResource()); + // members of this group are always participants also + securityManager.createAndPersistPolicy(participantGroup, Constants.PERMISSION_HASROLE, Constants.ORESOURCE_PARTICIPANT); + addedEntry.setParticipantGroup(participantGroup); + + // Do set access for owner at the end, because unfinished course should be + // invisible + addedEntry.setAccess(RepositoryEntry.ACC_OWNERS); + rm.saveRepositoryEntry(addedEntry); + return addedEntry; } private File getTmpFile(String suffix) { diff --git a/src/main/java/org/olat/restapi/repository/course/CoursesWebService.java b/src/main/java/org/olat/restapi/repository/course/CoursesWebService.java index 013568f8b4d90509ed129c978953f582dd616081..d1c5d10d63d3f746d191dc957674d97786e021dd 100644 --- a/src/main/java/org/olat/restapi/repository/course/CoursesWebService.java +++ b/src/main/java/org/olat/restapi/repository/course/CoursesWebService.java @@ -24,12 +24,16 @@ import static org.olat.restapi.security.RestSecurityHelper.getRoles; import static org.olat.restapi.security.RestSecurityHelper.getUserRequest; import static org.olat.restapi.security.RestSecurityHelper.isAuthor; +import java.io.File; import java.util.ArrayList; import java.util.List; 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.Produces; @@ -52,8 +56,11 @@ import org.olat.core.id.OLATResourceable; import org.olat.core.id.Roles; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; +import org.olat.core.util.CodeHelper; +import org.olat.core.util.FileUtils; import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; +import org.olat.core.util.WebappHelper; import org.olat.core.util.coordinate.LockResult; import org.olat.core.util.resource.OresHelper; import org.olat.course.CourseFactory; @@ -70,7 +77,9 @@ import org.olat.repository.handlers.RepositoryHandler; import org.olat.repository.handlers.RepositoryHandlerFactory; import org.olat.resource.OLATResource; import org.olat.resource.OLATResourceManager; +import org.olat.restapi.security.RestSecurityHelper; import org.olat.restapi.support.MediaTypeVariants; +import org.olat.restapi.support.MultipartReader; import org.olat.restapi.support.ObjectFactory; import org.olat.restapi.support.vo.CourseConfigVO; import org.olat.restapi.support.vo.CourseVO; @@ -203,6 +212,57 @@ public class CoursesWebService { return Response.ok(vo).build(); } + /** + * + * + * + * @param request + * @param access + * @return + */ + @POST + @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) + @Consumes({MediaType.MULTIPART_FORM_DATA}) + public Response importCourse(@Context HttpServletRequest request) { + if(!isAuthor(request)) { + return Response.serverError().status(Status.UNAUTHORIZED).build(); + } + + Identity identity = RestSecurityHelper.getUserRequest(request).getIdentity(); + + File tmpFile = null; + try { + MultipartReader partsReader = new MultipartReader(request); + tmpFile = partsReader.getFile(); + long length = tmpFile.length(); + if(length > 0) { + Long accessRaw = partsReader.getLongValue("access"); + int access = accessRaw != null ? accessRaw.intValue() : RepositoryEntry.ACC_OWNERS; + String softKey = partsReader.getValue("softkey"); + + ICourse course = importCourse(identity, tmpFile, softKey, access); + CourseVO vo = ObjectFactory.get(course); + return Response.ok(vo).build(); + } + return Response.serverError().status(Status.NO_CONTENT).build(); + } catch (Exception e) { + log.error("Error while importing a file",e); + } finally { + if(tmpFile != null && tmpFile.exists()) { + tmpFile.delete(); + } + } + + CourseVO vo = null; + return Response.ok(vo).build(); + } + + public static ICourse importCourse(Identity identity, File fCourseImportZIP, String softKey, int access) { + RepositoryEntry re = CourseFactory.deployCourseFromZIP(fCourseImportZIP, identity.getName(), softKey, access); + ICourse course = CourseFactory.loadCourse(re.getOlatResource()); + return course; + } + public static ICourse copyCourse(Long copyFrom, UserRequest ureq, String name, String longTitle, CourseConfigVO courseConfigVO) { String shortTitle = name; //String learningObjectives = name + " (Example of creating a new course)"; diff --git a/src/main/java/org/olat/restapi/support/MultipartReader.java b/src/main/java/org/olat/restapi/support/MultipartReader.java new file mode 100644 index 0000000000000000000000000000000000000000..ec2b1b994310bae99acffddc0ac82a4917011d71 --- /dev/null +++ b/src/main/java/org/olat/restapi/support/MultipartReader.java @@ -0,0 +1,143 @@ +/** + * <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> + * 12.10.2011 by frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.restapi.support; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.fileupload.FileItemIterator; +import org.apache.commons.fileupload.FileItemStream; +import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.apache.commons.fileupload.util.Streams; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; + +/** + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +public class MultipartReader { + + private static final OLog log = Tracing.createLoggerFor(MultipartReader.class); + + private String filename; + private String contentType; + private File file; + private Map<String, String> fields = new HashMap<String, String>(); + + public MultipartReader(HttpServletRequest request) { + long uploadLimit = -1l; + apache(request, uploadLimit); + } + + private final void apache(HttpServletRequest request, long uploadLimit) { + ServletFileUpload uploadParser = new ServletFileUpload(); + uploadParser.setSizeMax((uploadLimit * 1024l) + 512000l); + // Parse the request + try { + FileItemIterator iter = uploadParser.getItemIterator(request); + while (iter.hasNext()) { + FileItemStream item = iter.next(); + String itemName = item.getFieldName(); + InputStream itemStream = item.openStream(); + if (item.isFormField()) { + String value = Streams.asString(itemStream, "UTF-8"); + fields.put(itemName, value); + } else { + // File item, store it to temp location + filename = item.getName(); + contentType = item.getContentType(); + file = new File(System.getProperty("java.io.tmpdir"), "upload-" + UUID.randomUUID().toString().replace("-", "")); + try { + save(itemStream, file); + } catch (Exception e) { + log.error("", e); + } + } + } + } catch (Exception e) { + log.error("", e); + } + } + + public String getFilename() { + return filename; + } + + public String getContentType() { + return contentType; + } + + public String getText() { + return fields.get("text"); + } + + public String getValue(String key) { + String value = fields.get(key); + return value; + } + + public Long getLongValue(String key) { + String value = fields.get(key); + if (value == null) { return null; } + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + return null; + } + } + + public File getFile() { + return file; + } + + private void save(InputStream source, File targetFile) + throws IOException { + InputStream in = new BufferedInputStream(source); + OutputStream out = new FileOutputStream(targetFile); + + byte[] buffer = new byte[4096]; + + int c; + while ((c = in.read(buffer, 0, buffer.length)) != -1) { + out.write(buffer, 0, c); + } + + out.flush(); + out.close(); + in.close(); + } + + public void close() { + if (file != null) { + file.delete(); + } + fields.clear(); + } + +} diff --git a/src/test/java/org/olat/restapi/CoursesTest.java b/src/test/java/org/olat/restapi/CoursesTest.java index 61abfc7b64983a93d459faf440d8c9be3de0d547..f727a6b62506fde6a3e57308f284469c21060b75 100644 --- a/src/test/java/org/olat/restapi/CoursesTest.java +++ b/src/test/java/org/olat/restapi/CoursesTest.java @@ -31,18 +31,26 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; import java.util.List; +import java.util.UUID; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.UriBuilder; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntity; +import org.apache.http.entity.mime.content.FileBody; +import org.apache.http.entity.mime.content.StringBody; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.type.TypeReference; import org.junit.After; @@ -166,6 +174,45 @@ public class CoursesTest extends OlatJerseyTestCase { assertNotNull(re.getOwnerGroup()); } + @Test + public void testImportCourse() throws IOException, URISyntaxException { + URL cpUrl = CoursesTest.class.getResource("Very_small_course.zip"); + assertNotNull(cpUrl); + File cp = new File(cpUrl.toURI()); + + assertTrue(conn.login("administrator", "openolat")); + + URI request = UriBuilder.fromUri(getContextURI()).path("repo/courses").build(); + HttpPost method = conn.createPost(request, MediaType.APPLICATION_JSON, true); + MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); + entity.addPart("file", new FileBody(cp)); + entity.addPart("filename", new StringBody("Very_small_course.zip")); + entity.addPart("resourcename", new StringBody("Very small course")); + entity.addPart("displayname", new StringBody("Very small course")); + entity.addPart("access", new StringBody("3")); + String softKey = UUID.randomUUID().toString().replace("-", "").substring(0, 30); + entity.addPart("softkey", new StringBody(softKey)); + method.setEntity(entity); + + HttpResponse response = conn.execute(method); + assertTrue(response.getStatusLine().getStatusCode() == 200 || response.getStatusLine().getStatusCode() == 201); + + InputStream body = response.getEntity().getContent(); + + CourseVO vo = parse(body, CourseVO.class); + assertNotNull(vo); + assertNotNull(vo.getRepoEntryKey()); + assertNotNull(vo.getKey()); + + Long repoKey = vo.getRepoEntryKey(); + RepositoryEntry re = RepositoryManager.getInstance().lookupRepositoryEntry(repoKey); + assertNotNull(re); + assertNotNull(re.getOwnerGroup()); + assertNotNull(re.getOlatResource()); + assertEquals("Very small course", re.getDisplayname()); + assertEquals(softKey, re.getSoftkey()); + } + @Test public void testGetCourseInfos() throws IOException, URISyntaxException { boolean loggedIN = conn.login("administrator", "openolat"); diff --git a/src/test/java/org/olat/restapi/Very_small_course.zip b/src/test/java/org/olat/restapi/Very_small_course.zip new file mode 100644 index 0000000000000000000000000000000000000000..8e6d123ddf009e18441dcb219c551e6841e86135 Binary files /dev/null and b/src/test/java/org/olat/restapi/Very_small_course.zip differ