- An Introduction to the MID Profile
- The Client Side
- The Server Side
- Conclusion
The Client Side
In order for this mechanism to work, a servlet is needed for handling the server side. The servlet is presented as an example of J2EE deployment.
The interesting piece here is the MIDLicenseManager class, reported in Listing 4.
The License Manager
Listing 4 The MIDLicenseManager Class
package com.marinilli; import javax.microedition.io.*; import javax.microedition.midlet.*; import javax.microedition.rms.*; import javax.microedition.lcdui.*; import java.io.*; /** * Chapter 5 - A General Purpose, Simple License Manager * * @author Mauro Marinilli * @version 1.0 */ public class MIDLicenseManager implements CommandListener { private RecordStore keyStore; private MIDlet midlet; private String licenseId = ""; private String passwd = ""; private Form form; private TextField idTextField; private TextField pwdTextField; private final static String SERVER_URL = "http://localhost:8080/license/midp?license-id="; private boolean successfullyRegistered = false; private Displayable returnDisplayable; /** * */ public MIDLicenseManager(MIDlet m) { midlet = m; // Create a new record store for keys for this midlet suite try { keyStore = RecordStore.openRecordStore("keys", true); int items = keyStore.getNumRecords(); System.out.println("items="+items); if (items > 0) { String roughLicenseId = new String(keyStore.getRecord(1)); if (roughLicenseId!=null) licenseId = unscramble(roughLicenseId); String roughPasswd = new String(keyStore.getRecord(2)); if (roughPasswd!=null) passwd = unscramble(roughPasswd); } else { // the recordstore has to be initialized String n = ""; keyStore.addRecord(n.getBytes(), 0, n.getBytes().length); keyStore.addRecord(n.getBytes(), 0, n.getBytes().length); } } catch (RecordStoreException rse) { System.out.println("MIDLicenseManager() "+rse); } } /** * */ public boolean isLicensed() { return (licenseId != ""); } /** * scrambles a string */ public String scramble(String s) { return s; } /** * un-scramble a string */ public String unscramble(String s) { return s; } /** * Registering a copy */ public void register(){ returnDisplayable = Display.getDisplay(midlet).getCurrent(); if (returnDisplayable==null) returnDisplayable = new Form("Hello"); if (isLicensed()) { Form f = new Form("Software Already Licensed"); f.append("Impossible to register an already registered copy."); f.append("Please contact support for help"); Display.getDisplay(midlet).setCurrent(f); f.setCommandListener(this); f.addCommand(new Command("back",Command.BACK,1)); return; } idTextField = new TextField("Insert Licensed User", "", 12, TextField.ANY); pwdTextField = new TextField("Insert License Code", "", 15, TextField.PASSWORD); form = new Form("Registration"); form.append(idTextField); form.append(pwdTextField); form.addCommand(new Command("ok",Command.OK,1)); form.setCommandListener(this); Display.getDisplay(midlet).setCurrent(form); } /** * connect to the license server */ public String connect() { HttpConnection c = null; InputStream is = null; OutputStream os = null; StringBuffer message = new StringBuffer(); try { c = (HttpConnection)Connector.open(createURL()); String outcome = c.getHeaderField("outcome"); //in case of no connection return null if (outcome==null) { return null; } if (outcome.equals("true")) successfullyRegistered = true; // open the InputStream is = c.openInputStream(); // read the servlet output int ch; while ((ch = is.read()) != -1) { message.append((char)ch); } } catch (Exception exc) { System.out.println("connect() "+exc); } return message.toString(); } /** * process commands */ public void commandAction(Command c, Displayable d) { if (c.getCommandType()==Command.OK) { String s = ""; Gauge gau = new Gauge("Checking License",false,8,0); Form frm = new Form("Please Wait.. ",new Item[] {gau}); gau.setValue(2); Display.getDisplay(midlet).setCurrent(frm); licenseId= idTextField.getString(); passwd = pwdTextField.getString(); gau.setValue(4); String msg = connect(); gau.setValue(6); if (msg==null) { s = "Connection Unavailable" ; msg = "Please try again later."; licenseId = ""; passwd = ""; } else { gau.setValue(8); if(successfullyRegistered) { save(licenseId, passwd); s = "Registration Successful"; } else { licenseId = ""; passwd = ""; s = "Invalid License"; } } Form f = new Form(s); f.append(msg); Display.getDisplay(midlet).setCurrent(f); f.setCommandListener(this); f.addCommand(new Command("back",Command.BACK,1)); } if (c.getCommandType()==Command.BACK) { Display.getDisplay(midlet).setCurrent(returnDisplayable); } } /** * save data persistently */ private void save(String id, String pwd) { try { String scrambled = scramble(licenseId); keyStore.setRecord(1, scrambled.getBytes(), 0, scrambled.getBytes().length); scrambled = scramble(passwd); keyStore.setRecord(2, scrambled.getBytes(), 0, scrambled.getBytes().length); //close the recordStore keyStore.closeRecordStore(); } catch (RecordStoreException rse) { System.out.println("MIDLicenseManager-save() "+rse); } } /** * create an URL */ private String createURL(){ return SERVER_URL + scramble(licenseId) + "&license-pwd=" + scramble(passwd); } }
Discussing the Code
In the constructor (beginning at line 31), the repository where the (encrypted) license data is stored is retrieved from the device's persistent memory. In case it is not yet present, it is created from scratch. For development purposes, MIDlet emulators have a command for clearing up the persistent memory. You can use it to test out this example more than one time.
The registration procedure begins with the register method (lines 80103) that prompts the user for the license data. Users obtain these codes after purchasing the software license from a Web site or by a sales representative, etc. After filling in the form, when the user activates the "ok" command, the commandAction method at line 140 is invoked. A progress bar is shown to the user while trying to connect with the server. The connection is handled by the connect method at lines 108135. Then, accordingly with the connection results, the persistent data are updated (at line 160), and proper explanatory messages are shown to the user.
By means of the isLicensed method at lines 5860, the client MIDlet can query the MIDLicenseManager instance, and can dynamically enable more features available only to registered copies.
The server URL (line 24) corresponds to the deployment scheme presented in Listing 6, together with the details of the client-server interaction.
The security implementation has been kept minimal to not lengthen the code too much. Methods such as scramble (lines 6668) for encrypting sensitive strings and unscramble (lines 7375) to decrypt them are just illustrative. Data is encrypted both when saved persistently on the client device and when transmitted to the server for validation.
This class uses the persistence service offered by the MIDlet profile. This way, thanks to the persistent data stored in the hosting device, the MIDLicenseManager instance is able to recognize an already registered copy, as depicted in Figure 4.
Figure 4 The response of the registration procedure for an already registered copy.