Friday, June 14, 2013

Mobilizing MySQL: Backend Integration Deep Dive

In our previous post, we covered how to build, deploy and run the Android App that mobilizes a MySQL database.

In this post we will look at the code that goes into making this App. We will focus on the Backend OpenMobster API used to integrate the MySQL database with the Sync+Push engine. The next post will cover the Android code that processes the data being mobilized.

Objective
The OpenMobster Backend API consists of two main components. A MobileBean and a Channel.

To mobilize the MySQL database in our App, we need to develop a MobileBean component and a corresponding Channel component. Once, they are developed we register these components with the OpenMobster platform, and the Backend integration will be done.

MobileBean Component
Designing a MobileBean component depends on the object graph that is in question of being mobilized to the device. In our App we are going to mobilize a MySQL table called data_object. This table consists of the following columns:
  • id: A self incrementing primary key
  • syncId: a unique sync id to identify the object uniquely with the Sync Engine. This must be of type String

  • field1: contains field1 data
  • field2: contains field2 data
  • field3: contains field3 data
  • field4: contains field4 data
On the Java MobileBean side, this component is represented as follows:
import java.io.Serializable;
import org.openmobster.cloud.api.sync.MobileBean;
import org.openmobster.cloud.api.sync.MobileBeanId;

/**
 * This is the data that will be mobilized to the device. Over there it will be accessed using the MobileBean generic API
 * 
 * 
 * 
 * @author openmobster@gmail.com
 */
public class DataObject implements Serializable, MobileBean

The DataObject class implements the org.openmobster.cloud.api.sync.MobileBean interface. This is just a marker interface to signal to the Sync Engine that this object should be mobilized.
        private long id; //database oid
 
 @MobileBeanId
 private String syncId; //a unique sync id to identify the object uniquely with the Sync Engine
 private String field1;
 private String field2;
 private String field3;
 private String field4;

As you see in the code, you must designate a field that represents a unique syncId for the MobileBean component. You do that using the org.openmobster.cloud.api.sync.MobileBeanId annotation.

Full Source Code for this MobileBean component: https://openmobster.googlecode.com/svn/samples/mysqlapp/cloud/src/main/java/com/mysqlapp/cloud/DataObject.java

Channel Component
The Channel component exposes a CRUD (Create,Read,Update, and Delete) interface between the MobileBean component and the Sync+Push Engine. The developer must implement this component and register it with the OpenMobster platform. I will now briefly describe the Channel component for the MySQL app using some source code.

 
@ChannelInfo(uri="data_object_channel", mobileBeanClass="com.mysqlapp.cloud.DataObject")
public class DataObjectChannel implements Channel 

To start with you implement the org.openmobster.cloud.api.sync.Channel interface. In this process, you also provide some Channel metadata via the org.openmobster.cloud.api.sync.ChannelInfo annotation. The annotation takes two properties.

  • uri: unique name for the channel. This channel is identified on the device side with this name
  • mobileBeanClass: Informs the consumers of this component (the Sync+Push Engine) about the class that implements its corresponding MobileBean component
The MySQL App Channel uses the Hibernate framework to access the data in the relational database. You could use straight JDBC to hook in to the database as well. However, Hibernate provides a much nicer abstraction and we can easily extend this App to other databases like Oracle, MS SQL Server, PostgreSQL, etc, by some minor configuration changes.

readAll
 public List readAll() 
 {
  Session session = null;
  Transaction tx = null;
  try
  {
   List objects = new ArrayList();
   
   session = this.hibernateManager.getSessionFactory().getCurrentSession();
   tx = session.beginTransaction();
   
   String query = "from DataObject";
   
   List cour = session.createQuery(query).list();
   
   if(cour != null)
   {
    objects.addAll(cour);
   }
      
   tx.commit();
   
   return objects;
  }
  catch(Exception e)
  {
   log.error(this, e);
   
   if(tx != null)
   {
    tx.rollback();
   }
   throw new RuntimeException(e);
  }
 }


The readAll method returns all the MobileBeans managed by this component for mobilization with the device. Inside this method, you can always decide how much/which beans you want mobilized. A lot of the times, the App requires only a subset of all the data stored in the backend and not all the data. Another thing this method can do is figure out which user is accessing the Sync Engine and perform filtering/data isolation based on the user in question. In this particular App, it is returning all the beans stored in the data_object database table.

bootup
 public List bootup() 
 {
  //This implementation boots up the device with all the data in the database table to keep things simple
  return this.readAll();
 }

The bootup method is called by the Sync Engine when the channel on the device side is being initialized. This method returns a subset of all the MobileBeans that will be returned to the device. It returns just enough beans to make sure the App is in a functional state. How many beans and which beans to return is at the discretion of the App being developed. In our case, since there are not that many beans in question and to keep the implementation simple we just return all the beans stored in the database table.

read
public MobileBean read(String syncId) 
 {
  Session session = null;
  Transaction tx = null;
  try
  {
   DataObject dataObject = null;
   
   session = this.hibernateManager.getSessionFactory().getCurrentSession();
   tx = session.beginTransaction();
   
   String query = "from DataObject where syncId=?";
   
   dataObject = (DataObject)session.createQuery(query).setParameter(0, syncId).uniqueResult();
      
   tx.commit();
   
   return dataObject;
  }
  catch(Exception e)
  {
   log.error(this, e);
   
   if(tx != null)
   {
    tx.rollback();
   }
   throw new RuntimeException(e);
  }
 }

read, reads the MobileBean uniquely identified by the specified syncId from the backend storage system. In our case, it reads a row of data from the MySQL database table whose syncId matches the incoming syncId value

create
public String create(MobileBean mobileBean) 
 {
  ExecutionContext context = ExecutionContext.getInstance();
  Device device = context.getDevice();
  DataObject newObject = (DataObject)mobileBean;
  Session session = null;
  Transaction tx = null;
  try
  {
   session = this.hibernateManager.getSessionFactory().getCurrentSession();
   tx = session.beginTransaction();

   session.save(newObject);
      
   tx.commit();
   
   this.newBeanDetector.addSyncId(device, newObject.getSyncId());
   
   return newObject.getSyncId();
  }
  catch(Exception e)
  {
   log.error(this, e);
   
   if(tx != null)
   {
    tx.rollback();
   }
   
   throw new RuntimeException(e);
  }
 }


Creates a new instance of the specified Mobile Bean within the MySQL Database. It returns the syncId assigned to the MobileBean once persisted into the backend. This is invoked when a new MobileBean is created on the device and synchronized back with the backend.

update
public void update(MobileBean mobileBean) 
 {
  Session session = null;
  Transaction tx = null;
  DataObject updateThis = (DataObject)mobileBean;
  DataObject stored = (DataObject)this.read(updateThis.getSyncId());
  updateThis.setId(stored.getId());
  try
  {
   session = this.hibernateManager.getSessionFactory().getCurrentSession();
   tx = session.beginTransaction();
      
   session.update(updateThis);
      
   tx.commit();
  }
  catch(Exception e)
  {
   log.error(this, e);
   
   if(tx != null)
   {
    tx.rollback();
   }
   throw new RuntimeException(e);
  }
 }

Updates an existing instance of the specified Mobile Bean within the backend storage system. This is invoked when a MobileBean instance is updated on the device and is synchronized with the backend.

delete
public void delete(MobileBean mobileBean) 
 { 
  Session session = null;
  Transaction tx = null;
  DataObject deleteThis = (DataObject)mobileBean;
  DataObject stored = (DataObject)this.read(deleteThis.getSyncId());
  deleteThis.setId(stored.getId());
  try
  {
   session = this.hibernateManager.getSessionFactory().getCurrentSession();
   tx = session.beginTransaction();
   
   session.delete(deleteThis);
      
   tx.commit();
  }
  catch(Exception e)
  {
   log.error(this, e);
   
   if(tx != null)
   {
    tx.rollback();
   }
   throw new RuntimeException(e);
  }
 }

Permanently deletes the specified MobileBean from the backend storage system. This is invoked when a MobileBean is deleted on the device. The same change is then reflected on the backend by invoking this method on the Channel

Continuous Scanning and Data Push
In the OpenMobster platform, there is a provision to scan a Channel continuously for data changes. If data changes are found, these changes are pushed to the respective device. The scanning is done for three types of changes. Newly added data to the backend, data updated by other apps in the backend, and data deleted by others in the backend.  To support scanning and data push the Channel must implement a set of methods covered below.

scanForNew
public String[] scanForNew(Device device, Date lastScanTimestamp) 
 {
  Set newBeans = this.newBeanDetector.scan(device);
  
  if(newBeans != null && !newBeans.isEmpty())
  {
   return newBeans.toArray(new String[0]);
  }
  
  return null;
 }


Scan for any MobileBean creations in the backend that need to be synchronized with the specified device. Usually the algorithm to detect these changes depends on the type and schema of the backend being mobilized. In this particular app, the Channel keeps track of all MobileBeans associated with a device. When it finds a particular bean not associated yet, it detects the bean and signals the Push Engine to push those beans to the device.


Configuration
Now that the two Backend components have been developed, they need to be registered for execution within the OpenMobster platform. You do that using the META-INF/openmobster-config.xml file.

META-INF/openmobster-config.xml
The configuration registers two beans with the system. First one is a HibernateManager bean that provides all the necessary Hibernate services to the App. The second one, is the DataObjectChannel component. It injects the HibernateManager and the DeviceController services into the Channel. These services will be used by the Channel for their respective functions. To learn more about how these services are used by the Channel, I recommend looking at the full source code of the Channel component.

Hibernate Configuration

  • mysqlapp.cfg.xml: This is where you setup the connection to the MySQL database. You specify the JDBC Driver to be used, and the JDBC Url to access the database. You also specify other Hibernate settings in this file. This is the file to modify if you want to switch integration from MySQL to some other database like Oracle, MS SQL Server, PostgreSQL, etc.
  • mysqlapp.hbm.xml: This is the Hibernate mapping file used to map domain objects to tables in the database. This is a very simple mapping file for this App. All it does is it maps the DataObject  MobileBean component to a data_object table in the database.
Getting the Source Code for this App
The best way to learn the details of any new platform is to study the source code for an App that runs on that platform. The same applies to this MySQL App. You can get the source code for this App using the following command:

svn checkout http://openmobster.googlecode.com/svn/samples/mysqlapp
For a guide on how to Build, Deploy, and Run this App please look at: http://openmobster.blogspot.com/2013/06/enterprise-mobile-app-mobilizing-mysql.html

Our next post will cover developing an Android App that integrates with this MySQL Backend we developed in this post. Until then, let us know how our tutorials can be improved for the benefit of the OpenMobster Community!!!!

Thanks
Sohil
CEO, OpenMobster, Open Source MBaaS Platform


ShareThis

No comments:

Post a Comment