← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 20575: Made uploads from DHIS2 to file store (local or external providers alike) asynchronous. When a Fi...

 

------------------------------------------------------------
revno: 20575
committer: Halvdan Hoem Grelland <halvdanhg@xxxxxxxxx>
branch nick: dhis2
timestamp: Thu 2015-10-08 01:01:57 +0200
message:
  Made uploads from DHIS2 to file store (local or external providers alike) asynchronous. When a FileResource is posted the supplied content is verified to the best of our abilities and a FileResource metadata object is created, saved and immediately returned to the requester. In the meantime the supplied file is temporarily saved to disk on the server and an upload/put to the file store is performed in a background thread. Whilst the file is not yet uploaded/arrived at the destination file store it will have a FileResourceStorageStatus of PENDING and any GET of the file will be blocked (since there is no resource to return yet), yet the FileResource can still be referenced in a DataValue. Upon completed (or failed) upload of the resource the status is switched to STORED and GETs will return the content.
added:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/fileresource/FileResourceStorageStatus.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/fileresource/FileResourceUploadCallbackProvider.java
modified:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/fileresource/FileResource.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/fileresource/FileResourceContentStore.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/fileresource/FileResourceService.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/fileresource/DefaultFileResourceService.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/fileresource/JCloudsFileResourceContentStore.java
  dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml
  dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/fileresource/hibernate/FileResource.hbm.xml
  dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataValueController.java
  dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/FileResourceController.java


--
lp:dhis2
https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk

Your team DHIS 2 developers is subscribed to branch lp:dhis2.
To unsubscribe from this branch go to https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk/+edit-subscription
=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/fileresource/FileResource.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/fileresource/FileResource.java	2015-10-01 07:51:29 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/fileresource/FileResource.java	2015-10-07 23:01:57 +0000
@@ -76,6 +76,11 @@
      */
     private FileResourceDomain domain;
 
+    /**
+     * Current storage status of content.
+     */
+    private FileResourceStorageStatus storageStatus = FileResourceStorageStatus.NONE;
+
     // -------------------------------------------------------------------------
     // Constructors
     // -------------------------------------------------------------------------
@@ -175,6 +180,19 @@
         return assigned;
     }
 
+    public void setStorageStatus( FileResourceStorageStatus storageStatus )
+    {
+        this.storageStatus = storageStatus;
+    }
+
+    @JsonProperty
+    @JsonView( { DetailedView.class, ExportView.class } )
+    @JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0 )
+    public FileResourceStorageStatus getStorageStatus()
+    {
+        return storageStatus;
+    }
+
     public void setAssigned( boolean assigned )
     {
         this.assigned = assigned;

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/fileresource/FileResourceContentStore.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/fileresource/FileResourceContentStore.java	2015-10-07 13:46:41 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/fileresource/FileResourceContentStore.java	2015-10-07 23:01:57 +0000
@@ -30,6 +30,7 @@
 
 import com.google.common.io.ByteSource;
 
+import java.io.File;
 import java.net.URI;
 
 /**
@@ -55,6 +56,16 @@
     String saveFileResourceContent( String key, ByteSource content, long size, String contentMd5 );
 
     /**
+     * Save the content of the file to the file store.
+     * @param key the key to use. Must be unique in the file store.
+     * @param file the file. The file will be consumed and deleted upon completion.
+     * @param size the byte length of the file.
+     * @param contentMd5 the MD5 digest of the file.
+     * @return the key on success or null if saving failed.
+     */
+    String saveFileResourceContent( String key, File file, long size, String contentMd5 );
+
+    /**
      * Delete the content bytes of a file resource.
      * @param key the key.
      */

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/fileresource/FileResourceService.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/fileresource/FileResourceService.java	2015-10-06 22:00:49 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/fileresource/FileResourceService.java	2015-10-07 23:01:57 +0000
@@ -30,6 +30,7 @@
 
 import com.google.common.io.ByteSource;
 
+import java.io.File;
 import java.net.URI;
 import java.util.List;
 
@@ -43,7 +44,9 @@
     List<FileResource> getFileResources( List<String> uids );
     
     String saveFileResource( FileResource fileResource, ByteSource content );
-    
+
+    String saveFileResourceAsync( FileResource fileResource, File file );
+
     void deleteFileResource( String uid );
     
     ByteSource getFileResourceContent( FileResource fileResource );

=== added file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/fileresource/FileResourceStorageStatus.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/fileresource/FileResourceStorageStatus.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/fileresource/FileResourceStorageStatus.java	2015-10-07 23:01:57 +0000
@@ -0,0 +1,40 @@
+package org.hisp.dhis.fileresource;
+
+/*
+ * Copyright (c) 2004-2015, University of Oslo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * Neither the name of the HISP project nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @author Halvdan Hoem Grelland
+ */
+public enum FileResourceStorageStatus
+{
+    NONE,       // No content stored
+    PENDING,    // In transit to store, not available
+    FAILED,     // Storing the resource failed
+    STORED      // Is available from store
+}

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/fileresource/DefaultFileResourceService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/fileresource/DefaultFileResourceService.java	2015-10-07 13:46:41 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/fileresource/DefaultFileResourceService.java	2015-10-07 23:01:57 +0000
@@ -28,14 +28,16 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+import com.google.common.io.ByteSource;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.hisp.dhis.common.GenericIdentifiableObjectStore;
+import org.hisp.dhis.system.scheduling.Scheduler;
 import org.springframework.transaction.annotation.Transactional;
-
-import com.google.common.io.ByteSource;
-
+import org.springframework.util.concurrent.ListenableFuture;
+
+import java.io.File;
 import java.net.URI;
 import java.util.List;
 
@@ -65,6 +67,20 @@
         this.fileResourceContentStore = fileResourceContentStore;
     }
 
+    private Scheduler scheduler;
+
+    public void setScheduler( Scheduler scheduler )
+    {
+        this.scheduler = scheduler;
+    }
+
+    private FileResourceUploadCallbackProvider uploadCallbackProvider;
+
+    public void setUploadCallbackProvider( FileResourceUploadCallbackProvider uploadCallbackProvider )
+    {
+        this.uploadCallbackProvider = uploadCallbackProvider;
+    }
+
     // -------------------------------------------------------------------------
     // FileResourceService implementation
     // -------------------------------------------------------------------------
@@ -109,6 +125,26 @@
 
     @Transactional
     @Override
+    public String saveFileResourceAsync( FileResource fileResource, File file )
+    {
+        fileResource.setStorageStatus( FileResourceStorageStatus.PENDING );
+        fileResourceStore.save( fileResource );
+
+        String storageKey = getRelativeStorageKey( fileResource );
+
+        ListenableFuture<String> saveContentTask =
+            scheduler.executeTask( () -> fileResourceContentStore.saveFileResourceContent(
+                storageKey, file, fileResource.getContentLength(), fileResource.getContentMd5() ) );
+
+        String uid = fileResource.getUid();
+
+        saveContentTask.addCallback( uploadCallbackProvider.getCallback( uid ) );
+
+        return uid;
+    }
+
+    @Transactional
+    @Override
     public void deleteFileResource( String uid )
     {
         if ( uid == null )

=== added file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/fileresource/FileResourceUploadCallbackProvider.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/fileresource/FileResourceUploadCallbackProvider.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/fileresource/FileResourceUploadCallbackProvider.java	2015-10-07 23:01:57 +0000
@@ -0,0 +1,82 @@
+package org.hisp.dhis.fileresource;
+
+/*
+ * Copyright (c) 2004-2015, University of Oslo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * Neither the name of the HISP project nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.hisp.dhis.common.IdentifiableObjectManager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.concurrent.ListenableFutureCallback;
+
+/**
+ * @author Halvdan Hoem Grelland
+ */
+public class FileResourceUploadCallbackProvider
+{
+    Log log = LogFactory.getLog( FileResourceUploadCallbackProvider.class );
+
+    @Autowired
+    private IdentifiableObjectManager idObjectManager;
+
+    public ListenableFutureCallback<String> getCallback( String fileResourceUid )
+    {
+        return new ListenableFutureCallback<String>()
+        {
+            @Override
+            public void onFailure( Throwable ex )
+            {
+                log.error( "Saving file content failed", ex );
+
+                FileResource fetchedFileResource = idObjectManager.get( FileResource.class, fileResourceUid );
+                fetchedFileResource.setStorageStatus( FileResourceStorageStatus.FAILED );
+                idObjectManager.update( fetchedFileResource );
+            }
+
+            @Override
+            public void onSuccess( String result )
+            {
+                log.info( "File content uploaded: " + result );
+
+                FileResource fetchedFileResource = idObjectManager.get( FileResource.class, fileResourceUid );
+
+                if ( result != null && fetchedFileResource != null )
+                {
+                    fetchedFileResource.setStorageStatus( FileResourceStorageStatus.STORED );
+                }
+                else
+                {
+                    log.error( "Conflict: content was stored but FileResource with uid: " + fileResourceUid + " could not be found." );
+                    return;
+                }
+
+                idObjectManager.update( fetchedFileResource );
+            }
+        };
+    }
+}

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/fileresource/JCloudsFileResourceContentStore.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/fileresource/JCloudsFileResourceContentStore.java	2015-10-07 13:46:41 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/fileresource/JCloudsFileResourceContentStore.java	2015-10-07 23:01:57 +0000
@@ -49,9 +49,11 @@
 import org.jclouds.http.HttpRequest;
 import org.joda.time.Minutes;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
+import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -266,6 +268,35 @@
     }
 
     @Override
+    public String saveFileResourceContent( String key, File file, long size, String contentMd5 )
+    {
+        Blob blob = blobStore.blobBuilder( key )
+            .payload( file )
+            .contentLength( size )
+            .contentMD5( HashCode.fromString( contentMd5 ) )
+            .build();
+
+        if ( blob == null )
+        {
+            return null;
+        }
+
+        putBlob( blob );
+
+        try
+        {
+            Files.deleteIfExists( file.toPath() );
+        }
+        catch ( IOException ioe )
+        {
+            // Intentionally ignored. If it can't be deleted, it can't be deleted
+            log.warn( "Temporary file '" + file.toPath() + "' could not be deleted.", ioe );
+        }
+
+        return key;
+    }
+
+    @Override
     public void deleteFileResourceContent( String key )
     {
         deleteBlob( key );
@@ -281,7 +312,7 @@
             return null;
         }
 
-        HttpRequest httpRequest = null;
+        HttpRequest httpRequest;
 
         try
         {

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml'
--- dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml	2015-10-07 04:35:59 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml	2015-10-07 23:01:57 +0000
@@ -578,6 +578,10 @@
     <property name="locationManager" ref="locationManager" />
   </bean>
 
+  <bean id="org.hisp.dhis.fileresource.FileResourceUploadCallbackProvider"
+    class="org.hisp.dhis.fileresource.FileResourceUploadCallbackProvider"
+    scope="prototype" />
+
   <bean id="org.hisp.dhis.keyjsonvalue.KeyJsonValueStore" class="org.hisp.dhis.keyjsonvalue.hibernate.HibernateKeyJsonValueStore">
     <property name="clazz" value="org.hisp.dhis.keyjsonvalue.KeyJsonValue" />
     <property name="sessionFactory" ref="sessionFactory" />
@@ -588,6 +592,8 @@
   <bean id="org.hisp.dhis.fileresource.FileResourceService" class="org.hisp.dhis.fileresource.DefaultFileResourceService">
     <property name="fileResourceStore" ref="org.hisp.dhis.fileresource.FileResourceStore" />
     <property name="fileResourceContentStore" ref="org.hisp.dhis.fileresource.FileResourceContentStore" />
+    <property name="scheduler" ref="scheduler" />
+    <property name="uploadCallbackProvider" ref="org.hisp.dhis.fileresource.FileResourceUploadCallbackProvider" />
   </bean>
 
   <bean id="org.hisp.dhis.dataelement.DataElementOperandService" class="org.hisp.dhis.dataelement.DefaultDataElementOperandService">

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/fileresource/hibernate/FileResource.hbm.xml'
--- dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/fileresource/hibernate/FileResource.hbm.xml	2015-09-21 10:09:44 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/fileresource/hibernate/FileResource.hbm.xml	2015-10-07 23:01:57 +0000
@@ -27,8 +27,15 @@
     <property name="storageKey" column="storagekey" not-null="true" unique="true" length="1024" />
 
     <property name="assigned" column="isassigned" not-null="true" />
+    
+    <property name="storageStatus" length="40" column="storagestatus">
+      <type name="org.hibernate.type.EnumType">
+        <param name="enumClass">org.hisp.dhis.fileresource.FileResourceStorageStatus</param>
+        <param name="type">12</param>
+      </type>
+    </property>
 
-    <property name="domain" length="40">
+    <property name="domain" length="40" column="domain">
       <type name="org.hibernate.type.EnumType">
         <param name="enumClass">org.hisp.dhis.fileresource.FileResourceDomain</param>
         <param name="type">12</param>

=== modified file 'dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataValueController.java'
--- dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataValueController.java	2015-10-06 22:00:49 +0000
+++ dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/DataValueController.java	2015-10-07 23:01:57 +0000
@@ -49,10 +49,13 @@
 import org.hisp.dhis.datavalue.DataValue;
 import org.hisp.dhis.datavalue.DataValueService;
 import org.hisp.dhis.dxf2.utils.InputUtils;
+import org.hisp.dhis.dxf2.webmessage.WebMessage;
 import org.hisp.dhis.dxf2.webmessage.WebMessageException;
+import org.hisp.dhis.dxf2.webmessage.responses.FileResourceWebMessageResponse;
 import org.hisp.dhis.fileresource.FileResource;
 import org.hisp.dhis.fileresource.FileResourceDomain;
 import org.hisp.dhis.fileresource.FileResourceService;
+import org.hisp.dhis.fileresource.FileResourceStorageStatus;
 import org.hisp.dhis.organisationunit.OrganisationUnit;
 import org.hisp.dhis.organisationunit.OrganisationUnitService;
 import org.hisp.dhis.period.Period;
@@ -446,14 +449,22 @@
 
         FileResource fileResource = fileResourceService.getFileResource( uid );
 
-        if ( fileResource == null )
+        if ( fileResource == null || fileResource.getDomain() != FileResourceDomain.DATA_VALUE )
         {
-            throw new WebMessageException( WebMessageUtils.notFound( "The file resource reference id " + uid + " was not found." ) );
+            throw new WebMessageException( WebMessageUtils.notFound( "A data value file resource with id " + uid + " does not exist." ) );
         }
 
-        if ( fileResource.getDomain() != FileResourceDomain.DATA_VALUE )
+        if ( fileResource.getStorageStatus() != FileResourceStorageStatus.STORED )
         {
-            throw  new WebMessageException( WebMessageUtils.conflict( "File resource domain must be DATA_VALUE" ) );
+            // Special case:
+            //  The FileResource exists and has been tied to this DataValue, however, the underlying file
+            //  content is still in transit (PENDING) to the (most likely external) file store provider.
+
+            WebMessage webMessage = WebMessageUtils.conflict( "The content is being processed and is not available yet. Try again later.",
+                "The content requested is in transit to the file store and will be available at a later time." );
+            webMessage.setResponse( new FileResourceWebMessageResponse( fileResource ) );
+
+            throw new WebMessageException( webMessage );
         }
 
         ByteSource content = fileResourceService.getFileResourceContent( fileResource );

=== modified file 'dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/FileResourceController.java'
--- dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/FileResourceController.java	2015-10-07 13:46:41 +0000
+++ dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/FileResourceController.java	2015-10-07 23:01:57 +0000
@@ -55,8 +55,11 @@
 import org.springframework.web.bind.annotation.ResponseBody;
 import org.springframework.web.multipart.MultipartFile;
 
+import javax.servlet.ServletContext;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.file.Files;
 import java.util.Date;
 
 /**
@@ -79,6 +82,9 @@
     @Autowired
     private CurrentUserService currentUserService;
 
+    @Autowired
+    private ServletContext servletContext;
+
     // ---------------------------------------------------------------------
     // Controller methods
     // ---------------------------------------------------------------------
@@ -109,7 +115,7 @@
 
         if ( contentLength <= 0 )
         {
-            throw new WebMessageException( WebMessageUtils.conflict( "Could not read file or file is empty" ) );
+            throw new WebMessageException( WebMessageUtils.conflict( "Could not read file or file is empty." ) );
         }
 
         ByteSource bytes = new ByteSource()
@@ -135,14 +141,16 @@
         fileResource.setCreated( new Date() );
         fileResource.setUser( currentUserService.getCurrentUser() );
 
-        String uid = fileResourceService.saveFileResource( fileResource, bytes );
+        File tmpFile = toTempFile( file );
+
+        String uid = fileResourceService.saveFileResourceAsync( fileResource, tmpFile );
 
         if ( uid == null )
         {
-            throw new WebMessageException( WebMessageUtils.error( "Saving the file failed" ) );
+            throw new WebMessageException( WebMessageUtils.error( "Saving the file failed." ) );
         }
 
-        WebMessage webMessage = new WebMessage( WebMessageStatus.OK, HttpStatus.CREATED );
+        WebMessage webMessage = new WebMessage( WebMessageStatus.OK, HttpStatus.ACCEPTED );
         webMessage.setResponse( new FileResourceWebMessageResponse( fileResource ) );
 
         return webMessage;
@@ -165,4 +173,17 @@
 
         return true;
     }
+
+    private File toTempFile( MultipartFile multipartFile )
+        throws IOException
+    {
+        File tempDir = (File) servletContext.getAttribute( ServletContext.TEMPDIR );
+        File tmpFile = Files.createTempFile( tempDir.toPath(), "org.hisp.dhis", null ).toFile();
+
+        System.out.println( "TEMP FILE: " + tmpFile.getAbsolutePath() );
+
+        multipartFile.transferTo( tmpFile );
+
+        return tmpFile;
+    }
 }