org.openmrs.module.sync.web.controller.ImportListController.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.module.sync.web.controller.ImportListController.java

Source

/**
 * The contents of this file are subject to the OpenMRS Public License
 * Version 1.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://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */
package org.openmrs.module.sync.web.controller;

import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.api.context.Context;
import org.openmrs.module.sync.SyncConstants;
import org.openmrs.module.sync.SyncTransmission;
import org.openmrs.module.sync.SyncTransmissionState;
import org.openmrs.module.sync.SyncUtil;
import org.openmrs.module.sync.SyncUtilTransmission;
import org.openmrs.module.sync.api.SyncIngestService;
import org.openmrs.module.sync.api.SyncService;
import org.openmrs.module.sync.ingest.SyncDeserializer;
import org.openmrs.module.sync.ingest.SyncImportRecord;
import org.openmrs.module.sync.ingest.SyncTransmissionResponse;
import org.openmrs.module.sync.server.ConnectionRequest;
import org.openmrs.module.sync.server.ConnectionResponse;
import org.openmrs.module.sync.server.RemoteServer;
import org.springframework.validation.BindException;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ImportListController extends SimpleFormController {

    /** Logger for this class and subclasses */
    protected final Log log = LogFactory.getLog(getClass());

    /**
     * @see org.springframework.web.servlet.mvc.BaseCommandController#initBinder(javax.servlet.http.HttpServletRequest,
     *      org.springframework.web.bind.ServletRequestDataBinder)
     */
    protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
        super.initBinder(request, binder);
    }

    @Override
    protected ModelAndView processFormSubmission(HttpServletRequest request, HttpServletResponse response,
            Object obj, BindException errors) throws Exception {

        log.info("***********************************************************\n");
        log.info("Inside SynchronizationImportListController");

        // just fail fast if in the midst of refreshing the context, as this was causing issues, see SYNC-318
        if (Context.isRefreshingContext()) {
            return null;
        }

        // There are 3 ways to come to this point, so we'll handle all of them:
        // 1) uploading a file (results in a file attachment as response)
        // 2) posting data to page (results in pure XML output)
        // 3) remote connection (with username + password, also posting data) (results in pure XML)
        // none of these result in user-friendly - so no comfy, user-friendly stuff needed here

        //outputing statistics: debug only!
        log.info("HttpServletRequest INFO:");
        log.info("ContentType: " + request.getContentType());
        log.info("CharacterEncoding: " + request.getCharacterEncoding());
        log.info("ContentLength: " + request.getContentLength());
        log.info("checksum: " + request.getParameter("checksum"));
        log.info("syncData: " + request.getParameter("syncData"));
        log.info("syncDataResponse: " + request.getParameter("syncDataResponse"));

        long checksum = 0;
        Integer serverId = 0;
        boolean isResponse = false;
        boolean isUpload = false;
        boolean useCompression = false;

        String contents = "";
        String username = "";
        String password = "";

        //file-based upload, and multi-part form submission
        if (request instanceof MultipartHttpServletRequest) {
            log.info("Processing contents of syncDataFile multipart request parameter");
            MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
            serverId = ServletRequestUtils.getIntParameter(multipartRequest, "serverId", 0);
            isResponse = ServletRequestUtils.getBooleanParameter(multipartRequest, "isResponse", false);
            useCompression = ServletRequestUtils.getBooleanParameter(multipartRequest, "compressed", false);
            isUpload = ServletRequestUtils.getBooleanParameter(multipartRequest, "upload", false);
            username = ServletRequestUtils.getStringParameter(multipartRequest, "username", "");
            password = ServletRequestUtils.getStringParameter(multipartRequest, "password", "");

            log.info("Request class: " + request.getClass());
            log.info("serverId: " + serverId);
            log.info("upload = " + isUpload);
            log.info("compressed = " + useCompression);
            log.info("response = " + isResponse);
            log.info("username = " + username);

            log.info("Request content length: " + request.getContentLength());
            MultipartFile multipartFile = multipartRequest.getFile("syncDataFile");
            if (multipartFile != null && !multipartFile.isEmpty()) {
                InputStream inputStream = null;
                try {
                    // Decompress content in file
                    ConnectionResponse syncResponse = new ConnectionResponse(
                            new ByteArrayInputStream(multipartFile.getBytes()), useCompression);

                    log.info("Content to decompress: " + multipartFile.getBytes());
                    log.info("Content received: " + syncResponse.getResponsePayload());
                    log.info("Decompression Checksum: " + syncResponse.getChecksum());

                    contents = syncResponse.getResponsePayload();
                    checksum = syncResponse.getChecksum();

                    log.info("Final content: " + contents);

                } catch (Exception e) {
                    log.warn("Unable to read in sync data file", e);
                } finally {
                    IOUtils.closeQuietly(inputStream);
                }
            }
        } else {
            log.debug("seems we DO NOT have a file object");
        }

        // prepare to process the input: contents now contains decompressed request ready to be processed
        SyncTransmissionResponse str = new SyncTransmissionResponse();
        str.setErrorMessage(SyncConstants.ERROR_TX_NOT_UNDERSTOOD);
        str.setFileName(SyncConstants.FILENAME_TX_NOT_UNDERSTOOD);
        str.setUuid(SyncConstants.UUID_UNKNOWN);
        str.setSyncSourceUuid(SyncConstants.UUID_UNKNOWN);
        str.setSyncTargetUuid(SyncConstants.UUID_UNKNOWN);
        str.setState(SyncTransmissionState.TRANSMISSION_NOT_UNDERSTOOD);
        str.setTimestamp(new Date()); //set the timestamp of the response

        if (log.isInfoEnabled()) {
            log.info("CONTENT IN IMPORT CONTROLLER: " + contents);
        }

        //if no content, nothing to process just send back response
        if (contents == null || contents.length() < 0) {
            log.info("returning from ingest: nothing to process.");
            this.sendResponse(str, isUpload, response);
            return null;
        }

        // if this is option 3 (posting from remote server), we need to authenticate
        if (!Context.isAuthenticated()) {
            try {
                Context.authenticate(username, password);
            } catch (Exception e) {
            }
        }
        // Could not authenticate user: send back error
        if (!Context.isAuthenticated()) {
            str.setErrorMessage(SyncConstants.ERROR_AUTH_FAILED);
            str.setFileName(SyncConstants.FILENAME_AUTH_FAILED);
            str.setState(SyncTransmissionState.AUTH_FAILED);

            this.sendResponse(str, isUpload, response);
            return null;
        }

        //Fill-in the server uuid for the response: since request was authenticated we can start letting callers
        //know about us
        str.setSyncTargetUuid(Context.getService(SyncService.class).getServerUuid());

        //Checksum check before doing anything at all: on unreliable networks we can get seemingly
        //valid HTTP POST but content is messed up, defend against it with custom checksums
        long checksumReceived = ServletRequestUtils.getLongParameter(request, "checksum", -1);
        log.info("checksum value received in POST: " + checksumReceived);
        log.info("checksum value of payload: " + checksum);
        log.info("SIZE of payload: " + contents.length());
        if (checksumReceived > 0 && (checksumReceived != checksum)) {
            log.error("ERROR: FAILED CHECKSUM!");
            str.setState(SyncTransmissionState.TRANSMISSION_NOT_UNDERSTOOD);

            this.sendResponse(str, isUpload, response);
            return null;
        }

        //Test message. Test message was sent (i.e. using 'test connection' button on server screen)
        //just send empty acknowledgment
        if (SyncConstants.TEST_MESSAGE.equals(contents)) {
            str.setErrorMessage("");
            str.setState(SyncTransmissionState.OK);
            str.setUuid("");
            str.setFileName(SyncConstants.FILENAME_TEST);

            this.sendResponse(str, isUpload, response);
            return null;
        }

        if (SyncConstants.CLONE_MESSAGE.equals(contents)) {
            try {
                log.info("CLONE MESSAGE RECEIVED, TRYING TO CLONE THE DB");
                File file = Context.getService(SyncService.class).generateDataFile();
                StringWriter writer = new StringWriter();
                IOUtils.copy(new FileInputStream(file), writer);
                this.sendCloneResponse(writer.toString(), response, false);

                boolean clonedDBLog = Boolean.parseBoolean(Context.getAdministrationService()
                        .getGlobalProperty(SyncConstants.PROPERTY_SYNC_CLONED_DATABASE_LOG_ENABLED, "true"));

                if (!clonedDBLog) {
                    file.delete();
                }
            } catch (Exception ex) {
                log.warn(ex.toString());
                ex.printStackTrace();
            }
            return null;
        }

        /*************************************************************************************************************************
         * This is a real transmission: - user was properly authenticated - checksums match - it is
         * not a test transmission Start processing! 1. Deserialize what was sent; it can be either
         * SyncTransmssion, or SyncTransmissionResponse 2. If it is a response,
         *************************************************************************************************************************/
        SyncTransmission st = null;

        if (!isResponse) {
            //this is not 'response' to something we sent out; thus the contents should contain plan SyncTransmission 
            try {
                log.info("xml to sync transmission with contents: " + contents);
                st = SyncDeserializer.xmlToSyncTransmission(contents);
            } catch (Exception e) {
                log.error("Unable to deserialize the following: " + contents, e);
                str.setErrorMessage("Unable to deserialize transmission contents into SyncTansmission.");
                str.setState(SyncTransmissionState.TRANSMISSION_NOT_UNDERSTOOD);
                this.sendResponse(str, isUpload, response);
                return null;
            }
        } else {
            log.info("Processing a response, not a transmission");
            SyncTransmissionResponse priorResponse = null;

            try {
                // this is the confirmation of receipt of previous transmission
                priorResponse = SyncDeserializer.xmlToSyncTransmissionResponse(contents);
                log.info("This is a response from a previous transmission.  Uuid is: " + priorResponse.getUuid());
            } catch (Exception e) {
                log.error("Unable to deserialize the following: " + contents, e);
                str.setErrorMessage("Unable to deserialize transmission contents into SyncTransmissionResponse.");
                str.setState(SyncTransmissionState.TRANSMISSION_NOT_UNDERSTOOD);
                this.sendResponse(str, isUpload, response);
                return null;
            }

            // figure out where this came from:
            // for responses, the target ID contains the server that generated the response
            String sourceUuid = priorResponse.getSyncTargetUuid();
            log.info("SyncTransmissionResponse has a sourceUuid of " + sourceUuid);
            RemoteServer origin = Context.getService(SyncService.class).getRemoteServer(sourceUuid);
            if (origin == null) {
                log.error("Source server not registered locally. Unable to find source server by uuid: "
                        + sourceUuid);
                str.setErrorMessage(
                        "Source server not registered locally. Unable to find source server by uuid " + sourceUuid);
                str.setState(SyncTransmissionState.INVALID_SERVER);
                this.sendResponse(str, isUpload, response);
                return null;
            } else {
                log.info("Found source server by uuid: " + sourceUuid + " = " + origin.getNickname());
                log.info("Source server is " + origin.getNickname());
            }

            if (priorResponse == null) {
            }

            // process response that was sent to us; the sync response normally contains:
            //a) results of the records that we sent out
            //b) new records from 'source' to be applied against this server
            if (priorResponse.getSyncImportRecords() == null) {
                log.debug("No records to process in response");
            } else {
                // now process each incoming syncImportRecord, this is just status update
                for (SyncImportRecord importRecord : priorResponse.getSyncImportRecords()) {
                    Context.getService(SyncIngestService.class).processSyncImportRecord(importRecord, origin);
                }
            }

            // now pull out the data that originated on the 'source' server and try to process it
            st = priorResponse.getSyncTransmission();

        }

        // now process the syncTransmission if one was received                    
        if (st != null) {
            str = SyncUtilTransmission.processSyncTransmission(st,
                    SyncUtil.getGlobalPropetyValueAsInteger(SyncConstants.PROPERTY_NAME_MAX_RECORDS_WEB));
        } else
            log.info("st was null");

        //send response
        this.sendResponse(str, isUpload, response);

        // never a situation where we want to actually use the model/view - either file download or http request
        return null;
    }

    /**
     * This is called prior to displaying a form for the first time. It tells Spring the
     * form/command object to load into the request
     * 
     * @see org.springframework.web.servlet.mvc.AbstractFormController#formBackingObject(javax.servlet.http.HttpServletRequest)
     */
    protected Object formBackingObject(HttpServletRequest request) throws ServletException {
        // default empty Object
        return "";
    }

    private void sendResponse(SyncTransmissionResponse str, boolean isUpload, HttpServletResponse response)
            throws Exception {
        String content = null;
        try {
            str.createFile(false);
            content = str.getFileOutput();
        } catch (Exception e) {
            log.error(
                    "Could not get output while writing file.  In case problem writing file, trying again to just get output.");
        }

        if (content.length() == 0) {
            try {
                str.createFile(false);
                content = str.getFileOutput();
            } catch (Exception e) {
                log.error(
                        "Could not get output while writing file.  In case problem writing file, trying again to just get output.");
            }
        }

        // If the file was uploaded manually, we'll send back an XML response
        if (isUpload) {
            response.setHeader("Content-Disposition", "attachment; filename=" + str.getFileName() + ".xml");
            InputStream in = new ByteArrayInputStream(content.getBytes());
            IOUtils.copy(in, response.getOutputStream());
            return;
        }

        // We're sending back a new sync transmission (an update).
        // We need to check the local server about whether we should apply compression.
        boolean useCompression = Boolean.parseBoolean(Context.getAdministrationService()
                .getGlobalProperty(SyncConstants.PROPERTY_ENABLE_COMPRESSION, "true"));
        log.debug("Global property sychronization.enable_compression = " + useCompression);

        // Otherwise, all other requests are compressed and sent back to the client 
        ConnectionRequest syncRequest = new ConnectionRequest(content, useCompression);
        log.info("Compressed content length: " + syncRequest.getContentLength());
        log.info("Compression Checksum: " + syncRequest.getChecksum());
        log.info("Full Content to send: " + content);

        response.setContentLength((int) syncRequest.getContentLength());
        response.addHeader("Enable-Compression", String.valueOf(useCompression));
        response.addHeader("Content-Checksum", String.valueOf(syncRequest.getChecksum()));
        response.addHeader("Content-Encoding", "gzip");

        // Write compressed sync data to response
        InputStream in = new ByteArrayInputStream(syncRequest.getBytes());
        IOUtils.copy(in, response.getOutputStream());

        return;
    }

    private void sendCloneResponse(String content, HttpServletResponse response, boolean isUpload)
            throws Exception {

        boolean useCompression = Boolean.parseBoolean(Context.getAdministrationService()
                .getGlobalProperty(SyncConstants.PROPERTY_ENABLE_COMPRESSION, "true"));
        log.debug("Global property sychronization.enable_compression = " + useCompression);

        // Otherwise, all other requests are compressed and sent back to the
        // client
        ConnectionRequest syncRequest = new ConnectionRequest(content, useCompression);
        log.info("Compressed content length: " + syncRequest.getContentLength());
        log.info("Compression Checksum: " + syncRequest.getChecksum());

        response.setContentLength((int) syncRequest.getContentLength());
        response.addHeader("Enable-Compression", String.valueOf(useCompression));
        response.addHeader("Content-Checksum", String.valueOf(syncRequest.getChecksum()));
        response.addHeader("Content-Encoding", "gzip");

        // Write compressed sync data to response
        InputStream in = new ByteArrayInputStream(syncRequest.getBytes());
        IOUtils.copy(in, response.getOutputStream());

        return;

    }
}