------------------------------------------------------------ revno: 21505 committer: Lars Helge Overland <larshe...@gmail.com> branch nick: dhis2 timestamp: Mon 2015-12-21 22:50:29 +0100 message: App store. Introduced domain model for app store, apps and app versions. Introduced AppStoreManager. added: dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore/ dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore/AppStore.java dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore/AppStoreManager.java dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore/DefaultAppStoreManager.java dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore/WebApp.java dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore/WebAppVersion.java modified: dhis-2/dhis-api/src/main/java/org/hisp/dhis/appmanager/AppStatus.java dhis-2/dhis-services/dhis-service-administration/pom.xml dhis-2/dhis-services/dhis-service-administration/src/main/resources/META-INF/dhis/beans.xml dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AppController.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/appmanager/AppStatus.java' --- dhis-2/dhis-api/src/main/java/org/hisp/dhis/appmanager/AppStatus.java 2015-12-21 20:17:43 +0000 +++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/appmanager/AppStatus.java 2015-12-21 21:50:29 +0000 @@ -34,7 +34,8 @@ NAMESPACE_TAKEN( "Namespace defined in manifest is protected" ), INVALID_ZIP_FORMAT( "Zipfile could not be read" ), INVALID_MANIFEST_JSON( "Invalid JSON in app manifest file" ), - INSTALLATION_FAILED( "App could not be installed on file system" ); + INSTALLATION_FAILED( "App could not be installed on file system" ), + NOT_FOUND( "App could not be found" ); private String message; === modified file 'dhis-2/dhis-services/dhis-service-administration/pom.xml' --- dhis-2/dhis-services/dhis-service-administration/pom.xml 2015-10-20 22:50:17 +0000 +++ dhis-2/dhis-services/dhis-service-administration/pom.xml 2015-12-21 21:50:29 +0000 @@ -25,6 +25,10 @@ <groupId>org.hisp.dhis</groupId> <artifactId>dhis-service-core</artifactId> </dependency> + <dependency> + <groupId>org.hisp.dhis</groupId> + <artifactId>dhis-service-dxf2</artifactId> + </dependency> <!-- Other --> === added directory 'dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore' === added file 'dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore/AppStore.java' --- dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore/AppStore.java 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore/AppStore.java 2015-12-21 21:50:29 +0000 @@ -0,0 +1,83 @@ +package org.hisp.dhis.appstore; + +/* + * 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 java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author Lars Helge Overland + */ +public class AppStore +{ + private String name; + + private String description; + + private List<WebApp> apps = new ArrayList<>(); + + public AppStore() + { + } + + @JsonProperty + public String getName() + { + return name; + } + + public void setName( String name ) + { + this.name = name; + } + + @JsonProperty + public String getDescription() + { + return description; + } + + public void setDescription( String description ) + { + this.description = description; + } + + @JsonProperty + public List<WebApp> getApps() + { + return apps; + } + + public void setApps( List<WebApp> apps ) + { + this.apps = apps; + } +} === added file 'dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore/AppStoreManager.java' --- dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore/AppStoreManager.java 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore/AppStoreManager.java 2015-12-21 21:50:29 +0000 @@ -0,0 +1,40 @@ +package org.hisp.dhis.appstore; + +/* + * 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 java.io.IOException; + +/** + * @author Lars Helge Overland + */ +public interface AppStoreManager +{ + AppStore getAppStore() + throws IOException; +} === added file 'dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore/DefaultAppStoreManager.java' --- dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore/DefaultAppStoreManager.java 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore/DefaultAppStoreManager.java 2015-12-21 21:50:29 +0000 @@ -0,0 +1,136 @@ +package org.hisp.dhis.appstore; + +/* + * 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 java.io.BufferedInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import java.util.Optional; + +import org.apache.commons.io.IOUtils; +import org.hisp.dhis.appmanager.AppManager; +import org.hisp.dhis.appmanager.AppStatus; +import org.hisp.dhis.dxf2.common.JacksonUtils; +import org.hisp.dhis.setting.SettingKey; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.client.RestTemplate; + +/** + * @author Lars Helge Overland + */ +public class DefaultAppStoreManager + implements AppStoreManager +{ + @Autowired + private RestTemplate restTemplate; + + @Autowired + private AppManager appManager; + + // ------------------------------------------------------------------------- + // AppStoreManager implementation + // ------------------------------------------------------------------------- + + public AppStore getAppStore() + throws IOException + { + String input = restTemplate.getForObject( SettingKey.APP_STORE_INDEX_URL.getDefaultValue().toString(), String.class ); + + return JacksonUtils.fromJson( input, AppStore.class ); + } + + public AppStatus installAppFromAppstore( String id ) + { + try + { + Optional<WebAppVersion> webAppVersion = getWebAppVersion( id ); + + if ( webAppVersion.isPresent() ) + { + WebAppVersion version = webAppVersion.get(); + + URL url = new URL( version.getDownloadUrl() ); + + String filename = url.getFile(); + + return appManager.installApp( getFile( url ), filename, null ); + } + + return AppStatus.NOT_FOUND; + } + catch ( IOException ex ) + { + throw new RuntimeException( "Failed to install app", ex ); + } + } + + // ------------------------------------------------------------------------- + // Supportive methods + // ------------------------------------------------------------------------- + + private Optional<WebAppVersion> getWebAppVersion( String id ) + throws IOException + { + AppStore appStore = getAppStore(); + + for ( WebApp app : appStore.getApps() ) + { + for ( WebAppVersion version : app.getVersions() ) + { + if ( id.equals( version.getId() ) ) + { + return Optional.of( version ); + } + } + } + + return Optional.empty(); + } + + private static File getFile( URL url ) + throws IOException + { + URLConnection connection = url.openConnection(); + + BufferedInputStream in = new BufferedInputStream( connection.getInputStream() ); + + File tempFile = File.createTempFile( "dhis", null ); + + tempFile.deleteOnExit(); + + FileOutputStream out = new FileOutputStream( tempFile ); + + IOUtils.copy( in, out ); + + return tempFile; + } +} === added file 'dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore/WebApp.java' --- dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore/WebApp.java 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore/WebApp.java 2015-12-21 21:50:29 +0000 @@ -0,0 +1,109 @@ +package org.hisp.dhis.appstore; + +/* + * 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 java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author Lars Helge Overland + */ +public class WebApp +{ + private String name; + + private String description; + + private String imgLarge; + + private String developer; + + private List<WebAppVersion> versions = new ArrayList<>(); + + public WebApp() + { + } + + @JsonProperty + public String getName() + { + return name; + } + + public void setName( String name ) + { + this.name = name; + } + + @JsonProperty + public String getDescription() + { + return description; + } + + public void setDescription( String description ) + { + this.description = description; + } + + @JsonProperty( value = "img_large" ) + public String getImgLarge() + { + return imgLarge; + } + + public void setImgLarge( String imgLarge ) + { + this.imgLarge = imgLarge; + } + + @JsonProperty + public String getDeveloper() + { + return developer; + } + + public void setDeveloper( String developer ) + { + this.developer = developer; + } + + @JsonProperty + public List<WebAppVersion> getVersions() + { + return versions; + } + + public void setVersions( List<WebAppVersion> versions ) + { + this.versions = versions; + } +} === added file 'dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore/WebAppVersion.java' --- dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore/WebAppVersion.java 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/appstore/WebAppVersion.java 2015-12-21 21:50:29 +0000 @@ -0,0 +1,119 @@ +package org.hisp.dhis.appstore; + +/* + * 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 com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author Lars Helge Overland + */ +public class WebAppVersion +{ + private String id; + + private String version; + + private String minPlatformVersion; + + private String maxPlatformVersion; + + private String downloadUrl; + + private String demoUrl; + + public WebAppVersion() + { + } + + @JsonProperty + public String getId() + { + return id; + } + + public void setId( String id ) + { + this.id = id; + } + + @JsonProperty + public String getVersion() + { + return version; + } + + public void setVersion( String version ) + { + this.version = version; + } + + @JsonProperty( value = "min_platform_version" ) + public String getMinPlatformVersion() + { + return minPlatformVersion; + } + + public void setMinPlatformVersion( String minPlatformVersion ) + { + this.minPlatformVersion = minPlatformVersion; + } + + @JsonProperty( value = "max_platform_version" ) + public String getMaxPlatformVersion() + { + return maxPlatformVersion; + } + + public void setMaxPlatformVersion( String maxPlatformVersion ) + { + this.maxPlatformVersion = maxPlatformVersion; + } + + @JsonProperty( value = "download_url" ) + public String getDownloadUrl() + { + return downloadUrl; + } + + public void setDownloadUrl( String downloadUrl ) + { + this.downloadUrl = downloadUrl; + } + + @JsonProperty( value = "demo_url" ) + public String getDemoUrl() + { + return demoUrl; + } + + public void setDemoUrl( String demoUrl ) + { + this.demoUrl = demoUrl; + } +} === modified file 'dhis-2/dhis-services/dhis-service-administration/src/main/resources/META-INF/dhis/beans.xml' --- dhis-2/dhis-services/dhis-service-administration/src/main/resources/META-INF/dhis/beans.xml 2015-12-08 20:41:02 +0000 +++ dhis-2/dhis-services/dhis-service-administration/src/main/resources/META-INF/dhis/beans.xml 2015-12-21 21:50:29 +0000 @@ -2,6 +2,10 @@ <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd"> + <!-- App store --> + + <bean id="org.hisp.dhis.appstore.AppStoreManager" class="org.hisp.dhis.appstore.DefaultAppStoreManager" /> + <!-- ResourceTable --> <bean id="org.hisp.dhis.resourcetable.ResourceTableStore" class="org.hisp.dhis.resourcetable.jdbc.JdbcResourceTableStore"> === modified file 'dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AppController.java' --- dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AppController.java 2015-12-21 20:17:43 +0000 +++ dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/AppController.java 2015-12-21 21:50:29 +0000 @@ -34,6 +34,8 @@ import org.hisp.dhis.appmanager.App; import org.hisp.dhis.appmanager.AppManager; import org.hisp.dhis.appmanager.AppStatus; +import org.hisp.dhis.appstore.AppStore; +import org.hisp.dhis.appstore.AppStoreManager; import org.hisp.dhis.dxf2.render.DefaultRenderService; import org.hisp.dhis.dxf2.render.RenderService; import org.hisp.dhis.dxf2.webmessage.WebMessageException; @@ -52,7 +54,6 @@ import org.springframework.stereotype.Controller; import org.springframework.util.StreamUtils; import org.springframework.web.bind.annotation.*; -import org.springframework.web.client.RestTemplate; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.multipart.MultipartFile; @@ -76,18 +77,22 @@ @Autowired private AppManager appManager; + + @Autowired + private AppStoreManager appStoreManager; @Autowired private RenderService renderService; @Autowired private LocationManager locationManager; - - @Autowired - private RestTemplate restTemplate; - + private final ResourceLoader resourceLoader = new DefaultResourceLoader(); + // ------------------------------------------------------------------------- + // Resources + // ------------------------------------------------------------------------- + @RequestMapping( method = RequestMethod.GET, produces = ContextUtils.CONTENT_TYPE_JSON ) public void getApps( @RequestParam( required = false ) String key, HttpServletResponse response ) throws IOException @@ -273,9 +278,10 @@ } @RequestMapping( value = "/appStore", method = RequestMethod.GET, produces = "application/json" ) - public @ResponseBody String getAppStore() + public @ResponseBody AppStore getAppStore( HttpServletResponse response ) + throws IOException { - return restTemplate.getForObject( SettingKey.APP_STORE_INDEX_URL.getDefaultValue().toString(), String.class ); + return appStoreManager.getAppStore(); } //--------------------------------------------------------------------------
_______________________________________________ Mailing list: https://launchpad.net/~dhis2-devs Post to : dhis2-devs@lists.launchpad.net Unsubscribe : https://launchpad.net/~dhis2-devs More help : https://help.launchpad.net/ListHelp