Menu

How_to_-_XMCDA_web_services

Olivier Cailloux

This page provides documentation about how to provide your own XMCDA Web Service in Java. We assume you know what is an XMCDA Web Service and have read the documentation about How to create an XMCDA web service-able program?. You should probably download the diviz client and try it a bit to get at ease with the concept of an XMCDA web service, if not done already.

Note that if you use types for which parsing has been implemented in the J-MCDA library (see below), you do not need to know how to parse XML files to provide your own XMCDA web service.

Note that most of the documentation on the Decision Deck website is language independent, while this document explains how to implement an XMCDA Web Service in Java.

Requirements

  1. Start a new Java project in your preferred development environment. By the way, we also recommend the use of Maven for easier dependency management, but that's up to you.
  2. The required libraries may be taken on the sourceforge download page. You need XMCDA-WS, which depends on XMCDA and on J-MCDA. Simply download a XMCDA-WS release and unzip it: it contains all the required dependencies.
  3. Add the dependencies to your project, as with any Java library. While you're at it, it is strongly recommended to also configure your development environment to show the javadoc, or even better the source, for the J-MCDA libraries your just added, and for the Google Guava project. The sources for all J-MCDA libraries are also available on the sourceforge download page.

Example

Let‘s start with an example: the CutRelation XMCDA web service. It is not necessary to understand exactly what it does to follow this explanation, but here is a short description. The service may be described as taking a fuzzy relation as input and a cut threshold, and outputting a binary relation. If you don't know what are fuzzy relations, think in terms of matrixes: the service takes as input a matrix having Alternatives as rows and Alternatives as columns. The input matrix has, for each pairs (a1, a2), a real value between zero or one that we write V(a1, a2). The service also takes a real number known as the cutting threshold, also between zero and one, that we write C. The service then returns a matrix having Alternatives as rows and Alternatives as columns, the same set of alternatives as the input matrix, and having for each pair (a1, a2) the value one if the input matrix has V(a1, a2) > C, and the value zero otherwise.

As you can see, the supplementary code necessary to turn a class into a web service is minimal. This allows you to concentrate on implementing what your service should really do, in this example, cut the matrix, rather than how it should parse the input files, manage exceptions, and so on. Have a look at other examples available in the same package if you wish so. Note that these examples are also published as web services and are reachable from e.g. the diviz client.

Overview

To provide your own XMCDA Web Service:

  1. create a new class that implements IXWS, the interface for XMCDA Web Services;
  2. for each input necessary for your service, add a public field to your class, and annotate it with @XWSInput (see below for the possible types you may use for these fields);
  3. for each output provided by your service, add a public field to your class, and annotate it with @XWSOutput (see below for the possible types you may use for these fields);
  4. add a public field having type List<InvalidInputException> which will receive possible exceptions occurring while parsing the input files, and annotate it with @XWSExceptions.
  5. Possibly, add a public field annotated with @XWSInputDirectory, which will receive the path to the input directory given as argument to the executor;
  6. similarily, a public field may be annotated with @XWSOutputDirectory. These two fields are not mandatory, contrary to the @XWSExceptions annotation.
  7. Implement an #execute() method. When that method is run, you may expect that the input fields have been set correctly. The contract of that method is simply to set the fields marked as output fields. That’s where your service really works.
  8. The class must also have a public no-argument constructor.
  9. The last step is not really about programming, but consists in writing a specification.xml file documenting your inputs and outputs, as indicated on the “How to create an XMCDA web service-able program?” document referenced here above.

The @XWSInput and @XWSOutput annotations may come with a "name" field, as in the example, which gives the name of the XML file to be read from, in case of an input, or to be written, in case of an output. This must be a simple file name with no path. The web service executor will ensure the files are read and written at their expected locations. To mark an input as optional, use the field "optional" from @XWSInput.

To start your web service from a command line, use the main method from the class XWSExecutor and give as arguments the qualified name of your class (in our example this would be org.decisiondeck.jmcda.xws.ws.XWSCutRelation), the input directory where the input files are to be found, and the output directory where the input files will be written.

How this works

When the XWSExecutor executes your web service, it knows the input directory where the files lie. This is typically given as argument by the application launcher. It reads, from your @XWSInput annotated fields, the source file names. For each input, it considers the corresponding field type. This indicates the XWSExecutor what it is supposed to search for in the input file and how it should be parsed. For example, if your field type is Set<Alternative>, the executor will search in the input file for an XML tag "Alternatives". You do not need to worry about how it does the job, however. The only thing you must ensure is that the input field type you use is a type the executor knows.

  • The simplest way is to choose a type from the following list of types the executor knows by default how to read.
  • The more complex way is to implement your own parser and add it to the list of parsers the executor knows (see method #getInputTransformer in XWSExecutor). Parsers are called functions and are expected to transform from an XMCDA tag, or a Collection of XMCDA tags, to a type of your choice. See for example the input transformers used by default by the executor in the appropriate package, whose names start with To. If you implement supplementary transformers, please think about contributing your code to this project (or publishing it somewhere).

The executor also sets the input and output directory to the fields annotated with @XWSInputDirectory and @XWSOutputDirectory, if these exist. When this is done and the input files have been parsed, and supposing no exception occurred while parsing, the executor executes your web service (heh), that is, the #execute() method your class implements. Supposedly, this computes some results using the input data and sets the output fields appropriately. When the execute method returns, the executor writes the output data to the corresponding output files. As with the input parsing, writing the output requires the executor to know how to transform each of your output fields to an XMCDA conforming file. It will choose a transformer by looking at the declared field type. Once again you may use built-in types, which are those for which a transformer exists, or provide your own transformer.

Supported input types

The type of the input and output fields will determine how automatic reading and writing will occur. The executor of the web service must know how to read a given type from an XMCDA conforming file. Here is a list of types you may use without adding your own file reader. The list describes the types you may use by increasing amount of work for the executor, or equivalently, by decreasing amount of work for you. You probably want to jump to the last line of this list, other types are provided for more subtle use cases, for example if parsing of one input file depends on the state of an other input file.

  • The File type. This is simply a reference to the file in the input directory given as argument to the Executor followed by the name of the file.
  • The InputSupplier<InputStream> type. This is an input supplier initialized to give a stream to the associated input file.
  • The XMCDADoc type. In this case the executor will simply read the main tag from the input file, ensure it validates, and assign the result to your field. You must do the rest of the deserialization.
  • Any type corresponding to a valid XML tag in a XMCDA conformant file, as a type implementing XmlObject, or as a List (or a Collection) having as a generic argument a type implementing XmlObject. Types implementing XmlObject reflects the tag names defined in the XMCDA XSL file, with a prefixed X letter. For example, you may use XAlternatives. In this case, the executor will read the appropriate tag or tags from the corresponding file, ensure it validates, and assign the result to your field. You must do the rest of the deserialization. For example, if your input field has type List<XAlternatives>, the executor will set as its value the list of Alternatives tags read from the corresponding input file. The list is guaranteed not to be empty if the field is not marked as optional: if the input file contains no XAlternatives tag, this is considered as an invalid input and treated as indicated here below.
  • Any built-in input type, that is, any type corresponding to a return type from one of the default input transform functions. An input transform function transform XMCDA files (or tags) to a determined type. The default functions have a name prefixed with "To" to indicate that it transforms to some determined type. They are collected in a given package. For example, the default transform ToAlternatives reads a collection of XMCDA tags and returns a Set<Alternative>. Therefore you may use a field whose type is Set<Alternative> as an input field and you know the default transformer that will be used to read the input will be given by the class ToAlternatives. Read from the available transformers to know which field types you may use and how the parsing is done.

Missing or invalid input

The easy rule to remember, summarizing this subsection, is: if you mark the field as optional, be prepared for its value to be possibly null. If the field is not annotated as optional, its value is guaranteed not be null when the execute method is called by the executor. Read on for the details.

If for a given field the corresponding input file does not exist (or perhaps if one of the transformers in the chain returns null), if the field is not marked as optional, the executor will raise an exception. If the file does not exist and the field is marked as optional, the field value will be null. This remark holds independently of the field type, including if the type is File.

If an exception happens while reading one of the input files (thus, if the field is not optional and the file is missing; or if the parsing failed for some reason), the executor does not call the execute method. Instead, it assigns the exceptions to the field annotated with @XWSException, and it immediately proceeds to writing the output. If the field annotated with @XWSException is also annotated with @XWSOutput, the exceptions will be written to an output file. This is probably the only field that will be written in this case, considering that the other fields have not received a chance to be initialized by the #execute() method. However, you may have initialized other fields before starting the executor, for example in the class constructor. This is useful if you want to give default return values even in case of invalid input files. Not that we think doing this is a good idea, generally speaking...

The executor will try to parse each input file and collect exceptions instead of stopping at the first invalid input file, which is why the field annotated with @XWSException must be a list. This is useful to give the caller more informations about why its input data streams are not valid.

If the execute method itself throws an exception, the executor collects it in the same exceptions list. It then proceeds to write the output files, as indicated above. Note that if the execute method did assign a certain number of output fields before throwing, those values will be written to output files.

Your execute method may also populate the exceptions list on its own.

Example files

The J-MCDA project also features a collection of sample XMCDA files that are correctly parsed by its parsing methods. You may want to refer to these files to know what the parsers expect, and direct the users of your web services to appropriate example files if you use the built-in parsers.

Starting the service manually

You may also start your web service programmatically by instantiating the XWSExecutor class. Methods are provided to enable easy testing, e.g. disable writing, simulate input files, etc. Refer to the javadoc for details. This is especially useful if you like unit testing. Many example tests are available from the XMCDA-WS-Example module, refer to the XWSServicesTest class. This also permits you to write your own main method to replace the one of the executor if deemed necessary.

More documentation

  • Refer to the javadoc of the XMCDA-WS module. Read especially documentation related to XWSExecutor and to the annotations.
  • Do not hesitate to ask questions. We welcome user feedback. Use the sourceforge forums.

Related

Wiki: Main_Page

MongoDB Logo MongoDB