← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 15487: introduces two new services: ContextServices and LinkService. LinkService will generate links aut...

 

------------------------------------------------------------
revno: 15487
committer: Morten Olav Hansen <mortenoh@xxxxxxxxx>
branch nick: dhis2
timestamp: Sat 2014-05-31 18:27:04 +0200
message:
  introduces two new services: ContextServices and LinkService. LinkService will generate links automatic based on presence of getUid and setHepref. Also extends SchemaService with new method getDynamicSchema, auto-generates schema based on class, useful for classes that have Jackson exposed properties but have no SchemaDescriptor.
added:
  dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/service/
  dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/utils/ContextService.java
  dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/utils/DefaultContextService.java
  dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/utils/DefaultLinkService.java
  dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/utils/LinkService.java
modified:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/schema/SchemaService.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/schema/DefaultSchemaService.java
  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/dataelement/DataElementOperandController.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/schema/SchemaService.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/schema/SchemaService.java	2014-05-28 11:02:29 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/schema/SchemaService.java	2014-05-31 16:27:04 +0000
@@ -35,11 +35,46 @@
  */
 public interface SchemaService
 {
+    /**
+     * Get schema which has been generated by a SchemaDescriptor.
+     *
+     * @param klass Class to get for
+     * @return Schema for class, or null
+     * @see org.hisp.dhis.schema.SchemaDescriptor
+     */
     Schema getSchema( Class<?> klass );
 
+    /**
+     * Get schema if it has been described by a SchemaDescriptor, if not, it will
+     * generate a Schema dynamically from the class. Only the properties part of the
+     * Schema will be usable (together with parts which can be auto-generated like isIdentifiableObject).
+     *
+     * @param klass Class to get for
+     * @return Schema for class, or null
+     * @see org.hisp.dhis.schema.SchemaDescriptor
+     */
+    Schema getDynamicSchema( Class<?> klass );
+
+    /**
+     * Get schema which has been generated by a SchemaDescriptor by singular name.
+     *
+     * @param name Name to get Schema for, will be matched against Schema.getSingular().
+     * @return Schema for class, or null
+     * @see org.hisp.dhis.schema.SchemaDescriptor
+     */
     Schema getSchemaBySingularName( String name );
 
+    /**
+     * Get all available schemas (which are generated with a schema descriptor).
+     *
+     * @return List of all available schemas
+     */
     List<Schema> getSchemas();
 
+    /**
+     * Get all available schemas which have the metadata property set to true.
+     *
+     * @return List of all available metadata schemas
+     */
     List<Schema> getMetadataSchemas();
 }

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/schema/DefaultSchemaService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/schema/DefaultSchemaService.java	2014-05-28 11:24:18 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/schema/DefaultSchemaService.java	2014-05-31 16:27:04 +0000
@@ -30,6 +30,7 @@
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import javassist.util.proxy.ProxyFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.core.OrderComparator;
 
@@ -79,27 +80,41 @@
             return null;
         }
 
-        /*
         if ( ProxyFactory.isProxyClass( klass ) )
         {
             klass = klass.getSuperclass();
         }
-        */
 
         if ( classSchemaMap.containsKey( klass ) )
         {
             return classSchemaMap.get( klass );
         }
 
-        if ( classSchemaMap.containsKey( klass.getSuperclass() ) )
-        {
-            return classSchemaMap.get( klass.getSuperclass() );
-        }
-
         return null;
     }
 
     @Override
+    public Schema getDynamicSchema( Class<?> klass )
+    {
+        if ( klass == null )
+        {
+            return null;
+        }
+
+        Schema schema = getSchema( klass );
+
+        if ( schema != null )
+        {
+            return schema;
+        }
+
+        schema = new Schema( klass, klass.getName(), klass.getName() );
+        schema.setProperties( propertyIntrospectorService.getProperties( schema.getKlass() ) );
+
+        return schema;
+    }
+
+    @Override
     public Schema getSchemaBySingularName( String name )
     {
         return singularSchemaMap.get( name );

=== 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	2014-05-31 07:39:57 +0000
+++ dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AbstractCrudController.java	2014-05-31 16:27:04 +0000
@@ -52,7 +52,7 @@
 import org.hisp.dhis.user.CurrentUserService;
 import org.hisp.dhis.webapi.controller.exception.NotFoundException;
 import org.hisp.dhis.webapi.utils.ContextUtils;
-import org.hisp.dhis.webapi.utils.WebUtils;
+import org.hisp.dhis.webapi.utils.LinkService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
@@ -98,6 +98,9 @@
     protected SchemaService schemaService;
 
     @Autowired
+    protected LinkService linkService;
+
+    @Autowired
     protected RenderService renderService;
 
     @Autowired
@@ -107,7 +110,7 @@
     // GET
     //--------------------------------------------------------------------------
 
-    @RequestMapping( method = RequestMethod.GET )
+    @RequestMapping(method = RequestMethod.GET)
     public String getObjectList(
         @RequestParam Map<String, String> parameters, Model model, HttpServletResponse response, HttpServletRequest request )
     {
@@ -136,11 +139,11 @@
         return StringUtils.uncapitalize( getEntitySimpleName() ) + "List";
     }
 
-    @RequestMapping( method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE } )
+    @RequestMapping(method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
     public void getObjectListJson(
-        @RequestParam( required = false ) String include,
-        @RequestParam( required = false ) String exclude,
-        @RequestParam( value = "filter", required = false ) List<String> filters,
+        @RequestParam(required = false) String include,
+        @RequestParam(required = false) String exclude,
+        @RequestParam(value = "filter", required = false) List<String> filters,
         @RequestParam Map<String, String> parameters, Model model, HttpServletResponse response, HttpServletRequest request ) throws IOException
     {
         WebOptions options = new WebOptions( parameters );
@@ -227,8 +230,8 @@
     }
 
 
-    @RequestMapping( value = "/{uid}", method = RequestMethod.GET )
-    public String getObject( @PathVariable( "uid" ) String uid, @RequestParam Map<String, String> parameters,
+    @RequestMapping(value = "/{uid}", method = RequestMethod.GET)
+    public String getObject( @PathVariable("uid") String uid, @RequestParam Map<String, String> parameters,
         Model model, HttpServletRequest request, HttpServletResponse response ) throws Exception
     {
         WebOptions options = new WebOptions( parameters );
@@ -241,7 +244,7 @@
 
         if ( options.hasLinks() )
         {
-            WebUtils.generateLinks( entity );
+            linkService.generateLinks( entity );
         }
 
         if ( aclService.isSupported( getEntityClass() ) )
@@ -262,7 +265,7 @@
     // POST
     //--------------------------------------------------------------------------
 
-    @RequestMapping( method = RequestMethod.POST, consumes = { "application/xml", "text/xml" } )
+    @RequestMapping(method = RequestMethod.POST, consumes = { "application/xml", "text/xml" })
     public void postXmlObject( HttpServletResponse response, HttpServletRequest request, InputStream input ) throws Exception
     {
         if ( !aclService.canCreate( currentUserService.getCurrentUser(), getEntityClass() ) )
@@ -275,7 +278,7 @@
         renderService.toJson( response.getOutputStream(), summary );
     }
 
-    @RequestMapping( method = RequestMethod.POST, consumes = "application/json" )
+    @RequestMapping(method = RequestMethod.POST, consumes = "application/json")
     public void postJsonObject( HttpServletResponse response, HttpServletRequest request, InputStream input ) throws Exception
     {
         if ( !aclService.canCreate( currentUserService.getCurrentUser(), getEntityClass() ) )
@@ -292,9 +295,9 @@
     // PUT
     //--------------------------------------------------------------------------
 
-    @RequestMapping( value = "/{uid}", method = RequestMethod.PUT, consumes = { MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE } )
-    @ResponseStatus( value = HttpStatus.NO_CONTENT )
-    public void putXmlObject( HttpServletResponse response, HttpServletRequest request, @PathVariable( "uid" ) String uid, InputStream
+    @RequestMapping(value = "/{uid}", method = RequestMethod.PUT, consumes = { MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE })
+    @ResponseStatus(value = HttpStatus.NO_CONTENT)
+    public void putXmlObject( HttpServletResponse response, HttpServletRequest request, @PathVariable("uid") String uid, InputStream
         input ) throws Exception
     {
         T object = getEntity( uid );
@@ -317,9 +320,9 @@
         renderService.toJson( response.getOutputStream(), summary );
     }
 
-    @RequestMapping( value = "/{uid}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE )
-    @ResponseStatus( value = HttpStatus.NO_CONTENT )
-    public void putJsonObject( HttpServletResponse response, HttpServletRequest request, @PathVariable( "uid" ) String uid, InputStream
+    @RequestMapping(value = "/{uid}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE)
+    @ResponseStatus(value = HttpStatus.NO_CONTENT)
+    public void putJsonObject( HttpServletResponse response, HttpServletRequest request, @PathVariable("uid") String uid, InputStream
         input ) throws Exception
     {
         T object = getEntity( uid );
@@ -346,9 +349,9 @@
     // DELETE
     //--------------------------------------------------------------------------
 
-    @RequestMapping( value = "/{uid}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE )
-    @ResponseStatus( value = HttpStatus.NO_CONTENT )
-    public void deleteObject( HttpServletResponse response, HttpServletRequest request, @PathVariable( "uid" ) String uid ) throws
+    @RequestMapping(value = "/{uid}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE)
+    @ResponseStatus(value = HttpStatus.NO_CONTENT)
+    public void deleteObject( HttpServletResponse response, HttpServletRequest request, @PathVariable("uid") String uid ) throws
         Exception
     {
         T object = getEntity( uid );
@@ -452,7 +455,7 @@
     {
         if ( options != null && options.hasLinks() )
         {
-            WebUtils.generateLinks( metaData, deep );
+            linkService.generateLinks( metaData );
         }
 
         if ( !JacksonUtils.isSharingView( options.getViewClass( "basic" ) ) )
@@ -479,7 +482,7 @@
 
     private String entitySimpleName;
 
-    @SuppressWarnings( "unchecked" )
+    @SuppressWarnings("unchecked")
     protected Class<T> getEntityClass()
     {
         if ( entityClass == null )
@@ -511,7 +514,7 @@
         return entitySimpleName;
     }
 
-    @SuppressWarnings( "unchecked" )
+    @SuppressWarnings("unchecked")
     protected T getEntityInstance()
     {
         try

=== modified file 'dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataelement/DataElementOperandController.java'
--- dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataelement/DataElementOperandController.java	2014-05-28 09:57:38 +0000
+++ dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/dataelement/DataElementOperandController.java	2014-05-31 16:27:04 +0000
@@ -81,11 +81,11 @@
 
             if ( dataElementGroup == null )
             {
-                entityList = new ArrayList<DataElementOperand>();
+                entityList = new ArrayList<>();
             }
             else
             {
-                entityList = new ArrayList<DataElementOperand>( dataElementOperandService.getDataElementOperandByDataElementGroup( dataElementGroup ) );
+                entityList = new ArrayList<>( dataElementOperandService.getDataElementOperandByDataElementGroup( dataElementGroup ) );
             }
         }
         else if ( options.hasPaging() )
@@ -95,12 +95,12 @@
             Pager pager = new Pager( options.getPage(), count, options.getPageSize() );
             metaData.setPager( pager );
 
-            entityList = new ArrayList<DataElementOperand>( dataElementOperandService.getAllDataElementOperands(
+            entityList = new ArrayList<>( dataElementOperandService.getAllDataElementOperands(
                 pager.getOffset(), pager.getPageSize() ) );
         }
         else
         {
-            entityList = new ArrayList<DataElementOperand>( dataElementOperandService.getAllDataElementOperands() );
+            entityList = new ArrayList<>( dataElementOperandService.getAllDataElementOperands() );
         }
 
         return entityList;

=== added directory 'dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/service'
=== added file 'dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/utils/ContextService.java'
--- dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/utils/ContextService.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/utils/ContextService.java	2014-05-31 16:27:04 +0000
@@ -0,0 +1,60 @@
+package org.hisp.dhis.webapi.utils;
+
+/*
+ * Copyright (c) 2004-2014, 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 javax.servlet.http.HttpServletRequest;
+
+/**
+ * @author Morten Olav Hansen <mortenoh@xxxxxxxxx>
+ */
+public interface ContextService
+{
+    /**
+     * Get full path of servlet.
+     *
+     * @return Full HREF to servlet
+     * @see javax.servlet.http.HttpServletRequest
+     */
+    String getServletPath();
+
+    /**
+     * Get HREF to context.
+     *
+     * @return Full HREF to context (context root)
+     * @see javax.servlet.http.HttpServletRequest
+     */
+    String getContextPath();
+
+    /**
+     * Get active HttpServletRequest
+     *
+     * @return HttpServletRequest
+     */
+    HttpServletRequest getRequest();
+}

=== added file 'dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/utils/DefaultContextService.java'
--- dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/utils/DefaultContextService.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/utils/DefaultContextService.java	2014-05-31 16:27:04 +0000
@@ -0,0 +1,95 @@
+package org.hisp.dhis.webapi.utils;
+
+/*
+ * Copyright (c) 2004-2014, 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.springframework.stereotype.Service;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * @author Morten Olav Hansen <mortenoh@xxxxxxxxx>
+ */
+@Service
+public class DefaultContextService implements ContextService
+{
+    @Override
+    public String getServletPath()
+    {
+        HttpServletRequest request = getRequest();
+        return getContextPath() + request.getServletPath();
+    }
+
+    @Override
+    public String getContextPath()
+    {
+        HttpServletRequest request = getRequest();
+        StringBuilder builder = new StringBuilder();
+        String xForwardedProto = request.getHeader( "X-Forwarded-Proto" );
+        String xForwardedPort = request.getHeader( "X-Forwarded-Port" );
+
+        if ( xForwardedProto != null && (xForwardedProto.equalsIgnoreCase( "http" ) || xForwardedProto.equalsIgnoreCase( "https" )) )
+        {
+            builder.append( xForwardedProto );
+        }
+        else
+        {
+            builder.append( request.getScheme() );
+        }
+
+        builder.append( "://" ).append( request.getServerName() );
+
+        int port;
+
+        try
+        {
+            port = Integer.parseInt( xForwardedPort );
+        }
+        catch ( NumberFormatException e )
+        {
+            port = request.getServerPort();
+        }
+
+        if ( port != 80 && port != 443 )
+        {
+            builder.append( ":" ).append( port );
+        }
+
+        builder.append( request.getContextPath() );
+
+        return builder.toString();
+    }
+
+    @Override
+    public HttpServletRequest getRequest()
+    {
+        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
+    }
+}

=== added file 'dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/utils/DefaultLinkService.java'
--- dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/utils/DefaultLinkService.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/utils/DefaultLinkService.java	2014-05-31 16:27:04 +0000
@@ -0,0 +1,182 @@
+package org.hisp.dhis.webapi.utils;
+
+/*
+ * Copyright (c) 2004-2014, 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 javassist.util.proxy.ProxyFactory;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.hibernate.collection.spi.PersistentCollection;
+import org.hisp.dhis.schema.Property;
+import org.hisp.dhis.schema.Schema;
+import org.hisp.dhis.schema.SchemaService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collection;
+
+/**
+ * @author Morten Olav Hansen <mortenoh@xxxxxxxxx>
+ */
+@Service
+public class DefaultLinkService implements LinkService
+{
+    private static final Log log = LogFactory.getLog( DefaultLinkService.class );
+
+    @Autowired
+    private SchemaService schemaService;
+
+    @Autowired
+    private ContextService contextService;
+
+    @Override
+    public <T> void generateLinks( T object )
+    {
+        generateLinks( object, contextService.getServletPath() );
+    }
+
+    @Override
+    public <T> void generateLinks( T object, String hrefBase )
+    {
+        if ( Collection.class.isInstance( object ) )
+        {
+            Collection<?> collection = (Collection<?>) object;
+
+            for ( Object collectionObject : collection )
+            {
+                generateLink( collectionObject, hrefBase, false );
+            }
+        }
+        else
+        {
+            generateLink( object, hrefBase, true );
+        }
+    }
+
+    private <T> void generateLink( T object, String hrefBase, boolean deepScan )
+    {
+        Schema schema = schemaService.getDynamicSchema( object.getClass() );
+
+        if ( schema == null )
+        {
+            log.warn( "Could not find schema for object of type " + object.getClass().getName() + "." );
+            return;
+        }
+
+        generateHref( object, hrefBase );
+
+        if ( !deepScan )
+        {
+            return;
+        }
+
+        for ( Property property : schema.getProperties() )
+        {
+            try
+            {
+                // TODO should we support non-idObjects?
+                if ( property.isIdentifiableObject() )
+                {
+                    Object propertyObject = property.getGetterMethod().invoke( object );
+
+                    if ( propertyObject == null )
+                    {
+                        continue;
+                    }
+
+                    // unwrap hibernate PersistentCollection
+                    if ( PersistentCollection.class.isAssignableFrom( propertyObject.getClass() ) )
+                    {
+                        PersistentCollection collection = (PersistentCollection) propertyObject;
+                        propertyObject = collection.getValue();
+                    }
+
+                    if ( !property.isCollection() )
+                    {
+                        generateHref( propertyObject, hrefBase );
+                    }
+                    else
+                    {
+                        Collection<?> collection = (Collection<?>) propertyObject;
+
+                        for ( Object collectionObject : collection )
+                        {
+                            generateHref( collectionObject, hrefBase );
+                        }
+                    }
+
+                }
+            }
+            catch ( InvocationTargetException | IllegalAccessException ignored )
+            {
+            }
+        }
+    }
+
+    private <T> void generateHref( T object, String hrefBase )
+    {
+        if ( object == null )
+        {
+            return;
+        }
+
+        Class<?> klass = object.getClass();
+
+        if ( ProxyFactory.isProxyClass( klass ) )
+        {
+            klass = klass.getSuperclass();
+        }
+
+        Schema schema = schemaService.getDynamicSchema( klass );
+
+        if ( !schema.haveEndpoint() )
+        {
+            return;
+        }
+
+        try
+        {
+            Method getUid = object.getClass().getMethod( "getUid" );
+            Object value = getUid.invoke( object );
+
+            if ( !String.class.isInstance( value ) )
+            {
+                log.warn( "getUid on object of type " + object.getClass().getName() + " does not return a String." );
+                return;
+            }
+
+            Method setHref = object.getClass().getMethod( "setHref", String.class );
+            setHref.invoke( object, hrefBase + schema.getApiEndpoint() + "/" + value );
+        }
+        catch ( NoSuchMethodException | InvocationTargetException | IllegalAccessException ignored )
+        {
+        }
+    }
+}

=== added file 'dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/utils/LinkService.java'
--- dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/utils/LinkService.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/utils/LinkService.java	2014-05-31 16:27:04 +0000
@@ -0,0 +1,55 @@
+package org.hisp.dhis.webapi.utils;
+
+/*
+ * Copyright (c) 2004-2014, 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 Morten Olav Hansen <mortenoh@xxxxxxxxx>
+ */
+public interface LinkService
+{
+    /**
+     * Generate HREF and set it using reflection, required a setHref(String) method in your class.
+     *
+     * Uses hrefBase from ContextService.getServletPath().
+     *
+     * @param object   Object (can be collection) to set HREFs on
+     * @see javax.servlet.http.HttpServletRequest
+     * @see org.hisp.dhis.webapi.utils.ContextService
+     */
+    <T> void generateLinks( T object );
+
+    /**
+     * Generate HREF and set it using reflection, required a setHref(String) method in your class.
+     *
+     * @param object   Object (can be collection) to set HREFs on
+     * @param hrefBase Used as starting point of HREF
+     * @see javax.servlet.http.HttpServletRequest
+     */
+    <T> void generateLinks( T object, String hrefBase );
+}