← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 18657: wip, support partial update of object (PATCH)

 

------------------------------------------------------------
revno: 18657
committer: Morten Olav Hansen <mortenoh@xxxxxxxxx>
branch nick: dhis2
timestamp: Mon 2015-03-23 12:42:24 +0700
message:
  wip, support partial update of object (PATCH)
modified:
  dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/render/DefaultRenderService.java
  dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/render/RenderService.java
  dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AbstractCrudController.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-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/render/DefaultRenderService.java'
--- dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/render/DefaultRenderService.java	2015-01-17 07:41:26 +0000
+++ dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/render/DefaultRenderService.java	2015-03-23 05:42:24 +0000
@@ -104,6 +104,12 @@
     }
 
     @Override
+    public <T> T fromJson( String input, Class<T> klass ) throws IOException
+    {
+        return jsonMapper.readValue( input, klass );
+    }
+
+    @Override
     public <T> void toXml( OutputStream output, T value ) throws IOException
     {
         xmlMapper.writeValue( output, value );
@@ -121,10 +127,26 @@
         return xmlMapper.readValue( input, klass );
     }
 
+    @Override
+    public <T> T fromXml( String input, Class<T> klass ) throws IOException
+    {
+        return xmlMapper.readValue( input, klass );
+    }
+
     //--------------------------------------------------------------------------
     // Helpers
     //--------------------------------------------------------------------------
 
+    public ObjectMapper getJsonMapper()
+    {
+        return jsonMapper;
+    }
+
+    public XmlMapper getXmlMapper()
+    {
+        return xmlMapper;
+    }
+
     private void configureObjectMappers()
     {
         ObjectMapper[] objectMappers = new ObjectMapper[]{ jsonMapper, xmlMapper };

=== modified file 'dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/render/RenderService.java'
--- dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/render/RenderService.java	2015-01-17 07:41:26 +0000
+++ dhis-2/dhis-services/dhis-service-dxf2/src/main/java/org/hisp/dhis/dxf2/render/RenderService.java	2015-03-23 05:42:24 +0000
@@ -47,9 +47,13 @@
 
     <T> T fromJson( InputStream input, Class<T> klass ) throws IOException;
 
+    <T> T fromJson( String input, Class<T> klass ) throws IOException;
+
     <T> void toXml( OutputStream output, T value ) throws IOException;
 
     <T> void toXml( OutputStream output, T value, Class<?> klass ) throws IOException;
 
     <T> T fromXml( InputStream input, Class<T> klass ) throws IOException;
+
+    <T> T fromXml( String input, Class<T> klass ) throws IOException;
 }

=== modified file 'dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AbstractCrudController.java'
--- dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AbstractCrudController.java	2015-03-20 03:00:33 +0000
+++ dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AbstractCrudController.java	2015-03-23 05:42:24 +0000
@@ -28,6 +28,9 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
 import com.google.common.base.Enums;
 import com.google.common.base.Joiner;
 import com.google.common.base.Optional;
@@ -47,6 +50,7 @@
 import org.hisp.dhis.dxf2.metadata.ImportService;
 import org.hisp.dhis.dxf2.metadata.ImportTypeSummary;
 import org.hisp.dhis.dxf2.objectfilter.ObjectFilterService;
+import org.hisp.dhis.dxf2.render.DefaultRenderService;
 import org.hisp.dhis.dxf2.render.RenderService;
 import org.hisp.dhis.hibernate.exception.CreateAccessDeniedException;
 import org.hisp.dhis.hibernate.exception.DeleteAccessDeniedException;
@@ -76,6 +80,7 @@
 import org.hisp.dhis.webapi.webdomain.WebOptions;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.MediaType;
+import org.springframework.util.StreamUtils;
 import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -88,6 +93,7 @@
 import java.io.IOException;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
+import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -235,6 +241,86 @@
         return getObjectInternal( pvUid, rpParameters, Lists.<String>newArrayList(), Lists.newArrayList( pvProperty + fieldFilter ), translateOptions );
     }
 
+    @RequestMapping( value = "/{uid}", method = RequestMethod.PATCH )
+    public void partialUpdateObject(
+        @PathVariable( "uid" ) String pvUid, @RequestParam Map<String, String> rpParameters,
+        HttpServletRequest request, HttpServletResponse response ) throws IOException
+    {
+        WebOptions options = new WebOptions( rpParameters );
+        List<T> entities = getEntity( pvUid, options );
+
+        if ( entities.isEmpty() )
+        {
+            ContextUtils.notFoundResponse( response, getEntityName() + " does not exist: " + pvUid );
+            return;
+        }
+
+        T persistedObject = entities.get( 0 );
+
+        if ( !aclService.canUpdate( currentUserService.getCurrentUser(), persistedObject ) )
+        {
+            throw new UpdateAccessDeniedException( "You don't have the proper permissions to update this object." );
+        }
+
+        String payload = StreamUtils.copyToString( request.getInputStream(), Charset.forName( "UTF-8" ) );
+        List<String> properties = new ArrayList<>();
+        T object = null;
+
+        if ( isJson( request ) )
+        {
+            properties = getJsonProperties( payload );
+            object = renderService.fromJson( payload, getEntityClass() );
+        }
+        else if ( isXml( request ) )
+        {
+            properties = getXmlProperties( payload );
+            object = renderService.fromXml( payload, getEntityClass() );
+        }
+
+        properties = getPersistedProperties( properties );
+
+        if ( properties.isEmpty() || object == null )
+        {
+            response.setStatus( HttpServletResponse.SC_NO_CONTENT );
+            return;
+        }
+
+        response.setStatus( HttpServletResponse.SC_NO_CONTENT );
+    }
+
+    private List<String> getJsonProperties( String payload ) throws IOException
+    {
+        ObjectMapper mapper = ((DefaultRenderService) renderService).getJsonMapper();
+        JsonNode root = mapper.readTree( payload );
+
+        return Lists.newArrayList( root.fieldNames() );
+    }
+
+    private List<String> getXmlProperties( String payload ) throws IOException
+    {
+        XmlMapper mapper = ((DefaultRenderService) renderService).getXmlMapper();
+        JsonNode root = mapper.readTree( payload );
+
+        return Lists.newArrayList( root.fieldNames() );
+    }
+
+    private List<String> getPersistedProperties( List<String> properties )
+    {
+        List<String> persistedProperties = new ArrayList<>();
+
+        Schema schema = getSchema();
+
+        for ( String property : properties )
+        {
+            if ( schema.havePersistedProperty( property ) )
+            {
+                persistedProperties.add( property );
+            }
+        }
+
+        return persistedProperties;
+    }
+
     @RequestMapping( value = "/{uid}/{property}", method = { RequestMethod.PUT, RequestMethod.PATCH } )
     public void updateObjectProperty(
         @PathVariable( "uid" ) String pvUid, @PathVariable( "property" ) String pvProperty, @RequestParam Map<String, String> rpParameters,
@@ -952,7 +1038,16 @@
      */
     protected boolean isJson( HttpServletRequest request )
     {
-        return isCompatibleWith( request.getContentType(), MediaType.APPLICATION_JSON );
+        String type = request.getContentType();
+        type = !StringUtils.isEmpty( type ) ? type : MediaType.APPLICATION_JSON_VALUE;
+
+        // allow type to be overridden by path extension
+        if ( request.getPathInfo().endsWith( ".json" ) )
+        {
+            type = MediaType.APPLICATION_JSON_VALUE;
+        }
+
+        return isCompatibleWith( type, MediaType.APPLICATION_JSON );
     }
 
     /**
@@ -963,7 +1058,16 @@
      */
     protected boolean isXml( HttpServletRequest request )
     {
-        return isCompatibleWith( request.getContentType(), MediaType.APPLICATION_XML );
+        String type = request.getContentType();
+        type = !StringUtils.isEmpty( type ) ? type : MediaType.APPLICATION_JSON_VALUE;
+
+        // allow type to be overridden by path extension
+        if ( request.getPathInfo().endsWith( ".xml" ) )
+        {
+            type = MediaType.APPLICATION_XML_VALUE;
+        }
+
+        return isCompatibleWith( type, MediaType.APPLICATION_XML );
     }
 
     protected boolean isCompatibleWith( String type, MediaType mediaType )