org.olat.core.util.vfs.restapi.VFSWebservice.java Source code

Java tutorial

Introduction

Here is the source code for org.olat.core.util.vfs.restapi.VFSWebservice.java

Source

/**
 * <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.core.util.vfs.restapi;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.Normalizer;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
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.core.CacheControl;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.PathSegment;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.StringHelper;
import org.olat.core.util.WebappHelper;
import org.olat.core.util.vfs.VFSConstants;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.vfs.VFSStatus;
import org.olat.restapi.support.MultipartReader;
import org.olat.restapi.support.vo.File64VO;
import org.olat.restapi.support.vo.FileMetadataVO;
import org.olat.restapi.support.vo.FileVO;

public class VFSWebservice {

    private static final String VERSION = "1.0";

    private static final OLog log = Tracing.createLoggerFor(VFSWebservice.class);

    public static CacheControl cc = new CacheControl();
    static {
        cc.setMaxAge(-1);
    }

    private final VFSContainer container;

    public VFSWebservice(VFSContainer container) {
        this.container = container;
    }

    /**
     * Retrieves the version of the Folder Course Node Web Service.
     * @response.representation.200.mediaType text/plain
     * @response.representation.200.doc The version of this specific Web Service
     * @response.representation.200.example 1.0
     * @return
     */
    @GET
    @Path("version")
    @Produces(MediaType.TEXT_PLAIN)
    public Response getVersion() {
        return Response.ok(VERSION).build();
    }

    /**
     * This retrieves the files or a specific file in the root folder
     * @response.representation.200.doc The list of files
     * @response.representation.200.qname {http://www.example.com}linkVOes
     * @param uriInfo The uri infos
     * @param request The REST request
     * @return 
     */
    @GET
    @Produces({ "*/*", MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_HTML,
            MediaType.APPLICATION_OCTET_STREAM })
    public Response listFiles(@Context UriInfo uriInfo, @Context Request request) {
        return get(Collections.<PathSegment>emptyList(), uriInfo, request);
    }

    /**
     * This retrieves the files or a specific file in a folder
     * @response.representation.200.doc The list of files or the file
     * @response.representation.200.qname {http://www.example.com}linkVOes
     * @param path the path to the folder
     * @param uriInfo The uri infos
     * @param request The REST request
     * @return 
     */
    @GET
    @Path("{path:.*}")
    @Produces({ "*/*", MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_HTML,
            MediaType.APPLICATION_OCTET_STREAM })
    public Response listFiles(@PathParam("path") List<PathSegment> path, @Context UriInfo uriInfo,
            @Context Request request) {
        return get(path, uriInfo, request);
    }

    /**
     * This retrieves some metadata of a specific file in a folder
     * The metadata are: filename, size, date of last modification, MIME-type and file href for downloading via REST 
     * @response.representation.200.doc The metadata of the file
     * @response.representation.200.qname {http://www.example.com}fileMetadataVO
     * @response.representation.200.example {@link org.olat.restapi.support.vo.Examples#SAMPLE_FILE_METADATA}
     * @param path the path to the file
     * @param uriInfo The uri infos
     * @return 
     */
    @GET
    @Path("metadata/{path:.*}")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Response getFileMetadata(@PathParam("path") List<PathSegment> path, @Context UriInfo uriInfo) {
        return getFMetadata(path, uriInfo);
    }

    /**
     * Upload a file to the root folder or create a new folder. One of the two sets
     * of parameters must be set: foldername to create
     * @response.representation.200.doc The link to the created file
     * @response.representation.200.qname {http://www.example.com}linkVO
     * @param foldername The name of the new folder (optional)
     * @param filename The name of the file (optional)
     * @param file The content of the file (optional)
     * @param uriInfo The uri infos
     * @return The link to the created file
     */
    @POST
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Response postFileToRoot(@Context UriInfo uriInfo, @Context HttpServletRequest request) {
        return addFileToFolder(uriInfo, Collections.<PathSegment>emptyList(), request);
    }

    /**
     * Upload a file to the root folder or create a new folder. One of the two sets
     * of parameters must be set: foldername to create
     * @response.representation.200.doc The link to the created file
     * @response.representation.200.qname {http://www.example.com}linkVO
     * @param foldername The name of the new folder (optional)
     * @param filename The name of the file (optional)
     * @param file The content of the file (encoded with Base64)
     * @param uriInfo The uri infos
     * @return The link to the created file
     */
    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Response postFile64ToRoot(@FormParam("foldername") String foldername,
            @FormParam("filename") String filename, @FormParam("file") String file, @Context UriInfo uriInfo) {
        byte[] fileAsBytes = Base64.decodeBase64(file);
        InputStream in = new ByteArrayInputStream(fileAsBytes);
        return putFile(foldername, filename, in, uriInfo, Collections.<PathSegment>emptyList());
    }

    /**
     * Upload a file to the specified folder or create a new folder
     * @response.representation.200.doc The link to the created file
     * @response.representation.200.qname {http://www.example.com}linkVO
     * @param foldername The name of the new folder (optional)
     * @param filename The name of the file (optional)
     * @param file The content of the file (optional)
     * @param uriInfo The uri infos
     * @param path The path to the folder
     * @return The link to the created file
     */
    @POST
    @Path("{path:.*}")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces({ "*/*", MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Response postFileToFolder(@Context UriInfo uriInfo, @PathParam("path") List<PathSegment> path,
            @Context HttpServletRequest request) {
        return addFileToFolder(uriInfo, path, request);
    }

    /**
     * Upload a file to the specified folder or create a new folder
     * @response.representation.200.doc The link to the created file
     * @response.representation.200.qname {http://www.example.com}linkVO
     * @param foldername The name of the new folder (optional)
     * @param filename The name of the file (optional)
     * @param file The content of the file (encoded with Base64)
     * @param uriInfo The uri infos
     * @param path The path to the folder
     * @return The link to the created file
     */
    @POST
    @Path("{path:.*}")
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Produces({ "*/*", MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Response postFile64ToFolder(@FormParam("foldername") String foldername,
            @FormParam("filename") String filename, @FormParam("file") String file, @Context UriInfo uriInfo,
            @PathParam("path") List<PathSegment> path) {
        byte[] fileAsBytes = Base64.decodeBase64(file);
        InputStream in = new ByteArrayInputStream(fileAsBytes);
        return putFile(foldername, filename, in, uriInfo, path);
    }

    /**
     * Upload a file to the root folder or create a new folder
     * @response.representation.200.doc The link to the created file
     * @response.representation.200.qname {http://www.example.com}linkVO
     * @param foldername The name of the new folder (optional)
     * @param filename The name of the file (optional)
     * @param file The content of the file (optional)
     * @param uriInfo The uri infos
     * @return The link to the created file
     */
    @PUT
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Response putFileToRoot(@Context UriInfo uriInfo, @Context HttpServletRequest request) {
        return addFileToFolder(uriInfo, Collections.<PathSegment>emptyList(), request);
    }

    /**
     * Upload a file to the root folder or create a new folder
     * @response.representation.200.doc The link to the created file
     * @response.representation.200.qname {http://www.example.com}linkVO
     * @param foldername The name of the new folder (optional)
     * @param filename The name of the file (optional)
     * @param file The content of the file (optional)
     * @param uriInfo The uri infos
     * @return The link to the created file
     */
    @PUT
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Response putFile64VOToRoot(File64VO file, @Context UriInfo uriInfo) {
        byte[] fileAsBytes = Base64.decodeBase64(file.getFile());
        InputStream in = new ByteArrayInputStream(fileAsBytes);
        return putFile(null, file.getFilename(), in, uriInfo, Collections.<PathSegment>emptyList());
    }

    /**
     * Upload a file to the specified folder or create a new folder
     * @response.representation.200.doc The link to the created file
     * @response.representation.200.qname {http://www.example.com}linkVO
     * @param foldername The name of the new folder (optional)
     * @param filename The name of the file (optional)
     * @param file The content of the file (optional)
     * @param uriInfo The uri infos
     * @param path The path to the folder
     * @return The link to the created file
     */
    @PUT
    @Path("{path:.*}")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces({ "*/*", MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Response putFileToFolder(@Context UriInfo uriInfo, @PathParam("path") List<PathSegment> path,
            @Context HttpServletRequest request) {
        return addFileToFolder(uriInfo, path, request);
    }

    private Response addFileToFolder(UriInfo uriInfo, List<PathSegment> path, HttpServletRequest request) {
        InputStream in = null;
        MultipartReader partsReader = null;
        try {
            partsReader = new MultipartReader(request);
            File tmpFile = partsReader.getFile();
            if (tmpFile != null) {
                in = new FileInputStream(tmpFile);
            }
            String filename = partsReader.getValue("filename");
            String foldername = partsReader.getValue("foldername");
            return putFile(foldername, filename, in, uriInfo, path);
        } catch (FileNotFoundException e) {
            log.error("", e);
            return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
        } finally {
            MultipartReader.closeQuietly(partsReader);
            IOUtils.closeQuietly(in);
        }
    }

    /**
     * Upload a file to the specified folder or create a new folder
     * @response.representation.200.doc The link to the created file
     * @response.representation.200.qname {http://www.example.com}linkVO
     * @param foldername The name of the new folder (optional)
     * @param filename The name of the file (optional)
     * @param file The content of the file (optional)
     * @param uriInfo The uri infos
     * @param path The path to the folder
     * @return The link to the created file
     */
    @PUT
    @Path("{path:.*}")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Response putFile64ToFolder(File64VO file, @Context UriInfo uriInfo,
            @PathParam("path") List<PathSegment> path) {
        byte[] fileAsBytes = Base64.decodeBase64(file.getFile());
        InputStream in = new ByteArrayInputStream(fileAsBytes);
        return putFile(null, file.getFilename(), in, uriInfo, path);
    }

    /**
     * Create folders
     * @response.representation.200.doc The link to the created file
     * @response.representation.200.qname {http://www.example.com}linkVO
     * @param uriInfo The uri infos
     * @param path The path to the folder
     * @return The link to the created file
     */
    @PUT
    @Path("{path:.*}")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Response putFolders(@Context UriInfo uriInfo, @PathParam("path") List<PathSegment> path) {
        return createFolders(uriInfo, path);
    }

    @DELETE
    @Path("{path:.*}")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Response deleteItem(@PathParam("path") List<PathSegment> path) {
        if (container.getLocalSecurityCallback() != null && !container.getLocalSecurityCallback().canDelete()) {
            return Response.serverError().status(Status.UNAUTHORIZED).build();
        }

        VFSItem item = resolveFile(path);
        if (item == null) {
            return Response.serverError().status(Status.NOT_FOUND).build();
        }

        VFSStatus status = item.delete();
        if (status == VFSConstants.YES) {
            return Response.ok().build();
        }
        //need something better
        return Response.ok().build();
    }

    protected Response createFolders(UriInfo uriInfo, List<PathSegment> path) {
        if (container.getLocalSecurityCallback() != null && !container.getLocalSecurityCallback().canWrite()) {
            return Response.serverError().status(Status.UNAUTHORIZED).build();
        }

        VFSContainer directory = resolveContainer(path, true);
        if (directory == null) {
            return Response.serverError().status(Status.NOT_FOUND).build();
        }
        return Response.ok(createFileVO(directory, uriInfo)).build();
    }

    protected Response putFile(String foldername, String filename, InputStream file, UriInfo uriInfo,
            List<PathSegment> path) {
        if (container.getLocalSecurityCallback() != null && !container.getLocalSecurityCallback().canWrite()) {
            return Response.serverError().status(Status.UNAUTHORIZED).build();
        }

        VFSContainer directory = resolveContainer(path, true);
        if (directory == null) {
            return Response.serverError().status(Status.NOT_FOUND).build();
        }

        if (filename == null && file == null) {
            //only create folders
            if (foldername != null) {
                VFSItem newFolder = directory.resolve(foldername);
                if (newFolder instanceof VFSContainer) {
                    return Response.ok(createFileVO(newFolder, uriInfo)).build();
                } else if (newFolder == null) {
                    newFolder = directory.createChildContainer(foldername);
                    if (newFolder != null) {
                        return Response.ok(createFileVO(newFolder, uriInfo)).build();
                    }
                }
            }
            return Response.serverError().status(Status.CONFLICT).build();
        }

        VFSItem newItem = directory.resolve(filename);
        VFSLeaf newFile;
        if (newItem == null) {
            newFile = directory.createChildLeaf(filename);
        } else if (newItem instanceof VFSLeaf) {
            newFile = (VFSLeaf) newItem;
        } else {
            return Response.serverError().status(Status.FORBIDDEN).build();
        }

        try {
            OutputStream out = newFile.getOutputStream(false);
            IOUtils.copy(file, out);
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(file);
            return Response.ok(createFileVO(newFile, uriInfo)).build();
        } catch (IOException e) {
            return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
        }
    }

    protected Response get(List<PathSegment> path, UriInfo uriInfo, Request request) {
        VFSItem vItem = resolveFile(path);
        if (vItem == null) {
            return Response.serverError().status(Status.NOT_FOUND).build();
        } else if (vItem instanceof VFSContainer) {
            VFSContainer directory = (VFSContainer) vItem;
            List<VFSItem> items = directory.getItems(new SystemItemFilter());
            int count = 0;
            FileVO[] links = new FileVO[items.size()];
            for (VFSItem item : items) {
                UriBuilder builder = uriInfo.getAbsolutePathBuilder();
                String uri = builder.path(normalize(item.getName())).build().toString();
                if (item instanceof VFSLeaf) {
                    links[count++] = new FileVO("self", uri, item.getName(), ((VFSLeaf) item).getSize());
                } else {
                    links[count++] = new FileVO("self", uri, item.getName());
                }
            }

            return Response.ok(links).build();
        } else if (vItem instanceof VFSLeaf) {
            VFSLeaf leaf = (VFSLeaf) vItem;
            Date lastModified = new Date(leaf.getLastModified());
            Response.ResponseBuilder response = request.evaluatePreconditions(lastModified);
            if (response == null) {
                String mimeType = WebappHelper.getMimeType(leaf.getName());
                if (mimeType == null) {
                    mimeType = MediaType.APPLICATION_OCTET_STREAM;
                }
                response = Response.ok(leaf.getInputStream(), mimeType).lastModified(lastModified).cacheControl(cc);
            }
            return response.build();

        }
        return Response.serverError().status(Status.BAD_REQUEST).build();
    }

    protected Response getFMetadata(List<PathSegment> path, UriInfo uriInfo) {
        VFSItem vItem = resolveFile(path);
        if (vItem == null) {
            return Response.serverError().status(Status.NOT_FOUND).build();
        } else if (vItem instanceof VFSContainer) {
            return Response.serverError().status(Status.NOT_ACCEPTABLE).build();
        } else if (vItem instanceof VFSLeaf) {
            VFSLeaf leaf = (VFSLeaf) vItem;
            UriBuilder builder = uriInfo.getAbsolutePathBuilder();
            String uri = builder.build().toString();
            String[] uriArray = uri.split("metadata/");
            uri = uriArray[0] + uriArray[1];
            FileMetadataVO metaVo = new FileMetadataVO(uri, leaf);
            return Response.ok(metaVo).build();
        }
        return Response.serverError().status(Status.BAD_REQUEST).build();
    }

    protected VFSContainer resolveContainer(List<PathSegment> path, boolean create) {
        VFSContainer directory = container;
        boolean notFound = false;

        //remove trailing segment if a trailing / is used
        if (path.size() > 0 && !StringHelper.containsNonWhitespace(path.get(path.size() - 1).getPath())) {
            path = path.subList(0, path.size() - 1);
        }

        a_a: for (PathSegment seg : path) {
            String segPath = seg.getPath();
            for (VFSItem item : directory.getItems(new SystemItemFilter())) {
                if (item instanceof VFSLeaf) {
                    //
                } else if (item instanceof VFSContainer && normalize(item.getName()).equals(segPath)) {
                    directory = (VFSContainer) item;
                    continue a_a;
                }
            }
            if (create) {
                directory = directory.createChildContainer(segPath);
            } else if (path.get(path.size() - 1) == seg) {
                break a_a;
            } else {
                notFound = true;
            }
        }

        if (notFound) {
            return null;
        }
        return directory;
    }

    protected VFSItem resolveFile(List<PathSegment> path) {
        VFSContainer directory = container;
        VFSItem resolvedItem = directory;
        boolean notFound = false;

        //remove trailing segment if a trailing / is used
        if (path.size() > 0 && !StringHelper.containsNonWhitespace(path.get(path.size() - 1).getPath())) {
            path = path.subList(0, path.size() - 1);
        }

        a_a: for (PathSegment seg : path) {
            String segPath = seg.getPath();
            for (VFSItem item : directory.getItems(new SystemItemFilter())) {
                if (normalize(item.getName()).equals(segPath)) {
                    if (item instanceof VFSLeaf) {
                        if (path.get(path.size() - 1) == seg) {
                            resolvedItem = item;
                            break a_a;
                        }
                    } else if (item instanceof VFSContainer) {
                        resolvedItem = directory = (VFSContainer) item;
                        continue a_a;
                    }
                }
            }
            notFound = true;
        }

        if (notFound) {
            return null;
        }
        return resolvedItem;
    }

    public static FileVO createFileVO(VFSItem item, UriInfo uriInfo) {
        UriBuilder builder = uriInfo.getAbsolutePathBuilder();
        String uri = builder.path(normalize(item.getName())).build().toString();
        FileVO link = new FileVO("self", uri, item.getName());
        if (item instanceof VFSLeaf) {
            link.setSize(((VFSLeaf) item).getSize());
        }
        return link;
    }

    public static String normalize(String segment) {
        segment = segment.replace(" ", "_");
        segment = Normalizer.normalize(segment, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", "");
        return segment;
    }
}