maandag 15 februari 2010

IE7 alongside IE8?

I'm working on a web app which I need to test on IE7 and IE8, among other browsers. These tests are important because with each new release of IE, your web-apps get an unsolicited remake.

Amazingly, I discovered this morning that it is NOT POSSIBLE to install both those browsers on the same machine.

But, you say, IE7 and IE8 both have HUGE market shares, so each site should be tested on both browsers?? Sure, this is true, but you need two machines for that!

Can you believe that??

woensdag 19 augustus 2009

Overriding default CSS properties in GWT

For some applications you may want to change CSS properties for some standard UI elements in the google web toolkit. For instance, you may want all buttons to use a large font, displayed in blue. Now, if we look at the standard css poperties of a gwt button, as defined in the `standard.css' file included in the GWT-user.jar file, it says:

.gwt-Button {
margin: 0;
padding: 3px 5px;
text-decoration: none;
font-size: small;
cursor: pointer;
cursor: hand;
background: url("images/hborder.png") repeat-x 0px -27px;
border: 1px outset #ccc;
}


Here, the font size is set to small and no color is set. So, an obvious attempt would be to (re)define the font-size and color properties in our application css:

gwt-button {
color: blue;
font-size: x-large;
}


This way, you can make any changes you want. However, you may be surprised that in some cases your changes are ignored! This has to do with the order in which the css files are processed, and the css precedence rules: Obviously, we have two conflicting styling rules here, and in that case the FIRST RULE that was processed TAKES PRECEDENCE.

Now, it depends on how you include your css file whether it is processed before or after the GWT system css file:
  • If you include your application css file from the host html page, it will be processed *after* the GWT system css file. Any changes that conflict with the default css values will be ignored. In this case, only the color change has been picked up, since it was not defined before, whereas the font-size has been ignored.
  • If you include your application css file form your module xml file using a element, your css file will be processed *before* the system css file, and *all* the changes will take effect. (At least, that's my empirical observation.)
To see which styles are applied and in which order, you can use the invaluable firebug debugger from within firefox.

Of course, the wisest thing to do is not to tamper with GWT standard styles, but to define and assign a secondary style, although this does require java code.

donderdag 30 april 2009

Howto: external tomcat in GWT 1.6, eclipse 3.4

When you develop apps with GWT, these apps often involve RPC calls. The server functionality accessed through the RPC calls may require extensive server configuration, for instance, it may
require access to a data source. It is next to impossible to configure the built-in jetty server in these cases, and the solution is to use your own servlet container for handling the server requests.

In 1.6 is has become easier to use an external servlet container than in previous versions, because the GWT compiler places its output in a war directory that has identical structure as the
directories holding web application in tomcat and other servlet containers. This makes it easier to `copy' the project to an external servlet container.

These are the steps you must take to use an external tomcat instance that runs within eclipse instead of the GWT-built-in Jetty instance.

1. Create GWT project and host page., assuming it does not exist already. Look up the host page and put a landmark in there (some text or html) so that you can recognize it later on. The host page is located in the war directory of the project.

Compile the project by pressing the `compile/browse' button in the hosted mode browser. This will create a new subdirectory in the war directory of the project that holds your compiled code. The name of the subdirectory is identical to the project name in lowercase.

2. Create a `dynamic web project' in Eclipse. Instruct eclipse to use `war' as content folder, by changing the content folder form `WebContent' into `war'. (This can be done in the 2nd screen of the `create new web project' dialog.) It is smart to give this project a helpful name, such as YourGWTProjectName-ExternalServer.

3. Make a symbolic link to GWT war folder from the dynamic web project.

The war directories play an identical role in both projects.
(If you look at the directory form which a regular
tomcat runs its applications (e.g. $CATALINA_HOME/webapps/) you will see that all subdirectories have identical structure to the war directories in both projects.) They both hold the contents for the web application. Hence, we can plug the GWT-war folder into the dynamic web project.

First, note the file-system location of the `war' folder in the GWT project by looking at the properties of the folder in the project explorer. Then, delete the war folder in the dynamic web project. Create a new folder in the new project, called `war'. Press the `advanced' button, and have the new directory link to the war directory of the GWT project you noted earlier.

After this step, there will probably still be some errors in the project.

4. Remove errors. Add gwt-servlet.jar to the dynamic web project though the `configure build path' option in eclipse. This will get rid of some of the errors. Possibly there are more and you may need to include more jar's, either in the WEB-INF/lib directory of the dynamic web project, or in the lib directory of the tomcat home directory. (Usually called $CATALINA_HOME/lib .) Besides adding the JAR's, in my case, the order of the elements in the web.xml file was incorrect, so that gave a validation error. Changing the order fixed that. (I had to place the welcome-file-list element just before the closing tag.)

4. Deploy the `dynamic web project' you created in step 2 to the tomcat instance in your workspace. If you do not have a tomcat instance in your workspace yet, you must create one first through new | other | server etcetera.

5. Start the tomcat server. Easiest by pressing the `run' button in the servers tab in the JavaEE view, but there may be dozens of other ways to start tomcat from eclipse.

6. Look at your landmark page. Navigate your browser to your host page on the tomcat server. The url will be something like
http://localhost:8080/YourWebbProjectName/YourHostFileName

You should see host html page with the landmark you placed in it earlier.

7. Use external tomcat from GWT project. Instruct GWT hosted mode to use your tomcat server instead of the embedded Jetty server. This can be done by opening the run configuration for the GWT project (run | run configurations). Then, In the 'Main' tab, under the Embedded Server section, uncheck the "Run built-in server" checkbox.

Next, in the "GWT" tab, change the URL to point to the URL you used in the previous step.

8. Start the gwt app in hosted mode. You will see a reference to your tomcat server in the address bar.

9. Re-organize packages. Of course, your client code stays in the GWT project, so you can run it with the hosted mode, which enables debugging of the client code from within eclipse.

However, there is no need to have the server code in the GWT project anymore, so that can be moved to the dynamic web project project. (Move the packages into the src directory on the server project.)

The classes that are common to client and server can either be copied into both projects. This is the easy way: it works but it leads to code duplication. If you want to do it the neat way, the
classes can be placed in a separate `common' package in its own project. From there, you can create a GWT module for the common classes that can be inherited by your client module. for the
server, you can just add a project dependency in eclipse.

10. CONFIGURE TOMCAT. NOW THAT YOU ARE USING YOUR OWN TOMCAT INSTANCE, YOU CAN CONFIGURE THE TOMCAT INSTANCE ANY WAY YOU WANT, I.E. ADD DATA SOURCES TO IT.

Also, because the symbolic link in the file system exists, there is no need to explicitly copy the contents of the GWT-war folder to the tomcat server. (In a previous version of this blog post, this directory was copied manually, and this copy-step had to be repeated periodically.) However, you *should* re-compile the GWT project into javascript and press the refresh button (F5) on the dynamic web project now and then.

woensdag 8 april 2009

Declare element to be space preserving in XSD

Today i was working with a self-defined XML format. One element type in my xml files is the <scriptblock> element. This element contains snippets of script code.

When i was finished composing my XML file in my XML editor, told the editor to autoformat the XML document. Autoformatting is a tool, i use it about every 10 seconds when writing source code. However, in this case the autoformatter screwed up my <scriptblock> elements: It treated the contents like plain text, just pasting all code in a single text paragraph. New lines started halfway comment strings or String arguments, and all indentation was lost.

Obviously, for source code, layout is important and i wanted a way to make sure that XML processors like the autoformatter would keep their hands off my carefully applied manual formatting in the <scriptblock> elements...

That turned out to be pretty easy: in the .xsd (schema definition) for my xml format, i had to add an attribute called "xml:space" with the "scriptblock" element type, and set it to fixed value "preserve".


<xs:element
name="scriptblock"
minOccurs="0">
<xs:complexType>
<xs:simpleContent>
<xs:extension
base="xs:string">
<xs:attribute
ref="xml:space"
fixed="preserve" />
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>


This attribute refers to an externally defined attribute (xml:space). Hence, you must import the xml namespace definition in the top of your document, and also declare the xml namespace in your schema element:

<xs:schema
targetNamespace="http://www.yourcompany.com"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns="http://www.yourcompany.com">

<xs:import
namespace="http://www.w3.org/XML/1998/namespace"
schemaLocation="http://www.w3.org/2001/xml.xsd" />



After that, the autoformatter left my code blocks alone, and formatting was preserved. (Although i had to re-start the XML editor first.)

Hope this is useful for some.

Mike

donderdag 5 maart 2009

What should be in the XML header for validation?

Today i was looking for a way to let users configurate the application that i'm writing. The obvious way to do that is by having the users create an XML file with configuration information.

However, to avoid errors in these user-XML files, they should ve validated against a schema definition or .XSD file. The headers of the XML files i saw in the wild always looked a bit daunting to me: The `schema' element just has so many cryptic attributes. So here's an explanation of what they mean and how to set them.

First of all, you should create a schema definition for your XML files. In my case, i want to specify an XML format for configuring the `proxy' component of my application. Hence i call the main element `proxyConfig'. Below, you see a snippet from the XSD file with comments to clarify what the header lines mean.


<xs:schema
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="http://www.mycompany.com"
targetNamespace="http://www.mycompany.com"
elementFormDefault="qualified">

<xs:element name="proxyConfig">
<xs:complexType>
[stuff omitted]
</xs:complexType>
</xs:element>
<xs:schema>


The first line xmlns:xs="http://www.w3.org/2001/XMLSchema" links namespace `xs' to the schema namespace http://www.w3.org/2001/XMLSchema. Now, if elements are prefixed with `xs:' they are assumed to refer to elements in the said namespace.

The second line xmlns="http://www.mycompany.com" defines the default namespace for THIS document. (I.e. the .xsd document you see above.) If an element used in this document does not have a namespace prefix, it is assumed to refer to the namespace http://www.mycompany.com.

The third line targetNamespace="http://www.mycompany.com" defines the namespace that the elements defined in this .xsd file should be placed in. This line is important, because we'll refer to it in our XML file below!

(Aside: You see that the target namespace and all referred namespaces here are URL's. As far as i understand, this is not actually necessary, it is merely a convention. This is a source for confusion, since it suggests that the URL's can be accessed to fetch information about syntax of the namespace. This is not true, it is just an arbitrary name.)

Finally, after creating the XSD file, we place it somewhere on the internet, e.g. on http://www.mycompany.com/download XML/proxyConfig.xsd .

Now for the XML file. A simple XML file is shown below:

<proxyConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.mycompany.com"
xsi:schemaLocation="http://www.mycompany.com http://www.mycompany.com/download/XML/proxyConfig.xsd">

[stuff omitted]

</proxyConfig>


You see that in the first line xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" we say that namespace xsi refers to namespace "http://www.w3.org/2001/XMLSchema-instance". This namespace contains a number of attributes, among wchich the attribute xsi:schemaLocation. This attribute can be used to indicate the location for a namespace. Here, we use it in line 3:

xsi:schemaLocation="http://www.mycompany.com http://www.mycompany.com/download/XML/proxyConfig.xsd"

to indicate that the location of the .XSD file that defined namespace http://www.mycompany.com is http://www.mycompany.com/download/XML/proxyConfig.xsd. Remember that we set the namespace name (http://www.mycompany.com) and the xsd location http://www.mycompany.com/download /XML/proxyConfig.xsd ourselves when we created the .xsd file.

Finally, the second line xmlns="http://www.mycompany.com" defines the default namespace for the XML document.

Another source of confusion (to me) was the fact that your `own' namespaces need to be pointed at by a schemaLocation attribute. However, when referring to the http://www.w3.org/2001/XMLSchema or the http://www.w3.org/2001/XMLSchema-instance namespaces above, it is not necessary to explicitly give the location of the defining .XSD. This does not seem very consequent to me. (Presumably, this is because these latter namespaces are W3C namespaces somehow.) Maybe someone can explain this?

donderdag 26 februari 2009

Authentication when using RMI

Today, i was working on an RMI application and i ran into the following problem: I wanted my RMI clients to authenticate themselves when accessing the RMI service, so that clients cannot access eachother's data and the service cannot be abused by un-authenticated clients.

So, how do you implement client authentication? I wanted a SIMPLE solution without setting up a public key infrastructure and without using SSL/TLS, as this is just too heavy for my simple requirements.

Then i ran into an authentication mechanism called CRAM-MD5, which is an abbreviation of Challenge Response Authentication Method. It operates as follows:



Each user (client) has a password that is also known to the server but secret otherwise.


When the client wants to authenticate itself to the server, it requests a `challenge' which is just a random sequence. The challenge is prepended with the password and the result is transformed with a `MessageDigest' transformation, e.g. MD5. This is a one-way transformation: it is (in practice) impossible to recover the input value from the transformed value... However, the transformed value is unique to the input value. The client then sends the transformed value over the network to the server.


The server performs the same computation as the client and compares the two transformed values. When they are equal, the server concludes that the client must have known the password and the client is authenticated.




To implement this in a toy example, you need the following classes/interfaces:

The RMI service definition interface:
package demo.authenticate;


import java.math.BigInteger;
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Authenticate extends Remote {

String sayHello() throws RemoteException;

BigInteger challenge(String username) throws RemoteException;

boolean authenticate(String username, BigInteger digestClient)
throws RemoteException;
}



The RMI service:
package demo.authenticate;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;

/**
* Class for testing MD5 authentication in RMI connection.
*
* Each user (client) has a password that is also known to the server but secret
* otherwise.
*
* When the client wants to authenticate itself to the server, it requests a
* `challenge' which is just a random sequence. The challenge is prepended with
* the password and the result is transformed with a `MessageDigest'
* transformation, e.g. MD5. This is a one-way transformation: it is (in
* practice) impossible to recover the input value from the transformed value...
* However, the transformed value is unique to the input value. The client then
* sends the transformed value over the network to the server.
*
* The server performs the same computation as the client and compares the two
* transformed values. When they are equal, the server concludes that the client
* must have known the password and the client is authenticated.
*/
public class Server implements Authenticate {

private static Map<String, String> passwords;

private static Map<String, BigInteger> serverChallenges;

private static SecureRandom secureRandom;

public Server() {
}

public String sayHello() throws RemoteException {
return "Hello. Please authenticate!";
}

public static void main(String args[]) {

try {
secureRandom = new SecureRandom();
passwords = new HashMap<String, String>();
serverChallenges = new HashMap<String, BigInteger>();

// put some data in passwords map
passwords.put("John", "JohnsSecret");
passwords.put("Jane", "JanesSecret");

// instantiate the rmi service
Server obj = new Server();
Authenticate stub = (Authenticate) UnicastRemoteObject
.exportObject(obj, 0);

// Bind the remote object's stub in the registry
Registry registry = LocateRegistry.getRegistry();
registry.rebind("Authenticate", stub);

System.err.println("Server ready");
} catch (Exception e) {
System.err.println("Server exception: " + e.toString());
e.printStackTrace();
}
}

public boolean authenticate(String username, BigInteger digestClient)
throws RemoteException {

if (!passwords.containsKey(username))
throw new RemoteException("Unknown user trying to authenticate.");
if (serverChallenges.get(username) == null)
throw new RemoteException("Unable to authenticate " + username
+ ". Missing authentication challenge.");

try {
// First compute the server-side digest
MessageDigest messageDigest = java.security.MessageDigest
.getInstance("SHA-256");
// feed stored challenge and stored password
messageDigest.reset();
messageDigest.update(serverChallenges.get(username).toByteArray());
messageDigest.update(passwords.get(username).getBytes("UTF-16"));
// and read the digest into a BigInteger
BigInteger digestServer = new BigInteger(1, messageDigest.digest());

System.out.println("Digest server: " + digestServer);
System.out.println("Digest client: " + digestClient);

// The see if match. If so, client must have known password.
if (digestClient.equals(digestServer)) {
System.out.println("Authentication succeeded");
return true;
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}

System.out.println("Authentication failed");
return false;
}

public BigInteger challenge(String username) throws RemoteException {
byte[] bytes = new byte[100];

if (!passwords.containsKey(username))
throw new RemoteException("Unknown user trying to authenticate.");

// generate a random sequence of bytes
secureRandom.nextBytes(bytes);
// transform the bytes into a BigInteger
BigInteger serverChallenge = new BigInteger(1, bytes);
serverChallenges.put(username, serverChallenge);
// and return it...
return serverChallenge;
}
}


The RMI service sends the challenge in the form of a BigInteger. The challenge is initially created as an array of random bytes using the SecureRandom class. But this array cannot be transported over the network: When an array is given as an argument just its starting address in memory is passed. An easy way around it is to creat a BigInteger based on the array. (The byte array is then interpreted as a twos complement integer of arbitrary length.) The BigInteger can be given as argument since it is passed by value.

The RMI client:
package demo.authenticate;

import java.math.BigInteger;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.security.MessageDigest;

public class Client {

private static String password = "JohnsSecret";

private Client() {}

public static void main(String[] args) {
MessageDigest digest;

String host = (args.length < 1) ? null : args[0];
try {
Registry registry = LocateRegistry.getRegistry(host);
Authenticate stub = (Authenticate) registry.lookup("Authenticate");

// Then authenticate myself...
digest = java.security.MessageDigest.getInstance("SHA-256");
digest.reset();
digest.update(stub.challenge("John").toByteArray());
digest.update(password.getBytes("UTF-16"));
BigInteger digestClient = new BigInteger(1,digest.digest());

boolean success = stub.authenticate("John", digestClient);
System.out.println("Client digest: "+ digestClient);
System.out.println("Client: authentication success: "+ success);

} catch (Exception e) {
System.err.println("Client exception: " + e.toString());
e.printStackTrace();
}
}
}



There seems to be a security problem with the SHA algorithm and similar digest algorithms, so the method is not watertight. (See http://www.win.tue.nl/hashclash/rogue-ca/ ). But if your security requirements are not very strict, as in my case, you should keep most crooks outdoor!

(Don't take my word for it, i'm not an expert.)

zaterdag 6 december 2008

Where do GWT widget events come from??

(This post applies to GWT 1.5. In GWT 1.6 the event system has been changed.)

The Google Web Toolkit works with an event mechanism that implements the Observer pattern: If an event occurs within a certain object called the `observed' object, this triggers a method invocation on each object within a set of `observing' objects called the listeners.

Various event types exist. Most of them (or all of them -- i'm not sure) represent events happening on the user-interface, e.g. a mouse entering or leaving a widget, a mouse click on a widget, etcetera. It was unclear to me how this widget event model worked, and i did not find a good explanation in the docs, so that's why i wrote this post.

The `observed' widget acts as an event source, e.g., a Button acts as a source for click events. In GWT, if a widget acts as an event source it must implement a sourcesXXXEvents interface. There are 15 somewhat sourcesXXXEvents interfaces defined in the GWT API. SourcesClickEvents, SourcesChangeEvents, SourcesKeyboardEvents, etcetera. These interfaces require that event listeners can be added to and removed from the listener collection of the event source.

The `observing' objects -- the ones that are registered as listeners -- must implement methods to deal with events if they are notified. E.g., an object listening to a SourcesKeyboardEvents object must implement the KeyBoardListener interface, requiring it to implement the onKeyDown, onKeyPress, onKeyUp methods. As such, each listener interface requires its own methods to be implemented. This is all pretty standard and straightforward if you ever did some event programming in something like Swing or Windows.

So-far-so-good, but one question remains: what is the rock-bottom source of events in GWT? Is declaring the implementation of an SourcesXXXEvents interface enough to have the events actually generated?

For instance, the Button class does not implement the SourcesMouseEvents interface. (At least not in GWT 1.5.3.) The SourcesMouseEvents interface contains methods onMouseEnter() and onMouseLeave(). These could be used to e.g. change the widget's text when the mouse hovers over it. Suppose i subclass Button as follows:

public class MouseEventsButton extends Button implements SourcesMouseEvents {

MouseListenerCollection mouseListenerCollection;

public MouseEventsButton(String string) {
super(string);
}

public void addMouseListener(MouseListener listener) {
if (mouseListenerCollection == null) {
mouseListenerCollection = new MouseListenerCollection();
}
mouseListenerCollection.add(listener);
}

public void removeMouseListener(MouseListener listener) {
if (mouseListenerCollection != null) {
mouseListenerCollection.remove(listener);
}
}


}

Will my new button respond to a mouse entering and leaving it now?? Let's try.

public class DemoGwtEvents implements EntryPoint {

public void onModuleLoad() {

VerticalPanel vPanel = new VerticalPanel();
vPanel.setWidth("100%");
vPanel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
RootPanel.get().add(vPanel);

// create the new button
MouseEventsButton mouseEventsButton = new MouseEventsButton("Rub me!");

// add a listener to it that changes text on events
mouseEventsButton.addMouseListener(new MouseListenerAdapter() {

public void onMouseEnter(Widget sender) {
Button b = (Button) sender;
b.setHTML("thank you");
}

public void onMouseLeave(Widget sender) {
Button b = (Button) sender;
b.setHTML("rub me again!!");
}
});

vPanel.add(mouseEventsButton);

}
}
NOOOOO, that doesn't work... Nothing happens if i move my mouse over the MouseEventsButton.

The key is that i must make a connection between low-level DOM-events and high-level GWT events somehow. DOM-events are events that happen in the browser window. GWT events happen in the Java layer of a GWT app. Somehow, the DOM-events must be captured and re-launched into the Java layer. This is done in two steps:
  1. Tell the new widget (the button) to CAPTURE certain DOM-events by calling sinkEvents. In our case this is this.sinkEvents(Event.ONMOUSEOVER | Event.ONMOUSEOUT);
  2. Write an event handler to capture the sunk DOM events and re-launch them in the java world. This is done by overriding the onBrowserEvent method of the widget. In our case this is
     public void onBrowserEvent(Event event) {
    super.onBrowserEvent(event);
    // Handle events as a normal Button would
    int type = DOM.eventGetType(event);
    // Look at the type of event again
    switch (type) {
    case Event.ONMOUSEOVER:
    mouseListenerCollection.fireMouseEnter(this);
    break;
    case Event.ONMOUSEOUT:
    mouseListenerCollection.fireMouseLeave(this);
    }
    }
    Note that to throw the events into the java world, the MouseListenerCollection class provides utility methods as `fireMouseLeave' etc.
Putting it all together, we get the following code for MouseEventsButton:

public class MouseEventsButton extends Button implements SourcesMouseEvents {

MouseListenerCollection mouseListenerCollection;

public MouseEventsButton(String string) {
super(string);
this.sinkEvents(Event.ONMOUSEOVER | Event.ONMOUSEOUT);
}

public void addMouseListener(MouseListener listener) {
if (mouseListenerCollection == null) {
mouseListenerCollection = new MouseListenerCollection();
}
mouseListenerCollection.add(listener);
}

public void removeMouseListener(MouseListener listener) {
if (mouseListenerCollection != null) {
mouseListenerCollection.remove(listener);
}
}

public void onBrowserEvent(Event event) {
super.onBrowserEvent(event);
// Handle events as a normal Button would
int type = DOM.eventGetType(event);
// Look at the type of event again
switch (type) {
case Event.ONMOUSEOVER:
mouseListenerCollection.fireMouseEnter(this);
break;
case Event.ONMOUSEOUT:
mouseListenerCollection.fireMouseLeave(this);
}
}
}
So, in summary we may say that GWT widget events are created explicitly as 2-nd layer events after explicitly catching and handling low-level DOM events.

Looking at the current version of the source code (dec. 8, 2008) checked out from svn, many of the classes and methods involved in event handling have been deprecated, so it looks asif a major refactoring of the event mechanism is pending. Hopefully, this will be better documented than it was in the current version.