Skip to end of metadata
Go to start of metadata

RAMP: Advanced Guide

Introduction

The Getting Started guide showed how to create some simple applications quickly by focusing primarily on client-side RAMP development. In particular, the RAMP feature server was set to the default RAMP server maintained by Virtual Mobile Technologies (VMT). Similarly, the client invoked web services was hosted by the default RAMP server. In this guide we show how to:

  • Set up a feature server
  • Generate RSA private keys
  • Sign your features so that users can be confident that they are running applications supplied by you.
  • Create secure web services.
  • Monitor the RAMP server
  • Internationalize clients
  • Advanced IDE features

This document assumes that you read the material in the Getting Started guide on setting up a server to handle web service calls.

The RAMP Feature Server

The Getting Started guide gives some details on setting up a RAMP server. The RAMP server includes the "feature server" that can issue RAMP applications to RAMP clients, and the "service server" that provides web services to the client. This means that you can issue your RAMP applications from the same RAMP server that handles web service calls. Running the services server and the feature server on the same machine has a significant advantage: whenever the feature is changed, the client will be notified when it invokes a web service. Consequently, the next time that the RAMP client is started it knows to retrieve a new version of the feature from the feature server.

A core configuration file in the setting up of a RAMP server is the main-context.xml file. The Getting Started guide described much of the contents of the example main-context.xml file as "boilerplate". In this guide, we see that it's not true "boilerplate" because you might want to change it.

For reference, here's the main-context.xml file used by the Twi service in the Getting Started guide.

main-context.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"

	xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd
       ">

   <!--
        You can regard this as boiler-plate.
        The advanced guide covers this in more detail.
     -->
   <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
      <property name="locations" ref="propertyFiles"/>
   </bean>  
	
   <bean id="serviceRequestManager" class="com.vmt.ramp.integration.impl.ServiceRequestManagerImpl">
      <property name="mapper" ref="serviceRequestMapper" />
   </bean>

   <bean id="serviceRequestMapper" class="com.vmt.ramp.integration.impl.ServiceRequestMapper">
      <property name="handlers" ref="handlers" />
   </bean>

   <bean id="featureBundleProvider" class="com.vmt.ramp.integration.impl.MultiDirectoryFeatureBundleProviderImpl">
      <property name="rootDirectory" value="${scripts.dir}"/>
      <property name="signer" ref="featureSigner"/>
   </bean>

   <bean id="featureSigner" class="com.vmt.ramp.integration.impl.security.SoftwareSigner">
      <property name="rsaPrivateKeyDao" ref="rsaPrivateKeyDao"/>
   </bean>
   
   <bean id="featureVersionFilter" class="com.vmt.ramp.integration.impl.FeatureVersionAfterServiceRequestFilter">
      <property name="multiFBP" ref="featureBundleProvider"/>
   </bean>
   
   <bean id="rsaPrivateKeyDao" class="com.vmt.ramp.integration.impl.security.TestKeysDao" />
   
   
   <!--
        This is the important stuff: Declare your services and the classes that 
        implement the handlers.
     -->
   <util:map id="handlers">
      <entry key="accessTokenHandler" value-ref="accessTokenHandler" />
      <entry key="requestTokenHandler" value-ref="requestTokenHandler" />
   </util:map>

   <bean id="accessTokenHandler" class="twiservice.integration.AccessTokenHandler">
   	<constructor-arg><ref bean="twitterService"/></constructor-arg>
   </bean>
   
   <bean id="requestTokenHandler" class="twiservice.integration.RequestTokenHandler">
   	<constructor-arg><ref bean="twitterService"/></constructor-arg>
   </bean>
   
   <bean id="twitterService" class="twiservice.remote.TwitterService">
   	 <constructor-arg><value>${consumer.key}</value></constructor-arg>
   	 <constructor-arg><value>${consumer.secret}</value></constructor-arg>
   </bean>

</beans>

Setting up a Feature Bundle Provider

The following lines configure your RAMP server

featureBundleProvider xml
<bean id="featureBundleProvider" class="com.vmt.ramp.integration.impl.MultiDirectoryFeatureBundleProviderImpl">
   <property name="rootDirectory" value="${scripts.dir}"/>
   <property name="signer" ref="featureSigner"/>
</bean>

The variable scripts.dir is defined in the ramp-config.properties file located in the /etc/ramp directory. When you upload a feature from the RAMP IDE to a feature server, the application (RAMPScript, UXML and CSS) and images get stored in the file system of the server. In our example, scripts.dir has the value file:/etc/ramp/integration/script_dirs/ which means that any features will be stored in a subdirectory of /etc/ramp/integration/script_dirs/.

The "signer" property of the feature bundle provider references a class that will be used to sign applications. When a feature is downloaded to the RAMP client, the client needs to check that the feature was issued by the same company that deployed the client. This is achieved by having the feature bundle provider sign every feature that is sent to a RAMP client. The client verifies the signature before installing the feature (if the signature cannot be verified, the client reports an error to the user and does not run the application).

The "featureSigner" is created in the following lines of code

<bean id="featureSigner" class="com.vmt.ramp.integration.impl.security.SoftwareSigner">
   <property name="rsaPrivateKeyDao" ref="rsaPrivateKeyDao"/>
</bean>

<bean id="rsaPrivateKeyDao" class="com.vmt.ramp.integration.impl.security.TestKeysDao" />

The signer class here is an instance of SoftwareSigner that is included in the RAMP server. If you use a Hardware Security Module (HSM), you could implement your own signing class. The SoftwareSigner class needs a DAO (data access object) that maps a key ID to a RSA private key. Here, the DAO is an instance of TestKeysDao. This test DAO contains three RSA (private) test keys. Since every distribution of the RAMP platform includes them, they offer no security at all and are (as their name suggests) intended solely for testing.

Finally, the "featureVersionFilter" bean ensures that when a service is invoked, the client is notified whether there is a new version of the feature available.

<bean id="featureVersionFilter" class="com.vmt.ramp.integration.impl.FeatureVersionAfterServiceRequestFilter">
   <property name="multiFBP" ref="featureBundleProvider"/>
</bean>

If the "Production builds?" checkbox is ticked when building an application on the deployment platform, the feature will only be downloaded if it was notified that a new feature is available. If this box is left unticked, the feature will be downloaded every time the application starts up.

For a client to be notified that a new feature is available, the feature server and service server need to be running on the same RAMP server.

Using your own Feature Server

In this section, we'll show you how to build an application which uses your own feature server and the test feature-signing key. You'll need to create a new application on the RAMP deployment platform. We'll suppose that you call it twi2.

Create your application as before (in the Getting Started guide), except this time you'll want to change the feature server. We'll assume that you're going to test with the emulator on your local machine. Click the "Set feature server" button and enter the feature server URL as "http://127.0.0.1:8080/ramp/ramp/feature/ft".

You'll also need to set the feature signing key modulus. In the testkeys directory of the RAMP SDK, you'll find a file, publickeys.txt. The file contains three RSA public key values. You'll want the value of the feature key (d8b5e5...4dd4c1). Cut-and-paste the value into the "key modulus" field (as below).

You can now wait a few minutes for your project to be built. When it's built, download the emulator.

Uploading the Feature

If you're not already running the RAMP server, start it now.

Start up the RAMP IDE and select the twi feature project. Right click on it and select "Upload RAMP feature...".

Enter the following values:

  • The Endpoint is "http://127.0.0.1:8080/ramp/webservices"
  • The Username is "admin" (you can change this).
  • The Password is "password" (you can change this, too).
  • The Company ID has the value that you chose when creating your RAMP account.
  • The Application ID is "twi2" (assuming you used this name when creating the application).

Click "OK". After a few seconds, the feature should be deployed.

Go into the /etc/ramp/integration/script_dirs directory and check that a subdirectory called root was created. If you navigate through the subdirectories of root you should find a file called "main.feature".

If you'd like to change the default username and password, you can edit the values of the following two properties in ramp-config.properties (in the /etc/ramp directory):

  • admin.user
  • admin.password

Retrieving the Feature

Start up the emulator that you downloaded. The Twi application should be loaded and displayed.

Of course, you might run into problems. Some mistakes that you might make are:

  • You uploaded the application to the wrong location. E.g. you might have uploaded the application with the wrong application ID (e.g. twi rather than twi2). In this case, the client will report that it cannot locate the main feature.
  • You might have specified the wrong key modulus when setting the feature key. In this case, the client will report that the application cannot be authenticated (since the client will not be able to verify the signature associated with the feature).

Generating your own Private Keys

Signing a feature with the test feature signing key is not recommended since it offers no security at all. If you intend on sending data to web services securely, the test web service key ("keyexchange") also isn't suitable. You should generate your own RSA private keys.

The RAMP SDK comes with a key generation tool that generates RSA keys and exports them in a suitable format for use with the RAMP platform. To generate feature and web service RSA private keys, navigate to the keygen directory of the SDK. You will find two script files (with extensions .bat and .sh) that can be used to generate keys.

On Windows, run the following:

keygen.bat -p pubkey.txt -s privkey.txt feature myownkey

On UNIX derivatives, run the following:

./keygen.sh -p pubkey.txt -s privkey.txt feature myownkey

You should see messages indicating that keys are being generated, followed by messages indicating that the private keys and the public keys have been exported.

Exporting the Private Keys to the RAMP Server

Copy the file privkey.txt to /etc/ramp/integration. We'll want to use the private key in this file for signing features, rather than the test keys. You'll need to change the RSA key DAO to access your new private key. Edit the main-context.xml file by replacing,

<bean id="rsaPrivateKeyDao" class="com.vmt.ramp.integration.impl.security.TestKeysDao" />

with

<bean id="rsaPrivateKeyDao" class="com.vmt.ramp.integration.impl.security.PropertyFileRsaKeyDao">
   <property name="propertyFile" value="file:/etc/ramp/integration/privkey.txt"/>
</bean>

The result of this change is that you're now using a DAO that can read files that contain RSA keys in the format exported by the key generation tool.

Stop the RAMP server and restart it, for the change to have an effect.

For an interesting result, run the emulator again. The emulator will retrieve the feature but then report an error ("The application cannot be authenticated."). This is since the client is expecting the feature to be signed with the test key, not with your new key.

For more details on the keygen tool, refer to the manual in keygen/doc folder.

Exporting the Public Keys to the RAMP Client

Create a new application (called "twi3") on the RAMP deployment platform. Open the pubkey.txt file generated by the key generation tool and cut-and-paste the rsakey.feature.mod modulus into the feature server key field. As before, set the feature server URL to be "http://127.0.0.1:8080/ramp/ramp/feature/ft".

If you intend on securing web service calls, you'll need to set the service server host and key. First delete the existing service server host and key. Then add a new host by clicking on the "Add service server" button. The server should have an address like http://127.0.0.1:8080/ramp. Now add a service server key. The name of the key should be "myownkey" and the modulus value can be retrieved from pubkey.txt.

Create the application and when you receive an email telling you that the build has finished, download the emulator.

Testing your New Keys

From the RAMP IDE, upload the Twi feature, but change the application ID to "twi3". Run the emulator. If you followed the previous instructions correctly, you'll be able to run the Twi application.

The Twi feature does not use a secure web service, but you will be able to test your generated "myownkey" in the next section.

Creating Secure Web Services

Many times you will want to send information "securely" across the internet. By "securely" we mean that confidential information should be encrypted and the information should be authenticated (e.g. using a message authentication code or MAC) so that the client or server can reject data that has been tampered with. Typical examples of data that you might want to secure are a username and password.

An Example

The easiest way to see how to create a secure web service is by means of an example. We'll create a very simple example: a log in form that prompts for a user name and password. After the user has entered their particulars, a secure session will be established with a RAMP server after which the user name and password will be sent to the server encrypted and authenticated. The server will send back personal information if the user name and password are OK. This sensitive user information will also be encrypted.

The sample code for this application can be found in the apps directory in the secureclient and secureserver directories.

The Server Code

Writing a Secure Handler

Unlike the Getting Started guide where we looked at the client code first, here it's easier to start by looking at the server-side code. In the Getting Started guide, we wrote a class that implemented the ServiceRequestHandler class. That was pretty easy. The nice thing is that to write a secure handler is probably even easier, you simply need to extend the SecureHandler class and implement the (abstract) processSecureRequest method.

The code for our (secure) LoginHandler class is shown below.

package com.virtualmobiletech.secureserver;

import java.util.HashMap;
import java.util.Map;

import com.vmt.ramp.integration.impl.security.Session;
import com.vmt.ramp.integration.impl.security.SecureHandler;

public class LoginHandler extends SecureHandler {

@Override
public ServiceResponse processSecureRequest(ServiceRequest request) {
	String username = (String) request.get("username");
	String password = (String) request.get("password");
	ServiceResponse response = new ServiceResponse();
	if (username.equals("admin") && password.equals("password")) {
		response.put("resultcode", "1");
		response.put("name", "Aaron Aardvark");
		response.put("accountnumber", "123-456-789-X");
		response.put("portfoliovalue", "ZAR 1000000.00");
	} else {
		response.put("resultcode", "0");
	}
	return response;
}

If the user name and password are as expected ("admin" and "password") we return a result code of 1, an account number and a portfolio value. If the user could not be authenticated, we return a result code of 0.

The parent class (SecureHandler) takes care of decrypting the request and encrypting the response.

Wiring up the Secure Handler

Given that writing a secure handler looks as easy as writing an insecure one, why didn't we use a SecureHandler in the Getting Started guide? The problem is that while the Java is no more complicated, the XML configuration is quite a bit more detailed.

The main-context.xml file for the secure server can be found in the usual place (the "integration_config" directory of the project). It's reprinted below:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd
">

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
   <property name="locations" ref="propertyFiles"/>
</bean>

<bean id="serviceRequestManager" class="com.vmt.ramp.integration.impl.ServiceRequestManagerImpl">
   <property name="mapper" ref="serviceRequestMapper" />
</bean>

<bean id="serviceRequestMapper" class="com.vmt.ramp.integration.impl.ServiceRequestMapper">
   <property name="handlers" ref="handlers" />
</bean>

<bean id="featureBundleProvider" class="com.vmt.ramp.integration.impl.MultiDirectoryFeatureBundleProviderImpl">
   <property name="rootDirectory" value="${scripts.dir}"/>
   <property name="signer" ref="featureSigner"/>
</bean>

<bean id="featureVersionFilter" class="com.vmt.ramp.integration.impl.FeatureVersionAfterServiceRequestFilter">
   <property name="multiFBP" ref="featureBundleProvider"/>
</bean>

<bean id="featureSigner" class="com.vmt.ramp.integration.impl.security.SoftwareSigner">
   <property name="rsaPrivateKeyDao" ref="rsaPrivateKeyDao"/>
</bean>


<!-- NB stuff -->
<util:map id="handlers">
   <entry key="loginHandler" value-ref="loginHandler" />
   <entry key="establishSessionHandler" value-ref="establishSessionHandler" />
</util:map>

<bean id="loginHandler" class="com.virtualmobiletech.secureserver.LoginHandler">
   <property name="sessionManager" ref="sessionManager"/>
   <property name="symmetricCrypto" ref="symCrypt"/>
</bean>

<bean id="establishSessionHandler" class="com.vmt.ramp.integration.impl.security.EstablishSessionHandler">
   <property name="asymCrypt" ref="asymCrypt"/>
   <property name="symCrypt" ref="symCrypt"/>
   <property name="sessionManager" ref="sessionManager"/>
</bean>

<bean id="asymCrypt" class="com.vmt.ramp.integration.impl.security.SoftwareAsymmetricCrypto">
   <property name="keyDao" ref="rsaPrivateKeyDao"/>
</bean>

<bean id="rsaPrivateKeyDao" class="com.vmt.ramp.integration.impl.security.TestKeysDao"/>

<bean id="symCrypt" class="com.vmt.ramp.integration.impl.security.TDesSymmetricCrypto"/>

<bean id="sessionManager" class="com.vmt.ramp.integration.impl.security.SessionManager" init-method="init" destroy-method="destroy"/>

</beans>

First of all, note that although we wrote only one handler, we have defined two handlers in the XML:

<util:map id="handlers">
   <entry key="loginHandler" value-ref="loginHandler" />
   <entry key="establishSessionHandler" value-ref="establishSessionHandler" />
</util:map>

If you want to use the SecureHandler class, you will need to support the "establishSessionHandler" that allows the client to establish keys for encrypting and authenticating data with the server.

Also, the classes need to be configured in the XML. Notice that the code that instantiates our handlers sets several properties:

<bean id="loginHandler" class="com.virtualmobiletech.secureserver.LoginHandler">
   <property name="sessionManager" ref="sessionManager"/>
   <property name="symmetricCrypto" ref="symCrypt"/>
</bean>

<bean id="establishSessionHandler" class="com.vmt.ramp.integration.impl.security.EstablishSessionHandler">
   <property name="asymCrypt" ref="asymCrypt"/>
   <property name="symCrypt" ref="symCrypt"/>
   <property name="sessionManager" ref="sessionManager"/>
</bean>

The classes need to be injected with a "session manager" that retains the cryptographic keys for communicating with the client. Also, the server needs access to a class that can perform symmetric cryptographic operations.
The session handler also depends on a class that can do asymmetric cryptography (in this case RSA) to establish symmetric keys.

The Client Code

The UXML components in the "secureclient" project should be straightforward, so we'll ignore it and fast forward to the RAMPScript which is contained in the file main.rs

The main.rs code illustrates the use of the RAMP webServiceSecureSetKey and webServiceSecure functions.

/*
 * @file:   main.rs
 * 
 * Start up, error handling and logging in
 * functionality.
 */
/*
 * Startup code:
 *	initializes public key crypto.
 *  goes to login form.
 */
function main() {
    webServiceSecureSetKey("keyexchange");
	goto("loginform");
}
/*
 * Error handling code.
 *
 * @param message  The error text to display.
 */
function error(message) {
	errortext.text = message;
	goto("errorform");
}
/*
 * Logs in using the secure web service code to
 * encrypt the user's credentials.  The
 * response data is also encrypted.
 */
function login() {
	// Call the secure loginHandler service.
	// The return value (result) is a Map.
	result = webServiceSecure("loginHandler", "username", username.text, "password", password.text);
	
	// Check whether we logged in OK.
	resultCode = mapGet(result, "resultcode");
	if (resultCode != 1) {
		error("You could not be logged in.");
	} else {
		name.text = mapGet(result, "name");
		accountnumber.text = mapGet(result, "accountnumber");
		portfoliovalue.text = mapGet(result, "portfoliovalue");
		goto("details");
	}
}

Notice how we call the webServiceSecureSetKey function from our main function.

Inside the login function we call the webServiceSecure function. Like the webService function, it takes an odd number of parameters i.e. the handler name followed by any number of key and value pairs. Remember from the LoginHandler that the server retrieves the username and password values by calling the ServiceRequest.get() method with the appropriate keys. The webServiceSecure function always returns a Map containing the keys and values added to the ServiceResponse object by the handler.

Using your own keys

To use your own keys, you'll need to generate them as outlined in Generating your own Private Keys.

Configuring the Server

To make use of your own keys, you'll need to edit the main-context.xml file as outlined in Exporting the Private Keys to the RAMP Server.

Configuring the Client Code

You must edit your main function to use your own key, rather than the test key. Replace

function main() {
	webServiceSecureSetKey("keyexchange");
	goto("loginform");
}

with

function main() {
	webServiceSecureSetKey("myownkey");
	goto("loginform");
}
Creating an application

To create an application that uses your own keys you should follow the section Exporting the Public Keys to the RAMP Client.

Monitoring

The RAMP server can be monitored using JMX. Each ServiceRequestHandler can be individually monitored for availability and performance.

In order to activate monitoring on all ServiceRequestHandlers, simply deploy "monitor-context.xml" along side your "main-context.xml". The "monitor-context.xml" can be found in the "war" directory of the RAMP distribution.

The RAMP server will then automatically detect your ServiceRequestHandlers and make them available via JMX.

The JVM needs to be configured to expose a JMX connection, and then a tool like JConsole can be used to monitor the stats. For more information about JMX visit http://java.sun.com/javase/6/docs/technotes/guides/management/agent.html

Internationalization

The RAMP server can be configured to provide localization for RAMP clients. The localization is based on the language setting of the phone running the client.

An example project is included in the SDK under the apps directory: "helloworld_i18n" and "helloworld_i18n_server" is the client-server pair.

We start off explaining how to authorize client localization.

Client localization

If you want to provide a localized version of a piece of text in your client, assign the text to a property and replace all instances of the text in the code with this property. This is done by naming the piece of text and replacing all its instances with this name in curly brackets, preceded with the dollar sign.

Consider the exit button in the example project that is to be displayed in the users native language. Note that the label of the exit button is assigned to the property "exit".

 <Button label="${exit}" click="exit()"/>

The actual localized text is then stored with the RAMP server and not in the client code. For each supported locale, the server has a file that contains a list of the property names and the text they resolve to. The format of a locale file is very simple – consider the format of the German locale file in the example:

German locale file
hello=Hallo
world=welt
exit=Ausfahrt

A useful feature of the locale files is that they support Unicode codes. To display a Unicode character simply give its hex code prefixed with "\u". The example makes use of Unicode codes to represent the Japanese localization in the Hiragana writing system:

Japanese locale file
hello=\u3053\u3093\u306b\u3061\u306f
world=\u305b\u304b\u3044
exit=\u3044\u3067\u3050\u3061

The Japanese version of the example's client then looks like this:

When the client is launched, the RAMP server (if internationalization is enabled) will automatically find all the dollar-curly-bracket properties and replace them with the text referenced to when combining the property name and the language setting of the phone. Note however that the property names are case sensitive.

Now to enable internationalization on the RAMP server.

Configure RAMP server for internationalization

Note

For internationalization to work the RAMP server must be using its own feature server to issue the RAMP feature to the client.

Enabling internationalization requires adding an internationalizer to the feature bundle provider of the RAMP server. In the "main-context.xml":

<bean id="featureBundleProvider" class="com.vmt.ramp.integration.impl.MultiDirectoryFeatureBundleProviderImpl">
    <property name="rootDirectory" value="${scripts.dir}"/>
    <property name="signer" ref="featureSigner"/>
    <property name="preProcessors">
        <list>
            <ref bean="internationalizer"/>
        </list>
    </property>
</bean>

The internationalizer now needs to be told where it can find the texts for all the different locales. The following code (extracted from the main-context.xml of the example) informs the internationalizer to look in the sub-directory "i18n_config" of the directory referred to as integration.context.path in the ramp-config.properties file. The internationalizer is further informed that all the locale files are prefixed with the string "message". This is not to be mistaken for a "message" sub-directory. There should be no "message" sub-directory in the "i18n_config" directory.

<bean name="internationalizer" class="com.vmt.ramp.integration.impl.i18n.Internationalizer">
   <property name="msgSource" ref="messageSource"></property>
</bean>
     
<bean name="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
    <property name="basename" value="${integration.context.path}/i18n_config/message"></property>
</bean>

Internationalization is now enabled. All that remains is to create the locale files and match a locale to its correct file.

Match localization files

Each supported locale is represented by a file that contains its locale-specific text. The format of a locale file has already been illustrated, but how is a locale matched to the correct file?

A locale file's name is a combination of the prefix specified in main-context.xml, joined with an underscore to a unique locale code, followed with the extension ".properties".

All locales are identified by a two-letter language code that is possibly further joined with an underscore to a two-letter region code. The language code is all lower-case while the region code is all upper-case.

Here is a table of all the locales supported in the example given with their locale code and filename:

locale

locale code

file name

English United States

en_US

message_en_US.properties

English Australia

en_AU

message_en_AU.properties

English

en

message_en.properties

German

de

message_de.properties

Japanese

ja

message_ja.properties

When the server attempts to localize a property, it searches through all the files and finds the one with the locale code that matches the locale received from the phone the closest. For instance, when looking at the files in the table – the closest match to English Great Britain (en_GB) would be the file message_en.properties.

If a property can not be found in the closest matching file, the server continues to search for this property in the next closest matching file. This means that not all locales have to support all properties. In the example, the Australia and United States locales only specify one property and the remaining properties are retrieved from the English locale.

Note that normally a file without a localization code is also provided. This file is used for the locales that are not specifically supported. For the example project this file is titled "message.properties".

Advanced IDE features

Using different resolution images for different screen resolutions

In the Getting Started guide we saw how separate directories could be used to split the UI e.g. src, src_smart, uxml, uxml_smart, css, css_smart, images, images_smart etc.

In addition to this, it is possible to ensure that different images are used on phones with different screen resolutions, independent of the other image splitting. To do this, one needs to create subdirectories inside of the images, images_smart etc. directories named '{X resolution}x{Y resolution}' e.g. '320x480' for a screen resolution of width 320 and height 480.

By default the RAMP server scales images by a factor of the device screen X resolution divided by the default screen X resolution of 240. 240 is also the X resolution of the J2ME emulator downloaded from the RAMP deployment platform, so this scaling ensures that the ratio of the image width to the screen width on all devices will be the same as on the emulator. For smart phones the RAMP server will do high quality image scaling, and for feature phones standard quality. This is to keep the feature size to a minimum on feature phones.

However, no matter how good the quality of scaling is, some quality will always be lost when enlarging an image. If the image quality after scaling isn't sufficient, one can use the image resolution directories to control exactly what resolution images different screen resolutions will get.

Here's an example of a directory structure and the resulting images served to different devices with different screen resolutions.

Phone type

Screen Resolution

Image name referenced

Image served

Image scaled

Scaling quality

Scaling factor

Feature

240x320

image1

images/image1.png

No

-

-

Feature

320x480

image1

images/320x480/image1.png

No

-

-

Feature

480x640

image1

images/image1.png

Yes

Standard

2.0

Feature

240x320

image2

images/image2.png

No

-

-

Feature

320x480

image2

images/320x480/image2.png

No

-

-

Feature

480x640

image2

images/image2.png

Yes

Standard

2.0

Smart

240x320

image1

images_smart/image1.png

No

-

-

Smart

320x480

image1

images_smart/320x480/image1.png

No

-

-

Smart

480x640

image1

images_smart/image1.png

Yes

High

2.0

Smart

240x320

image2

images/image2.png

No

-

-

Smart

320x480

image2

images/320x480/image2.png

No

-

-

Smart

480x640

image2

images/image2.png

Yes

High

2.0

Determining the Generic Feature Size

It is sometimes useful to determine the size of the generic feature since some older phones have limited storage space. To do this, right click on the project in eclipse and select "Get Generic Feature Size...". If when creating the application on the deployment platform the default feature server was changed, you'll need to specify this feature server in the dialog, otherwise you can leave the default value. You can also specify the device resolution and locale which can potentially affect the feature size. When finished, click OK. Another dialog will appear indicating the feature size.

Conclusion

This guide has provided details on:

  • How to configure your own feature server
  • How to generate keys for signing features and for securing web services
  • How to deploy secure web services
  • How to monitor your RAMP server
  • How to perform internationalization of clients
  • How to use the advanced IDE features
  • No labels