io.liveoak.mongo.gridfs.GridFSBlobResource.java Source code

Java tutorial

Introduction

Here is the source code for io.liveoak.mongo.gridfs.GridFSBlobResource.java

Source

/*
 * Copyright 2014 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Eclipse Public License version 1.0, available at http://www.eclipse.org/legal/epl-v10.html
 */
package io.liveoak.mongo.gridfs;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;
import java.util.function.Supplier;

import com.mongodb.BasicDBObject;
import com.mongodb.gridfs.GridFS;
import com.mongodb.gridfs.GridFSDBFile;
import com.mongodb.gridfs.GridFSInputFile;
import io.liveoak.spi.MediaType;
import io.liveoak.spi.RequestContext;
import io.liveoak.spi.resource.BlockingResource;
import io.liveoak.spi.resource.async.BinaryContentSink;
import io.liveoak.spi.resource.async.BinaryResource;
import io.liveoak.spi.resource.async.Responder;
import io.liveoak.spi.state.LazyResourceState;
import io.liveoak.spi.state.ResourceState;
import io.netty.buffer.ByteBuf;
import org.bson.types.ObjectId;
import org.vertx.java.core.file.AsyncFile;

/**
 * @author <a href="mailto:marko.strukelj@gmail.com">Marko Strukelj</a>
 */
public class GridFSBlobResource extends GridFSResource implements BlockingResource, BinaryResource {

    public GridFSBlobResource(RequestContext ctx, GridFSDirectoryResource parent, String id,
            GridFSDBObject fileInfo, GridFSResourcePath path) {
        super(ctx, parent, id, fileInfo, path);
    }

    @Override
    public MediaType mediaType() {
        return new MediaType(fileInfo().getString("contentType"));
    }

    @Override
    public long contentLength() {
        return fileInfo().getLong("length");
    }

    @Override
    public void readContent(RequestContext ctx, BinaryContentSink sink) throws Exception {

        GridFS gridfs = getUserspace().getGridFS();

        // TODO - maybe use netty directly here?
        // TODO - BinaryContentSink should flush to socket (pipeline channel), not queue to memory

        // get tmp file
        File tmpFile = getTempFile();

        // sync copy from db to tmp file first - no async API
        GridFSDBFile file = gridfs.findOne(new BasicDBObject("_id", fileInfo().getId()));
        file.writeTo(tmpFile);

        // async copy from local tmp file to sink
        getRoot().vertx().fileSystem().open(tmpFile.getPath(), (result) -> {
            if (result.succeeded()) {
                AsyncFile asyncFile = result.result();
                asyncFile.dataHandler((buffer) -> {
                    sink.accept(buffer.getByteBuf());
                });
                asyncFile.endHandler((end) -> {
                    sink.close();
                    asyncFile.close();
                    tmpFile.delete();
                });
            } else {
                sink.close();
                tmpFile.delete();
            }
        });
    }

    private File getTempFile() {
        // use configured temp.dir for temp file location
        return new File(getRoot().tempDir(), UUID.randomUUID().toString());
    }

    @Override
    public boolean willProcessUpdate(RequestContext ctx, ResourceState state, Responder responder)
            throws Exception {
        // no reason to reject reading the body
        return true;
    }

    public void updateContent(RequestContext ctx, ResourceState state, Responder responder) throws Exception {
        if (state instanceof LazyResourceState == false) {
            responder.internalError("Expected state instanceof LazyResourceState, not " + state.getClass());
        }

        GridFSDBObject blob = fileInfo();
        boolean isNew = blob.getId() == null;

        LazyResourceState request = (LazyResourceState) state;
        if (request.hasBigContent()) {
            File tmpFile = request.contentAsFile();
            if (tmpFile != null) {
                try {
                    GridFSFilesPathItemResource response = pushToDB(ctx, request.getContentType(), blob, () -> {
                        return tmpFile;
                    });
                    if (isNew) {
                        responder.resourceCreated(response);
                    } else {
                        responder.resourceUpdated(response);
                    }
                } catch (Exception e) {
                    responder.internalError(e);
                }
            } else {
                responder.internalError("No cache file for request: " + request);
            }
        } else {
            InputStream stream = request.contentAsStream();
            if (stream != null) {
                try {
                    GridFSFilesPathItemResource response = pushToDB(ctx, request.getContentType(), fileInfo(),
                            () -> {
                                return stream;
                            });
                    if (isNew) {
                        responder.resourceCreated(response);
                    } else {
                        responder.resourceUpdated(response);
                    }
                } catch (Exception e) {
                    responder.internalError(e);
                }
            } else {
                responder.internalError("No Stream for request: " + request);
            }
        }
    }

    public void delete(RequestContext ctx, Responder responder) throws Exception {
        GridFSDBObject info = fileInfo();
        if (info.getId() == null) {
            responder.noSuchResource(id());
            return;
        }
        GridFS gridfs = getUserspace().getGridFS();
        gridfs.remove(info.getId());
        info.dbObject().put("length", 0L);
        info.dbObject().put("contentType", null);
        responder.resourceDeleted(this);
    }

    private GridFSFilesPathItemResource pushToDB(RequestContext ctx, MediaType contentType, GridFSDBObject fileInfo,
            Supplier contentProvider) throws IOException {
        ObjectId currentId = fileInfo.getId();
        boolean fileExists = currentId != null;

        // update the targeted userspace - hopefully current user has rights to do that
        GridFS gridfs = getUserspace().getGridFS();

        Object content = contentProvider.get();
        GridFSInputFile blob;
        if (fileExists) {
            // here is a time gap when file doesn't exist for a while when being updated.
            // making the switch instantaneous would require another layer of indirection
            // - not using file_id as canonical id, but some other id, mapped to a file.
            // there would still remain a moment between mapping from old file to new file
            // involving two separate file items and a moment in time when during a switch
            // no file would match a filename, nor file id.
            gridfs.remove(currentId);
        }
        if (content instanceof File) {
            blob = gridfs.createFile((File) content);
        } else if (content instanceof InputStream) {
            blob = gridfs.createFile((InputStream) content);
        } else if (content instanceof ByteBuf) {
            blob = gridfs.createFile(((ByteBuf) content).array());
        } else {
            throw new IllegalArgumentException("Unsupported value supplied: " + content.getClass());
        }

        // meta data
        if (fileExists) {
            blob.setId(currentId);
        }
        blob.setFilename(fileInfo().getString("filename"));
        blob.setContentType(contentType != null ? contentType.toString() : "application/octet-stream");
        blob.put("parent", fileInfo().getParentId());
        blob.save();

        String oid = blob.getId().toString();

        return new GridFSFilesPathItemResource(ctx, getFilesRoot(), oid, new GridFSDBObject(blob),
                GridFSResourcePath.fromContext(ctx));
    }

    @Override
    public String toString() {
        return "[GridFSBlobResource: id=" + this.id() + ", path=" + path() + "]";
    }
}