mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

ctissot
20.55.2007 c2beb1994315ebd33bbdd186a3c97d62a194a9ec
I use this bug id (138) as an umbrella for a list of 
bugfixes/improvements for the DSML gateway.

Here is the detail file by file:
. DSMLAbandonOperation
-> error handling : abandon Id is now mapped to an unwilling to
perform error (was IOException)

. DSMLDeleteOperation
-> added matchedDN in response (was missing)

. DSMLServlet
-> added schema validation during JAXB parsing
-> dropped usage of an internal JAXB class (useless and risky as this
class was renamed in Java 6)
-> removed doGet method (useless/cleanup)
-> check that if authorization is tagged in http request (bindDN,
passwd) are both provided and valid
-> better error handling : safer processing with appropriate error
messages instead of global try/catch with generic message
- in case of parsing error, the request in reparsed with SAX
instead of JAXB in order to extract requestID and build appropriate response
- in case of processing exception, a mapping (hard-coded) is
performed and set in the error response message
-> fixed processing onError: default behaviour was to stop once a
request failed but the specification defines to continue

. DSMLSearchOperation
-> added missing filters : "and", "or", "not", "substrings",
"greaterOrEqual", "lessOrEqual", "approxMatch", "extensibleMatch"
("equalityMatch" and "present" were provided)
-> fixed ability to return attribute names only (typesOnly)

. build.xml
-> added schema in DSML war file to enable JAXB schema validation
5 files modified
1070 ■■■■ changed files
opends/build.xml 5 ●●●●● patch | view | raw | blame | history
opends/src/dsml/org/opends/dsml/protocol/DSMLAbandonOperation.java 11 ●●●● patch | view | raw | blame | history
opends/src/dsml/org/opends/dsml/protocol/DSMLDeleteOperation.java 7 ●●●●● patch | view | raw | blame | history
opends/src/dsml/org/opends/dsml/protocol/DSMLSearchOperation.java 460 ●●●● patch | view | raw | blame | history
opends/src/dsml/org/opends/dsml/protocol/DSMLServlet.java 587 ●●●●● patch | view | raw | blame | history
opends/build.xml
@@ -1007,6 +1007,11 @@
      </classpath>
    </javac>
     <mkdir dir="${dsml.classes.dir}/resources" />
     <copy file="${dsml.dir}/schema/DSMLv2.xsd"
           tofile="${dsml.classes.dir}/resources/DSMLv2.xsd"
           overwrite="true"/>
    <war destfile="${classes.dir}/${SHORT_NAME}-${VERSION_NUMBER_STRING}-DSML.war"
         webxml="${dsml.dir}/webapp/web.xml">
      <fileset file="${dsml.dir}/webapp/server.properties" />
opends/src/dsml/org/opends/dsml/protocol/DSMLAbandonOperation.java
@@ -27,11 +27,14 @@
package org.opends.dsml.protocol;
import java.io.IOException;
import org.opends.messages.Message;
import org.opends.server.protocols.ldap.AbandonRequestProtocolOp;
import org.opends.server.protocols.ldap.LDAPMessage;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.protocols.ldap.ProtocolOp;
import org.opends.server.tools.LDAPConnection;
import org.opends.server.types.LDAPException;
@@ -66,13 +69,10 @@
   */
  public LDAPResult doOperation(ObjectFactory objFactory,
        AbandonRequest abandonRequest)
    throws IOException
    throws LDAPException, IOException
  {
    LDAPResult abandonResponse = objFactory.createLDAPResult();
    // Set the id for the response.
    abandonResponse.setRequestID(abandonRequest.getRequestID());
    String abandonIdStr = abandonRequest.getAbandonID();
    int abandonId = 0;
    try
@@ -80,7 +80,8 @@
      abandonId = Integer.parseInt(abandonIdStr);
    } catch (NumberFormatException nfe)
    {
      throw new IOException(nfe.getMessage());
      throw new LDAPException(LDAPResultCode.UNWILLING_TO_PERFORM,
                              Message.raw(nfe.getMessage()));
    }
    // Create and send an LDAP request to the server.
opends/src/dsml/org/opends/dsml/protocol/DSMLDeleteOperation.java
@@ -36,6 +36,7 @@
import org.opends.server.protocols.ldap.LDAPMessage;
import org.opends.server.protocols.ldap.ProtocolOp;
import org.opends.server.tools.LDAPConnection;
import org.opends.server.types.DN;
import org.opends.server.types.LDAPException;
@@ -107,6 +108,12 @@
    code.setCode(resultCode);
    delResponse.setResultCode(code);
    // set the match DN
    DN dn = delOp.getMatchedDN();
    if ( dn != null ) {
      delResponse.setMatchedDN(dn.toString());
    }
    return delResponse;
  }
opends/src/dsml/org/opends/dsml/protocol/DSMLSearchOperation.java
@@ -34,23 +34,22 @@
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import org.opends.server.protocols.asn1.ASN1Element;
import javax.xml.bind.JAXBElement;
import org.opends.server.protocols.asn1.ASN1Exception;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.asn1.ASN1Sequence;
import org.opends.server.protocols.ldap.LDAPAttribute;
import org.opends.server.protocols.ldap.LDAPConstants;
import org.opends.server.protocols.ldap.LDAPFilter;
import org.opends.server.protocols.ldap.LDAPMessage;
import org.opends.server.protocols.ldap.ProtocolOp;
import org.opends.server.protocols.ldap.SearchRequestProtocolOp;
import org.opends.server.protocols.ldap.SearchResultEntryProtocolOp;
import org.opends.server.protocols.ldap.SearchResultReferenceProtocolOp;
import org.opends.server.protocols.ldap.SearchResultDoneProtocolOp;
import org.opends.server.tools.LDAPConnection;
import org.opends.server.types.ByteString;
import org.opends.server.types.DereferencePolicy;
import org.opends.server.types.LDAPException;
import org.opends.server.types.RawFilter;
import org.opends.server.types.SearchScope;
@@ -74,6 +73,283 @@
    this.connection = connection;
  }
  /**
   * Returns a new AND search filter with the provided filter components.
   *
   * @param filterSet The filter components for this filter
   *
   * @return a new AND search filter with the provided filter components.
   *
   * @throws LDAPException an LDAPException is thrown if the creation of a
   *                       filter component fails.
   */
  private static LDAPFilter createANDFilter(FilterSet filterSet)
          throws LDAPException {
    List<JAXBElement<?>> list = filterSet.getFilterGroup();
    ArrayList<RawFilter> filters = new ArrayList<RawFilter>(list.size());
    for(JAXBElement<?> filter : list) {
      filters.add(createFilter(filter));
    }
    return LDAPFilter.createANDFilter(filters);
  }
  /**
   * Returns a new Approximate search filter with the provided information.
   *
   * @param ava the attribute value assertion for this approximate filter.
   *
   * @return a new Approximate search filter with the provided information.
   */
  private static LDAPFilter createApproximateFilter(AttributeValueAssertion ava)
  {
    return LDAPFilter.createApproximateFilter(ava.getName(),
                                           new ASN1OctetString(ava.getValue()));
  }
  /**
   * Returns a new Equality search filter with the provided information.
   *
   * @param ava the attribute value assertion for this Equality filter.
   *
   * @return a new Equality search filter with the provided information.
   */
  private static LDAPFilter createEqualityFilter(AttributeValueAssertion ava) {
    return LDAPFilter.createEqualityFilter(ava.getName(),
                                          new ASN1OctetString(ava.getValue()));
  }
  /**
   * Returns a new Extensible search filter with the provided information.
   *
   * @param mra the matching rule assertion for this Extensible filter.
   *
   * @return a new Extensible search filter with the provided information.
   */
  private static LDAPFilter createExtensibleFilter(MatchingRuleAssertion mra) {
    return LDAPFilter.createExtensibleFilter(mra.getMatchingRule(),
                                        mra.getName(),
                                        new ASN1OctetString(mra.getValue()),
                                        mra.isDnAttributes());
  }
  /**
   * Returns a new GreaterOrEqual search filter with the provided information.
   *
   * @param ava the attribute value assertion for this GreaterOrEqual filter.
   *
   * @return a new GreaterOrEqual search filter with the provided information.
   */
  private static LDAPFilter createGreaterOrEqualFilter(
                              AttributeValueAssertion ava) {
    return LDAPFilter.createGreaterOrEqualFilter(ava.getName(),
                                          new ASN1OctetString(ava.getValue()));
  }
  /**
   * Returns a new LessOrEqual search filter with the provided information.
   *
   * @param ava the attribute value assertion for this LessOrEqual filter.
   *
   * @return a new LessOrEqual search filter with the provided information.
   */
  private static LDAPFilter createLessOrEqualFilter(
                              AttributeValueAssertion ava) {
    return LDAPFilter.createLessOrEqualFilter(ava.getName(),
                                          new ASN1OctetString(ava.getValue()));
  }
  /**
   * Returns a new NOT search filter with the provided information.
   *
   * @param filter the filter for this NOT filter.
   *
   * @return a new NOT search filter with the provided information.
   *
   * @throws LDAPException an LDAPException is thrown if the creation of the
   *                       provided filter fails.
   */
  private static LDAPFilter createNOTFilter(Filter filter)
          throws LDAPException {
    return LDAPFilter.createNOTFilter(createFilter(filter));
  }
  /**
   * Returns a new OR search filter with the provided filter components.
   *
   * @param filterSet The filter components for this filter
   *
   * @return a new OR search filter with the provided filter components.
   *
   * @throws LDAPException an LDAPException is thrown if the creation of a
   *                       filter component fails.
   */
  private static LDAPFilter createORFilter(FilterSet filterSet)
          throws LDAPException {
    List<JAXBElement<?>> list = filterSet.getFilterGroup();
    ArrayList<RawFilter> filters = new ArrayList<RawFilter>(list.size());
    for(JAXBElement<?> filter : list) {
      filters.add(createFilter(filter));
    }
    return LDAPFilter.createORFilter(filters);
  }
  /**
   * Returns a new Present search filter with the provided information.
   *
   * @param ad the attribute description for this Present filter.
   *
   * @returna new Present search filter with the provided information.
   *
   * @throws LDAPException an LDAPException is thrown if the ASN.1 element
   *                       provided by the attribute description cannot be
   *                       decoded as a raw search filter.
   */
  private static LDAPFilter createPresentFilter(AttributeDescription ad)
          throws LDAPException {
    return LDAPFilter.decode(
             new StringBuilder(ad.getName()).append("=*").toString());
  }
  /**
   * Returns a new Substring search filter with the provided information.
   *
   * @param sf the substring filter for this Substring filter.
   *
   * @return a new Substring search filter with the provided information.
   */
  private static LDAPFilter createSubstringFilter(SubstringFilter sf) {
    List<String> anys = sf.getAny();
    ArrayList<ByteString> subAnyElements =
                                         new ArrayList<ByteString>(anys.size());
    for(String s : anys) {
      subAnyElements.add(new ASN1OctetString(s));
    }
    return LDAPFilter.createSubstringFilter(sf.getName(),
                                        new ASN1OctetString(sf.getInitial()),
                                        subAnyElements,
                                        new ASN1OctetString(sf.getFinal()));
  }
  /**
   * Returns a new LDAPFilter according to the tag name of the provided element
   * that can be "and", "or", "not", "equalityMatch", "substrings",
   * "greaterOrEqual", "lessOrEqual", "present", "approxMatch",
   * "extensibleMatch".
   *
   * @param xmlElement a JAXBElement that contains the name of the filter to
   *                   create and the associated argument.
   *
   * @return a new LDAPFilter according to the tag name of the provided element.
   *
   * @throws LDAPException an LDAPException is thrown if the creation of the
   *                       targeted filter fails.
   */
  private static LDAPFilter createFilter(JAXBElement<?> xmlElement)
          throws LDAPException {
    LDAPFilter result = null;
    String filterName = xmlElement.getName().getLocalPart();
    if ( "and".equals(filterName) ) {
      // <xsd:element name="and" type="FilterSet"/>
      result = createANDFilter((FilterSet)xmlElement.getValue());
    }
    else if ( "or".equals(filterName) ) {
      // <xsd:element name="or" type="FilterSet"/>
      result = createORFilter((FilterSet)xmlElement.getValue());
    }
    else if ( "not".equals(filterName) ) {
      // <xsd:element name="not" type="Filter"/>
      result = createNOTFilter((Filter)xmlElement.getValue());
    }
    else if ( "equalityMatch".equals(filterName) ) {
      // <xsd:element name="equalityMatch" type="AttributeValueAssertion"/>
      result = createEqualityFilter((AttributeValueAssertion)
                                                         xmlElement.getValue());
    }
    else if ( "substrings".equals(filterName) ) {
      // <xsd:element name="substrings" type="SubstringFilter"/>
      result = createSubstringFilter((SubstringFilter)xmlElement.getValue());
    }
    else if ( "greaterOrEqual".equals(filterName) ) {
      // <xsd:element name="greaterOrEqual" type="AttributeValueAssertion"/>
      result = createGreaterOrEqualFilter((AttributeValueAssertion)
                                                         xmlElement.getValue());
    }
    else if ( "lessOrEqual".equals(filterName) ) {
      // <xsd:element name="lessOrEqual" type="AttributeValueAssertion"/>
      result = createLessOrEqualFilter((AttributeValueAssertion)
                                                         xmlElement.getValue());
    }
    else if ( "present".equals(filterName) ) {
      // <xsd:element name="present" type="AttributeDescription"/>
      result = createPresentFilter((AttributeDescription)xmlElement.getValue());
    }
    else if ( "approxMatch".equals(filterName) ) {
      // <xsd:element name="approxMatch" type="AttributeValueAssertion"/>
      result = createApproximateFilter((AttributeValueAssertion)
                                                         xmlElement.getValue());
    }
    else if ( "extensibleMatch".equals(filterName) ) {
      // <xsd:element name="extensibleMatch" type="MatchingRuleAssertion"/>
      result = createExtensibleFilter((MatchingRuleAssertion)
                                                         xmlElement.getValue());
    }
    return result;
  }
  /**
   * Returns a new LDAPFilter according to the filter assigned to the provided
   * filter.
   *
   * @param filter a filter that contains the object filter to create.
   *
   * @return a new LDAPFilter according to the filter assigned to the provided
   *         filter.
   *
   * @throws LDAPException an LDAPException is thrown if the creation of the
   *                       targeted filter fails.
   */
  private static LDAPFilter createFilter(Filter filter)
          throws LDAPException {
    LDAPFilter result = null;
    if ( filter.getAnd() != null ) {
      result = createANDFilter(filter.getAnd());
    }
    else if ( filter.getApproxMatch() != null ) {
      result = createApproximateFilter(filter.getApproxMatch());
    }
    else if ( filter.getEqualityMatch() != null ) {
      result = createEqualityFilter(filter.getEqualityMatch());
    }
    else if ( filter.getExtensibleMatch() != null ) {
      result = createExtensibleFilter(filter.getExtensibleMatch());
    }
    else if ( filter.getGreaterOrEqual() != null ) {
      result = createGreaterOrEqualFilter(filter.getGreaterOrEqual());
    }
    else if ( filter.getLessOrEqual() != null ) {
      result = createLessOrEqualFilter(filter.getLessOrEqual());
    }
    else if ( filter.getNot() != null ) {
      result = createNOTFilter(filter.getNot());
    }
    else if ( filter.getOr() != null ) {
      result = createORFilter(filter.getOr());
    }
    else if ( filter.getPresent() != null ) {
      result = createPresentFilter(filter.getPresent());
    }
    else if ( filter.getSubstrings() != null ) {
      result = createSubstringFilter(filter.getSubstrings());
    }
    return result;
  }
  /**
   * Perform the LDAP SEARCH operation and send the result back to the
@@ -96,24 +372,8 @@
    SearchResponse searchResponse = objFactory.createSearchResponse();
    searchResponse.setRequestID(searchRequest.getRequestID());
    ArrayList<LDAPFilter> filters = new ArrayList<LDAPFilter> ();
    LDAPFilter f = null;
    if(searchRequest.getFilter().getPresent() != null)
    {
      f = LDAPFilter.decode(searchRequest.getFilter().getPresent().getName() +
          "=*");
    } else if(searchRequest.getFilter().getEqualityMatch() != null)
    {
      AttributeValueAssertion fgem =
           searchRequest.getFilter().getEqualityMatch();
    LDAPFilter filter = createFilter(searchRequest.getFilter());
      f = LDAPFilter.createEqualityFilter(fgem.getName(),
      new ASN1OctetString(fgem.getValue()));
    }
    if(f != null)
    {
      filters.add(f);
    }
    DereferencePolicy derefPolicy = DereferencePolicy.NEVER_DEREF_ALIASES;
    String derefStr = searchRequest.getDerefAliases().toLowerCase();
    if (derefStr.equals("derefinsearching"))
@@ -151,113 +411,91 @@
      }
    }
    for (LDAPFilter filter: filters)
    SearchRequestProtocolOp protocolOp = new SearchRequestProtocolOp(
        new ASN1OctetString(searchRequest.getDn()),
        scope, derefPolicy,
                (int) searchRequest.getSizeLimit(),
        (int) searchRequest.getTimeLimit(),
        searchRequest.isTypesOnly(), filter, attributes);
    try
    {
      LDAPMessage msg = new LDAPMessage(DSMLServlet.nextMessageID(),
                                        protocolOp);
      connection.getLDAPWriter().writeMessage(msg);
      SearchRequestProtocolOp protocolOp = new SearchRequestProtocolOp(
          new ASN1OctetString(searchRequest.getDn()),
          scope, derefPolicy,
                  (int) searchRequest.getSizeLimit(),
          (int) searchRequest.getTimeLimit(),
          false, filter, attributes);
      try
      byte opType;
      do
      {
        LDAPMessage msg = new LDAPMessage(DSMLServlet.nextMessageID(),
                                          protocolOp);
        connection.getLDAPWriter().writeMessage(msg);
        int resultCode = 0;
        Message errorMessage = null;
        LDAPMessage responseMessage =
             connection.getLDAPReader().readMessage();
        byte opType;
        do
        opType = responseMessage.getProtocolOpType();
        switch(opType)
        {
          int resultCode = 0;
          Message errorMessage = null;
          LDAPMessage responseMessage =
               connection.getLDAPReader().readMessage();
          case LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY:
            SearchResultEntryProtocolOp searchEntryOp =
              responseMessage.getSearchResultEntryProtocolOp();
          opType = responseMessage.getProtocolOpType();
          switch(opType)
          {
            case LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY:
              SearchResultEntryProtocolOp searchEntryOp =
                responseMessage.getSearchResultEntryProtocolOp();
            SearchResultEntry entry = objFactory.createSearchResultEntry();
            java.util.List<DsmlAttr> attrList = entry.getAttr();
        SearchResultEntry entry = objFactory.createSearchResultEntry();
        java.util.List<DsmlAttr> attrList = entry.getAttr();
            LinkedList<LDAPAttribute> attrs = searchEntryOp.getAttributes();
        LinkedList<LDAPAttribute> attrs = searchEntryOp.getAttributes();
            for(LDAPAttribute attr : attrs)
            {
              String nm = attr.getAttributeType();
              DsmlAttr dsmlAttr = objFactory.createDsmlAttr();
        for(LDAPAttribute attr : attrs)
        {
          String nm = attr.getAttributeType();
          DsmlAttr dsmlAttr = objFactory.createDsmlAttr();
          dsmlAttr.setName(nm);
          List<String> dsmlAttrVal = dsmlAttr.getValue();
          ArrayList<ASN1OctetString> vals = attr.getValues();
          for(ASN1OctetString val : vals)
          {
            dsmlAttrVal.add(val.toString());
          }
          attrList.add(dsmlAttr);
        }
        entry.setDn(searchEntryOp.getDN().toString());
        searchResponse.getSearchResultEntry().add(entry);
        /*
              StringBuilder sb = new StringBuilder();
              searchEntryOp.toLDIF(sb, 80);
              System.out.println(sb.toString());
        */
              break;
            case LDAPConstants.OP_TYPE_SEARCH_RESULT_REFERENCE:
              SearchResultReferenceProtocolOp searchRefOp =
                responseMessage.getSearchResultReferenceProtocolOp();
              System.out.println(searchRefOp.toString());
              break;
            case LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE:
              SearchResultDoneProtocolOp searchOp =
                responseMessage.getSearchResultDoneProtocolOp();
              resultCode = searchOp.getResultCode();
              errorMessage = searchOp.getErrorMessage();
              LDAPResult result = objFactory.createLDAPResult();
              ResultCode code = objFactory.createResultCode();
              code.setCode(resultCode);
              result.setResultCode(code);
              result.setErrorMessage(
                      errorMessage != null ? errorMessage.toString() : null);
              if(searchOp.getMatchedDN() != null)
              dsmlAttr.setName(nm);
              List<String> dsmlAttrVal = dsmlAttr.getValue();
              ArrayList<ASN1OctetString> vals = attr.getValues();
              for(ASN1OctetString val : vals)
              {
                 result.setMatchedDN(searchOp.getMatchedDN().toString());
                dsmlAttrVal.add(val.toString());
              }
              searchResponse.setSearchResultDone(result);
              break;
            default:
               // FIXME - throw exception
               System.err.println("Invalid protocol operation:" + opType);
               break;
           }
              attrList.add(dsmlAttr);
            }
           if(resultCode != 0 && resultCode != 10)
           {
             org.opends.server.types.ResultCode rc =
                  org.opends.server.types.ResultCode.valueOf(resultCode);
            entry.setDn(searchEntryOp.getDN().toString());
            searchResponse.getSearchResultEntry().add(entry);
            break;
             // TODO:  FIXME - null message
             throw new LDAPException(resultCode, null, rc.getResultCodeName());
           }
          case LDAPConstants.OP_TYPE_SEARCH_RESULT_REFERENCE:
            SearchResultReferenceProtocolOp searchRefOp =
              responseMessage.getSearchResultReferenceProtocolOp();
            break;
        } while(opType != LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE);
          case LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE:
            SearchResultDoneProtocolOp searchOp =
              responseMessage.getSearchResultDoneProtocolOp();
            resultCode = searchOp.getResultCode();
            errorMessage = searchOp.getErrorMessage();
            LDAPResult result = objFactory.createLDAPResult();
            ResultCode code = objFactory.createResultCode();
            code.setCode(resultCode);
            result.setResultCode(code);
            result.setErrorMessage(
                    errorMessage != null ? errorMessage.toString() : null);
            if(searchOp.getMatchedDN() != null)
            {
               result.setMatchedDN(searchOp.getMatchedDN().toString());
            }
            searchResponse.setSearchResultDone(result);
            break;
          default:
             throw new RuntimeException("Invalid protocol operation:" + opType);
         }
      } while(opType != LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE);
      } catch(ASN1Exception ae)
      {
        ae.printStackTrace();
        throw new IOException(ae.getMessage());
      }
    } catch(ASN1Exception ae)
    {
      ae.printStackTrace();
      throw new IOException(ae.getMessage());
    }
    return searchResponse;
  }
}
opends/src/dsml/org/opends/dsml/protocol/DSMLServlet.java
@@ -27,7 +27,14 @@
package org.opends.dsml.protocol;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.text.ParseException;
import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI;
import javax.xml.bind.JAXBException;
import org.opends.messages.Message;
import org.opends.server.core.DirectoryServer;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.tools.LDAPConnection;
import org.opends.server.tools.LDAPConnectionOptions;
import org.opends.server.util.Base64;
@@ -47,11 +54,21 @@
import javax.xml.soap.*;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicInteger;
import javax.xml.validation.SchemaFactory;
import org.opends.server.tools.LDAPConnectionException;
import org.opends.server.types.LDAPException;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;
/**
@@ -67,15 +84,33 @@
  private static final long serialVersionUID = -3748022009593442973L;
  private static final AtomicInteger nextMessageID = new AtomicInteger(1);
  // definitions of return error messages
  private static final String MALFORMED_REQUEST = "malformedRequest";
  private static final String NOT_ATTEMPTED = "notAttempted";
  private static final String AUTHENTICATION_FAILED = "authenticationFailed";
  private static final String COULD_NOT_CONNECT = "couldNotConnect";
  private static final String GATEWAY_INTERNAL_ERROR = "gatewayInternalError";
  private static final String UNKNOWN_ERROR = "Unknown error";
  // definitions of onError values
  private static final String ON_ERROR_RESUME = "resume";
  private static final String ON_ERROR_EXIT = "exit";
  private Unmarshaller unmarshaller;
  private Marshaller marshaller;
  private ObjectFactory objFactory;
  private MessageFactory messageFactory;
  private DocumentBuilder db;
  // this extends the default handler of SAX parser. It helps to retrieve the
  // requestID value when the xml request is malformed and thus unparsable
  // using SOAP or JAXB.
  private DSMLContentHandler contentHandler;
  private String hostName;
  private Integer port;
  /**
   * This method will be called by the Servlet Container when
   * this servlet is being placed into service.
@@ -85,19 +120,22 @@
   * @throws ServletException If an error occurs during processing.
   */
  public void init(ServletConfig config) throws ServletException {
    try {
      hostName = config.getServletContext().getInitParameter(HOST);
      port = new Integer(config.getServletContext().getInitParameter(PORT));
      JAXBContext jaxbContext = JAXBContext.newInstance(PKG_NAME);
      unmarshaller = jaxbContext.createUnmarshaller();
      // assign the DSMLv2 schema for validation
      URL schema = getClass().getResource("/resources/DSMLv2.xsd");
      if ( schema != null ) {
        SchemaFactory sf = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI);
        unmarshaller.setSchema(sf.newSchema(schema));
      }
      marshaller = jaxbContext.createMarshaller();
      marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper",
        new NamespacePrefixMapperImpl());
      objFactory = new ObjectFactory();
      messageFactory = MessageFactory.newInstance();
@@ -105,29 +143,15 @@
      dbf.setNamespaceAware(true);
      db = dbf.newDocumentBuilder();
      DirectoryServer.bootstrapClient();
      this.contentHandler = new DSMLContentHandler();
      DirectoryServer.bootstrapClient();
    } catch (Exception je) {
      je.printStackTrace();
      throw new ServletException(je.getMessage());
    }
  }
  /**
   * The HTTP GET operation.
   *
   * @param req Information about the request received from the client.
   * @param res Information about the response to send to the client.
   * @throws ServletException If an error occurs during servlet processing.
   * @throws IOException      If an error occurs while interacting with the client.
   */
  public void doGet(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException {
    super.doGet(req, res);
  }
  /**
   * The HTTP POST operation. This servlet expects a SOAP message
   * with a DSML request payload.
@@ -135,222 +159,370 @@
   * @param req Information about the request received from the client.
   * @param res Information about the response to send to the client.
   * @throws ServletException If an error occurs during servlet processing.
   * @throws IOException      If an error occurs while interacting with the client.
   * @throws IOException   If an error occurs while interacting with the client.
   */
  public void doPost(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException {
    SOAPMessage reply;
  throws ServletException, IOException {
    LDAPConnectionOptions connOptions = new LDAPConnectionOptions();
    LDAPConnection connection = null;
    BatchRequest batchRequest = null;
    // Keep the Servlet input stream buffered in case the SOAP unmarshalling
    // fails, the SAX parsing will be able to retrieve the requestID even if
    // the XML is malmformed by resetting the input stream.
    BufferedInputStream is = new BufferedInputStream(req.getInputStream(),
                                                     65536);
    if ( is.markSupported() ) {
      is.mark(65536);
    }
    try {
      MimeHeaders mimeHeaders = new MimeHeaders();
      Enumeration en = req.getHeaderNames();
      String bindDN = "";
      String bindPassword = "";
      while (en.hasMoreElements()) {
        String headerName = (String) en.nextElement();
        String headerVal = req.getHeader(headerName);
        if (headerName.equalsIgnoreCase("authorization")) {
          if (headerVal.startsWith("Basic ")) {
            String authorization = headerVal.substring(6).trim();
            // Decode and parse the authorization credentials
            String unencoded =
              new String(Base64.decode(authorization));
    // Create response in the beginning as it might be used if the parsing
    // failes.
    BatchResponse batchResponse = objFactory.createBatchResponse();
    List<JAXBElement<?>> batchResponses = batchResponse.getBatchResponses();
    Document doc = db.newDocument();
    SOAPBody soapBody = null;
    MimeHeaders mimeHeaders = new MimeHeaders();
    Enumeration en = req.getHeaderNames();
    String bindDN = null;
    String bindPassword = null;
    boolean authorizationInHeader = false;
    while (en.hasMoreElements()) {
      String headerName = (String) en.nextElement();
      String headerVal = req.getHeader(headerName);
      if (headerName.equalsIgnoreCase("authorization")) {
        if (headerVal.startsWith("Basic ")) {
          authorizationInHeader = true;
          String authorization = headerVal.substring(6).trim();
          try {
            String unencoded = new String(Base64.decode(authorization));
            int colon = unencoded.indexOf(':');
            if (colon < 0)
              continue;
            bindDN = unencoded.substring(0, colon).trim();
            bindPassword = unencoded.substring(colon + 1);
            if (colon > 0) {
              bindDN = unencoded.substring(0, colon).trim();
              bindPassword = unencoded.substring(colon + 1);
            }
          } catch (ParseException ex) {
            // DN:password parsing error
            batchResponses.add(
              createErrorResponse(
                    new LDAPException(LDAPResultCode.INVALID_CREDENTIALS,
                    Message.raw(ex.getMessage()))));
            break;
          }
        }
        StringTokenizer tk = new StringTokenizer(headerVal, ",");
        while (tk.hasMoreTokens()) {
          mimeHeaders.addHeader(headerName, tk.nextToken().trim());
        }
      }
      StringTokenizer tk = new StringTokenizer(headerVal, ",");
      while (tk.hasMoreTokens()) {
        mimeHeaders.addHeader(headerName, tk.nextToken().trim());
      }
    }
      SOAPMessage message =
        messageFactory.createMessage(mimeHeaders, req.getInputStream());
      message.writeTo(System.out);
    if ( ! authorizationInHeader ) {
      // if no authorization, set default user
      bindDN = "";
      bindPassword = "";
    } else {
      // otherwise if DN or password is null, send back an error
      if ( (bindDN == null || bindPassword == null)
         && batchResponses.size()==0) {
        batchResponses.add(
              createErrorResponse(
                    new LDAPException(LDAPResultCode.INVALID_CREDENTIALS,
                    Message.raw("Unable to retrieve credentials."))));
      }
    }
      Document doc = db.newDocument();
      SOAPBody body = message.getSOAPBody();
    // if an error already occured, the list is not empty
    if ( batchResponses.size() == 0 ) {
      try {
        SOAPMessage message = messageFactory.createMessage(mimeHeaders, is);
        soapBody = message.getSOAPBody();
      } catch (SOAPException ex) {
        // SOAP was unable to parse XML successfully
        batchResponses.add(
          createXMLParsingErrorResponse(is,
                                        batchResponse,
                                        String.valueOf(ex.getCause())));
      }
    }
      Iterator it = body.getChildElements();
    if ( soapBody != null ) {
      Iterator it = soapBody.getChildElements();
      while (it.hasNext()) {
        Object obj = it.next();
        if (!(obj instanceof SOAPElement)) {
          continue;
        }
        SOAPElement se = (SOAPElement) obj;
        JAXBElement<BatchRequest> batchRequestElement =
          unmarshaller.unmarshal(se, BatchRequest.class);
        BatchRequest batchRequest = batchRequestElement.getValue();
        BatchResponse batchResponse = objFactory.createBatchResponse();
        List<JAXBElement<?>> batchResponses = batchResponse.getBatchResponses();
        List<DsmlMessage> list = batchRequest.getBatchRequests();
        for (DsmlMessage nextElement : list) {
          if (nextElement instanceof SearchRequest) {
            // Process the search request.
        JAXBElement<BatchRequest> batchRequestElement = null;
        try {
          batchRequestElement = unmarshaller.unmarshal(se, BatchRequest.class);
        } catch (JAXBException e) {
          // schema validation failed
          batchResponses.add(createXMLParsingErrorResponse(is,
                                                       batchResponse,
                                                       String.valueOf(e)));
        }
        if ( batchRequestElement != null ) {
          batchRequest = batchRequestElement.getValue();
          // set requestID in response
          batchResponse.setRequestID(batchRequest.getRequestID());
          boolean connected = false;
          if ( connection == null ) {
            connection = new LDAPConnection(hostName, port, connOptions);
            connection.connectToHost(bindDN, bindPassword);
            SearchRequest sr = (SearchRequest) nextElement;
            DSMLSearchOperation ds = new DSMLSearchOperation(connection);
            SearchResponse searchResponse = ds.doSearch(objFactory, sr);
            JAXBElement<SearchResponse> searchResponseEl =
              objFactory.createBatchResponseSearchResponse(searchResponse);
            batchResponses.add(searchResponseEl);
          } else if (nextElement instanceof AddRequest) {
            // Process the add request.
            connection = new LDAPConnection(hostName, port, connOptions);
            connection.connectToHost(bindDN, bindPassword);
            AddRequest ar = (AddRequest) nextElement;
            DSMLAddOperation addOp = new DSMLAddOperation(connection);
            LDAPResult addResponse = addOp.doOperation(objFactory, ar);
            JAXBElement<LDAPResult> addResponseEl =
              objFactory.createBatchResponseAddResponse(addResponse);
            batchResponses.add(addResponseEl);
          } else if (nextElement instanceof AbandonRequest) {
            // Process the abandon request.
            connection = new LDAPConnection(hostName, port, connOptions);
            connection.connectToHost(bindDN, bindPassword);
            AbandonRequest ar = (AbandonRequest) nextElement;
            DSMLAbandonOperation ao = new DSMLAbandonOperation(connection);
            LDAPResult abandonResponse = ao.doOperation(objFactory, ar);
          } else if (nextElement instanceof ExtendedRequest) {
            // Process the extended request.
            connection = new LDAPConnection(hostName, port, connOptions);
            connection.connectToHost(bindDN, bindPassword);
            ExtendedRequest er = (ExtendedRequest) nextElement;
            DSMLExtendedOperation eo = new DSMLExtendedOperation(connection);
            ExtendedResponse extendedResponse = eo.doOperation(objFactory, er);
            JAXBElement<ExtendedResponse> extendedResponseEl =
              objFactory.createBatchResponseExtendedResponse(extendedResponse);
            batchResponses.add(extendedResponseEl);
          } else if (nextElement instanceof DelRequest) {
            // Process the delete request.
            connection = new LDAPConnection(hostName, port, connOptions);
            connection.connectToHost(bindDN, bindPassword);
            DelRequest dr = (DelRequest) nextElement;
            DSMLDeleteOperation delOp = new DSMLDeleteOperation(connection);
            LDAPResult delResponse = delOp.doOperation(objFactory, dr);
            JAXBElement<LDAPResult> delResponseEl =
              objFactory.createBatchResponseDelResponse(delResponse);
            batchResponses.add(delResponseEl);
          } else if (nextElement instanceof CompareRequest) {
            // Process the compare request.
            connection = new LDAPConnection(hostName, port, connOptions);
            connection.connectToHost(bindDN, bindPassword);
            CompareRequest cr = (CompareRequest) nextElement;
            DSMLCompareOperation compareOp =
              new DSMLCompareOperation(connection);
            LDAPResult compareResponse = compareOp.doOperation(objFactory, cr);
            JAXBElement<LDAPResult> compareResponseEl =
              objFactory.createBatchResponseCompareResponse(compareResponse);
            batchResponses.add(compareResponseEl);
          } else if (nextElement instanceof ModifyDNRequest) {
            // Process the Modify DN request.
            connection = new LDAPConnection(hostName, port, connOptions);
            connection.connectToHost(bindDN, bindPassword);
            ModifyDNRequest mr = (ModifyDNRequest) nextElement;
            DSMLModifyDNOperation moddnOp =
              new DSMLModifyDNOperation(connection);
            LDAPResult moddnResponse = moddnOp.doOperation(objFactory, mr);
            JAXBElement<LDAPResult> moddnResponseEl =
              objFactory.createBatchResponseModDNResponse(moddnResponse);
            batchResponses.add(moddnResponseEl);
          } else if (nextElement instanceof ModifyRequest) {
            // Process the Modify request.
            connection = new LDAPConnection(hostName, port, connOptions);
            connection.connectToHost(bindDN, bindPassword);
            ModifyRequest modr = (ModifyRequest) nextElement;
            DSMLModifyOperation modOp = new DSMLModifyOperation(connection);
            LDAPResult modResponse = modOp.doOperation(objFactory, modr);
            JAXBElement<LDAPResult> modResponseEl =
              objFactory.createBatchResponseModifyResponse(modResponse);
            batchResponses.add(modResponseEl);
          } else {
            String msg = "Invalid DSML request:" + nextElement;
            throw new IOException(msg);
            try {
              connection.connectToHost(bindDN, bindPassword);
              connected = true;
            } catch (LDAPConnectionException e) {
              // if connection failed, return appropriate error response
              batchResponses.add(createErrorResponse(e));
            }
          }
          if ( connected ) {
            List<DsmlMessage> list = batchRequest.getBatchRequests();
            for (DsmlMessage request : list) {
              JAXBElement<?> result = performLDAPRequest(connection, request);
              if ( result != null ) {
                batchResponses.add(result);
              }
              // evaluate response to check if an error occured
              Object o = result.getValue();
              if ( o instanceof ErrorResponse ) {
                if ( ON_ERROR_EXIT.equals(batchRequest.getOnError()) ) {
                  break;
                }
              } else if ( o instanceof LDAPResult ) {
                int code = ((LDAPResult)o).getResultCode().getCode();
                if ( code != LDAPResultCode.SUCCESS
                  && code != LDAPResultCode.REFERRAL
                  && code != LDAPResultCode.COMPARE_TRUE
                  && code != LDAPResultCode.COMPARE_FALSE ) {
                  if ( ON_ERROR_EXIT.equals(batchRequest.getOnError()) ) {
                    break;
                  }
                }
              }
            }
          }
          // close connection to LDAP server
          if ( connection != null ) {
            connection.close(nextMessageID);
          }
        }
        JAXBElement<BatchResponse> batchResponseElement =
          objFactory.createBatchResponse(batchResponse);
        marshaller.marshal(batchResponseElement, System.out);
        marshaller.marshal(batchResponseElement, doc);
      }
      // Send the DSML response back to the client.
      reply = messageFactory.createMessage();
      sendResponse(doc, false, reply, res, null);
    } catch (Exception se) {
      se.printStackTrace();
      // send SOAP fault
      try {
        reply = messageFactory.createMessage();
        sendResponse(null, true, reply, res, se);
      } catch (Exception e) {
      }
    } finally {
      if (connection != null) {
        connection.close(nextMessageID);
      }
    }
    try {
      marshaller.marshal(objFactory.createBatchResponse(batchResponse), doc);
      sendResponse(doc, res);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
  /**
   * Returns an error response after a parsing error. The response has the
   * requestID of the batch request, the error response message of the parsing
   * exception message and the type 'malformed request'.
   *
   * @param is the xml InputStream to parse
   * @param batchResponse the JAXB object to fill in
   * @param parserErrorMessage the parsing error message
   *
   * @return a JAXBElement that contains an ErrorResponse
   */
  private JAXBElement<ErrorResponse> createXMLParsingErrorResponse(
                                                    InputStream is,
                                                    BatchResponse batchResponse,
                                                    String parserErrorMessage) {
    ErrorResponse errorResponse = objFactory.createErrorResponse();
    try {
      // try alternative XML parsing using SAX to retrieve requestID value
      XMLReader xmlReader = XMLReaderFactory.createXMLReader();
      // clear previous match
      this.contentHandler.requestID = null;
      xmlReader.setContentHandler(this.contentHandler);
      is.reset();
      xmlReader.parse(new InputSource(is));
    } catch (Throwable e) {
      // document is unparsable so will jump here
    }
    if ( parserErrorMessage!= null ) {
      errorResponse.setMessage(parserErrorMessage);
    }
    batchResponse.setRequestID(this.contentHandler.requestID);
    errorResponse.setType(MALFORMED_REQUEST);
    return objFactory.createBatchResponseErrorResponse(errorResponse);
  }
  /**
   * Returns an error response with attributes set according to the exception
   * provided as argument.
   *
   * @param t the exception that occured
   *
   * @return a JAXBElement that contains an ErrorResponse
   */
  private JAXBElement<ErrorResponse> createErrorResponse(Throwable t) {
    // potential exceptions are IOException, LDAPException, ASN1Exception
    ErrorResponse errorResponse = objFactory.createErrorResponse();
    errorResponse.setMessage(String.valueOf(t));
    if ( t instanceof LDAPException ) {
      switch(((LDAPException)t).getResultCode()) {
        case LDAPResultCode.AUTHORIZATION_DENIED:
        case LDAPResultCode.INAPPROPRIATE_AUTHENTICATION:
        case LDAPResultCode.INVALID_CREDENTIALS:
        case LDAPResultCode.STRONG_AUTH_REQUIRED:
          errorResponse.setType(AUTHENTICATION_FAILED);
          break;
        case LDAPResultCode.CLIENT_SIDE_CONNECT_ERROR:
          errorResponse.setType(COULD_NOT_CONNECT);
          break;
        case LDAPResultCode.UNWILLING_TO_PERFORM:
          errorResponse.setType(NOT_ATTEMPTED);
          break;
        default:
          errorResponse.setType(UNKNOWN_ERROR);
          break;
      }
    } else if ( t instanceof LDAPConnectionException ) {
      errorResponse.setType(COULD_NOT_CONNECT);
    } else {
      errorResponse.setType(GATEWAY_INTERNAL_ERROR);
    }
    return objFactory.createBatchResponseErrorResponse(errorResponse);
  }
  /**
   * Performs the LDAP operation and sends back the result (if any). In case
   * of error, an error reponse is returned.
   *
   * @param connection a connected connection
   * @param request the JAXB request to perform
   *
   * @return null for an abandon request, the expect result for all other
   *         requests or an error in case of unexpected behaviour.
   */
  private JAXBElement<?> performLDAPRequest(LDAPConnection connection,
                                            DsmlMessage request) {
    try {
      if (request instanceof SearchRequest) {
        // Process the search request.
        SearchRequest sr = (SearchRequest) request;
        DSMLSearchOperation ds = new DSMLSearchOperation(connection);
        SearchResponse searchResponse = ds.doSearch(objFactory, sr);
        return objFactory.createBatchResponseSearchResponse(searchResponse);
      } else if (request instanceof AddRequest) {
        // Process the add request.
        AddRequest ar = (AddRequest) request;
        DSMLAddOperation addOp = new DSMLAddOperation(connection);
        LDAPResult addResponse = addOp.doOperation(objFactory, ar);
        return objFactory.createBatchResponseAddResponse(addResponse);
      } else if (request instanceof AbandonRequest) {
        // Process the abandon request.
        AbandonRequest ar = (AbandonRequest) request;
        DSMLAbandonOperation ao = new DSMLAbandonOperation(connection);
        LDAPResult abandonResponse = ao.doOperation(objFactory, ar);
        return null;
      } else if (request instanceof ExtendedRequest) {
        // Process the extended request.
        ExtendedRequest er = (ExtendedRequest) request;
        DSMLExtendedOperation eo = new DSMLExtendedOperation(connection);
        ExtendedResponse extendedResponse = eo.doOperation(objFactory, er);
        return objFactory.createBatchResponseExtendedResponse(extendedResponse);
      } else if (request instanceof DelRequest) {
        // Process the delete request.
        DelRequest dr = (DelRequest) request;
        DSMLDeleteOperation delOp = new DSMLDeleteOperation(connection);
        LDAPResult delResponse = delOp.doOperation(objFactory, dr);
        return objFactory.createBatchResponseDelResponse(delResponse);
      } else if (request instanceof CompareRequest) {
        // Process the compare request.
        CompareRequest cr = (CompareRequest) request;
        DSMLCompareOperation compareOp =
                new DSMLCompareOperation(connection);
        LDAPResult compareResponse = compareOp.doOperation(objFactory, cr);
        return objFactory.createBatchResponseCompareResponse(compareResponse);
      } else if (request instanceof ModifyDNRequest) {
        // Process the Modify DN request.
        ModifyDNRequest mr = (ModifyDNRequest) request;
        DSMLModifyDNOperation moddnOp =
                new DSMLModifyDNOperation(connection);
        LDAPResult moddnResponse = moddnOp.doOperation(objFactory, mr);
        return objFactory.createBatchResponseModDNResponse(moddnResponse);
      } else if (request instanceof ModifyRequest) {
        // Process the Modify request.
        ModifyRequest modr = (ModifyRequest) request;
        DSMLModifyOperation modOp = new DSMLModifyOperation(connection);
        LDAPResult modResponse = modOp.doOperation(objFactory, modr);
        return objFactory.createBatchResponseModifyResponse(modResponse);
      } else if (request instanceof AuthRequest) {
        // Process the Auth request.
        // Only returns an BatchReponse with an AuthResponse containing the
        // LDAP result code AUTH_METHOD_NOT_SUPPORTED
        ResultCode resultCode = objFactory.createResultCode();
        resultCode.setCode(LDAPResultCode.AUTH_METHOD_NOT_SUPPORTED);
        LDAPResult ldapResult = objFactory.createLDAPResult();
        ldapResult.setResultCode(resultCode);
        return objFactory.createBatchResponseAuthResponse(ldapResult);
      }
    } catch (Throwable t) {
      return createErrorResponse(t);
    }
    // should never happen as the schema was validated
    return null;
  }
  /**
   * Send a response back to the client. This could be either a SOAP fault
   * or a correct DSML response.
   *
   * @param doc   The document to include in the response.
   * @param error Indicates whether an error occurred.
   * @param reply The reply to send to the client.
   * @param res   Information about the HTTP response to the client.
   * @param e     Information about any exception that was thrown.
   *
   * @throws IOException   If an error occurs while interacting with the client.
   * @throws SOAPException If an encoding or decoding error occurs.
   */
  private void sendResponse(Document doc, boolean error, SOAPMessage reply,
                            HttpServletResponse res, Exception e)
  private void sendResponse(Document doc, HttpServletResponse res)
    throws IOException, SOAPException {
    SOAPMessage reply = messageFactory.createMessage();
    SOAPHeader header = reply.getSOAPHeader();
    header.detachNode();
    SOAPBody replyBody = reply.getSOAPBody();
    res.setHeader("Content-Type", "text/xml");
    if (error) {
      SOAPFault fault = replyBody.addFault();
      Name faultName = SOAPFactory.newInstance().createName("Server",
        "", SOAPConstants.URI_NS_SOAP_ENVELOPE);
      fault.setFaultCode(faultName);
      fault.setFaultString("Server Error: " + e.getMessage());
      // FIXME - Set correct fault actor
      fault.setFaultActor("http://localhost:8080");
    } else {
      SOAPElement bodyElement = replyBody.addDocument(doc);
    }
    SOAPElement bodyElement = replyBody.addDocument(doc);
    reply.saveChanges();
    OutputStream os = res.getOutputStream();
    reply.writeTo(os);
    os.flush();
  }
  /**
   * Retrieves a message ID that may be used for the next LDAP message sent to
   * the Directory Server.
@@ -358,15 +530,32 @@
   * @return  A message ID that may be used for the next LDAP message sent to
   *          the Directory Server.
   */
  public static int nextMessageID()
  {
  public static int nextMessageID() {
    int nextID = nextMessageID.getAndIncrement();
    if (nextID == Integer.MAX_VALUE)
    {
    if (nextID == Integer.MAX_VALUE) {
      nextMessageID.set(1);
    }
    return nextID;
  }
  /**
   * This class is used when a xml request is malformed to retrieve the
   * requestID value using an event xml parser.
   */
  private static class DSMLContentHandler extends DefaultHandler {
    private String requestID;
    /*
     * This function fetches the requestID value of the batchRequest xml
     * element and call the default implementation (super).
     */
    public void startElement(String uri, String localName, String qName,
                             Attributes attributes) throws SAXException {
      if ( requestID==null && localName.equals("batchRequest") ) {
        requestID = attributes.getValue("requestID");
      }
      super.startElement(uri, localName, qName, attributes);
    }
  }
}