Monday, July 25, 2011

VMWare Cloud Foundry - Developing "cloud-ready" web applications has never been so easy

A lot has been said and done about services on the cloud lately and "Platform as a Service" (PaaS) is probably one of the most popular buzz-word that's been around for quite sometime now. However, I sincerely feel that the common man (read as "developer") is yet to get a taste of what it really means. Now, this very paradigm seems to be soon changing, with VMWare Cloud Foundry. Cloud Foundry is the world’s very first open Platform as a Service (PaaS). It is designed to help developers easily create web applications, using multiple programming frameworks including Spring for Java, Ruby on Rails and Sinatra for Ruby; that can run upon public and private cloud environments, with just about no additional learning curve. So, by now if you are dreaming of a possibility to port your existing web-applications to the cloud with minimal efforts, then let me give you the good news - "it's all possible here..."

Well, looks like i've done a lot of sales talks in favour of VMWare Cloud Foundry. Now lets get under the hood and get hands-on with developing an extreamly simple web application that demonstrates the following -

1. Setting up VMWare Cloud Foundry
2. Consuming Cloud Foundry's MySQL service in a JSP, using the standard JDBC way
3. Deploying a web archive and running the JSP on Cloud Foundry

Setting up VMWare Cloud Foundry

Windows Setup

  1. Apply for a Cloud Foundry account at www.cloudfoundry.com; you will be notified by email when your account is activated
  2. Install Ruby from www.rubyinstaller.org; The Cloud Foundry cloud-controller command line is built in Ruby so this is required. As the installer runs, make sure to check the boxes to add the ruby directory to your command path
  3. Start the Windows command line client (Start Menu -> Run -> "cmd")
  4. To install the cloud-controller tool type : gem install vmc (If you are behind a firewall then : gem install --http-proxy http://proxy.vmware.com:3128 vmc)
Congratulations! The Cloud Foundry Cloud Controller is now installed. From here on, you may type Cloud Foundry commands into the Windows command window.

  1. Inform Cloud-Foundry which cloud you want to connect to : vmc target api.cloudfoundry.com
  2. To login to Cloud Foundry type : vmc login (enter your account credentials when prompted)

Linux Setup

The document here is the best source of information for setting up the cloud-controller upon various Linux flavours.

Consuming Cloud Foundry's MySQL service

There are plenty of elaborate tutorials available on the web, that illustrate accessing the MySQL service at Cloud foundry using Spring. However, this one is a bit different, because it does not use Spring at all and demonstrates the same functionality with plain and simple Java and standard JDBC API.

The following code snippet demonstrates the approach -

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@page import="java.sql.*,javax.sql.*"%>
<%@page import="org.cloudfoundry.services.*"%>
<%
String query = "Select * FROM users";
%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Cloud Foundry with Simple Java</title>
</head>
<body>
Welcome to the simple web application on cloud-foundry...
<%
Connection connection = null;

try {
// establish connection to MySQL Service
ServiceManager services = ServiceManager.INSTANCE;
connection = (Connection) services.getInstance(CloudFoundryServices.MYSQL);

if (connection != null && !connection.isClosed()) {
out.println("<p>Successfully connected to MySQL service</p>");

// creating a database table and populating some values
Statement s = connection.createStatement();
int count;
s.executeUpdate("DROP TABLE IF EXISTS animal");
s.executeUpdate("CREATE TABLE animal ("
+ "id INT UNSIGNED NOT NULL AUTO_INCREMENT,"
+ "PRIMARY KEY (id),"
+ "name CHAR(40), category CHAR(40))");

out.println("<p>[1] Table successfully created.</p>");

count = s.executeUpdate("INSERT INTO animal (name, category)"
+ " VALUES"
+ "('snake', 'reptile'),"
+ "('frog', 'amphibian'),"
+ "('tuna', 'fish'),"
+ "('racoon', 'mammal')");

out.println("<p>[2] " + count + " rows were inserted.</p>");

count = 0;
ResultSet rs = s.executeQuery("select * from animal");
while (rs.next()) {
count++;
}
out.println("<p>[3] " + count + " rows were fetched.</p>");

s.close();
}
} catch (Exception e) {
out.println(e.getMessage());
} finally {
if (connection != null && !connection.isClosed()) {
connection.close();
}

connection = null;
}
%>
</body>
</html>

In the above code, org.cloudfoundry.services is a custom package which primarily contains a Singleton implementation, namely "ServiceManager"; and an interface to hold the constants, namely "CloudFoundryServices". Following are the sources for the same -

CloudFoundryServices.java


package org.cloudfoundry.services;

public interface CloudFoundryServices {
public static final int MYSQL = 1;
}

ServiceManager.java


package org.cloudfoundry.services;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import argo.jdom.JdomParser;
import argo.jdom.JsonNode;
import argo.jdom.JsonRootNode;

public enum ServiceManager implements CloudFoundryServices {

INSTANCE;

private static final String NULL_STRING = "";

public Object getInstance(int service_type) throws Exception {
if (service_type == MYSQL) {
return getMySQLConnection();
} else {
throw new IllegalArgumentException("Service for id " + service_type + " not found...");
}
}

/*
* This method is responsible for establishing a valid connection to the MySQL service,
* using the credentials available in the environment variable, namely "VCAP_SERVICES".
*
* The content of VCAP_SERVICES environment variable is a JSON string, thus this method
* uses standard interfaces from the Argo JSON parsing API to extract the credentials.
*/

private Object getMySQLConnection() throws SQLException {
String vcap_services = System.getenv("VCAP_SERVICES");

String hostname = NULL_STRING;
String dbname = NULL_STRING;
String user = NULL_STRING;
String password = NULL_STRING;
String port = NULL_STRING;

if (vcap_services != null && vcap_services.length() > 0) {
try {
JsonRootNode root = new JdomParser().parse(vcap_services);

JsonNode mysqlNode = root.getNode("mysql-5.1");
JsonNode credentials = mysqlNode.getNode(0).getNode("credentials");

dbname = credentials.getStringValue("name");
hostname = credentials.getStringValue("hostname");
user = credentials.getStringValue("user");
password = credentials.getStringValue("password");
port = credentials.getNumberValue("port");

String dbUrl = "jdbc:mysql://" + hostname + ":" + port + "/" + dbname;

Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(dbUrl, user, password);
return connection;
} catch (Exception e) {
throw new SQLException(e);
}
}

return null;
}
}

Now all you need to do now is, bundle everything together and get yourself a standard WAR (web archive) file.

Deploying a web archive and running the JSP on Cloud Foundry

Once you have the WAR file in place, browse to the location where the file is placed, using your command-line console; and execute the following cloud-controller commands -
  • In case you are not yet logged in to the cloud, type : vmc login (and, provide the required credentials when prompted)
  • vmc push
  • Enter a unique application name when prompted, could be anything like in my case its my name "rahulr"
  • Select 'Y' when prompted to bind a service and select the MySQL service from the offered list of services
Finally the status "Starting Application : OK", at the end of the command execution, indicates a successful deployment. So, now you are all set to start-up a browser and fire-up your first "cloud-enabled" web application, at the url <app-name>.cloudfoundry.com. Optionally, you might want to check the one that I've hosted at http://rahulr.cloudfoundry.com

Thats all for now, I hope you liked the post and should you have any issues getting this up and running feel free to reach out to me.

Additional Resources

Following are some links which I found useful -

3 comments:

Anonymous said...

hi roy

i tried the code to connect mysql but its showing the following error when deploying it

HTTP Status 500 -

--------------------------------------------------------------------------------

type Exception report

message

description The server encountered an internal error () that prevented it from fulfilling this request.

exception

org.apache.jasper.JasperException: An exception occurred processing JSP page /index.jsp at line 25

22: try {
23: // establish connection to MySQL Service
24: ServiceManager services = ServiceManager.INSTANCE;
25: connection = (Connection) services.getInstance(1);
26:
27: //if (connection != null && !connection.isClosed()) {

Stacktrace:
org.apache.jasper.servlet.JspServletWrapper.handleJspException(JspServletWrapper.java:519)
org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:410)
org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:313)
org.apache.jasper.servlet.JspServlet.service(JspServlet.java:260)
javax.servlet.http.HttpServlet.service(HttpServlet.java:717)


root cause

javax.servlet.ServletException: java.lang.NoClassDefFoundError: argo/jdom/JdomParser
org.apache.jasper.runtime.PageContextImpl.doHandlePageException(PageContextImpl.java:865)
org.apache.jasper.runtime.PageContextImpl.handlePageException(PageContextImpl.java:794)
org.apache.jsp.index_jsp._jspService(index_jsp.java:133)
org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:386)
org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:313)
org.apache.jasper.servlet.JspServlet.service(JspServlet.java:260)
javax.servlet.http.HttpServlet.service(HttpServlet.java:717)


root cause

java.lang.NoClassDefFoundError: argo/jdom/JdomParser
org.cloudfoundry.services.ServiceManager.getMySQLConnection(ServiceManager.java:45)
org.cloudfoundry.services.ServiceManager.getInstance(ServiceManager.java:20)
org.apache.jsp.index_jsp._jspService(index_jsp.java:81)
org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:386)
org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:313)
org.apache.jasper.servlet.JspServlet.service(JspServlet.java:260)
javax.servlet.http.HttpServlet.service(HttpServlet.java:717)


root cause

java.lang.ClassNotFoundException: argo.jdom.JdomParser
org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1680)
org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1526)
org.cloudfoundry.services.ServiceManager.getMySQLConnection(ServiceManager.java:45)
org.cloudfoundry.services.ServiceManager.getInstance(ServiceManager.java:20)
org.apache.jsp.index_jsp._jspService(index_jsp.java:81)
org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:386)
org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:313)
org.apache.jasper.servlet.JspServlet.service(JspServlet.java:260)
javax.servlet.http.HttpServlet.service(HttpServlet.java:717)


note The full stack trace of the root cause is available in the Apache Tomcat/6.0.32 logs.


--------------------------------------------------------------------------------

Apache Tomcat/6.0.32

please help me to solve this error

Thanks
sundar

JAVA初心者 said...

I am java, mysql beginner.
I do not understand English very much, but please teach it.
I copy this blog and want to display the same thing.
Will you send a project file to the following e-mail addresses if good?

- - - - -
My e-mail address,
stock0@cure11.com
- - - - -

In addition, of ServiceManager.java
dbname = credentials.getStringValue("name");
hostname = credentials.getStringValue("hostname");
user = credentials.getStringValue("user");
password = credentials.getStringValue("password");
port = credentials.getNumberValue("port");
I am thankful when I have you teach the concrete value that you should input into name, hostname, user, port.

Rahul Roy said...

Sundar,

It is obvious from the error that you are missing a argo parser in your classpath. Just ensure that you have the same in place and retry.

-- Rahul roy