Skip to Content

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:

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.

1-11-2013 2-04-15 PM.png

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:

1-11-2013 2-15-20 PM.png

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.

1-11-2013 2-19-07 PM.png

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.

1-11-2013 2-56-12 PM.png

1-11-2013 2-54-32 PM.png

1-11-2013 2-55-42 PM.png

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.

To report this post you need to login first.

14 Comments

You must be Logged on to comment or reply to a post.

  1. Michael Spahn

    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

    (0) 
  2. Jeff Collier

    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.

    (0) 
  3. Subhransu Behera

    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.

            <artifactId>nwcloud-maven-plugin</artifactId>
            <version>1.0.1.RELEASE</version>

    Thanks Jeff Collier for making it easier for me and making me not go through the windows way! 🙂 

    (0) 
      1. Vedran Lerenc

        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

        (0) 
  4. Vladislav Neberikutya

    Hello!

    Help me, please.

    I can’t work with database.

    I got an exception:

    • Error 500: Internal Server Error

    • URI
      /hello-grails/person/list
      Class
      com.sap.db.jdbc.exceptions.JDBCDriverException
      Message
      SAP DBTech JDBC: [259]: invalid table name: Could not find table/view PERSON in schema NEO_166O41IT5Z6KDYXCGI080M9KY: line 4294967295 col 4294967295 (at pos 4294967295)
    • Trace

    •     Line | Method ->>  334 | createException           in com.sap.db.jdbc.exceptions.SQLExceptionSapDB - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |    174 | generateDatabaseException in     '' |    105 | buildExceptionChain . . . in com.sap.db.jdbc.packet.ReplyPacket |    867 | execute                   in com.sap.db.jdbc.ConnectionSapDB |   1877 | sendCommand . . . . . . . in com.sap.db.jdbc.CallableStatementSapDB |    945 | sendSQL                   in com.sap.db.jdbc.StatementSapDB |    233 | doParse . . . . . . . . . in com.sap.db.jdbc.CallableStatementSapDB |    193 | constructor               in     '' |    104 | <init> . . . . . . . . .  in     '' |     31 | <init>                    in com.sap.db.jdbc.CallableStatementSapDBFinalize |   1085 | prepareStatement . . . .  in com.sap.db.jdbc.ConnectionSapDB |    413 | prepareStatement          in com.sap.db.jdbc.trace.Connection |     40 | invoke . . . . . . . . .  in com.sap.core.persistence.jdbc.trace.TraceableBase$1 |    120 | prepareStatement          in com.sap.core.persistence.jdbc.trace.TraceableConnection |    281 | prepareStatement . . . .  in org.apache.commons.dbcp.DelegatingConnection |    313 | prepareStatement          in org.apache.commons.dbcp.PoolingDataSource$PoolGuardConnectionWrapper |     55 | <init> . . . . . . . . .  in grails.orm.PagedResultList |     15 | list                      in com.sap.fooapp.PersonController |    195 | doFilter . . . . . . . .  in grails.plugin.cache.web.filter.PageFragmentCachingFilter |     63 | doFilter                  in grails.plugin.cache.web.filter.AbstractFilter |    191 | invoke . . . . . . . . .  in com.sap.security.auth.service.webcontainer.internal.Authenticator |    170 | invokeNextValve           in com.sap.core.tenant.valve.TenantValidationValve |     85 | invoke . . . . . . . . .  in     '' |     25 | invoke                    in com.sap.js.statistics.tomcat.valve.RequestTracingValve |     27 | invoke . . . . . . . . .  in com.sap.core.js.monitoring.tomcat.valve.RequestTracingValve |    895 | runTask                   in java.util.concurrent.ThreadPoolExecutor$Worker |    918 | run . . . . . . . . . . . in     '' ^    743 | run                       in java.lang.Thread
    (0) 
  5. Jessie Wu

    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??

    error.PNG

    (0) 
  6. Jessie Wu

    If I use the external config file for the web, how can I deploy the configuration file to the cloud platform?

    (0) 

Leave a Reply