← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 19296: WIP proper error reporting on failed GML parsing during import

 

------------------------------------------------------------
revno: 19296
committer: Halvdan Hoem Grelland <halvdanhg@xxxxxxxxx>
branch nick: dhis2
timestamp: Fri 2015-06-05 14:56:34 +0200
message:
  WIP proper error reporting on failed GML parsing during import
added:
  dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/gml/GmlPreProcessingResult.java
modified:
  dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/gml/DefaultGmlImportService.java
  dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/gml/GmlImportService.java
  dhis-2/dhis-services/dhis-service-dxf2/src/test/java/org/hisp/dhis/dxf2/gml/GmlImportServiceTest.java
  dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/scheduling/SpringScheduler.java
  dhis-2/dhis-web/dhis-web-importexport/src/main/java/org/hisp/dhis/importexport/action/dxf2/MetaDataImportAction.java
  dhis-2/dhis-web/dhis-web-importexport/src/main/java/org/hisp/dhis/importexport/action/util/ImportMetaDataGmlTask.java
  dhis-2/dhis-web/dhis-web-importexport/src/main/resources/org/hisp/dhis/importexport/i18n_module.properties
  dhis-2/dhis-web/dhis-web-importexport/src/main/webapp/dhis-web-importexport/importMetaDataSummary.vm


--
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-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/gml/DefaultGmlImportService.java'
--- dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/gml/DefaultGmlImportService.java	2015-06-03 15:00:19 +0000
+++ dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/gml/DefaultGmlImportService.java	2015-06-05 12:56:34 +0000
@@ -32,6 +32,8 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Maps;
+import org.apache.commons.io.IOUtils;
+import org.hibernate.Hibernate;
 import org.hisp.dhis.common.IdentifiableObjectManager;
 import org.hisp.dhis.common.IdentifiableProperty;
 import org.hisp.dhis.common.MergeStrategy;
@@ -88,13 +90,91 @@
     // GmlImportService implementation
     // -------------------------------------------------------------------------
 
+    @Transactional
+    @Override
+    public GmlPreProcessingResult preProcessGml( InputStream inputStream )
+    {
+        InputStream dxfStream = null;
+        MetaData metaData = null;
+
+        try
+        {
+            dxfStream = transformGml( inputStream );
+            metaData = renderService.fromXml( dxfStream, MetaData.class );
+        }
+        catch ( IOException | TransformerException e )
+        {
+            return GmlPreProcessingResult.failure( e );
+        }
+        finally
+        {
+            IOUtils.closeQuietly( dxfStream );
+        }
+
+        Map<String, OrganisationUnit> uidMap  = Maps.newHashMap(), codeMap = Maps.newHashMap(), nameMap = Maps.newHashMap();
+
+        matchAndFilterOnIdentifiers( metaData.getOrganisationUnits(), uidMap, codeMap, nameMap );
+
+        Map<String, OrganisationUnit> persistedUidMap  = getMatchingPersistedOrgUnits( uidMap.keySet(),  IdentifiableProperty.UID );
+        Map<String, OrganisationUnit> persistedCodeMap = getMatchingPersistedOrgUnits( codeMap.keySet(), IdentifiableProperty.CODE );
+        Map<String, OrganisationUnit> persistedNameMap = getMatchingPersistedOrgUnits( nameMap.keySet(), IdentifiableProperty.NAME );
+
+        Iterator<OrganisationUnit> persistedIterator = Iterators.concat( persistedUidMap.values().iterator(),
+            persistedCodeMap.values().iterator(), persistedNameMap.values().iterator() );
+
+        while ( persistedIterator.hasNext() )
+        {
+            OrganisationUnit persisted = persistedIterator.next(), imported = null;
+
+            if ( !Strings.isNullOrEmpty( persisted.getUid() ) && uidMap.containsKey( persisted.getUid() ) )
+            {
+                imported = uidMap.get( persisted.getUid() );
+            }
+            else if ( !Strings.isNullOrEmpty( persisted.getCode() ) && codeMap.containsKey( persisted.getCode() ) )
+            {
+                imported = codeMap.get( persisted.getCode() );
+            }
+            else if ( !Strings.isNullOrEmpty( persisted.getName() ) && nameMap.containsKey( persisted.getName() ) )
+            {
+                imported = nameMap.get( persisted.getName() );
+            }
+
+            if ( imported == null || imported.getCoordinates() == null || imported.getFeatureType() == null )
+            {
+                continue; // Failed to dereference a persisted entity for this org unit or geo data incomplete/missing, therefore ignore
+            }
+
+            mergeNonGeoData( persisted, imported );
+        }
+
+        return GmlPreProcessingResult.success( metaData );
+    }
+
     @Override
     public MetaData fromGml( InputStream inputStream )
         throws IOException, TransformerException
     {
-        InputStream dxfStream = transformGml( inputStream );
-        MetaData metaData = renderService.fromXml( dxfStream, MetaData.class );
-        dxfStream.close();
+        InputStream dxfStream;
+        MetaData metaData;
+
+        try
+        {
+            dxfStream = transformGml( inputStream );
+        }
+        catch (Exception e)
+        {
+            dxfStream = null;
+        }
+
+        if(dxfStream != null)
+        {
+            metaData = renderService.fromXml( dxfStream, MetaData.class );
+            dxfStream.close();
+        }
+        else
+        {
+            return null;
+        }
 
         Map<String, OrganisationUnit> uidMap  = Maps.newHashMap(), codeMap = Maps.newHashMap(), nameMap = Maps.newHashMap();
 
@@ -143,6 +223,7 @@
         importService.importMetaData( userUid, fromGml( inputStream ), importOptions, taskId );
     }
 
+    @Transactional
     @Override
     public void importGml( MetaData metaData, String userUid, ImportOptions importOptions, TaskId taskId )
     {

=== modified file 'dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/gml/GmlImportService.java'
--- dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/gml/GmlImportService.java	2015-06-03 15:00:19 +0000
+++ dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/gml/GmlImportService.java	2015-06-05 12:56:34 +0000
@@ -62,6 +62,24 @@
         throws IOException, TransformerException;
 
     /**
+     * Pre-process a GML document. The process, in short, entails the following:
+     * <ol>
+     *     <li>Parse the GML payload and transform it into DXF2 format</li>
+     *     <li>Get the given identifiers (uid, code or name) from the parsed payload and fetch
+     *     the corresponding entities from the DB</li>
+     *     <li>Merge the geospatial data given in the input GML into DB entities and return</li>
+     * </ol>
+     *
+     * The result of this process in returned in a {@link GmlPreProcessingResult} which
+     * encapsulates the returned {@link MetaData} object or the exception in cause of parse
+     * failure due to IO errors or malformed input.
+     *
+     * @param gmlInputStream the InputStream providing the GML input.
+     * @return a GmlPreProcessingResult representing the end result of the process.
+     */
+    GmlPreProcessingResult preProcessGml( InputStream gmlInputStream );
+
+    /**
      * Imports GML data and merges the geospatial data updates into the database.
      * See {@link #fromGml(InputStream)} for details on the underlying process.
      *

=== added file 'dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/gml/GmlPreProcessingResult.java'
--- dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/gml/GmlPreProcessingResult.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/gml/GmlPreProcessingResult.java	2015-06-05 12:56:34 +0000
@@ -0,0 +1,70 @@
+package org.hisp.dhis.dxf2.gml;
+
+import org.hisp.dhis.dxf2.metadata.MetaData;
+
+import java.io.InputStream;
+
+/**
+ * Wraps the result of {@link GmlImportService#preProcessGml(InputStream)}.
+ * This is necessary when performing GML import on a context where exceptions
+ * due to malformed input cannot be caught, thus leaving the user uninformed of
+ * the error. This class will wrap the failure and provide the Throwable to the
+ * consuming class on error or the resulting MetaData object on success.
+ *
+ * @author Halvdan Hoem grelland <halvdanhg@xxxxxxxxx>
+ */
+public final class GmlPreProcessingResult
+{
+    private boolean isSuccess;
+    private MetaData resultMetaData;
+    private Throwable throwable;
+
+    private GmlPreProcessingResult(){}
+
+    public static GmlPreProcessingResult success( MetaData resultMetaData ) {
+        GmlPreProcessingResult result = new GmlPreProcessingResult();
+        result.setResultMetaData( resultMetaData );
+        result.setSuccess( true );
+
+        return result;
+    }
+
+    public static GmlPreProcessingResult failure( Throwable throwable )
+    {
+        GmlPreProcessingResult result = new GmlPreProcessingResult();
+        result.setThrowable( throwable );
+        result.setSuccess( false );
+
+        return result;
+    }
+
+    private void setSuccess( boolean isSuccess )
+    {
+        this.isSuccess = isSuccess;
+    }
+
+    public boolean isSuccess()
+    {
+        return isSuccess;
+    }
+
+    private void setResultMetaData( MetaData resultMetaData )
+    {
+        this.resultMetaData = resultMetaData;
+    }
+
+    public MetaData getResultMetaData()
+    {
+        return resultMetaData;
+    }
+
+    private void setThrowable( Throwable throwable )
+    {
+        this.throwable = throwable;
+    }
+
+    public Throwable getThrowable()
+    {
+        return throwable;
+    }
+}

=== modified file 'dhis-2/dhis-services/dhis-service-dxf2/src/test/java/org/hisp/dhis/dxf2/gml/GmlImportServiceTest.java'
--- dhis-2/dhis-services/dhis-service-dxf2/src/test/java/org/hisp/dhis/dxf2/gml/GmlImportServiceTest.java	2015-01-17 07:41:26 +0000
+++ dhis-2/dhis-services/dhis-service-dxf2/src/test/java/org/hisp/dhis/dxf2/gml/GmlImportServiceTest.java	2015-06-05 12:56:34 +0000
@@ -117,4 +117,6 @@
         assertEquals( 1, units.get( "Blindern").getCoordinatesAsList().get( 0 ).getNumberOfCoordinates() );
         assertEquals( 76, units.get( "Forskningsparken" ).getCoordinatesAsList().get(0).getNumberOfCoordinates() );
     }
+
+    // TODO Add test for GmlImportService#preProcessGml(InputStream)
 }

=== modified file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/scheduling/SpringScheduler.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/scheduling/SpringScheduler.java	2015-05-30 13:36:07 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/scheduling/SpringScheduler.java	2015-06-05 12:56:34 +0000
@@ -77,7 +77,7 @@
     {
         taskExecutor.execute( task );
     }
-    
+
     @Override
     public boolean scheduleTask( String key, Runnable task, String cronExpr )
     {        

=== modified file 'dhis-2/dhis-web/dhis-web-importexport/src/main/java/org/hisp/dhis/importexport/action/dxf2/MetaDataImportAction.java'
--- dhis-2/dhis-web/dhis-web-importexport/src/main/java/org/hisp/dhis/importexport/action/dxf2/MetaDataImportAction.java	2015-05-28 16:10:07 +0000
+++ dhis-2/dhis-web/dhis-web-importexport/src/main/java/org/hisp/dhis/importexport/action/dxf2/MetaDataImportAction.java	2015-06-05 12:56:34 +0000
@@ -187,7 +187,7 @@
         }
         else if ( "gml".equals( importFormat ) )
         {
-            scheduler.executeTask( new ImportMetaDataGmlTask( userId, gmlImportService, importOptions, in, taskId ) );
+            scheduler.executeTask( new ImportMetaDataGmlTask( userId, gmlImportService, notifier, importOptions, in, taskId ) );
         }
         else if ( "json".equals( importFormat ) || "xml".equals( importFormat ) )
         {

=== modified file 'dhis-2/dhis-web/dhis-web-importexport/src/main/java/org/hisp/dhis/importexport/action/util/ImportMetaDataGmlTask.java'
--- dhis-2/dhis-web/dhis-web-importexport/src/main/java/org/hisp/dhis/importexport/action/util/ImportMetaDataGmlTask.java	2015-04-11 14:06:51 +0000
+++ dhis-2/dhis-web/dhis-web-importexport/src/main/java/org/hisp/dhis/importexport/action/util/ImportMetaDataGmlTask.java	2015-06-05 12:56:34 +0000
@@ -28,14 +28,18 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+import org.apache.commons.lang.exception.ExceptionUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.hisp.dhis.dxf2.common.ImportOptions;
 import org.hisp.dhis.dxf2.gml.GmlImportService;
+import org.hisp.dhis.dxf2.gml.GmlPreProcessingResult;
 import org.hisp.dhis.scheduling.TaskId;
+import org.hisp.dhis.system.notification.NotificationLevel;
+import org.hisp.dhis.system.notification.Notifier;
+import org.springframework.web.util.HtmlUtils;
+import org.xml.sax.SAXParseException;
 
-import javax.xml.transform.TransformerException;
-import java.io.IOException;
 import java.io.InputStream;
 
 /**
@@ -50,12 +54,15 @@
 
     private String userUid;
 
+
     // -------------------------------------------------------------------------
     // Dependencies
     // -------------------------------------------------------------------------
 
     private GmlImportService gmlImportService;
 
+    private Notifier notifier;
+
     private ImportOptions importOptions;
 
     private InputStream inputStream;
@@ -64,11 +71,12 @@
     // Constructors
     // -------------------------------------------------------------------------
 
-    public ImportMetaDataGmlTask( String userUid, GmlImportService gmlImportService,
+    public ImportMetaDataGmlTask( String userUid, GmlImportService gmlImportService, Notifier notifier,
         ImportOptions importOptions, InputStream inputStream, TaskId taskId )
     {
         this.userUid = userUid;
         this.gmlImportService = gmlImportService;
+        this.notifier = notifier;
         this.importOptions = importOptions;
         this.inputStream = inputStream;
         this.taskId = taskId;
@@ -83,15 +91,37 @@
     {
         importOptions.setImportStrategy( "update" ); // Force update only for GML import
 
-        try
-        {
-            gmlImportService.importGml( inputStream, userUid, importOptions, taskId );
-        }
-        catch ( IOException | TransformerException e )
-        {
-            log.error( "Unable to read GML data from input stream", e );
-
-            throw new RuntimeException( "Failed to parse GML input stream", e );
-        }
+        GmlPreProcessingResult gmlPreProcessingResult = gmlImportService.preProcessGml( inputStream );
+
+        if ( !gmlPreProcessingResult.isSuccess() )
+        {
+            Throwable throwable = gmlPreProcessingResult.getThrowable();
+            String message = createErrorMessage( throwable );
+
+            notifier.notify( taskId, NotificationLevel.ERROR, message, false );
+            log.error( "GML import failed: " + message, throwable );
+
+            return;
+        }
+
+        gmlImportService.importGml( gmlPreProcessingResult.getResultMetaData(), userUid, importOptions, taskId );
+    }
+
+    private String createErrorMessage( Throwable throwable )
+    {
+        String message = "";
+        Throwable rootThrowable = ExceptionUtils.getRootCause( throwable );
+
+        if ( rootThrowable instanceof SAXParseException )
+        {
+            SAXParseException e = (SAXParseException) rootThrowable;
+            message += "Syntax error on line " + e.getLineNumber() + ". " + e.getMessage();
+        }
+        else
+        {
+            message += rootThrowable.getMessage();
+        }
+
+        return HtmlUtils.htmlEscape( message );
     }
 }

=== modified file 'dhis-2/dhis-web/dhis-web-importexport/src/main/resources/org/hisp/dhis/importexport/i18n_module.properties'
--- dhis-2/dhis-web/dhis-web-importexport/src/main/resources/org/hisp/dhis/importexport/i18n_module.properties	2015-02-20 11:17:31 +0000
+++ dhis-2/dhis-web/dhis-web-importexport/src/main/resources/org/hisp/dhis/importexport/i18n_module.properties	2015-06-05 12:56:34 +0000
@@ -300,6 +300,7 @@
 ignored=Ignored
 conflicts=Conflicts
 no_conflicts_found=No conflicts found
+no_import_summary_available=No summary available
 type=Type
 count=Count
 export_as_xml=Export as XML

=== modified file 'dhis-2/dhis-web/dhis-web-importexport/src/main/webapp/dhis-web-importexport/importMetaDataSummary.vm'
--- dhis-2/dhis-web/dhis-web-importexport/src/main/webapp/dhis-web-importexport/importMetaDataSummary.vm	2013-03-21 09:15:24 +0000
+++ dhis-2/dhis-web/dhis-web-importexport/src/main/webapp/dhis-web-importexport/importMetaDataSummary.vm	2015-06-05 12:56:34 +0000
@@ -1,4 +1,6 @@
 <h3>$i18n.getString( "import_summary" )</h3>
+
+#if( $summary ) ## ImportSummary can be null on failed GML import (pre-processing fails)
 <h4>$i18n.getString( "import_count" )</h4>
 
 $summary.importCount.imported Imported<br/>
@@ -54,3 +56,6 @@
 #else
 <p>$i18n.getString( "no_conflicts_found" )</p>
 #end
+#else
+    $i18n.getString( "no_summary_available" )
+#end
\ No newline at end of file


Follow ups