/**
* OLAT - Online Learning and Training
* http://www.olat.org
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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.
*
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),
* University of Zurich, Switzerland.
*
*/
package org.olat.restapi.repository;
import static org.olat.restapi.security.RestSecurityHelper.getIdentity;
import java.io.File;
import java.io.InputStream;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.olat.core.commons.modules.bc.FolderConfig;
import org.olat.core.gui.media.MediaResource;
import org.olat.core.id.Identity;
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.StringHelper;
import org.olat.core.util.WebappHelper;
import org.olat.core.util.coordinate.LockResult;
import org.olat.fileresource.FileResourceManager;
import org.olat.fileresource.types.ImsCPFileResource;
import org.olat.repository.RepositoryEntry;
import org.olat.repository.RepositoryManager;
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.ObjectFactory;
import org.olat.restapi.support.vo.RepositoryEntryVO;
/**
* Description:
* Repository entry resource
*
*
* Initial Date: 19.05.2009
* @author patrickb, srosse, stephane.rosse@frentix.com
*/
public class RepositoryEntryResource {
private static final OLog log = Tracing.createLoggerFor(RepositoryEntryResource.class);
public static CacheControl cc = new CacheControl();
static {
cc.setMaxAge(-1);
}
/**
* get a resource in the repository
* @response.representation.200.qname {http://www.example.com}repositoryEntryVO
* @response.representation.200.mediaType application/xml, application/json
* @response.representation.200.doc Get the repository resource
* @response.representation.200.example {@link org.olat.restapi.support.vo.Examples#SAMPLE_REPOENTRYVO}
* @response.representation.404.doc The repository entry not found
* @param repoEntryKey The key or soft key of the repository entry
* @param request The REST request
* @return
*/
@GET
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response getById(@PathParam("repoEntryKey")String repoEntryKey, @Context Request request){
RepositoryEntry re = lookupRepositoryEntry(repoEntryKey);
if(re == null) {
return Response.serverError().status(Status.NOT_FOUND).build();
}
Date lastModified = re.getLastModified();
Response.ResponseBuilder response;
if(lastModified == null) {
EntityTag eTag = ObjectFactory.computeEtag(re);
response = request.evaluatePreconditions(eTag);
if(response == null) {
RepositoryEntryVO vo = ObjectFactory.get(re);
response = Response.ok(vo).tag(eTag).lastModified(lastModified);
}
} else {
EntityTag eTag = ObjectFactory.computeEtag(re);
response = request.evaluatePreconditions(lastModified, eTag);
if(response == null) {
RepositoryEntryVO vo = ObjectFactory.get(re);
response = Response.ok(vo).tag(eTag).lastModified(lastModified);
}
}
return response.build();
}
/**
* Download the export zip file of a repository entry.
* @response.representation.mediaType multipart/form-data
* @response.representation.doc Download the resource file
* @response.representation.200.mediaType application/zip
* @response.representation.200.doc Download the repository entry as export zip file
* @response.representation.200.example {@link org.olat.restapi.support.vo.Examples#SAMPLE_REPOENTRYVO}
* @response.representation.401.doc The roles of the authenticated user are not sufficient
* @response.representation.404.doc The resource could not found
* @response.representation.406.doc Download of this resource is not possible
* @response.representation.409.doc The resource is locked
* @param repoEntryKey
* @param request The HTTP request
* @return
*/
@GET
@Path("file")
@Produces({"application/zip", MediaType.APPLICATION_OCTET_STREAM})
public Response getRepoFileById(@PathParam("repoEntryKey")String repoEntryKey, @Context HttpServletRequest request) {
RepositoryEntry re = lookupRepositoryEntry(repoEntryKey);
if(re == null) return Response.serverError().status(Status.NOT_FOUND).build();
RepositoryHandler typeToDownload = RepositoryHandlerFactory.getInstance().getRepositoryHandler(re);
if(typeToDownload == null) return Response.serverError().status(Status.NOT_FOUND).build();
OLATResource ores = OLATResourceManager.getInstance().findResourceable(re.getOlatResource());
if(ores == null) return Response.serverError().status(Status.NOT_FOUND).build();
Identity identity = getIdentity(request);
boolean isAuthor = RestSecurityHelper.isAuthor(request);
boolean isOwner = RepositoryManager.getInstance().isOwnerOfRepositoryEntry(identity, re);
if(!(isAuthor | isOwner)) return Response.serverError().status(Status.UNAUTHORIZED).build();
boolean canDownload = re.getCanDownload() && typeToDownload.supportsDownload(re);
if(!canDownload) return Response.serverError().status(Status.NOT_ACCEPTABLE).build();
boolean isAlreadyLocked = typeToDownload.isLocked(ores);
LockResult lockResult = null;
try {
lockResult = typeToDownload.acquireLock(ores, identity);
if(lockResult == null || (lockResult != null && lockResult.isSuccess() && !isAlreadyLocked)) {
MediaResource mr = typeToDownload.getAsMediaResource(ores);
if(mr != null) {
RepositoryManager.getInstance().incrementDownloadCounter(re);
return Response.ok(mr.getInputStream()).cacheControl(cc).build(); // success
} else return Response.serverError().status(Status.NO_CONTENT).build();
} else return Response.serverError().status(Status.CONFLICT).build();
}
finally {
if((lockResult != null && lockResult.isSuccess() && !isAlreadyLocked)) typeToDownload.releaseLock(lockResult);
}
}
/**
* Replace a resource in the repository and update its display name. The implementation is
* limited to CP.
* @response.representation.mediaType multipart/form-data
* @response.representation.doc Import the resource file
* @response.representation.200.qname {http://www.example.com}repositoryEntryVO
* @response.representation.200.mediaType application/xml, application/json
* @response.representation.200.doc Replace the resource and return the updated repository entry
* @response.representation.200.example {@link org.olat.restapi.support.vo.Examples#SAMPLE_REPOENTRYVO}
* @response.representation.401.doc The roles of the authenticated user are not sufficient
* @param repoEntryKey The key or soft key of the repository entry
* @param filename The name of the file
* @param file The file input stream
* @param displayname The display name
* @param request The HTTP request
* @return
*/
@POST
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Consumes({MediaType.MULTIPART_FORM_DATA})
public Response replaceResource(@PathParam("repoEntryKey") String repoEntryKey,
@FormParam("filename") String filename, @FormParam("file") InputStream file,
@FormParam("displayname") String displayname, @Context HttpServletRequest request) {
if(!RestSecurityHelper.isAuthor(request)) {
return Response.serverError().status(Status.UNAUTHORIZED).build();
}
File tmpFile = null;
long length = 0;
try {
final RepositoryEntry re = lookupRepositoryEntry(repoEntryKey);
if(re == null) {
return Response.serverError().status(Status.NOT_FOUND).build();
}
Identity identity = RestSecurityHelper.getUserRequest(request).getIdentity();
String tmpName = StringHelper.containsNonWhitespace(filename) ? filename : "import.zip";
tmpFile = getTmpFile(tmpName);
FileUtils.save(file, tmpFile);
FileUtils.closeSafely(file);
length = tmpFile.length();
if(length == 0) {
return Response.serverError().status(Status.NO_CONTENT).build();
}
final RepositoryEntry replacedRe = replaceFileResource(identity, re, tmpFile);
if(replacedRe == null) {
return Response.serverError().status(Status.NOT_FOUND).build();
} else if (StringHelper.containsNonWhitespace(displayname)) {
replacedRe.setDisplayname(displayname);
RepositoryManager.getInstance().updateRepositoryEntry(replacedRe);
}
RepositoryEntryVO vo = ObjectFactory.get(replacedRe);
return Response.ok(vo).build();
} catch (Exception e) {
log.error("Error while importing a file",e);
} finally {
if(tmpFile != null && tmpFile.exists()) {
tmpFile.delete();
}
}
return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
}
private RepositoryEntry replaceFileResource(Identity identity, RepositoryEntry re, File fResource) {
if(re == null) throw new NullPointerException("RepositoryEntry cannot be null");
FileResourceManager frm = FileResourceManager.getInstance();
File currentResource = frm.getFileResource(re.getOlatResource());
if(currentResource == null || !currentResource.exists()) {
log.debug("Current resource file doesn't exist");
return null;
}
String typeName = re.getOlatResource().getResourceableTypeName();
if(typeName.equals(ImsCPFileResource.TYPE_NAME)) {
if(currentResource.delete()) {
FileUtils.copyFileToFile(fResource, currentResource, false);
String repositoryHome = FolderConfig.getCanonicalRepositoryHome();
String relUnzipDir = frm.getUnzippedDirRel(re.getOlatResource());
File unzipDir = new File(repositoryHome, relUnzipDir);
if(unzipDir != null && unzipDir.exists()) {
FileUtils.deleteDirsAndFiles(unzipDir, true, true);
}
frm.unzipFileResource(re.getOlatResource());
}
log.audit("Resource: " + re.getOlatResource() + " replaced by " + identity.getName());
return re;
}
log.debug("Cannot replace a resource of the type: " + typeName);
return null;
}
private File getTmpFile(String suffix) {
suffix = (suffix == null ? "" : suffix);
File tmpFile = new File(WebappHelper.getUserDataRoot() + "/tmp/", CodeHelper.getGlobalForeverUniqueID() + "_" + suffix);
FileUtils.createEmptyFile(tmpFile);
return tmpFile;
}
private RepositoryEntry lookupRepositoryEntry(String key) {
Long repoEntryKey = longId(key);
RepositoryEntry re = null;
if(repoEntryKey != null) {//looks like a primary key
re = RepositoryManager.getInstance().lookupRepositoryEntry(repoEntryKey);
}
if(re == null) {// perhaps a soft key
re = RepositoryManager.getInstance().lookupRepositoryEntryBySoftkey(key, false);
}
return re;
}
private Long longId(String key) {
try {
for(int i=key.length(); i-->0; ) {
if(!Character.isDigit(key.charAt(i))) {
return null;
}
}
return new Long(key);
} catch(NumberFormatException ex) {
return null;
}
}
}