3.3.2. Using the Framework’s Components in Independent Java Applications

During the development of QA systems which are based on the QALL-ME Framework, it often helps to invoke single WSs from a standard Java application and independent from the rest of the QA system. What’s more, it is sometimes desirable to reuse existing WS component implementations from the QALL-ME Framework in another application context. In this section we will make up an example for such an application and we will show how to integrate existing WSs. If you don’t know, yet, how WSs can be invoked from Java, then you may find this out in the following example, too.

In order to be able to follow this tutorial section, you will have to install the QALL-ME Framework SDK package along with the DemoKit that you probably already have. Make sure to have the main contents of both packages in the same directory, i.e., to have the deploy/, lib/, src/, etc. subdirectories of both packages all in the same directory. Some files may be contained in both packages, however, it should be safe to replace files from one package with files from the other. Additionally make sure to have both the libraries provided by your JAX-WS package (cf. section 3.1.4) as well as the Java EE 5 API (cf. section 3.1.5) in the classpath.

The sample application that we would like to develop is a temporal expression identifier for multiple languages. The user can pass in some text in any of the supported languages and will get back all temporal expressions that were found in the given text as normalized TIMEX2 annotations. The normalization will be performed relative to the time and date at which the temporal expression annotation was started. Here is the simple skeleton of our tutorial application. WS usage initialization and WS invocation parts are still missing (cf. “TODO” comments):

package com.example;

import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.bind.JAXBElement;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

import net.sf.qallme.WebServiceTools;
import net.sf.qallme.gen.ws.AnnotatedSentence;
import net.sf.qallme.gen.ws.InternalServiceFault;
import net.sf.qallme.gen.ws.AnnotatedSentence.Annotation;
import net.sf.qallme.gen.ws.languageidentification.LanguageIdentifier;
import net.sf.qallme.gen.ws.languageidentification.LanguageIdentifierWS;
import net.sf.qallme.gen.ws.timeannotation.AnnotationRequest;
import net.sf.qallme.gen.ws.timeannotation.ObjectFactory;
import net.sf.qallme.gen.ws.timeannotation.TimeAnnotator;
import net.sf.qallme.gen.ws.timeannotation.TimeAnnotatorWS;

/**
 * Finds temporal expressions in texts and normalizes them.
 */
public class TemporalExpressionFinder {

	/** the WSDL location of the {@link LanguageIdentifier} to use */
	private static final URL LANGUAGE_IDENTIFIER_LOC;
	static {
		try {
			LANGUAGE_IDENTIFIER_LOC = new URL("http://localhost:8080/qmfdemo/"
					+ "languageidentification/LanguageIdentifierWS?wsdl");
		} catch (MalformedURLException e) {
			// should never happen
			throw new IllegalStateException(e);
		}
	}

	/** the WSDL locations of the available {@link TimeAnnotator}s */
	private static Map<String, URL> ANNOTATOR_LOCS;
	static {
		ANNOTATOR_LOCS = new HashMap<String, URL>(3);
		try {
			ANNOTATOR_LOCS.put("de", new URL("http://localhost:8080/qmfdemo/"
					+ "de-timeannotation/TimeAnnotatorWS?wsdl"));
			ANNOTATOR_LOCS.put("es", new URL("http://localhost:8080/qmfdemo/"
					+ "es-timeannotation/TimeAnnotatorWS?wsdl"));
			ANNOTATOR_LOCS.put("en", new URL("http://localhost:8080/qmfdemo/"
					+ "en-timeannotation/TimeAnnotatorWS?wsdl"));
		} catch (MalformedURLException e) {
			// should never happen
			throw new IllegalStateException(e);
		}
	}

	/** an object factory for creating requests to the {@link TimeAnnotator} WS */
	private static final ObjectFactory OBJ_FACTORY = new ObjectFactory();

	/**
	 * an object factory for creating {@link AnnotatedSentence}s in requests to
	 * the {@link TimeAnnotator} WS
	 */
	private static final net.sf.qallme.gen.ws.ObjectFactory OBJ_FACTORY2 = new net.sf.qallme.gen.ws.ObjectFactory();

	/** the internally used {@link LanguageIdentifier} */
	private final LanguageIdentifier langIdentifier;

	/** the internally used {@link TimeAnnotator}s */
	private final Map<String, TimeAnnotator> timeAnnotators;

	/** the temporal context to normalize temporal expressions against */
	private final XMLGregorianCalendar calendar;

	/**
	 * Constructs and initializes a new {@link TemporalExpressionFinder} with
	 * the temporal context being the current date and time.
	 * 
	 * @throws RuntimeException
	 *             if there is a problem creating any of the web service
	 *             connections
	 */
	public TemporalExpressionFinder() throws RuntimeException {
		// set the temporal context to the current date and time
		try {
			this.calendar = DatatypeFactory
					.newInstance()
					.newXMLGregorianCalendar(
							(GregorianCalendar) GregorianCalendar.getInstance());
		} catch (DatatypeConfigurationException e) {
			// should not happen
			throw new IllegalStateException(e);
		}

		// TODO: correctly initialize the web services here:
		this.langIdentifier = null;
		this.timeAnnotators = new HashMap<String, TimeAnnotator>(ANNOTATOR_LOCS
				.size());
	}

	/**
	 * Finds temporal expressions in texts and normalizes them.
	 * 
	 * @param args
	 *            the text strings from which to extract and normalize temporal
	 *            expressions
	 * @throws IllegalArgumentException
	 *             if any of the given texts is of an unsupported language
	 * @throws InternalServiceFault
	 *             if there is some problem with web service invocation
	 */
	public static void main(String[] args) throws IllegalArgumentException,
			InternalServiceFault {
		TemporalExpressionFinder finder = new TemporalExpressionFinder();

		// find and normalize all temporal expressions in the given texts:
		for (String text : args) {
			System.out.println("Normalized temporal expressions of: " + text);
			for (String timex : finder.find(text)) {
				System.out.print("\t– ");
				System.out.println(timex);
			}
			System.out.println();
		}
	}

	/**
	 * Finds temporal expressions in the given text and returns them as
	 * normalized TIMEX2 annotations.
	 * 
	 * @param text
	 *            the text in which to find temporal expressions
	 * @return normalized TIMEX2 annotations
	 * @throws IllegalArgumentException
	 *             if the the given text is of an unsupported language
	 * @throws InternalServiceFault
	 *             if there is some problem with web service invocation
	 */
	private List<String> find(String text) throws IllegalArgumentException,
			InternalServiceFault {
		// identify the language of the given text and make sure that we have a
		// temporal expression annotator for this language
		String lang = this.langIdentifier.getLanguage(text);
		TimeAnnotator annotator = this.timeAnnotators.get(lang);
		if (annotator == null)
			throw new IllegalArgumentException("Language not supported: "
					+ lang);

		return findAndNormalize(annotator, text);
	}

	/**
	 * Finds temporal expressions in the given text and returns them as
	 * normalized TIMEX2 annotations. The given annotator is used for finding
	 * and normalizing.
	 * 
	 * @param annotator
	 *            the annotator to use for finding and normalizing
	 * @param text
	 *            the text in which to find temporal expressions
	 * @return normalized TIMEX2 annotations
	 * @throws InternalServiceFault
	 *             if there is some problem with web service invocation
	 */
	private List<String> findAndNormalize(TimeAnnotator annotator, String text)
			throws InternalServiceFault {
		List<String> result = new ArrayList<String>();

		// TODO use the selected time annotator to create the result

		return result;
	}

}

The code should be self-explanatory. The WSs to use still have to be initialized in the constructor. The find method contains the actual functionality of the TemporalExpressionFinder: it first uses an instance of the LanguageIdentifier WS to find the language of the given text and then uses an appropriate TimeAnnotator for the extraction of normalized temporal expressions. The latter is done in the findAndNormalize method which we will write in the following.

First – before actually using the WSs – we will have to initialize the connections to the WSs so that we can easily use them like normal Java objects. The URL of the WSDL description of every WS to use suffices for the initialization of these connections. In the code skeleton above we have already statically defined (or hard coded) all relevant URLs. We are now going to use these URLs in the constructor of the sample class. The QALL-ME Framework offers the WebServiceTools class (in package net.sf.qallme) of which the instantiateServiceClientInterface method can be used to establish a WS connection.[6] You just have to pass this method the WSDL URL and the Java class of the corresponding JAX-WS generated WS client interface (carrying a WebServiceClient annotation), e.g., LanguageIdentifierWS in the package net.sf.qallme.gen.ws.languageidentification. The initialization of the WS connections for our class in the constructor then looks like this:

public TemporalExpressionFinder() throws RuntimeException {
	// initialize connection to the LanguageIdentifier WS
	this.langIdentifier = WebServiceTools
			.instantiateServiceClientInterface(LanguageIdentifierWS.class,
					LANGUAGE_IDENTIFIER_LOC).getLanguageIdentifierPort();

	// initialize connections to the different TimeAnnotator WSs
	this.timeAnnotators = new HashMap<String, TimeAnnotator>(ANNOTATOR_LOCS
			.size());
	for (Entry<String, URL> annotLoc : ANNOTATOR_LOCS.entrySet())
		this.timeAnnotators.put(annotLoc.getKey(), WebServiceTools
				.instantiateServiceClientInterface(TimeAnnotatorWS.class,
						annotLoc.getValue()).getTimeAnnotatorPort());
}
All WS connections are now initialized; the WSs can easily be used via the JAX-WS generated Java interfaces such as LanguageIdentifier in the package net.sf.qallme.gen.ws.languageidentification.

We can now turn to the last step, using the WSs in our code. In the above skeleton the LanguageIdentifier is already used via its Java interface; this should not need any further explanations. The usage of the TimeAnnotator WSs is a little more complicated: we first have to create a dedicated AnnotationRequest object consisting of an AnnotatedSentence with no annotations at all and the temporal context of the given text; by our definition this context is the date and time of the creation of our example object. The request object is then simply passed to the annotator we have retrieved for the language of the given text and a new AnnotatedSentence object is returned. From this annotated sentence we can now easily extract all annotations and return their canonical forms as the result. Here is the missing code:

@SuppressWarnings("unchecked")
private List<String> findAndNormalize(TimeAnnotator annotator, String text)
		throws InternalServiceFault {
	List<String> result = new ArrayList<String>();

	// create a request to the TimeAnnotator:
	AnnotatedSentence annotSent = OBJ_FACTORY2.createAnnotatedSentence();
	annotSent.getContent().add(text);
	AnnotationRequest request = OBJ_FACTORY.createAnnotationRequest();
	request.setAnnotatedSentence(annotSent);
	request.setTemporalContext(this.calendar);

	// run the TimeAnnotator WS
	annotSent = annotator.annotateTime(request);

	// extract the annotations from the TimeAnnotator’s response and return
	// them
	for (Serializable s : annotSent.getContent())
		if (s instanceof JAXBElement)
			result.add(((JAXBElement<Annotation>) s).getValue()
					.getCanonicalForm());
	return result;
}

And we are done. With just about 200 lines of code (including comments) you have just created a temporal expression finder and normalizer for multiple languages. Test your class for example with an argument string like “Franz geht heute Abend ins Kino. Maria geht bereits um 16 Uhr mit Eva.” – or try your own natural language texts.

Note

When testing your implementation on the command line, make sure to really pass each text as a single command line argument. In most shells you will have to entirely quote each text for this. Otherwise the TemporalExpressionFinder will treat every single word as an extra text which is probably not desired.



[6] The mechanism used here and in the following relies on the availability of a JAX-WS API implementation. See section 3.1.4: “Java Web Service API And Tools” for information on how to get this API.