Sample RPC-Style Customizations

In the following code sample, a SOAP message is parsed to extract the username and password credentials, these values are applied to the MessageContext class for the invocation (and in the case of WebSphere, a login is performed), and control is returned to the superclass. Also a locale is specified for the call by setting property locale in the MessageContext object. The code sample is followed by an example of a SOAP message which would be processed by the code.

package webservice;

import curam.util.connectors.webservice.CuramEJBMethodProvider;
import curam.util.resources.Configuration;
import curam.util.resources.EnvironmentConstants;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.security.PrivilegedAction;
import java.util.Iterator;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginContext;
import javax.xml.soap.Name;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import org.apache.axis.AxisFault;
import org.apache.axis.Message;
import org.apache.axis.MessageContext;
import org.apache.axis.message.SOAPEnvelope;
import org.apache.axis.providers.java.EJBProvider;


/**
 * A web services hook which extends the Axis EJB provider to
 * enable the developer to access the SOAP message. In this case
 * it takes the username and password from the SOAP header and
 * sets them in the method call.
 *
 */
public class TestmodelProvider extends CuramEJBMethodProvider {

  /** The name of an XML attribute in a multi ref element. */
  private static final String kNameOfIdAttribute = "id";

  /** The name of an XML element in the SOAP body. */
  private static final String kMultiRefElementName = "multiRef";

  /**
   * The name of the attribute containing a `href` to another
   * element.
   */
  private static final String kHrefAttributeName = "href";

  /**
   * The name of the header element as defined in the WSDL file.
   */
  private static final String kNameOfHeaderElement = "inHeader";

  /** The name of the element containing the user name field. */
  private static final String kUsernameFieldName = "userName";

  /** The name of the element containing the password field. */
  private static final String kPasswordFieldName = "password";

  /** Cached Do As Method instance. */
  private static Method stSubjectDoAsMethod;

  /**
   * Hook which gets the credentials from the header of the soap
   * message and sets them in the message context for the call
   * before delegating back to the superclass method.
   *
   * @param msgContext The message context for the call.
   *
   * @throws AxisFault Generic Axis exception.
   */
  public void invoke(final MessageContext msgContext)
  throws AxisFault {

    final Message requestMessage = msgContext.getRequestMessage();
    final SOAPEnvelope envelope =
      requestMessage.getSOAPEnvelope();
    final SOAPElement element =
      getSoapElement(envelope, kNameOfHeaderElement);

    String userName = null;
    String password = null;
    try {
      // Get parameters from the SOAP header and set them in the
      // message context for the call.
      userName = getSubElementValue(element, envelope.createName(
        kUsernameFieldName));
      password = getSubElementValue(element, envelope.createName(
        kPasswordFieldName));

      // Check the soundness of our SOAP header processing before
      // we attempt to use the data for real. Otherwise bad or
      // missing data in these variables will simply manifest
      // itself misleadingly as a security configuration problem.
      if ((userName == null) || (userName.length() == 0)
        || (password == null) || (password.length() == 0)) {

        final AxisFault e = new AxisFault(
          "Bad username/password in SOAP" + " header '" + userName
          + "'/'" + password + "'");
        throw e;
      }

      msgContext.setUsername(userName);
      msgContext.setPassword(password);

      // Specify an absolute locale for the invocation:
      final String localeFrenchCanada = "fr_CA";
      msgContext.setProperty("locale", localeFrenchCanada);

    } catch (SOAPException e) {
      throw new AxisFault(e.getMessage(), e);
    }


    if (isRunningInWebSphere()) {
      // A WebSphere limitation means that it will not
      // automatically see the credentials we have just set, we
      // must also perform our login here.
      try {
        final Method doAsMethod = getDoAsMethod();
        final LoginContext loginContext =
          getLoginContext(userName, password);
        loginContext.login();
        final Subject subject = loginContext.getSubject();

        // Create a privileged action class which includes all the
        // information about this call.
        final PrivilegedAction action =
          new ProviderPrivilegedAction(this, msgContext);
        final Object[] parameterValues = {subject, action};

        // invoke the rest of the call under the new credentials.
        final Object axisFault =
          doAsMethod.invoke(null, parameterValues);

        // Exceptions cannot be thrown from the above invocation,
        // they are returned instead. If one was returned then
        // throw it now.
        if (axisFault != null) {
          throw new AxisFault("" + axisFault,
            (Exception) axisFault);
        }

      } catch (Exception e) {
        throw new AxisFault(e.getMessage(), e);
      }
    } else {
      // Not in WebSphere. Simply delegate straight through.
      super.invoke(msgContext);
    }

  }

  /**
   * An accessor for the invoke method in the superclass. Required
   * because it must be invoked by another class - the inner class
   * within this one.
   *
   * @param msgContext The message context object for this call.
   *
   * @throws AxisFault Generic Axis fault handler.
   */
  private void superInvoke(final MessageContext msgContext)
  throws AxisFault {

    super.invoke(msgContext);
  }

  /**
   * Indicates whether we are running within WebSphere in which
   * case we must delegate the rest of the call as a privileged
   * action.
   *
   * @return True if running under WebSphere, false otherwise.
   */
  private boolean isRunningInWebSphere() {
    final String vendorName = System.getProperty("java.vendor");
    return vendorName.startsWith("IBM");
  }

  /**
   * Gets a named element from the SOAP message, searching both
   * the body and header.
   *
   * @param envelope The SOAP envelope.
   * @param elementNameString The name of the element to get.
   *
   * @return The required element or null if it was not found.
   */
  private SOAPElement getSoapElement(final SOAPEnvelope envelope,
    final String elementNameString)  {

    SOAPElement result = null;
    try {
      final Name elementName =
        envelope.createName(elementNameString);
      final Name hrefAttributeName =
        envelope.createName(kHrefAttributeName);
      final SOAPHeader sh = envelope.getHeader();
      final SOAPBody sb = envelope.getBody();

      // first search the header.
      SOAPElement candidateElement = null;
      final Iterator headerIterator =
        sh.getChildElements(elementName);
      if (headerIterator.hasNext()) {
        candidateElement = (SOAPElement) headerIterator.next();
      }

      // search the body, if necessary.
      if (candidateElement == null) {
        final Iterator bodyIterator =
          sb.getChildElements();
        if (bodyIterator.hasNext()) {
          candidateElement = (SOAPElement) bodyIterator.next();
        }
      }

      // Now we need to check if this is literal or encoded
      // element. A literal one is embedded directly, an encoded
      // one means that this element is simply a pointer to an
      // element elsewhere in the message.
      if (candidateElement != null) {
        final String hrefValue =
          candidateElement.getAttributeValue(hrefAttributeName);
        if ((hrefValue != null) && (hrefValue.length() > 0)) {
          // it points to a multi ref, so get this instead.
          result = getMultiRefElement(envelope, hrefValue);
        } else {
          // It's literal so return it directly.
          result = candidateElement;
        }
      }
    } catch (SOAPException e) {
      e.printStackTrace();
    }
    return result;
  }

  /**
   * Gets a multi ref element from a SOAP message.
   *
   * @param envelope The SOAP envelope.
   * @param idStringWithPrefix The identifier of the multi ref
   * element.
   *
   * @return The matching element, or null if it was not found.
   *
   * @throws SOAPException If any SOAP error occurs.
   */
  private SOAPElement getMultiRefElement(
    final SOAPEnvelope envelope, final String idStringWithPrefix)
    throws SOAPException {

    SOAPElement result = null;
    // Remove the hash character:
    final String idString = idStringWithPrefix.substring(1);
    final Name idName = envelope.createName(kNameOfIdAttribute);

    final SOAPBody body = envelope.getBody();
    final Iterator multiRefIterator = body.getChildElements(
      envelope.createName(kMultiRefElementName));
    while (multiRefIterator.hasNext()) {
      final Object o = multiRefIterator.next();

      final SOAPElement currentElement = (SOAPElement) o;
      final String currentId =
        currentElement.getAttributeValue(idName);
      if (currentId.equals(idString)) {
        result = currentElement;
        break;
      }
    }
    return result;
  }

  /**
   * Gets the value of a specified element within the given
   * element. If multiple occurrences are present, the first one
   * is returned.
   *
   * @param element The element containing the required one.
   * @param elementName The name of the required element.
   *
   * @return The string value of the element, or null if the
   * specified sub element does not exist.
   */
  private String getSubElementValue(final SOAPElement element,
    final Name elementName) {

    String result = null;

    final Iterator elementIterator =
      element.getChildElements(elementName);
    if (elementIterator.hasNext()) {
      final SOAPElement subElement =
        (SOAPElement) elementIterator.next();
      result = subElement.getValue();
    }
    return result;
  }

  /**
   * Gets the hidden implementation class for the Login Context.
   *
   * @param userName The user name to login with.
   * @param password The password to login with.
   *
   * @return class for implementation
   *
   * @throws Exception if an error occurs getting an instance of
   * the LoginContext class
   */
  private LoginContext getLoginContext(
    final String userName, final String password)
    throws Exception {

    final LoginContext resultLoginContext;

    // Initialize WebSphere specific callback handler. Use
    // reflection to avoid a build time dependency on an IBM
    // class.
    final Class wsCallbackHandlerClass = Class.forName(
      EnvironmentConstants.kWSCallbackHandlerImplClassName);
    final Class[] parameters = { String.class, String.class };
    final Constructor constructor =
      wsCallbackHandlerClass.getConstructor(parameters);
    final Object[] parameterValues = {userName, password};

    // The WebSphere login
    resultLoginContext =
      new LoginContext(
        EnvironmentConstants.kWSLogin,
        (CallbackHandler) constructor.newInstance(
          parameterValues));

    return resultLoginContext;
  }

  /**
   * Gets the cached Do As method, initializing it if necessary.
   *
   * @return The Do As method for this server.
   *
   * @throws Exception If the method could not be obtained for
   * any reason.
   */
  private Method getDoAsMethod() throws Exception {

    if (stSubjectDoAsMethod != null) {
      return stSubjectDoAsMethod;
    }

    final Class wsSubjectClass =
      Class.forName(EnvironmentConstants.kWSSubjectClassName);
    final Class[] moreParameters =
      { Subject.class, PrivilegedAction.class };
    stSubjectDoAsMethod =
      wsSubjectClass.getDeclaredMethod(
        EnvironmentConstants.kDoAsMethodName,
        moreParameters);
    return stSubjectDoAsMethod;
  }

  /**
   *
   *
   */
  private class ProviderPrivilegedAction
  implements PrivilegedAction {

    /** The message context for the call. */
    private final MessageContext msgContext;

    /** The class whose method we must invoke. */
    private final TestmodelProvider ownerObject;

    /**
     * Constructor which initializes the fields.
     *
     * @param newOwnerObject The class whose method we will
     * invoke.
     * @param newMsgContext The message context for the call.
     */
    public ProviderPrivilegedAction(
      final TestmodelProvider newOwnerObject,
      final MessageContext newMsgContext) {

      ownerObject = newOwnerObject;
      msgContext = newMsgContext;
    }

    /**
     * Runs the privileged action using the fields of this class.
     *
     * @return The exception resulting from the call, or null if
     * none was thrown.
     */
    public Object run() {
      Object resultFault = null;
      try {
        ownerObject.superInvoke(msgContext);
      } catch (Exception e) {
        resultFault = e;
      }
      return resultFault;
    }

  }

}

The text below shows an actual SOAP message (with some formatting for readability) which is processed by the Java code above. Note that the SOAP header refers to a parameter named ` inCred ` which contains the username and password credentials. The actual data is not stored literally in the header but in a ` multiRef ` element in the message body.

<soapenv:Envelope xmlns:soapenv=
  "http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <soapenv:Header>
    <inCred href="#id0" xmlns=""/>
  </soapenv:Header>

  <soapenv:Body soapenc:encodingStyle=
  "http://schemas.xmlsoap.org/soap/encoding/">
    <opDemo xmlns="http://remote.feature">
      <in1 href="#id1" xmlns=""/>
    </opDemo>

    <multiRef id="id1" soapenc:root="0" soapenv:encodingStyle=
      "http://schemas.xmlsoap.org/soap/encoding/"
      xsi:type="ns-905576305:PersonDetailsWrapper"
      xmlns:ns-905576305="http://feature/struct/" xmlns="">

      <firstName xsi:type="xsd:string">Jimmy</firstName>
      <idNumber xsi:type="xsd:string">0000361i</idNumber>
      <surname xsi:type="xsd:string">Client</surname>
    </multiRef>

    <multiRef id="id0" soapenc:root="0" soapenv:encodingStyle=
     "http://schemas.xmlsoap.org/soap/encoding/"
      xsi:type="ns-905576305:CredentialsWrapper"
      xmlns:ns-905576305="http://feature/struct/" xmlns="">

      <password xsi:type="xsd:string">password</password>
      <userName xsi:type="xsd:string">superuser</userName>
    </multiRef>
  </soapenv:Body>

</soapenv:Envelope>