org.locationtech.geogig.rest.repository.UploadCommandResource.java Source code

Java tutorial

Introduction

Here is the source code for org.locationtech.geogig.rest.repository.UploadCommandResource.java

Source

/* Copyright (c) 2016 Boundless and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/org/documents/edl-v10.html
 *
 * Contributors:
 * Erik Merkle (Boundless) - initial implementation
 */
package org.locationtech.geogig.rest.repository;

import java.io.File;
import java.io.FileOutputStream;

import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.locationtech.geogig.web.api.ParameterSet;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.ext.fileupload.RepresentationContext;
import org.restlet.ext.fileupload.RestletFileUpload;
import org.restlet.resource.Representation;
import org.restlet.resource.Variant;
import org.restlet.util.ByteUtils;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;

/**
 * CommandResource extension that allows for POSTing file uploads.
 *
 * This class extends {@link CommandResource} to enable POST for resources. Currently, this resource
 * requires one, and only one, file upload in the Request body. The name of the entity MUST be
 * <b>fileUpload</b>.
 * <p>
 * This class overrides {@link CommandResource#buildParameterSet(org.restlet.data.Form)} to add the
 * file upload to the {@link ParameterSet} so that
 * {@link org.locationtech.geogig.rest.geotools.ImportWebOp ImportWebOp} can retrieve the uploaded
 * file to pass on to {@link org.locationtech.geogig.rest.geotools.DataStoreImportContextService
 * DataStoreImportContextService} implementations.
 */
public class UploadCommandResource extends CommandResource {

    /**
     * POSTed form data key for the file to upload. Any commands that must upload a file to the
     * server should POST form data with this value as the key for the file to be uploaded.
     */
    public static final String UPLOAD_FILE_KEY = "fileUpload";

    /**
     * Size, in bytes, of the memory threshold for storing uploaded data. Uploads larger than this
     * value will be flushed to disk for processing. Uploads smaller than this value will remain in
     * memory until processed. The threshold is 1MB.
     */
    private static final int UPLOAD_THRESHOLD = 0x1000 * 1000;

    private static final String FILE_UPLOAD_ERROR_TMPL = "There must be one and only one <%s> specified in the request";

    @Override
    protected String getCommandName() {
        // Overriding the getCommandName as this Resource is bound only to "import" context.
        return "import";
    }

    @Override
    protected ParameterSet buildParameterSet(Form options) {
        // Override the default ParameterSet to include the uploaded file.
        return new FormParams(options, consumeFileUpload(this.getRequest().getEntity()));
    }

    @Override
    protected MediaType resolveFormat(Form options, Variant variant) {
        if (options.getFirstValue("output_format") != null) {
            // requested output format exists, use that
            return super.resolveFormat(options, variant);
        }
        // output format not present, just return the preferred format
        return getPreferredVariant().getMediaType();
    }

    @Override
    public void handlePost() {
        // Request Entity may not be available, as in the case of a file upload.
        // as long as the entity is not null and at least transient, call post.
        final Optional<Representation> optional = Optional.fromNullable(getRequest().getEntity());
        if (optional.isPresent()) {
            final Representation entity = optional.get();
            if (entity.isAvailable() || entity.isTransient()) {
                post(getRequest().getEntity());
                return;
            }
        }
        // just call super
        super.handlePost();
    }

    @Override
    public void post(Representation entity) {
        // invoke the runCommand method on the parent and get the representation
        // set the representation on the response
        getResponse().setEntity(runCommand(entity, getRequest()));
    }

    /**
     * Consumes the data sent from the client and stores it into a temporary file to be processed.
     * This method is just looking through the request entity for form data named
     * {@value #UPLOAD_FILE_KEY}. If present, we will consume the data stream from the request and
     * store it in a temporary file.
     *
     * @param entity POSTed entity containing binary data to be processed.
     *
     * @return local File representation of the data streamed form the client.
     */
    private File consumeFileUpload(Representation entity) {
        File uploadedFile = null;
        // get a File item factory
        final DiskFileItemFactory factory = new DiskFileItemFactory();
        // set the threshold
        factory.setSizeThreshold(UPLOAD_THRESHOLD);
        // build a Restlet file upload with the factory
        final RestletFileUpload fileUploadUtil = new RestletFileUpload(factory);
        // try to extract the uploaded file entity
        try {
            // build a RepresentaionContext of the request entity
            final RepresentationContext context = new RepresentationContext(entity);
            // get an iterator to loop through the entity for the upload data
            final FileItemIterator iterator = fileUploadUtil.getItemIterator(context);
            // look for the the "fileUpload" form data
            while (iterator.hasNext()) {
                final FileItemStream fis = iterator.next();
                // see if this is the data we are looking for
                if (UPLOAD_FILE_KEY.equals(fis.getFieldName())) {
                    // if we've already ingested a fileUpload, then the request had more than one.
                    Preconditions.checkState(uploadedFile == null, FILE_UPLOAD_ERROR_TMPL, UPLOAD_FILE_KEY);
                    // found it, create a temp file
                    uploadedFile = File.createTempFile("geogig-" + UPLOAD_FILE_KEY + "-", ".tmp");
                    uploadedFile.deleteOnExit();
                    // consume the streamed contetn into the temp file
                    try (FileOutputStream fos = new FileOutputStream(uploadedFile)) {
                        ByteUtils.write(fis.openStream(), fos);
                        // flush the output stream
                        fos.flush();
                    }
                }
            }
            // if we don't have an uploaded file, we can't continue
            Preconditions.checkNotNull(uploadedFile, FILE_UPLOAD_ERROR_TMPL, UPLOAD_FILE_KEY);
        } catch (Exception ex) {
            // delete the temp file if it exists
            if (uploadedFile != null) {
                uploadedFile.delete();
            }
            // null out the file
            uploadedFile = null;
        }
        // return the uploaded entity data as a file
        return uploadedFile;
    }
}