Saturday, August 15, 2009

MTOM/XOP and performance improvements in CXF

Message Transmission Optimization Mechanism (MTOM) is a core feature of web service engine. CXF supports MTOM with XOP.

Brief description about MTOM/XOP in SOAP:

In standard SOAP messages, binary objects are base64-encoded and included in the message body. This increases their size by 33%, which for very large binary objects can significantly impact transmission time. The SOAP Message Transmission Optimization Mechanism (MTOM) and XML-binary Optimized Packaging (XOP) specifications, define a method for optimizing the transmission of large base64Binary data objects within SOAP messages:

XOP is used as the referencing mechanism in the serialised XML infoset. In theory, the abstract MTOM model could be used with a different referencing mechanism and/or different container format, over a different transport protocol instead of HTTP. In practice, MTOM is usually used with XOP, MIME and HTTP.

In MTOM, an Optimized MIME Multipart/Related Serialization of SOAP Messages is defined that summarises that the serialized XML infoset will include XML-binary Optimized Packaging (XOP) in place of the binary data, and the binary data (along with the serialized XML infoset with XOP placeholders) will be represented together in a MIME container.

Performance issue description: In CXF 2.0.7, MTOM performance was poor. In the initial set of tests I had done, For a 12 MB of data, it was taking around 1.2-1.4 seconds. Pretty Slow compared to other WS stacks MTOM performance.

Sharing approach: We intergrated CXF in Pramati Java EE 1.5 application server. I delved into this, firstly removed all in and out application server's (web container streams). removed out CXF Interceptors to finally come to the one which composes and decomposes MTOM SOAP message, to finally arrive at MimeBodyPartInputStream, PushbackInputStream.

MimeBodyPartInputStream doesn't implements read(byte[]) method, hence it delegates it to parent InputStream class. InputStream's read(byte[]) runs over a loop, eventually delegating to PushBackInputStream, which reads a single byte and performs boundary matching over that Byte.

A simple test program which takes the time taken by the MimeBodyPartInputStream to read from a loaded buffer shows that MimeBodyPartInputStream takes ~1200-1400 ms to read a 12MB buffer. On the other hand, an InputStream takes around ~100-150ms to do so. Yes, that's obvious as we all understand that MimeBodyPartInputStream has the logic of detecting a probable boundary and it performs multiple if checks to do so, also it calls read and unread over PushBackInputStream. But again considering number of the reads and unreads also, the time of MimeBodyPartInputStream looked poor.

An alternative implementation of MimeBodyPartInputStream which added dynamic buffer creation, buffer used for forward-backward index movement, and implements read(byte[] buffer, int off, int len) helped me to solve the performance problem. The functionality of reading the buffer, matching the process boundary is now performed by it.

The performance counters after the changes were (12 MB of data):

1. ~1578 ms (current CXF MBPIS)
2. ~172 ms (just read raw 12MB data)
3. ~188 ms (new CXF MBPIS)

They look more encouraging, sensible and charming :)

So next time you use MTOM feature in any CXF releases 2.0.10 or 2.1.4 after, you will get better performance.

In case you want to see the changes, they are below,

Index: trunk/rt/core/src/main/java/org/apache/cxf/attachment/MimeBodyPartInputStream.java
===================================================================
diff -u -N -r651669 -r718620
--- trunk/rt/core/src/main/java/org/apache/cxf/attachment/MimeBodyPartInputStream.java  
(.../branches/2.0.x-fixes/rt/core/src/main/java/org/apache/cxf/attachment/MimeBodyPartInputStream.java)  (revision 651669)
+++ trunk/rt/core/src/main/java/org/apache/cxf/attachment/MimeBodyPartInputStream.java  
(.../trunk/rt/core/src/main/java/org/apache/cxf/attachment/MimeBodyPartInputStream.java)  (revision 718620)
@@ -28,15 +28,159 @@
     PushbackInputStream inStream;
 
     boolean boundaryFound;
-
+    int pbAmount;
     byte[] boundary;
+    byte[] boundaryBuffer;
 
-    public MimeBodyPartInputStream(PushbackInputStream inStreamParam, byte[] boundaryParam) {
+    public MimeBodyPartInputStream(PushbackInputStream inStreamParam, 
+                                   byte[] boundaryParam,
+                                   int pbsize) {
         super();
         this.inStream = inStreamParam;
         this.boundary = boundaryParam;
+        this.pbAmount = pbsize;
     }
 
+    public int read(byte buf[], int origOff, int origLen) throws IOException {
+        byte b[] = buf;
+        int off = origOff;
+        int len = origLen;
+        if (boundaryFound) {
+            return -1;
+        }
+        if ((off < 0) || (off > b.length) || (len < 0) 
+            || ((off + len) > b.length) || ((off + len) < 0)) {
+
+            throw new IndexOutOfBoundsException();
+        }
+        if (len == 0) {
+            return 0;
+        }
+        boolean bufferCreated = false;
+        if (len < boundary.length * 2) {
+            //buffer is too short to detect boundaries with it.  We'll need to create a larger buffer   
+            bufferCreated = true;
+            if (boundaryBuffer == null) {
+                boundaryBuffer = new byte[boundary.length * 2];
+            }
+            b = boundaryBuffer;
+            off = 0;
+            len = boundaryBuffer.length;
+        }
+        if (len > pbAmount) {
+            len = pbAmount;  //can only pushback that much so make sure we can
+        }
+        if (len > 0) {
+            len = inStream.read(b, off, len);
+        }
+        int i = processBuffer(b, off, len);
+        if (bufferCreated && i > 0) {
+            // read more than we need, push it back
+            if (origLen >= i) {
+                System.arraycopy(b, 0, buf, origOff, i);
+            } else {
+                System.arraycopy(b, 0, buf, origOff, origLen);
+                inStream.unread(b, origLen, i - origLen);
+                i = origLen;
+            }
+        } else if (i == 0 && boundaryFound) {
+            return -1;
+        }
+        return i;
+    }
+
+    //Has Data after encountering CRLF
+    private boolean hasData(byte[] b, int initialPointer, int pointer, int off, int len)
+        throws IOException {
+        if (pointer < (off + len)) {
+            return true;
+        } else {
+            inStream.unread(b, initialPointer, (off + len) - initialPointer);
+            return false;
+        }
+    }
+
+    protected int processBuffer(byte[] buffer, int off, int len) throws IOException {
+        for (int i = off; i < (off + len); i++) {
+            boolean needUnread0d0a = false;
+            int value = buffer[i];
+            int initialI = i;
+            if (value == 13) {
+                if (!hasData(buffer, initialI, initialI + 1, off, len)) {
+                    return initialI - off;
+                }
+                value = buffer[initialI + 1];
+                if (value != 10) {
+                    continue;
+                } else {  //if it comes here then 13, 10 are values and will try to match boundaries
+                    if (!hasData(buffer, initialI, initialI + 2, off, len)) {
+                        return initialI - off;
+                    }
+                    value = buffer[initialI + 2];
+                    if ((byte) value != boundary[0]) {
+                        i++;
+                        continue;
+                    } else { //13, 10, boundaries first value matched
+                        needUnread0d0a = true;
+                        i += 2; //i after this points to boundary[0] element
+                    }
+                }
+            } else if (value != boundary[0]) {
+                continue;
+            }
+
+            int boundaryIndex = 0;
+            while ((boundaryIndex < boundary.length) && (value == boundary[boundaryIndex])) {
+                if (!hasData(buffer, initialI, i + 1, off, len)) {
+                    return initialI - off;
+                }                
+                value = buffer[++i];
+                boundaryIndex++;
+            }
+            if (boundaryIndex == boundary.length) {
+                // read the end of line character
+                if (initialI != off) {
+                    i = 1000000000;
+                }
+                if (!hasData(buffer, initialI, i + 1, off, len)) {
+                    return initialI - off;
+                }
+                boundaryFound = true;
+                int j = i + 1;
+                if (j < len && buffer[j] == 45 && value == 45) {
+                    // Last mime boundary should have a succeeding "--"
+                    // as we are on it, read the terminating CRLF
+                    i += 2;
+                    //last mime boundary
+                }
+
+                //boundary matched (may or may not be last mime boundary)
+                int processed = initialI - off;
+                if ((len - (i + 2)) > 0) {
+                    inStream.unread(buffer, i + 2, len - (i + 2));
+                }
+                return processed;
+            }
+
+            // Boundary not found. Restoring bytes skipped.
+            // write first skipped byte, push back the rest
+            if (value != -1) { //pushing back first byte of boundary
+                // Stream might have ended
+                i--;
+            }
+            if (needUnread0d0a) { //Pushing all,  returning 13
+                i = i - boundaryIndex;
+                i--; //for 10
+                value = 13;
+            } else {
+                i = i - boundaryIndex;
+                i++;
+                value = boundary[0];
+            }
+        }
+        return len;
+    }
+
     public int read() throws IOException {
         boolean needUnread0d0a = false;
         if (boundaryFound) {
@@ -77,8 +221,9 @@
         if (boundaryIndex == boundary.length) {
             // boundary found
             boundaryFound = true;
+            int dashNext = inStream.read();
             // read the end of line character
-            if (inStream.read() == 45 && value == 45) {
+            if (dashNext == 45 && value == 45) {
                 // Last mime boundary should have a succeeding "--"
                 // as we are on it, read the terminating CRLF
                 inStream.read();

Monday, July 06, 2009

Examples of WS-Security using CXF and WSS4J

If you are searching for some simple examples to understand and write WS-Security stuff (UsernameToken, Timestamp, Signature or Encryption) actions. I filed a jira task on JIRA, made some examples and Dan committed them in the svn. You can get them in CXF 2.2.3 distribution.
You can check the following (all included Timestamp action):

To get more detailed description about how it all works you can read this Configuring CXF for WS-Security... . I hope your initial queries, doubts while creating and understanding startup examples of WS-Security using CXF and WSS4J will be answered there :)

Monday, May 18, 2009

Specifying WS-SecurityPolicy, RM, Policy in a WSDL file

WS-Policy provides a way for the provider of a web service to convey conditions under which it provides the service. A invoker might use this policy to decide whether to use or not to use the service.WS-Policy just gives basic assertion support like, <wsp:all></wsp:all>, <wsp:exactlyone></wsp:exactlyone>to express one set of policy or alternatives too. Any conditions/requirements as assertions under <wsp:all></wsp:all>become mandatory,where as assertions inside <wsp:exactlyone></wsp:exactlyone>are considered as alternatives to each other.You can read more about various combinations/interactions/alternative in OASIS WS-PolicySpecification[1].


  • You can specify these WS-Poilcy assertions either inside input , output,fault, operation, port, or in binding wsdl elements.
  • You can either put them directly as child elements of them, or else you can refer them using <wsp:PolicyReference> element.

Policy can be attached as described by WS-PolicyAttached [4] at following levels:

  • Service Level: {Applied at <wsdl:Service>} - A policy associated with a service policy subject applies to any message exchange using any of the endpoints offered by that service.

  • Endpoint Level: {Applied at <wsdl:port> or <wsdl:portType> or <wsdl:binding>} - Since <wsdl:portType> can be used with multiple Bindings, hence it is RECOMMENDED to specify only abstract policy (binding independent) at this <wsdl:portType>. I prefer to use the other two options <wsdl:port> or <wsdl:binding> to apply endpoint policy. Policies associated with an endpoint policy subject apply to any message exchange made using that endpoint.

  • Operation Level: {Applied at <wsdl:portType/wsdl:operation> or <wsdl:binding/wsdl:operation>} - Policies associated with an operation policy subject apply to the message exchange described by that operation. Again, it is RECOMMENDED to specify only abstract policy at <wsdl:portType/wsdl:operation>, hence <wsdl:binding/wsdl:operation> is a prefered place to specify operation level policies.

  • Message Level: {Applied at <wsdl:message> or <wsdl:portType/wsdl:operation/wsdl:input> or <wsdl:portType/wsdl:operation/wsdl:output> or <wsdl:portType/wsdl:operation/wsdl:fault> or <wsdl:binding/wsdl:operation/wsdl:input> or <wsdl:binding/wsdl:operation/wsdl:output> or <wsdl:binding/wsdl:operation/wsdl:fault>} - Policies associated with a message policy subject apply to that message (i.e. input, output or fault message).

WS-Security Policy [2], WS-RM [3] assertions are build on top of WS-Policy for specifying security or reliable messaging requirements/constraints for aservice. For example, specifying security requirements for outgoing message, I can write as,

<wsp:Policy wsu:Id="Output_Policy">
<wsp:ExactlyOne>
<wsp:All>
<sp:SignedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">
<sp:Body/>
</sp:SignedParts>
<sp:EncryptedParts xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">
<wsu:Timestamp/>
</sp:EncryptedParts>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>


and I can refer this policy as,

<wsdl:binding name="WebTransactionServiceSoapBinding"
type="tns:CreditCard">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http"/>
<!--wsp:PolicyReference URI="#Endpoint_Policy"/-->
<wsdl:operation name="purchase">
<soap:operation soapAction="" style="document"/>
<wsdl:input name="purchase">
<!--wsp:PolicyReference URI="#Input_Policy"/-->
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="purchaseResponse">
<wsp:PolicyReference URI="#Output_Policy"/>
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>

or specifying RM assertions as Embedded instead of referred can be,

<wsdl:service name="CartSLSBBeanService">
<wsdl:port binding="ns1:CartSLSBBeanServiceSoapBinding"
name="CartSLSBBeanPort">
<wswa:UsingAddressing
xmlns:wswa="http://www.w3.org/2005/02/addressing/wsdl"/>
<soap:address location=" http://localhost:8181/cart/cart"/>
<wsp:Policy xmlns:wsp="http://www.w3.org/2006/07/ws-policy"
xmlns:wsu="
http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="RM">
<wsam:Addressing
xmlns:wsam=http://www.w3.org/2007/02/addressing/metadata>
<wsrmp:RMAssertion
xmlns:wsrmp=http://schemas.xmlsoap.org/ws/2005/02/rm/policy>
<wsrmp:BaseRetransmissionInterval Milliseconds="10000"/>
</wsrmp:RMAssertion>
</wsp:Policy>
</wsdl:port>
</wsdl:service>

You can read more about WS-SecurityPolicy[2] and WS RM[3] so as to understand what these assertions actually specify.I hope this helps
[1]. http://www.w3.org/TR/ws-policy/
[2]. http://docs.oasis-open.org/ws-sx/ws-securitypolicy/v1.2/ws-securitypolicy.html
[3].http://docs.oasis-open.org/ws-rx/wsrmp/200608/wsrmp-1.1-spec-cd-04.html
[4]. http://www.w3.org/TR/ws-policy-attach/

Monday, May 11, 2009

Understanding WS-Trust and configuration in CXF

Apache CXF is a high-performance, extensible, Intuitive & Easy to Use open source services framework. CXF helps you build and develop services using frontend programming APIs, like JAX-WS. CXF supports a variety of web service standards including SOAP, the Basic Profile, WSDL, WS-Addressing, WS-Policy, WS-ReliableMessaging, and WS-Security.

Apache CXF 2.2 is the third major release of CXF project. I found this release very interesting as it supports WS-SecurityPolicy and Partially supports WS-Trust. WS-Trust specifically deals with the issuing, renewing, and validating of security tokens, as well as with ways to establish, assess the presence of, and broker trust relationships between participants in a secure message exchange.

WS-SecurityPolicy is a very helpful and easier way of specifying your security requirements in an interoperable way. You can access a secured web service hosted on MS server by just pointing to the wsdl containing embedded WS-SecurityPolicy or a WS-Policy reference to a separate WS-SecurityPolicy file.

In CXF-2.2, WS-Trust is supported on client side, that means that now you can access a web services hosted by any WS vendor who requires you to produce tokens for authentication and authorization. For example, if I host a web service in Metro and configures a Metro STS (Secure Token Service - which provides tokens to clients). My CXF client will talk to Metro STS to acquire a token, once acquired CXF client can then produce this token to the Metro Service as an authentication and authorization token. An STS itself is a web service - a web service that issues security tokens.

Daniel Kulp has done a great job in making WS-Trust support up and working. In future release of CXF, we will come up with full support for WS-Trust. Any one who is interested can join the development I have filed a jira task for it.

How WS-Trust works: Basically on client side, ws-policy engine while parsing and understanding service wsdl, from <issuedtoken><issuer>... <issuer>assertion it learns that the service requires a security token to be produced. gives the WS-Addressing address of the STS service and WS-MEX (Metadata Exchange) address for getting the WSDL location of STS. STS may confirm the authenticity of the client who is requesting for the token. Based on the satisfactory confirmation of the authentication of the client, <issuedtoken> may have an assertion <requestsecuritytokentemplate> in which the service may specify what kind of RST request a client should make to STS, a service may specify it's particular requirements for the Token to be produced to it, the requirements can be KeySize, KeyType, TokenType, etc.

STS issues a RSTR (RequestSecurityTokenResponse) which contains requestedToken and proofkey. A proof key indicates proof of possession of the token associated with the requested security token. There are two types of proof keys: symmetric or asymmetric. A service can specify the proof key in the <keytype> element of <requestsecuritytokentemplate>.

In case of Symmetric key, STS creates and distributes the Symmetric key to both service and client. STS gives the key to the client in the <requestedprooftoken> element. STS gives the exact copy of the key to the service in the <keyinfo> element of the <subjectconfirmation> element in the issued SAML assertion in the RSTR response to the client. Client then forward the issued SAML assertion to the service.

In case of Public Key, client doesn't want to trust STS for issuing the secret key as proof of possession. In the RST (RequestSecurityToken) message client supplies RSA key or X.509 public key to STS. STS just embeds the RSA key or X.509 public key certificate in the <keyinfo> element of the <subjectconfirmation> element in the issued SAML assertion in the RSTR response to the client. Client then forward the issued SAML assertion to the service.

In case of No Proof Key, the issued token in <requestedsecuritytoken> itself works as proof of possession.

After getting RSTR, client composes the web service request as requested by WSDL requirements of the service. It embeds the SAML assertion into the request message to the service. Service and client uses the proof key for authentication and authorization of messages afterwards.

Configuring CXF for WS-Trust

Since currently only Client side support is provided in CXF 2.2 version (as I have discussed at the start of this blog entry), all we require to do is to configure a STSClient java class, org.apache.cxf.ws.security.trust.STSClient with the properties to wsdl location, service name, endpoint name. STSClient will look into wsdl, request for an issue, validate, renewal of the required token from STS and will put into the context of WS call so that finally the ws-request can be send with the required token, proof token and SAML assertion. Usually, you configure STS client in either of below two ways,

You can configure STSClient using code-first by creating a new instance of STSClient and calling setter methods of STSClient and finally setting a property "ws-security.sts.client" oof RequestContext() method on port instance as,

((BindingProvider)port).getRequestContext().put("ws-security.sts.client", stsinstance);

or, else you can configure spring bean as,

<jaxws:client name="{http://cxf.apache.org/}MyService">

<jaxws:properties>

<entry key="ws-security.sts.client">

<!-- direct STSClient config and creation -->

<bean class="org.apache.cxf.ws.security.trust.STSClient">

<constructor-arg ref="cxf"/>

<property name="wsdlLocation" value="target/wsdl/trust.wsdl"/>

<property name="serviceName" value="{http://cxf.apache.org/securitytokenservice}SecurityTokenService"/>

<property name="endpointName" value=""{http://cxf.apache.org/securitytokenservice}SecurityTokenEndpoint"/>

<property name="properties">

<map>

<entry key="ws-security.username" value="joe"/>

<entry key="ws-security.callback-handler" value="interop.client.KeystorePasswordCallback"/>

<entry key="ws-security.signature.properties" value="etc/alice.properties"/>

<entry key="ws-security.encryption.properties" value="etc/bob.properties"/> </map>

</property>

</bean>

</entry>

</jaxws:properties>

</jaxws:client>

Wednesday, January 21, 2009

Sample Annotation

A Novice Program for writing own Annotation. Annotations are not rocket science, they just have retention policy (runtime, source, class) and target (field, method, class, type, parameter) and finally can be looked using Reflection API.

MyAnnotation.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(value = RetentionPolicy.RUNTIME)

@Target(value = { ElementType.FIELD })

public abstract @interface MyAnnotation {
boolean enabled() default true;
String input() default "";
String output() default "";
int value() default -1;

}

Usage.java
import java.lang.reflect.Field;
public class Usage {
@MyAnnotation(input = "Hello")

public int a;
public void myMethod() {

Class<? extends Usage> c = this.getClass(); Field f = null;
try {
f = c.getField("a");
} catch (SecurityException e) { e.printStackTrace();
} catch (NoSuchFieldException e) { e.printStackTrace();
}
MyAnnotation ma = (MyAnnotation) f.getAnnotation(MyAnnotation.class);
System.out.println(ma.input());
}
public static void main(String args[]) {

Usage u = new Usage(); u.myMethod(); }
}