You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by ta...@apache.org on 2016/09/15 11:20:05 UTC

svn commit: r1760917 - in /ofbiz/trunk: README.md build.gradle framework/resources/templates/build.gradle

Author: taher
Date: Thu Sep 15 11:20:05 2016
New Revision: 1760917

URL: http://svn.apache.org/viewvc?rev=1760917&view=rev
Log:
Implemented: Create a (PoC) plugin system for OFBiz based on Gradle
(OFBIZ-7972)

This commit creates a basic plugin API for OFBiz that interoperates
with maven repositories. We are committing this code as "experimental"
to allow contributors to start experimenting with this system easily
as per the discussion on the above JIRA and in ML in the below thread.

This new plugin API is documented in README.md and provides the following
new gradle tasks for managing plugins:

- createPlugin: creates a new plugin based on templates, place it in
  the plugins directory and activate it (add to component-load.xml)
- installPlugin: Activate a plugin and run its Install task. Plugin
  must exist already in /specialpurpose
- uninstallPlugin: Deactivate a plugin and run its uninstall task
- removePlugin: Uninstall a plugin and then delete it from the filesystem
- pushPlugin: publish a plugin to local maven repository
- pullPlugin: download a plugin with its dependencies (plugins) from
  a repository and install them

Discussion: http://markmail.org/message/h7fiicba2rtyl6cb

Thanks: Jacopo Capellato, Michael Brohl, Gil Portenseigne for feedback
and testing

Added:
    ofbiz/trunk/framework/resources/templates/build.gradle   (with props)
Modified:
    ofbiz/trunk/README.md
    ofbiz/trunk/build.gradle

Modified: ofbiz/trunk/README.md
URL: http://svn.apache.org/viewvc/ofbiz/trunk/README.md?rev=1760917&r1=1760916&r2=1760917&view=diff
==============================================================================
--- ofbiz/trunk/README.md (original)
+++ ofbiz/trunk/README.md Thu Sep 15 11:20:05 2016
@@ -28,6 +28,7 @@ by creating the.classpath and .project f
 
 Security
 -------------------
+
 You can trust the OFBiz Project Management Committee members and committers do their best to keep OFBiz secure from external exploits, and fix vulnerabilities as soon as they are known. Despite these efforts, if ever you find and want to report a security issue, please report at: security @ ofbiz.apache.org, before disclosing them in a public forum.
 
 >_Note_: Be sure to read this Wiki page if ever you plan to use RMI, JNDI, JMX or Spring and maybe other Java classes OFBiz does not use Out Of The Box (OOTB): [The infamous Java serialization vulnerability](https://cwiki.apache.org/confluence/display/OFBIZ/The+infamous+Java+serialization+vulnerability)
@@ -384,8 +385,6 @@ listens on port __5005__
 
 #### Execute an integration test suite
 
-listens on port __5005__
-
 `gradlew "ofbiz --test component=widget --test suitename=org.apache.ofbiz.widget.test.WidgetMacroLibraryTests"`
 
 #### Execute an integration test suite in debug mode
@@ -419,19 +418,6 @@ in a list of favorites for frequent reus
 
 `gradlew clean build`
 
-#### Create a custom component in hot-deploy
-
-Create a new custom component. The following project parameters are passed:
-
-- componentName: mandatory
-- componentResourceName: optional, default is the value of componentName
-- webappName: optional, default is the value of componentName
-- basePermission: optional, default is the UPPERCASE value of componentName
-
-`gradlew createComponent -PcomponentName=Custom`
-
-`gradlew createComponent -PcomponentName=Custom -PcomponentResourceName=Custom -PwebappName=customweb -PbasePermission=MYSECURITY`
-
 #### Create an admin user account
 
 Create an admin user with login name MyUserName and default password
@@ -449,6 +435,102 @@ the necessary __.classpath__ and __.proj
 
 `gradlew eclipse`
 
+* * * * * * * * * * *
+
+OFBiz plugin system
+-------------------
+
+OFBiz provides an extension mechanism through plugins. Plugins are standard
+OFBiz components that reside in the specialpurpose directory. Plugins can be
+added manually or fetched from a maven repository. The standard tasks for
+managing plugins are listed below.
+
+>_Note_: OFBiz plugin versions follow [Semantic Versioning 2.0.0](http://semver.org/)
+
+### Pull (download and install) a plugin automatically
+
+Download a plugin with all its dependencies (plugins) and install them one-by-one
+starting with the dependencies and ending with the plugin itself.
+
+`gradlew pullPlugin -PdependencyId="org.apache.ofbiz.plugin:myplugin:0.1.0"`
+
+If the plugin resides in a custom maven repository (not jcenter or localhost) then
+you can use specify the repository using below command:
+
+`gradlew pullPlugin -PrepoUrl="http://www.example.com/custom-maven" -PdependencyId="org.apache.ofbiz.plugin:myplugin:0.1.0"`
+
+If you need username and password to access the custom repository:
+
+`gradlew pullPlugin -PrepoUrl="http://www.example.com/custom-maven" -PrepoUser=myuser -PrepoPassword=mypassword -PdependencyId="org.apache.ofbiz.plugin:myplugin:0.1.0"`
+
+### Install a plugin
+
+If you have a plugin called mycustomplugin and want to install it in OFBiz follow the
+below instructions:
+
+- Extract the plugin if it is compressed
+- Place the extracted directory into /specialpurpose
+- Run the below command
+
+`gradlew installPlugin -PpluginId=myplugin`
+
+The above commands achieve the following:
+
+- add the plugin to /specialpurpose/component-load.xml
+- executes the task "install" in the plugin's build.gradle file if it exists
+
+### Uninstall a plugin
+
+If you have an existing plugin called mycustomplugin and you wish to uninstall
+run the below command
+
+`gradlew uninstallPlugin -PpluginId=myplugin`
+
+The above commands achieve the following:
+
+- executes the task "uninstall" in the plugin's build.gradle file if it exists
+- removes the plugin from /specialpurpose/component-load.xml
+
+### Remove a plugin
+
+Calls __uninstallPlugin__ on an existing plugin and then delete it from the file-system
+
+`gradlew removePlugin -PpluginId=myplugin` 
+
+### Create a new plugin
+
+Create a new plugin. The following project parameters are passed:
+
+- pluginId: mandatory
+- pluginResourceName: optional, default is the Capitalized value of pluginId
+- webappName: optional, default is the value of pluginId
+- basePermission: optional, default is the UPPERCASE value of pluginId
+
+`gradlew createPlugin -PpluginId=myplugin`
+
+`gradlew createPlugin -PpluginId=myplugin -PpluginResourceName=MyPlugin -PwebappName=mypluginweb -PbasePermission=MYSECURITY`
+
+The above commands achieve the following:
+
+- create a new plugin in /specialpurpose/myplugin
+- add the plugin to /specialpurpose/component-load.xml
+
+### Push a plugin to a repository
+
+This task publishes an OFBiz plugin into a maven package and then uploads it to
+a maven repository. Currently, pushing is limited to localhost maven repository
+(work in progress). To push a plugin the following parameters are passed:
+
+- pluginId: mandatory
+- groupId: optional, defaults to org.apache.ofbiz.plugin
+- pluginVersion: optional, defaults to 0.1.0-SNAPSHOT
+- pluginDescription: optional, defaults to "Publication of OFBiz plugin ${pluginId}"
+
+`gradlew pushPlugin -PpluginId=myplugin`
+
+`gradlew pushPlugin -PpluginId=mycompany -PpluginGroup=com.mycompany.ofbiz.plugin -PpluginVersion=1.2.3 -PpluginDescription="Introduce special functionality X"`
+
+
 * * * * * * * * * * * *
 
 Useful Tips

Modified: ofbiz/trunk/build.gradle
URL: http://svn.apache.org/viewvc/ofbiz/trunk/build.gradle?rev=1760917&r1=1760916&r2=1760917&view=diff
==============================================================================
--- ofbiz/trunk/build.gradle (original)
+++ ofbiz/trunk/build.gradle Thu Sep 15 11:20:05 2016
@@ -24,11 +24,13 @@ import org.apache.tools.ant.filters.Repl
 
 apply plugin: 'java'
 apply plugin: 'eclipse'
+apply plugin: 'maven-publish'
 
 apply from: 'common.gradle'
 
-// operating system property
+// global properties
 ext.os = System.getProperty('os.name').toLowerCase()
+ext.pluginsDir = "${rootDir}/specialpurpose"
 
 // java settings
 def jvmArguments = ['-Xms128M', '-Xmx1024M']
@@ -48,6 +50,7 @@ defaultTasks 'build'
 allprojects {
     repositories{
         jcenter()
+        mavenLocal()
     }
 }
 
@@ -61,7 +64,13 @@ subprojects {
 }
 
 configurations {
-    junitLibs
+    junitReport {
+        description = 'libraries needed to run junitreport for OFBiz unit tests'
+    }
+    ofbizPlugins {
+        description = 'ofbiz plugin dependencies configuration'
+        transitive = true
+    }
 }
 
 dependencies {
@@ -158,9 +167,8 @@ dependencies {
     }
 
     // libs needed for junitreport
-    junitLibs 'junit:junit:4.12'
-    junitLibs 'org.apache.ant:ant-junit:1.9.7'
-    junitLibs 'org.apache.ant:ant-junit4:1.9.7'
+    junitReport 'junit:junit:4.12'
+    junitReport 'org.apache.ant:ant-junit:1.9.7'
 
     // local libs
     getDirectoryInActiveComponentsIfExists('lib').each { libDir ->
@@ -292,6 +300,7 @@ if (project.hasProperty('enableOwasp'))
 // ========== Task group labels ==========
 def cleanupGroup = 'Cleaning'
 def ofbizServer = 'OFBiz Server'
+def ofbizPlugin = 'OFBiz Plugin'
 def sysadminGroup = 'System Administration'
 def committerGroup = 'OFBiz committers'
 
@@ -454,116 +463,256 @@ task createTenant(group: ofbizServer, de
 }
 
 // ========== System Administration tasks ==========
-task createComponent(group: sysadminGroup, description: 'Create the layout of an OFBiz component in the hot-deploy folder.') << {
+task createTestReports(group: sysadminGroup, description: 'Generate HTML reports from junit XML output') << {
+    ant.taskdef(name: 'junitreport',
+        classname: 'org.apache.tools.ant.taskdefs.optional.junit.XMLResultAggregator',
+        classpath: configurations.junitReport.asPath)
+    ant.junitreport(todir: './runtime/logs/test-results') {
+        fileset(dir: './runtime/logs/test-results') {
+            include(name: '*.xml')
+        }
+        report(format:'frames', todir:'./runtime/logs/test-results/html')
+    }
+}
+/*
+ * TODO replace this code with something more declarative.
+ * We are using it so that if tests fail we still get HTML reports
+ */
+gradle.taskGraph.afterTask { Task task, TaskState state ->
+    if (task.name ==~ /^ofbiz.*--test.*/
+        || task.name ==~ /^ofbiz.*-t.*/) {
+        tasks.createTestReports.execute()
+    }
+}
 
-    if(!project.hasProperty('componentResourceName')) {
-        ext.componentResourceName = componentName
+// ========== OFBiz Plugin Management ==========
+task createPlugin(group: ofbizPlugin, description: 'create a new plugin component based on specified templates') << {
+    if(!project.hasProperty('pluginResourceName')) {
+        ext.pluginResourceName = pluginId.capitalize()
     }
     if(!project.hasProperty('webappName')) {
-        ext.webappName = componentName
+        ext.webappName = pluginId
     }
     if(!project.hasProperty('basePermission')) {
-        ext.basePermission = componentName.toUpperCase()
+        ext.basePermission = pluginId.toUpperCase()
     }
 
-    def filterTokens = ['component-name': componentName,
-        'component-resource-name': componentResourceName,
+    def filterTokens = ['component-name': pluginId,
+        'component-resource-name': pluginResourceName,
         'webapp-name': webappName,
         'base-permission': basePermission]
     def templateDir = "${rootDir}/framework/resources/templates"
-    def componentDir = "${rootDir}/hot-deploy/${componentName}"
+    def pluginDir = "${pluginsDir}/${pluginId}"
 
-    logger.info('Creating a component with the following properties: ')
-    logger.info(" - componentName: ${componentName}")
-    logger.info(" - componentResourceName: ${componentResourceName}")
-    logger.info(" - webappName: ${webappName}")
-    logger.info(" - basePermission: ${basePermission}")
-
-    mkdir componentDir
-    mkdir componentDir+"/config"
-    mkdir componentDir+"/data"
-    mkdir componentDir+"/data/helpdata"
-    mkdir componentDir+"/dtd"
-    mkdir componentDir+"/documents"
-    mkdir componentDir+"/entitydef"
-    mkdir componentDir+"/lib"
-    mkdir componentDir+"/patches"
-    mkdir componentDir+"/patches/test"
-    mkdir componentDir+"/patches/qa"
-    mkdir componentDir+"/patches/production"
-    mkdir componentDir+"/script"
-    mkdir componentDir+"/servicedef"
-    mkdir componentDir+"/src"
-    mkdir componentDir+"/testdef"
-    mkdir componentDir+"/webapp"
-    mkdir componentDir+"/webapp/${webappName}"
-    mkdir componentDir+"/webapp/${webappName}/error"
-    mkdir componentDir+"/webapp/${webappName}/WEB-INF"
-    mkdir componentDir+"/webapp/${webappName}/WEB-INF/actions"
-    mkdir componentDir+"/widget/"
+    mkdir pluginDir
+    mkdir pluginDir+"/config"
+    mkdir pluginDir+"/data"
+    mkdir pluginDir+"/data/helpdata"
+    mkdir pluginDir+"/dtd"
+    mkdir pluginDir+"/documents"
+    mkdir pluginDir+"/entitydef"
+    mkdir pluginDir+"/lib"
+    mkdir pluginDir+"/patches"
+    mkdir pluginDir+"/patches/test"
+    mkdir pluginDir+"/patches/qa"
+    mkdir pluginDir+"/patches/production"
+    mkdir pluginDir+"/script"
+    mkdir pluginDir+"/servicedef"
+    mkdir pluginDir+"/src"
+    mkdir pluginDir+"/testdef"
+    mkdir pluginDir+"/webapp"
+    mkdir pluginDir+"/webapp/${webappName}"
+    mkdir pluginDir+"/webapp/${webappName}/error"
+    mkdir pluginDir+"/webapp/${webappName}/WEB-INF"
+    mkdir pluginDir+"/webapp/${webappName}/WEB-INF/actions"
+    mkdir pluginDir+"/widget/"
 
-    generateFileFromTemplate(templateDir+"/ofbiz-component.xml", componentDir,
+    generateFileFromTemplate(templateDir+"/ofbiz-component.xml", pluginDir,
         filterTokens, "ofbiz-component.xml")
-    generateFileFromTemplate(templateDir+"/TypeData.xml", componentDir+"/data",
-        filterTokens, "${componentResourceName}TypeData.xml")
-    generateFileFromTemplate(templateDir+"/SecurityPermissionSeedData.xml", componentDir+"/data",
-        filterTokens, "${componentResourceName}SecurityPermissionSeedData.xml")
-    generateFileFromTemplate(templateDir+"/SecurityGroupDemoData.xml", componentDir+"/data",
-        filterTokens, "${componentResourceName}SecurityGroupDemoData.xml")
-    generateFileFromTemplate(templateDir+"/DemoData.xml", componentDir+"/data",
-        filterTokens, "${componentResourceName}DemoData.xml")
-    generateFileFromTemplate(templateDir+"/HELP.xml", componentDir+"/data/helpdata",
-        filterTokens, "HELP_${componentResourceName}.xml")
-    generateFileFromTemplate(templateDir+"/document.xml", componentDir+"/documents",
-        filterTokens, "${componentResourceName}.xml")
-    generateFileFromTemplate(templateDir+"/entitymodel.xml", componentDir+"/entitydef",
+    generateFileFromTemplate(templateDir+"/TypeData.xml", pluginDir+"/data",
+        filterTokens, "${pluginResourceName}TypeData.xml")
+    generateFileFromTemplate(templateDir+"/SecurityPermissionSeedData.xml", pluginDir+"/data",
+        filterTokens, "${pluginResourceName}SecurityPermissionSeedData.xml")
+    generateFileFromTemplate(templateDir+"/SecurityGroupDemoData.xml", pluginDir+"/data",
+        filterTokens, "${pluginResourceName}SecurityGroupDemoData.xml")
+    generateFileFromTemplate(templateDir+"/DemoData.xml", pluginDir+"/data",
+        filterTokens, "${pluginResourceName}DemoData.xml")
+    generateFileFromTemplate(templateDir+"/HELP.xml", pluginDir+"/data/helpdata",
+        filterTokens, "HELP_${pluginResourceName}.xml")
+    generateFileFromTemplate(templateDir+"/document.xml", pluginDir+"/documents",
+        filterTokens, "${pluginResourceName}.xml")
+    generateFileFromTemplate(templateDir+"/entitymodel.xml", pluginDir+"/entitydef",
         filterTokens, "entitymodel.xml")
-    generateFileFromTemplate(templateDir+"/services.xml", componentDir+"/servicedef",
+    generateFileFromTemplate(templateDir+"/services.xml", pluginDir+"/servicedef",
         filterTokens, "services.xml")
-    generateFileFromTemplate(templateDir+"/Tests.xml", componentDir+"/testdef",
-        filterTokens, "${componentResourceName}Tests.xml")
-    generateFileFromTemplate(templateDir+"/UiLabels.xml", componentDir+"/config",
-        filterTokens, "${componentResourceName}UiLabels.xml")
-    generateFileFromTemplate(templateDir+"/index.jsp", componentDir+"/webapp/${webappName}",
+    generateFileFromTemplate(templateDir+"/Tests.xml", pluginDir+"/testdef",
+        filterTokens, "${pluginResourceName}Tests.xml")
+    generateFileFromTemplate(templateDir+"/UiLabels.xml", pluginDir+"/config",
+        filterTokens, "${pluginResourceName}UiLabels.xml")
+    generateFileFromTemplate(templateDir+"/index.jsp", pluginDir+"/webapp/${webappName}",
         filterTokens, "index.jsp")
-    generateFileFromTemplate(templateDir+"/error.jsp", componentDir+"/webapp/${webappName}/error",
+    generateFileFromTemplate(templateDir+"/error.jsp", pluginDir+"/webapp/${webappName}/error",
         filterTokens, "error.jsp")
-    generateFileFromTemplate(templateDir+"/controller.xml", componentDir+"/webapp/${webappName}/WEB-INF",
+    generateFileFromTemplate(templateDir+"/controller.xml", pluginDir+"/webapp/${webappName}/WEB-INF",
         filterTokens, "controller.xml")
-    generateFileFromTemplate(templateDir+"/web.xml", componentDir+"/webapp/${webappName}/WEB-INF",
+    generateFileFromTemplate(templateDir+"/web.xml", pluginDir+"/webapp/${webappName}/WEB-INF",
         filterTokens, "web.xml")
-    generateFileFromTemplate(templateDir+"/CommonScreens.xml", componentDir+"/widget",
+    generateFileFromTemplate(templateDir+"/CommonScreens.xml", pluginDir+"/widget",
         filterTokens, "CommonScreens.xml")
-    generateFileFromTemplate(templateDir+"/Screens.xml", componentDir+"/widget",
-        filterTokens, "${componentResourceName}Screens.xml")
-    generateFileFromTemplate(templateDir+"/Menus.xml", componentDir+"/widget",
-        filterTokens, "${componentResourceName}Menus.xml")
-    generateFileFromTemplate(templateDir+"/Forms.xml", componentDir+"/widget",
-        filterTokens, "${componentResourceName}Forms.xml")
+    generateFileFromTemplate(templateDir+"/Screens.xml", pluginDir+"/widget",
+        filterTokens, "${pluginResourceName}Screens.xml")
+    generateFileFromTemplate(templateDir+"/Menus.xml", pluginDir+"/widget",
+        filterTokens, "${pluginResourceName}Menus.xml")
+    generateFileFromTemplate(templateDir+"/Forms.xml", pluginDir+"/widget",
+        filterTokens, "${pluginResourceName}Forms.xml")
+    generateFileFromTemplate(templateDir+"/build.gradle", pluginDir,
+        filterTokens, "build.gradle")
 
-    logger.info("Component successfully created in folder ${rootDir}/hot-deploy/${componentName}.")
-    logger.info("Restart OFBiz and then visit the URL: https://localhost:8443/${webappName}")
+    activatePlugin pluginId
+    println "plugin successfully created in directory ${pluginsDir}/${pluginId}."
 }
 
-task createTestReports(group: sysadminGroup, description: 'Generate HTML reports from junit XML output') << {
-    ant.taskdef(name: 'junitreport',
-        classname: 'org.apache.tools.ant.taskdefs.optional.junit.XMLResultAggregator',
-        classpath: configurations.junitLibs.asPath)
-    ant.junitreport(todir: './runtime/logs/test-results') {
-        fileset(dir: './runtime/logs/test-results') {
-            include(name: '*.xml')
+task installPlugin(group: ofbizPlugin, description: 'activate a plugin and run its install task if it exists') {
+
+    doFirst {
+        if(!project.hasProperty('pluginId')) {
+            throw new GradleException('Missing property \"pluginId\"')
+        }
+    }
+
+    if(project.hasProperty('pluginId')) {
+        if(subprojectExists(":specialpurpose:${pluginId}")) {
+            if(taskExistsInproject(":specialpurpose:${pluginId}", 'install')) {
+                dependsOn ":specialpurpose:${pluginId}:install"
+                doLast { println "installed plugin ${pluginId}" }
+            } else {
+                doLast { println "No install task defined for plugin ${pluginId}" }
+            }
+        } else {
+            /* if the plugin is not added to component-load.xml, then add
+             * it to the file and call gradle again to load the new plugin
+             * as a gradle subproject and install it i.e. gradle calling gradle
+             */
+            doLast {
+                activateAndInstallPlugin pluginId
+            }
         }
-        report(format:'frames', todir:'./runtime/logs/test-results/html')
     }
 }
-/*
- * TODO replace this code with something more declarative.
- * We are using it so that if tests fail we still get HTML reports
- */
-gradle.taskGraph.afterTask { Task task, TaskState state ->
-    if (task.name ==~ /^ofbiz.*--test.*/
-        || task.name ==~ /^ofbiz.*-t.*/) {
-        tasks.createTestReports.execute()
+
+task uninstallPlugin(group: ofbizPlugin, description: 'run the uninstall task if exists for a plugin and deactivate it') {
+
+    doFirst {
+        if(!project.hasProperty('pluginId')) {
+            throw new GradleException('Missing property \"pluginId\"')
+        }
+        if(!subprojectExists(":specialpurpose:${pluginId}")) {
+            throw new GradleException("Plugin \"${pluginId}\" does not exist")
+        }
+    }
+
+    if(project.hasProperty('pluginId') && taskExistsInproject(":specialpurpose:${pluginId}", 'uninstall')) {
+        dependsOn ":specialpurpose:${pluginId}:uninstall"
+    }
+
+    doLast {
+        deactivatePlugin pluginId
+    }
+}
+
+task removePlugin(group: ofbizPlugin, description: 'Uninstall a plugin and delete its files') {
+    if(project.hasProperty('pluginId') && subprojectExists(":specialpurpose:${pluginId}")) {
+        dependsOn uninstallPlugin
+    }
+
+    doLast {
+        if(file("${pluginsDir}/${pluginId}").exists()) {
+            delete "${pluginsDir}/${pluginId}"
+        } else {
+            throw new GradleException("Directory not found: ${pluginsDir}/${pluginId}")
+        }
+    }
+}
+
+task pushPlugin(group: ofbizPlugin, description: 'push an existing plugin to local maven repository') {
+
+    if(project.hasProperty('pluginId')) {
+        doFirst {
+            if(!subprojectExists(":specialpurpose:${pluginId}")) {
+                throw new GradleException("Plugin ${pluginId} does not exist, cannot publish")
+            }
+        }
+        task createPluginArchive(type: Zip) {
+            from "${pluginsDir}/${pluginId}"
+        }
+
+        publishing {
+            publications {
+                ofbizPluginPublication(MavenPublication) {
+                    artifactId pluginId
+                    groupId project.hasProperty('pluginGroup')? pluginGroup :'org.apache.ofbiz.plugin'
+                    version project.hasProperty('pluginVersion')? pluginVersion :'0.1.0-SNAPSHOT'
+        
+                    artifact createPluginArchive
+                    
+                    pom.withXml {
+                        if(project.hasProperty('pluginDescription')) {
+                            asNode().appendNode('description', pluginDescription)
+                        } else {
+                            asNode().appendNode('description', "Publication of OFBiz plugin ${pluginId}")
+                        }
+                    }
+                }
+            }
+        }
+
+        if(subprojectExists(":specialpurpose:${pluginId}")) {
+            dependsOn publishToMavenLocal
+        }
+
+    } else {
+        doFirst { throw new GradleException('Missing property \"pluginId\"') }
+    }
+}
+
+task pullPlugin(group: ofbizPlugin, description: 'Download and install a plugin with all dependencies') << {
+    if(!project.hasProperty('dependencyId')) {
+        throw new GradleException('You must pass the dependencyId of the plugin')
+    }
+
+    // Connect to a remote maven repository if defined
+    if(project.hasProperty('repoUrl')) {
+        repositories {
+            maven {
+                url repoUrl
+                if(project.hasProperty('repoUser') && project.hasProperty('repoPassword')) {
+                    credentials {
+                        username repoUser
+                        password repoPassword
+                    }
+                }
+            }
+        }
+    }
+
+    // download plugin and dependencies
+    dependencies {
+        ofbizPlugins dependencyId
+    }
+
+    // reverse the order of dependencies to install them before the plugin
+    def ofbizPluginArchives = new ArrayList(configurations.ofbizPlugins.files)
+    Collections.reverse(ofbizPluginArchives)
+
+    // Extract and install plugin and dependencies
+    ofbizPluginArchives.each { pluginArchive ->
+        ext.pluginId = dependencyId.tokenize(':').get(1)
+        println "installing plugin: ${pluginId}"
+        copy {
+            from zipTree(pluginArchive)
+            into "${pluginsDir}/${pluginId}"
+        }
+        activateAndInstallPlugin pluginId
     }
 }
 
@@ -601,7 +750,7 @@ task cleanXtra(group: cleanupGroup, desc
 task cleanGradle(group: cleanupGroup, description: 'clean generated files from Gradle') << {
     delete file("${rootDir}/.gradle")
 }
-task cleanAnt(group: cleanupGroup, type: Delete, description: "clean old artifacts generated by Ant") {
+task cleanAnt(group: cleanupGroup, type: Delete, description: "clean old artifacts generated by Ant") << {
     /* TODO this task is temporary and should be deleted after some
      * time when users have updated their trees. */
     ['framework', 'specialpurpose', 'applications'].each { componentGroup ->
@@ -832,3 +981,78 @@ def getJarManifestClasspathForCurrentOs(
     }
     return osClassPath
 }
+
+def componentExistsInRegister(componentRegister, componentName) {
+    def componentFound = false
+    componentRegister.children().each { component ->
+        if(componentName.equals(component.@"component-location")) {
+            componentFound = true
+        }
+    }
+    return componentFound
+}
+
+def subprojectExists(fullyQualifiedProject) {
+    def projectFound = false
+    subprojects.each { subproject ->
+        if(subproject.getPath().equals(fullyQualifiedProject.toString())) {
+            projectFound = true
+        }
+    }
+    return projectFound
+}
+
+def taskExistsInproject(fullyQualifiedProject, taskName) {
+    def taskExists = false
+    subprojects.each { subProject ->
+        if(subProject.getPath().equals(fullyQualifiedProject.toString())) {
+            subProject.tasks.each { projTask ->
+                if(taskName.equals(projTask.name)) {
+                    taskExists = true
+                }
+            }
+        }
+    }
+    return taskExists
+}
+
+def activatePlugin(pluginId) {
+    def pluginLoadFile = "${pluginsDir}/component-load.xml"
+    def componentRegister = new XmlParser().parse(pluginLoadFile)
+
+    // check that plugin directory exists.
+    if(!file("${pluginsDir}/${pluginId}").exists()) {
+        throw new GradleException("Cannot add plugin \"${pluginId}\", directory does not exist")
+    }
+
+    // only add plugin if it does not exist in component-load.xml
+    if(!componentExistsInRegister(componentRegister, pluginId)) {
+        componentRegister.appendNode('load-component', ['component-location':pluginId])
+        groovy.xml.XmlUtil.serialize(componentRegister, new FileWriter(pluginLoadFile))
+        println "Activated plugin ${pluginId}"
+    } else {
+        println "The plugin ${pluginId} is already activated"
+    }
+}
+
+def deactivatePlugin(pluginId) {
+    def pluginLoadFile = "${pluginsDir}/component-load.xml"
+    def componentRegister = new XmlParser().parse(pluginLoadFile)
+
+    // Ensure that the plugin exists in component-load.xml then remove it
+    if(componentExistsInRegister(componentRegister, pluginId)) {
+        componentRegister.children().removeIf { plugin ->
+            pluginId.equals(plugin.@"component-location")
+        }
+        groovy.xml.XmlUtil.serialize(componentRegister, new FileWriter(pluginLoadFile))
+        println "Deactivated plugin ${pluginId}"
+    } else {
+        println "The plugin ${pluginId} is not active"
+    }
+}
+
+def activateAndInstallPlugin(pluginId) {
+    activatePlugin pluginId
+    def gradleRunner = os.contains('windows') ? 'gradlew.bat' : './gradlew'
+    exec { commandLine gradleRunner, 'installPlugin', "-PpluginId=${pluginId}" }
+}
\ No newline at end of file

Added: ofbiz/trunk/framework/resources/templates/build.gradle
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/resources/templates/build.gradle?rev=1760917&view=auto
==============================================================================
--- ofbiz/trunk/framework/resources/templates/build.gradle (added)
+++ ofbiz/trunk/framework/resources/templates/build.gradle Thu Sep 15 11:20:05 2016
@@ -0,0 +1,14 @@
+dependencies {
+    //Examples of compile-time and runtime dependencies
+
+    //pluginLibsCompile 'junit:junit-dep:4.10'
+    //pluginLibsRuntime 'junit:junit-dep:4.10'
+}
+
+task install << {
+    // Install logic for this plugin
+}
+
+task uninstall << {
+    // uninstall logic for this plugin
+}

Propchange: ofbiz/trunk/framework/resources/templates/build.gradle
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/trunk/framework/resources/templates/build.gradle
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/trunk/framework/resources/templates/build.gradle
------------------------------------------------------------------------------
    svn:mime-type = text/plain