Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Open protocol for accessing question engines: Difference between revisions

From MoodleDocs
Line 182: Line 182:
===String getQuestionMetadata(String questionID, String questionVersion, String questionBaseURL)===
===String getQuestionMetadata(String questionID, String questionVersion, String questionBaseURL)===


This returns a snipped of XML like this:


<pre><nowiki>
<questionmetadata>
  <scoring>
    <marks>3</marks>
  </scoring>
  <plainmode>yes</plainmode>
</questionmetadata>
</mowiki></pre>
The scoring section is basically giving the maximum score for the question, but because a question can report scores against different 'axes', this may be more complicated.
plain mode is a feature of the OpenMark system designed to enhance accessibility. Since OpenMark use a lot of JavaScript and CSS which may not be accessible to some people, then every question should provide an alternative rendering using just plain HTML and standard from controls. When a student chooses to take a quiz in plain mode, they see the plain mode version of each question. However, not all questions provide a plain-mode version, in which case they should return ,plainmode>no</plainmode>.


===StartReturn start(String questionID, String questionVersion, String questionBaseURL, Map<String, String> initialParams, String[])===
===StartReturn start(String questionID, String questionVersion, String questionBaseURL, Map<String, String> initialParams, String[])===

Revision as of 15:49, 4 June 2007

History

Previously (2004 to early 2005) there was an attempt (by the Serving Maths Project, Gustav Delius and others) to create something called Remote Question Protocol. This was a web service interface designed to allow question from different question engines (for example AIM and STACK) to be included in Moodle quizzes. The effort ran out of resources at a time when the code was not in a working state. Several groups have an interest in getting RQP working again, although no one is currently working on it.

During 2005, The Open University (mainly Sam Marshall) developed in-house an online assessment system called OpenMark. Its architecture was of separate question engines and a test navigator, communicating via a web service link which we call Opaque (Open Protocol for Accessing QUestion Engines).

Then in May 2006, I (Tim Hunt) took over maintenance of the quiz module from Gustav. At which point I learned about RQP. I was immediately struck by how similar Opaque and RQP were conceptually, although they differed in details.

Now (autumn 2006), the OU wants to be able to include OM question in Moodle quizzes. Our choices are to either

  1. change OM to use RQP; or
  2. change (add to) Moodle to support Opaque.

We have pretty-much decided to do 2., because it is less work for us, and becuase Opaque currenly works and RQP doesn't. Therefore, RQP implementing question engines will have to change anyway, and since Opaque is conceptually similar to RQP, changing to Opaque won't be much more work that changed to whatever the next iteration of RQP would have been.

By the way, we are hoping that it will prove possible to open-source OpenMark, which will increase the number of Opaque compatible systems that are freely available.

Rationale

Why are people interested in this sort of protocol? The answer is separation of concerns. The things an online assessment system has to do are:

  1. Identify students, test, which students can do which tests at what times, and which questions make up each test.
  2. Record the scores and other information that results from students attempting test, and provide reporting fascilities for this information.
  3. Display questions to students and process the responses the students make to generate scores, feedback, etc.
  4. Allow teachers to create and configure the questions.

Moodle is very good at points 1 and 2, there is a lot of boring book-keeping do be done here, and it is already implemented in Moodle.

For the developers who are interested in Opaque/RQP, the really interesting part of an assessment system is 3. They want to be able to pose really intersting types of questions to students to really make them think, and then use sophisticated algorithms to analyse the student's response and give targeted feedback when they get things wrong.

Doing this may require specialist tools. For example, STACK and AIM both use Computer algebra systems to set and mark interesting mathematical questions. OpenMark lets question authors write answer marking and feedback generation algorithms in Java code, with a library of helpful classes and functions. It is simply not feasible to rewrite these question engines in PHP and turn them into Moodle question types. Hence the search for another way to get these questions into Moodle.

Of course, each of STACK, AIM, OM, etc. could programme their own solutions to the administration parts 1 and 2. However, this is a needless duplication of effort. It does not allow questions from different question engines to be combined in the same test. And people who are interested in writing advanced question engines are normally not very interested in writing student administration systems.

Finally, there is point 4, question authoring. Authoring advanced question types is a complicated task that will probably need a specialised interface. For example STACK provides its own editing interface. OM questions are authored in a Java IDE, then complied to a JAR file and uploaded to the server. This is all beyond the scope of Opaque. RQP started trying to address authoring at about the point that it was abandoned.

Our approach is that Opaque should be optimised point 3 above - presenting questions to students and processing their responses. This is the point where interoperability is required. Interoperability is hard, so let us keep the scope of the protocol as small as possible. Interoperability of student data and results is a separate problem. Authoring of advanced question types is specialised to the paticular question engine, and interoperability is not possible.

All this is in the context of an online assessment system. Therefore, we are interested in questions that get presented to students in their web browser as HTML, with accompanying CSS, Javascript, Images and other media files. Student responses get sent back as form sumissions, that is HTTP POST requests. Concievably the trendy XMLHttpRequest could be used instead, but it is not really necessary. Opaque only works with form submissions, which is simple and sufficient.

Overview of Opaque

Opaque is a web service protocol based on SOAP.

The parts of the system

OPAQUE block diagram.png

Opaque is only used at the point when questions are being presented to students, and possibly later when a student's attempt is being reviewed by a teacher.

It allows the test management system (TMS) to delegate the redering of questions, the scoring of responses and the generation of feedback to a remote question enging.

There may be a separate question bank where question definitions are stored, or question definitions may be stored within the question engine (e.g. Stack) or the test management system may also tale the role of question bank (e.g. OM).

The TMS takes full responsibility for authenticating students and controlling which questions they see. It asks an appropriate question engine to render each question. The question just had to process the questions and responses it is told to process.

Although Opque is designed to allow interoperability between arbitrary different types of question engines and TMSs, the expectation is that any online assessment system will only use particular instances of each component.

Identifying questions

When the TMS asks the question engine to render a particular qustion, it needs to identify that question. This is done using three pieces of information:

questionbank base URL
If the question engine is designed to fetch question definitions from a remote question bank, then this is base URL for fetching questions from. For example http://example.com/questionbank.
question id
This is a string conatining only the characters [_a-z0-9.], and also matching the syntactic rules for Java package names. That is, it is one or more 'words' separated by '.'s, each word matching the regexp [_a-z][_a-z0-9]*. This identifies the question withing the question bank. For example calculus.diff01a or _12345.
question version
This is a string of the fomat major.minor, where major and minor are both decimal integers with no leading zero. Version with the same major version number should all behave roughly the same way. minor version changes are things like bug fixes. Therefore, a test definition would probably only specify which major version of the question to use, and the most recent minor version would be picked by the TMS. The full version number is needed for resuming interrupted sessions and reviewing student responsees. See reproducability guarantee below. For example 1.1, 1.2.

If the question engine fetches question definitions from a remote question bank, then it will fetch a particular question by requesting the URL questionbankBaseURL/questionId.questionVersion (e.g. http://example.com/questionbank/calculus.diff01a.1.1). This returns a single block of data (e.g. OM questions are jar files conaining XML, media files and java code). The format of this data is up to the question engine. It is the responsibility of the TMS to only ask question engines to render questions that they can understand. In the question engine supports different question formats, then it may use the mime type of the response from the question bank to decide how to process this question.

If the question engine stores its own questions, then it can ignore the questionbank base URL, and use the question id and question version to uniquely identify the requested question within its datastore.

What can be included in rendered questions

When a question engine is asked to render a question, it returns a response which includes the following parts:

String XHTML
Well-formed XML which, when inserted into the body or div of an HTML page, will result in valid HTML.
String CSS
any CSS needed to style the content. The TMS will cache this, and include a link to it in the head of the HTML page.
Resource[] resources
an array of resources which the TMS must cache and make available.

A Resource has the following fields:

byte[] content
String encoding
String filename
String mimeType

The content is what is served when the resource is requested. It is served with the specified encoding and mimeType. The filename is used in the URL the resource will be made available at. If the question requires any javascript, this should be returned as a resource.

Both the XHTML and CSS may contain placeholders of the form %%identifier%%. These are substituted by the TMS. Recognised placeholders include:

%%RESOURCE%%
Path [relative or absolute] at which resources will become available. This should not include the terminating /. Example: If a resource has the name myfile.png, then <img src="%%RESOURCES%%/myfile.png"/> should work to include that image.
%%FORMTARGET%%
Full path [relative or absolute] to which form submissions should be sent.

Example: The form <form action="%%FORMTARGET%%" method="post"> should work

%%FORMFIELD%%
Question must include this after any opening <form> tag, providing an option for the test navigator to insert additional hidden fields if necessary.
%%IDPREFIX%%
This should be prepended to any id or class attribute in the XHTML and references to them in the CSS or Javascript. This allows multiple questions to be diplayed on one page, without conflict. [Note, this is not currently implemented since OM only ever displays one question per page. There is a problem here with javascript, since placeholders are not substituted in resources.
%%%%
Replaced with %%.

For perfomance reasons, whenever the TMS asks the question engine to render a question, it may send a list of resources that it already has cached for that question. The question engine may choose not to resend any of these resources.

How the student response is represented

The TMS receives the student's response as a HTML form submission, that is, as a HTTP POST request.

Because there might be more than one question per page, the post response to the TMS may contain many post variables. The TMS separates out the ones with the appropriate %%IDPREFIX%%, and forwards just those key->value pairs to the question engine (with idprefix removed from the key name), when it asks it to process that response.

(Another problem with multiple questions per page is that at the moment for form tag is included in the HTML returned by the question engine, whereas multiple questios per page needs a single form for all questions, provided by the TMS.)

What sort of results can be returned

Any request to the question engine to process some results will return HTML and resources etc. to be displayed to the student. In addition, it may (if the student has finished answering the question) include some stuctured results data to be stored in the results database. The Results structure includes:

String actionSummary
A log of the actions the student took in getting to their final answer. Intended for reports seen by test administrators.
String answerLine
A one-line summary of the student's final answer. For general reporting.
int attempts
Number of attempts it took the student to get their final answer.
String questionLine
A one line summary of the question that was asked. For reporting. Most useful when a question has different random variants.
Map<String, int> scores
The scores for this question. Normally the score will be a single integer, and the corresponding key will by the empty string. However, in some situations (e.g. psychological tests) the question may want to report scores against different 'axes'.

To repeat, these are stored in a database by the TMS, and used for reporting and computing overall test scores without needing to contact the question engines. It it is necessary to reconstruct exactly what the student saw on a particular question, and exactly what they entered, then this can be done using the question engine. See the next section.

Question sessions and the reproducability guarantee

Opaque is a stateful protocol, but in a weak sense.

In the protocol, a student's attempt on a question will require a call to the start() method, then one or more calls to the process() method. These calls may be separated by several minutes, but it could be any length of time.

Normally, what will happen is that the the start() call will return a question session id, and then each call to process() will refer to this session. This works most of the time, but on it's own would not be robust. What if a studnet stopped working on a question one day and expected to come back the next day and resume where they left off? What if the question engine server crashed?

To solve these problems, the question engine must guarantee that given a sequence of calls to start() and process() with exactly the same arguments, it will always return the same responses. The TMS should store all the information needed to recreate the sequence of calls it made on behalf of the student (that is, the question identity, the random seed, and the values submitted by students in each POST request). This means that if a question session is interrupted or lost, it can just be recreated.

Question sessions are designed to improve performance since the question definition only has to be read and processed once per session. They should also make implementing question engines easier, since state does not have to be saved in persistent storage, and most languages designed for writing server-side applications have session handling mechanisms.

If the TMS knows that a studnet has suspended thier attempt on a question (for example by navigating to a different question) it should call the stop() method to inform the question engine that that question session is no longer needed. The question enging may discard any question session that has been inactive for a period of time.

What happens in a typical attempt on a question

This diagram idicates a typical sequence of calls that happens when a student attempts a question.

OPAQUE sequence diagram.png

The Opaque data structures

At the moment, I am unlikely to find time to document these in details. Sorry.

StartReturn

ProcessReturn

Results

The Opaque API methods

At the moment, I am unlikely to find time to document these in details. Sorry.

String getEngineInfo()

This returns some XML data as a string (byte array). The format of the response is

<engineinfo>
    <name>[question engine]</name> ''<!-- Required -->''
    <usedmemory>['123 bytes' or '45 KB' or '67 MB']</usedmemory> ''<!-- Optional-->''
    <activesessions>[Number of question sessions active]</activesessions> ''<!-- Optional-->''
</engineinfo>

This method is only ever used for system monitoring - that is to ask a question engine "are you still there?". It is not essential to the functioning of the system.

The tags inside the outer <engineinfo> tag may be in any order. Other tags may also be included.

String getQuestionMetadata(String questionID, String questionVersion, String questionBaseURL)

This returns a snipped of XML like this:

<nowiki>
<questionmetadata> 
  <scoring> 
    <marks>3</marks> 
  </scoring> 
  <plainmode>yes</plainmode> 
</questionmetadata> 
</mowiki>

The scoring section is basically giving the maximum score for the question, but because a question can report scores against different 'axes', this may be more complicated.

plain mode is a feature of the OpenMark system designed to enhance accessibility. Since OpenMark use a lot of JavaScript and CSS which may not be accessible to some people, then every question should provide an alternative rendering using just plain HTML and standard from controls. When a student chooses to take a quiz in plain mode, they see the plain mode version of each question. However, not all questions provide a plain-mode version, in which case they should return ,plainmode>no</plainmode>.

StartReturn start(String questionID, String questionVersion, String questionBaseURL, Map<String, String> initialParams, String[])

ProcessReturn process(String questionSession, Map<String, String> response)

stop(String questionSession)

Opque WSDL

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://om.open.ac.uk/" xmlns:apachesoap="http://xml.apache.org/xml-soap" xmlns:impl="http://om.open.ac.uk/" xmlns:intf="http://om.open.ac.uk/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <wsdl:types>
  <schema targetNamespace="http://om.open.ac.uk/" xmlns="http://www.w3.org/2001/XMLSchema">
   <import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>

   <complexType name="ArrayOf_soapenc_string">
    <complexContent>
     <restriction base="soapenc:Array">
      <attribute ref="soapenc:arrayType" wsdl:arrayType="soapenc:string[]"/>
     </restriction>
    </complexContent>
   </complexType>

   <complexType name="Resource">
    <sequence>
     <element name="content" nillable="true" type="soapenc:base64Binary"/>
     <element name="encoding" nillable="true" type="soapenc:string"/>
     <element name="filename" nillable="true" type="soapenc:string"/>
     <element name="mimeType" nillable="true" type="soapenc:string"/>
    </sequence>
   </complexType>

   <complexType name="ArrayOfResource">
    <complexContent>
     <restriction base="soapenc:Array">
      <attribute ref="soapenc:arrayType" wsdl:arrayType="impl:Resource[]"/>
     </restriction>
    </complexContent>
   </complexType>

   <complexType name="StartReturn">
    <sequence>
     <element name="CSS" nillable="true" type="soapenc:string"/>
     <element name="XHTML" nillable="true" type="soapenc:string"/>
     <element name="progressInfo" nillable="true" type="soapenc:string"/>
     <element name="questionSession" nillable="true" type="soapenc:string"/>
     <element name="resources" nillable="true" type="impl:ArrayOfResource"/>
    </sequence>
   </complexType>

   <complexType name="OmException">
    <sequence/>
   </complexType>

   <complexType name="CustomResult">
    <sequence>
     <element name="name" nillable="true" type="soapenc:string"/>
     <element name="value" nillable="true" type="soapenc:string"/>
    </sequence>
   </complexType>

   <complexType name="ArrayOfCustomResult">
    <complexContent>
     <restriction base="soapenc:Array">
      <attribute ref="soapenc:arrayType" wsdl:arrayType="impl:CustomResult[]"/>
     </restriction>
    </complexContent>
   </complexType>

   <complexType name="Score">
    <sequence>
     <element name="axis" nillable="true" type="soapenc:string"/>
     <element name="marks" type="xsd:int"/>
    </sequence>
   </complexType>

   <complexType name="ArrayOfScore">
    <complexContent>
     <restriction base="soapenc:Array">
      <attribute ref="soapenc:arrayType" wsdl:arrayType="impl:Score[]"/>
     </restriction>
    </complexContent>
   </complexType>

   <complexType name="Results">
    <sequence>
     <element name="actionSummary" nillable="true" type="soapenc:string"/>
     <element name="answerLine" nillable="true" type="soapenc:string"/>
     <element name="attempts" type="xsd:int"/>
     <element name="customResults" nillable="true" type="impl:ArrayOfCustomResult"/>
     <element name="questionLine" nillable="true" type="soapenc:string"/>
     <element name="scores" nillable="true" type="impl:ArrayOfScore"/>
    </sequence>
   </complexType>

   <complexType name="ProcessReturn">
    <sequence>
     <element name="CSS" nillable="true" type="soapenc:string"/>
     <element name="XHTML" nillable="true" type="soapenc:string"/>
     <element name="progressInfo" nillable="true" type="soapenc:string"/>
     <element name="questionEnd" type="xsd:boolean"/>
     <element name="resources" nillable="true" type="impl:ArrayOfResource"/>
     <element name="results" nillable="true" type="impl:Results"/>
    </sequence>
   </complexType>

  </schema>

 </wsdl:types>

   <wsdl:message name="getEngineInfoResponse">
      <wsdl:part name="getEngineInfoReturn" type="soapenc:string"/>
   </wsdl:message>

   <wsdl:message name="processRequest">
      <wsdl:part name="questionSession" type="soapenc:string"/>
      <wsdl:part name="names" type="impl:ArrayOf_soapenc_string"/>
      <wsdl:part name="values" type="impl:ArrayOf_soapenc_string"/>
   </wsdl:message>

   <wsdl:message name="getEngineInfoRequest">
   </wsdl:message>

   <wsdl:message name="getQuestionMetadataResponse">
      <wsdl:part name="getQuestionMetadataReturn" type="soapenc:string"/>
   </wsdl:message>

   <wsdl:message name="processResponse">
      <wsdl:part name="processReturn" type="impl:ProcessReturn"/>
   </wsdl:message>

   <wsdl:message name="stopResponse">
   </wsdl:message>

   <wsdl:message name="OmException">
      <wsdl:part name="fault" type="impl:OmException"/>
   </wsdl:message>

   <wsdl:message name="startResponse">
      <wsdl:part name="startReturn" type="impl:StartReturn"/>
   </wsdl:message>

   <wsdl:message name="stopRequest">
      <wsdl:part name="questionSession" type="soapenc:string"/>
   </wsdl:message>

   <wsdl:message name="getQuestionMetadataRequest">
      <wsdl:part name="questionID" type="soapenc:string"/>
      <wsdl:part name="questionVersion" type="soapenc:string"/>
      <wsdl:part name="questionBaseURL" type="soapenc:string"/>
   </wsdl:message>

   <wsdl:message name="startRequest">
      <wsdl:part name="questionID" type="soapenc:string"/>
      <wsdl:part name="questionVersion" type="soapenc:string"/>
      <wsdl:part name="questionBaseURL" type="soapenc:string"/>
      <wsdl:part name="initialParamNames" type="impl:ArrayOf_soapenc_string"/>
      <wsdl:part name="initialParamValues" type="impl:ArrayOf_soapenc_string"/>
      <wsdl:part name="cachedResources" type="impl:ArrayOf_soapenc_string"/>
   </wsdl:message>

   <wsdl:portType name="OmService">

      <wsdl:operation name="start" parameterOrder="questionID questionVersion questionBaseURL initialParamNames initialParamValues cachedResources">
         <wsdl:input message="impl:startRequest" name="startRequest"/>
         <wsdl:output message="impl:startResponse" name="startResponse"/>
         <wsdl:fault message="impl:OmException" name="OmException"/>
      </wsdl:operation>

      <wsdl:operation name="stop" parameterOrder="questionSession">
         <wsdl:input message="impl:stopRequest" name="stopRequest"/>
         <wsdl:output message="impl:stopResponse" name="stopResponse"/>
         <wsdl:fault message="impl:OmException" name="OmException"/>
      </wsdl:operation>

      <wsdl:operation name="process" parameterOrder="questionSession names values">
         <wsdl:input message="impl:processRequest" name="processRequest"/>
         <wsdl:output message="impl:processResponse" name="processResponse"/>
         <wsdl:fault message="impl:OmException" name="OmException"/>
      </wsdl:operation>

      <wsdl:operation name="getEngineInfo">
         <wsdl:input message="impl:getEngineInfoRequest" name="getEngineInfoRequest"/>
         <wsdl:output message="impl:getEngineInfoResponse" name="getEngineInfoResponse"/>
      </wsdl:operation>

      <wsdl:operation name="getQuestionMetadata" parameterOrder="questionID questionVersion questionBaseURL">
         <wsdl:input message="impl:getQuestionMetadataRequest" name="getQuestionMetadataRequest"/>
         <wsdl:output message="impl:getQuestionMetadataResponse" name="getQuestionMetadataResponse"/>
         <wsdl:fault message="impl:OmException" name="OmException"/>
      </wsdl:operation>

   </wsdl:portType>

   <wsdl:binding name="OmSoapBinding" type="impl:OmService">
      <wsdlsoap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>

      <wsdl:operation name="start">
         <wsdlsoap:operation soapAction=""/>
         <wsdl:input name="startRequest">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://om.open.ac.uk/" use="encoded"/>
         </wsdl:input>
         <wsdl:output name="startResponse">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://om.open.ac.uk/" use="encoded"/>
         </wsdl:output>

         <wsdl:fault name="OmException">
            <wsdlsoap:fault encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" name="OmException" namespace="http://om.open.ac.uk/" use="encoded"/>
         </wsdl:fault>
      </wsdl:operation>

      <wsdl:operation name="stop">
         <wsdlsoap:operation soapAction=""/>
         <wsdl:input name="stopRequest">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://om.open.ac.uk/" use="encoded"/>
         </wsdl:input>

         <wsdl:output name="stopResponse">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://om.open.ac.uk/" use="encoded"/>
         </wsdl:output>
         <wsdl:fault name="OmException">
            <wsdlsoap:fault encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" name="OmException" namespace="http://om.open.ac.uk/" use="encoded"/>
         </wsdl:fault>
      </wsdl:operation>

      <wsdl:operation name="process">
         <wsdlsoap:operation soapAction=""/>
         <wsdl:input name="processRequest">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://om.open.ac.uk/" use="encoded"/>
         </wsdl:input>
         <wsdl:output name="processResponse">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://om.open.ac.uk/" use="encoded"/>
         </wsdl:output>
         <wsdl:fault name="OmException">
            <wsdlsoap:fault encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" name="OmException" namespace="http://om.open.ac.uk/" use="encoded"/>
         </wsdl:fault>
      </wsdl:operation>

      <wsdl:operation name="getEngineInfo">
         <wsdlsoap:operation soapAction=""/>
         <wsdl:input name="getEngineInfoRequest">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://om.open.ac.uk/" use="encoded"/>
         </wsdl:input>
         <wsdl:output name="getEngineInfoResponse">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://om.open.ac.uk/" use="encoded"/>
         </wsdl:output>
      </wsdl:operation>

      <wsdl:operation name="getQuestionMetadata">
         <wsdlsoap:operation soapAction=""/>
         <wsdl:input name="getQuestionMetadataRequest">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://om.open.ac.uk/" use="encoded"/>
         </wsdl:input>
         <wsdl:output name="getQuestionMetadataResponse">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://om.open.ac.uk/" use="encoded"/>
         </wsdl:output>
         <wsdl:fault name="OmException">
            <wsdlsoap:fault encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" name="OmException" namespace="http://om.open.ac.uk/" use="encoded"/>
         </wsdl:fault>
      </wsdl:operation>

   </wsdl:binding>

   <wsdl:service name="OmServiceService">
      <wsdl:port binding="impl:OmSoapBinding" name="Om">
         <wsdlsoap:address location="http://kestrel.open.ac.uk/om-qe/services/Om"/>
      </wsdl:port>
   </wsdl:service>

</wsdl:definitions>

See also: