org.opendatakit.api.forms.FormService.java Source code

Java tutorial

Introduction

Here is the source code for org.opendatakit.api.forms.FormService.java

Source

/*
 * 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.
 */
package org.opendatakit.api.forms;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.opendatakit.ContextUtils;
import org.opendatakit.aggregate.odktables.rest.ApiConstants;
import org.opendatakit.aggregate.odktables.rest.entity.Column;
import org.opendatakit.aggregate.odktables.rest.entity.OdkTablesFileManifest;
import org.opendatakit.api.forms.entity.FormUploadResult;
import org.opendatakit.api.odktables.FileManifestService;
import org.opendatakit.constants.BasicConsts;
import org.opendatakit.constants.ErrorConsts;
import org.opendatakit.constants.MimeTypes;
import org.opendatakit.constants.WebConsts;
import org.opendatakit.context.CallingContext;
import org.opendatakit.odktables.ConfigFileChangeDetail;
import org.opendatakit.odktables.FileContentInfo;
import org.opendatakit.odktables.FileManager;
import org.opendatakit.odktables.FileManifestManager;
import org.opendatakit.odktables.TableManager;
import org.opendatakit.odktables.exception.PermissionDeniedException;
import org.opendatakit.odktables.exception.TableAlreadyExistsException;
import org.opendatakit.odktables.security.TablesUserPermissions;
import org.opendatakit.odktables.util.ServiceUtils;
import org.opendatakit.persistence.exception.ODKDatastoreException;
import org.opendatakit.persistence.exception.ODKEntityPersistException;
import org.opendatakit.persistence.exception.ODKTaskLockException;
import org.springframework.beans.factory.annotation.Autowired;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.Authorization;

@Api(value = "/forms", description = "ODK Form Definition API", authorizations = {
        @Authorization(value = "basicAuth") })
@Path("/forms")
public class FormService {

    @Autowired
    private CallingContext callingContext;

    private static final Log logger = LogFactory.getLog(FormService.class);

    @POST
    @ApiOperation(value = "Upload a zipped form definition as multipart/form-data.", response = FormUploadResult.class)
    @Consumes({ MediaType.MULTIPART_FORM_DATA })
    @Produces({ MediaType.APPLICATION_JSON, ApiConstants.MEDIA_TEXT_XML_UTF8,
            ApiConstants.MEDIA_APPLICATION_XML_UTF8 })
    @Path("{appId}/{odkClientVersion}")
    public Response doPost(@Context HttpServletRequest req, @Context HttpServletResponse resp,
            @PathParam("odkClientVersion") String odkClientVersion, @PathParam("appId") String appId,
            @Context UriInfo info) throws IOException {
        logger.debug("Uploading...");
        ServiceUtils.examineRequest(req.getServletContext(), req);

        req.getContentLength();
        if (!ServletFileUpload.isMultipartContent(req)) {
            throw new WebApplicationException(ErrorConsts.NO_MULTI_PART_CONTENT,
                    HttpServletResponse.SC_BAD_REQUEST);
        }

        try {
            TablesUserPermissions userPermissions = ContextUtils.getTablesUserPermissions(callingContext);
            List<FileItem> items = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(req);
            Map<String, byte[]> files = null;
            String tableId = null;
            List<String> regionalOffices = new ArrayList<>();

            // unzipping files

            for (FileItem item : items) {

                // Retrieve all Regional Office IDs to which a form definition
                // is going to be assigned to
                if (item.getFieldName().equals(WebConsts.OFFICE_ID)) {
                    regionalOffices.add(item.getString());
                }

                String fieldName = item.getFieldName();
                String fileName = FilenameUtils.getName(item.getName());

                if (fieldName.equals(WebConsts.ZIP_FILE)) {
                    if (fileName == null || !(fileName.endsWith(".zip"))) {
                        throw new WebApplicationException(ErrorConsts.NO_ZIP_FILE,
                                HttpServletResponse.SC_BAD_REQUEST);
                    }

                    InputStream fileStream = item.getInputStream();
                    ZipInputStream zipStream = new ZipInputStream(fileStream);
                    files = processZipInputStream(zipStream);
                }
            }

            tableId = getTableIdFromFiles(files);

            FormUploadResult formUploadResult = uploadFiles(odkClientVersion, appId, tableId, userPermissions,
                    files, regionalOffices);

            FileManifestManager manifestManager = new FileManifestManager(appId, odkClientVersion, callingContext);
            OdkTablesFileManifest manifest = manifestManager.getManifestForTable(tableId);
            FileManifestService.fixDownloadUrls(info, appId, odkClientVersion, manifest);

            formUploadResult.setManifest(manifest);
            String eTag = Integer.toHexString(manifest.hashCode()); // Is this
                                                                    // right?

            return Response.status(Status.CREATED).entity(formUploadResult).header(HttpHeaders.ETAG, eTag)
                    .header(ApiConstants.OPEN_DATA_KIT_VERSION_HEADER, ApiConstants.OPEN_DATA_KIT_VERSION)
                    .header("Access-Control-Allow-Origin", "*").header("Access-Control-Allow-Credentials", "true")
                    .build();

        } catch (FileUploadException | ODKDatastoreException | ODKTaskLockException | PermissionDeniedException
                | TableAlreadyExistsException e) {
            logger.error("Error uploading zip", e);
            throw new WebApplicationException(ErrorConsts.PERSISTENCE_LAYER_PROBLEM + "\n" + e.toString(),
                    HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

        }
    }

    static Map<String, byte[]> processZipInputStream(ZipInputStream zipInputStream) throws IOException {
        int c;
        Map<String, byte[]> files = new HashMap<>();
        byte buffer[] = new byte[2084];
        ByteArrayOutputStream tempBAOS;
        ZipEntry zipEntry;
        while ((zipEntry = zipInputStream.getNextEntry()) != null) {
            if (!(zipEntry.isDirectory())) {
                tempBAOS = new ByteArrayOutputStream();
                while ((c = zipInputStream.read(buffer, 0, 2048)) > -1) {
                    tempBAOS.write(buffer, 0, c);
                }
                files.put("tables" + BasicConsts.FORWARDSLASH + zipEntry.getName(), tempBAOS.toByteArray());
            }
        }
        return files;
    }

    static String getDefinitionFromFiles(Map<String, byte[]> files) {
        String definition = null;
        if (files != null && !files.isEmpty()) {
            for (String filename : files.keySet()) {
                if (filename.endsWith("definition.csv")) {

                    definition = new String(files.get(filename));
                    break;
                }
            }
        }
        if (definition == null) {
            throw new WebApplicationException(ErrorConsts.NO_DEFINITION_FILE, HttpServletResponse.SC_BAD_REQUEST);
        }
        return definition;
    }

    static String getTableIdFromFiles(Map<String, byte[]> files) {
        String tableId = null;
        if (files != null && !files.isEmpty()) {
            for (String filename : files.keySet()) {
                if (filename.endsWith("definition.csv")) {

                    tableId = FileManager.getTableIdForFilePath(filename);
                    break;
                }
            }
        }
        if (tableId == null) {
            throw new WebApplicationException(ErrorConsts.NO_TABLE_ID_IN_DEFINITION_FILE,
                    HttpServletResponse.SC_BAD_REQUEST);
        }
        return tableId;
    }

    FormUploadResult uploadFiles(String odkClientVersion, String appId, String tableId,
            TablesUserPermissions userPermissions, Map<String, byte[]> files, List<String> regionalOffices)
            throws ODKEntityPersistException, TableAlreadyExistsException, PermissionDeniedException,
            ODKDatastoreException, ODKTaskLockException {

        FormUploadResult formUploadResult = new FormUploadResult();

        String definition = getDefinitionFromFiles(files);
        List<Column> columns = parseColumnsFromCsv(definition);

        List<String> notUploadedFiles = new ArrayList<>();
        List<String> uploadedFiles = new ArrayList<>();

        TableManager tm = new TableManager(appId, userPermissions, callingContext);
        tm.createTable(tableId, columns, regionalOffices);

        // uploading files
        for (Map.Entry<String, byte[]> entry : files.entrySet()) {
            String contentType = MimeTypes.MIME_TYPES
                    .get(entry.getKey().substring(entry.getKey().lastIndexOf(".") + 1));
            if (contentType == null) {
                contentType = "application/octet-stream";
            }

            FileManager fm = new FileManager(appId, callingContext);
            FileContentInfo fi = new FileContentInfo(entry.getKey(), contentType,
                    Long.valueOf(entry.getValue().length), null, entry.getValue());

            ConfigFileChangeDetail outcome = fm.putFile(odkClientVersion, tableId, fi, userPermissions);

            if (outcome == ConfigFileChangeDetail.FILE_NOT_CHANGED) {
                notUploadedFiles.add(entry.getKey());
            } else {
                uploadedFiles.add(entry.getKey());
            }
        }
        formUploadResult.setNotProcessedFiles(notUploadedFiles);
        return formUploadResult;
    }

    static List<Column> parseColumnsFromCsv(String definition) {
        List<Column> outcome = new ArrayList<>();
        String columnsStrings[] = definition.split("\\s+");
        Column temp;
        for (String column : columnsStrings) {
            if (column.startsWith("_")) {
                continue;
            }
            String fields[] = column.split(",", 4);
            temp = new Column(fields[0], fields[1], fields[2],
                    fields[3].replaceAll("\"\"", "\"").replaceAll("\\]\"", "\\]").replaceAll("\"\\[", "\\["));
            outcome.add(temp);
        }

        return outcome;
    }

}