Using SAP UI5 and NW Cloud Persistence Service with Grails Scaffolding
I’m a big fan of Grails and I’ve been using it since the early days. The built-in scaffolding functionality is really great and speeds up my development time by providing out-of-box CRUD pages without having to write a bunch of boilerplate codes. One thing that you’ll notice is that the UI generated by the scaffolding is very simple considering that we are now surrounded by tons of HTML5 and jquery-based UIs.
Recently, SAP launched the SAP NetWeaver Cloud and it’s accompanying UI5 library for generating nice-looking UI. I’ve started to play around with it and I decided to modify the default scaffolding templates of Grails so that it uses the SAP UI5 to generate the CRUD pages.
Prerequisites
Before we begin, there are stuff that you need to download and install first:
- Grails 2.2 (grails.org)
- Maven 3.0.3 (maven.apache.org)
- SAP Netweaver Cloud SDK (tools.netweaver.ondemand.com)
- SAP NetWeaver Cloud Deployment Plugin for Maven (https://github.com/sapnwcloudlabs/nwcloud-maven-plugin)
To get a trial account for SAP NetWeaver Cloud click here: https://help.netweaver.ondemand.com/default.htm?trial_account.html
Let’s Get Started
Assuming that you’ve already installed the prerequisites above, we can start by creating our Grails project:
grails create-app FooApp
cd FooApp
Next, for us to be able to take advantage of the SAP NetWeaver Cloud Deployment Plugin for Maven, we need to mavenized our project:
grails create-pom com.sap
This will create the pom.file for us that includes all the necessary dependencies with the groupId set to com.sap. Then we need to add our NW Cloud Plugin to the list of plugins in pom.xml:
<plugin>
<groupId>com.sap.research</groupId>
<artifactId>nwcloud-maven-plugin</artifactId>
<version>1.0.0.RELEASE</version>
<executions>
<execution>
<id>after.package</id>
<phase>package</phase>
<goals>
<goal>hint</goal>
</goals>
</execution>
</executions>
</plugin>
Then try to do a mvn clean compile to test our setup. If you see BUILD SUCCESS at the end then we can move on to the next part which is to setup our datasource.
Datasource
I want to be able to use the SAP NetWeaver Cloud’s built-in persistence service. There are several ways that we can connect to the persistence service but in this case, we don’t really need to care because Grails will handle it for us. We just need to declare the JNDI name in our grails-app/conf/DataSource.groovy and update web.xml to reflect the JNDI resource as well.
grails-app/conf/DataSource.groovy
production {
dataSource {
dbCreate = "create"
jndiName = "java:comp/env/jdbc/DefaultDB"
dialect = "org.hibernate.dialect.SAPDBDialect"
}
}
You’ll notice that we only declare the jndi datasource for the production mode because we still want to take advantage of the grails’ in-memory database when in development mode.
Declaring the JNDI name in web.xml requires a bit of effort because web.xml is also being generated automatically for you by Grails. What we need to do is to listen for the event when Grails is done generating our web.xml and modify it to append our jndi resource. To do this, you need to create the _Events.groovy in scripts folder.
scripts/_Events.groovy
eventWebXmlEnd = { String filename ->
def webxml = webXmlFile
def newxml = new File(webxml.path + ".bak")
def root = new XmlParser().parse(webxml)
// add the jdbc/mydatasource resource reference to the web.xml
def resourceRef = root.appendNode('resource-ref')
resourceRef.appendNode('description','The SAP NetWeaver JNDI Database resource')
resourceRef.appendNode('res-ref-name','jdbc/DefaultDB')
resourceRef.appendNode('res-type','javax.sql.DataSource')
def writer = new StringWriter()
new XmlNodePrinter(new PrintWriter(writer)).print(root)
newxml.withWriter { out ->
out.writeLine(writer.toString())
}
webxml.delete()
newxml.renameTo(webxml.path)
}
Here, we are appending the resource-ref node to the end of web.xml and finally replacing the original file with the modified one.
Lastly, we need to copy a jar file from the NW Cloud SDK to our lib folder. Go to $NW_CLOUD_SDK/repository/plugins/com.sap.security.core.server.csi_1.0.1.jar and copy this into your lib folder and we’re done with our data source configuration. Now let’s move on to create our first model.
Model
In this example, we are just going to create a very simple model Person with few properties.
grails create-domain-class com.sap.fooapp.Person
This will generate grails-app/domain/com/sap/fooapp/Person.groovy. Open that file and add the properties to make it look like below:
package com.sap.fooapp
class Person {
String firstName
String lastName
String department
String notes
static constraints = {
firstName blank:false, size: 2..20
lastName blank:false, size: 2..20
department inList: ["Sales", "Development", "HR"]
notes widget:'textarea'
}
}
Let’s generate the controller and views for our model and do our first run so that we can test if everything is still ok.
grails generate-all com.sap.fooapp.Person
grails run-app
And if you leave the default port to 8080, fire up your browser and open http://localhost:8080/FooApp and you should see the index page of your Grails app. You can open http://localhost:8080/FooApp and play around with the crud pages before we actually replace everything with UI5.
Templates
Now we need to install the templates used by Grails to generate the default views. Basically, we want to be able to generate the default CRUD pages that uses UI5 library. Let’s install the templates by executing:
grails install-templates
Several files will be added to the folder src/templates. Our target is to replace the files inside src/templates/scaffolding. Extract this zip file to this folder and overwrite all the files.
Then let’s generate our views and controllers again for our Person model and overwrite all files.
grails generate-all com.sap.fooapp.Person
Next, let’s modify our main layout file to include our SAP UI5 Javascript Library. Open grails-app/views/layouts/main.gsp and add the following line under the <head> section and just before <g:layoutHead/>:
<script id="sap-ui-bootstrap" type="text/javascript"
src="https://sapui5.netweaver.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-theme="sap_goldreflection"
data-sap-ui-libs="sap.ui.commons,sap.ui.table"></script>
Then we’re ready to test it out:
grails run-app
Browse http://localhost:8080/FooApp/person/ and if everything is good, you should see something like:
Try to add a new person and you can edit or delete an item by clicking on the far left side space in each row.
NOTE: The templates provided only supports String types for now but hopefully I may find time to add support for other types as well such as Date, boolean, Integers as well as associations.
Now we’re ready to deploy to the cloud!
Deployment to the Cloud
Issues with Manifest file
Grails automatically generates the WEB-INF/META-INF/MANIFEST.MF but it seems the NetWeaver cloud isn’t happy with the values present in the manifest file and good thing I stumbled upon this article by Jakub Sendor. Bascially the idea is to have a default manifest file that contains just a manifest version and use that to replace the one generated by Grails. Again, we can do this from the _Events.groovy by listening to the event when the creation of war file is going to start. Open up your scripts/_Events.groovy again and it should look something like:
includeTargets << grailsScript("_GrailsInit")
eventCreateWarStart = { warName, stagingDir ->
ant.copy(todir: "$stagingDir/META-INF", file: "web-app/META-INF/MANIFEST.MF", overwrite: true)
}
eventWebXmlEnd = { String filename ->
def webxml = webXmlFile
def newxml = new File(webxml.path + ".bak")
def root = new XmlParser().parse(webxml)
// add the jdbc/mydatasource resource reference to the web.xml
def resourceRef = root.appendNode('resource-ref')
resourceRef.appendNode('description','The SAP NetWeaver JNDI Database resource')
resourceRef.appendNode('res-ref-name','jdbc/DefaultDB')
resourceRef.appendNode('res-type','javax.sql.DataSource')
def writer = new StringWriter()
new XmlNodePrinter(new PrintWriter(writer)).print(root)
newxml.withWriter { out ->
out.writeLine(writer.toString())
}
webxml.delete()
newxml.renameTo(webxml.path)
}
Then create the dummy manifest file in web-app/META-INF/MANIFEST.MF and just write
Manifest-Version: 1.0
:
Cloud properties
Next, we need to create nwcloud.properties at the root of our project. This is going to be used by the NW Cloud Maven plugin to obtain necessary information about the credentials and deployment properties.
nwcloud.properties
sdk.dir=$YOUR_NW_CLOUD_SDK_DIR
host=https://nwtrial.ondemand.com
account=ACCOUNT_NAME
application=APPLICATION_NAME
user=YOUR_USERNAME
synchronous=true
Lastly, let’s create our WAR file by executing:
grails war
and now we’re ready to deploy to NetWeaver Cloud by simply executing:
mvn nwcloud:deploy
You’ll be asked to enter your password and after that you can now go to http://accounts.nwtrial.ondemand.com and start your application.
Improvements
Currently, I don’t think the codes in the templates provided are efficient and I wrote them only for demo purposes. One can improve the ui5 codes and probably restructure it so that it follows the MVC design. Also, not all data types are supported right now so that definitely needs some time to work on.
Conclusion
It was really fun to play with UI5 and the NetWeaver Cloud SDK. I can’t wait to try and explore Grails Scaffolding that uses UI5 for mobile.
Hi Marc,
good article!
Just a small note - the Maven plugin can be used not just for deployment, but also for starting/stopping the deployed app:
Start app:
mvn nwcloud:start
Stop app:
mvn nwcloud:stop
You could also deploy and start the app using just one command on the commandline, like this:
mvn nwcloud:deploy nwcloud:start
Best,
Michael
Thanks Michael!
Nice blog - thanks for sharing Marc!
Thank you for saving me countless hours with the solutions for the Events and Manifest files.
This is such as clever application for Grails. I look forward to seeing your next updates and ideas.
Thanks Jeff! Actually credit should go to Jakub Sendor for his initial implementation here :
article
I used the maven plugin on this link to get it working on my Mac: https://github.com/SAP/cloud-maven-plugin
And modified the plugin version to 1.0.1-RELEASE by default it was 1.0.0 if I am not wrong.
Thanks Jeff Collier for making it easier for me and making me not go through the windows way! 🙂
Hi Subhransu,
Thanks for your comment! Version 1.0.1-RELEASE is absolutely correct.
A current snippet for cut & paste can always be found in the section "Using the plugin" of "README.md" of the Maven plugin - just scroll down on the GitHub page:
https://github.com/SAP/cloud-maven-plugin
Cheers,
Michael
Hi guys,
one comment about the Maven plugin: SAP HANA Cloud Platform has meanwhile an official Maven plugin (the one above wasn't developed by the SAP HANA Cloud Platform team).
The official documentation can be found here: https://help.hana.ondemand.com/mavenSite/index.html
SAP HANA Cloud Platform SDK API, and Maven plugin are available on Maven Central. The following two documents may be of interest as well:
http://scn.sap.com/community/developer-center/cloud-platform/blog/2014/05/27/building-java-applications-with-maven
http://scn.sap.com/community/developer-center/cloud-platform/blog/2014/05/27/working-with-the-neo-maven-plugin
Cheers, Vedran
Hello!
Help me, please.
I can't work with database.
I got an exception:
Error 500: Internal Server Error
Trace
Hi Vladi,
the error that you get is pure related to persistence service.
The table "PERSON" does not seem to be created.
Read more about persistence on the cloud with JDBC here:
https://help.hana.ondemand.com/help/frameset.htm?e79cfef4bb57101494cde44e48426511.html#concept_F2A69956A9C5463BAF0B0CC188092097_88
Regards
Hello,
please help me... I follow the steps here but failed at the deploy step, got the error message as following. Anything wrong for the configuration??
Looking at the screenshot is seems like the host is wrong. Try "host=https://hanatrial.ondemand.com" instead.
Great.. Works now. Thanks for the help.
If I use the external config file for the web, how can I deploy the configuration file to the cloud platform?