Skip navigation.
Arch2Arch Tab BEA.com

SOAP Conversation Protocol (SOAP Conversation) 1.0

by David Bau, David Orchard
06/13/2002

Abstract


SOAP Conversation Specification describes SOAP Conversation, which is a set of SOAP headers, a protocol, and WSDL definitions designed to support asynchronous and conversational messaging.

Notice, Disclaimer and License


Copyright 2002, BEA Systems.

BEA Systems, Inc. ("BEA") assumes no responsibility for the accuracy of the information contained in the specification referenced herein ("Specification").

This Specification is provided to you "AS IS" and with all faults. BEA makes no warranties or representations as to the information contained in the Specification, including but not limited to its ability to help develop products, systems or applications as may be contemplated in such Specification.

Although BEA in its sole discretion may change any of the information in this Specification without notice, BEA makes no commitment to update the materials or information in this Specification. Furthermore, BEA will not provide any support services for this Specification. You use the information contained in this Specification at your sole risk.

BEA grants you a worldwide, non-exclusive, royalty-free, right and license to evaluate this Specification for internal use purposes only. The information contained herein is not a license to any other intellectual property owned or controlled by BEA and any other intellectual property not expressly licensed herein must be licensed separately under applicable BEA license(s). This license does not allow you to copy, modify, distribute, rebrand, create derivative works, sublicense, transfer, assign, or resell this Specification or any other BEA products. BEA may revoke this right and license at any time by providing you notice of such revocation. Except as granted herein, all right, title and interest in this Specification is retained by BEA.

IN NO EVENT SHALL BEA BE LIABLE TO YOU OR ANY OTHER PARTY FOR DAMAGES ARISING OUT OF YOUR ACCESS TO, OR USE OF ANY INFORMATION CONTAINED OR RELATED TO THE SPECIFICATION OR RELATED IN ANY WAY TO THIS AGREEMENT, AND REGARDLESS OF WHETHER THE CLAIM FOR SUCH DAMAGES IS BASED IN CONTRACT, TORT, WARRANTY, STRICT LIABILITY, OR OTHERWISE.

BEA SHALL NOT BE LIABLE TO YOU OR ANY OTHER PARTY FOR THE COST OF PROCURING SUBSTITUTE GOODS OR SERVICES OR FOR ANY DIRECT, INDIRECT, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR OTHER DAMAGES ARISING OUT OF YOUR ACCESS TO, OR USE OF ANY INFORMATION CONTAINED OR RELATED TO THE SPECIFICATION, OR IN ANY WAY RELATED TO THIS AGREEMENT. THE INFORMATION CONTAINED IN THE SPECIFICATION IS PROVIDED TO YOU "AS IS" AND WITH ALL FAULTS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, ACCURACY OR COMPLETENESS OF RESPONSES, OF DOCUMENT OR NON-INFRINGEMENT. THERE IS NO WARRANTY OR CONDITION OF TITLE, QUIET ENJOYMENT, QUIET POSSESSION, OR CORRESPONDENCE TO DESCRIPTION.

Status


IF BEA ELECTS TO GRANT ADDITIONAL LICENSES IN CONNECTION WITH THIS SPECIFICATION, INCLUDING WITHOUT LIMITATION, THE RIGHT TO REPRODUCE AND DISTRIBUTE IMPLEMENTATIONS OF THE SPECIFICATION, BEA PRESENTLY INTENDS TO OFFER SUCH LICENSES ON A ROYALTY FREE BASIS.

Introduction


SOAP Conversation (SOAP-Conversation) is a SOAP and WSDL based specification that defines long-running and asynchronous interactions between SOAP based senders and receivers. SOAP-Conversation is expressed as a SOAP header entry within a SOAP envelope making it relatively independent of the underlying protocol. This makes it easy to conduct stateful conversations between two parties asynchronously.

Goals


  1. The soap headers must be as simple as possible and meet the "80/20" rule for conversation and dynamic asynchrony functionality,
  2. the WSDL that describes the header and the actual header data must be capable of being consumed and produced by senders, proxies and receivers,
  3. The WSDL should reflect all the information that SOAP conversation treats as part of the contract, including the conversational phase of each operation.

Notational Conventions


The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 [2].


SOAP Conversation Message Exchange Model


SOAP Conversation Messages are SOAP messages that contain SOAP Conversation Headers. A SOAP Conversation node can be either a sender or a receiver, or both. A SOAP Conversation node MUST fully conform to this specification to be considered SOAP Conversation conformant. This specification uses the term "Header" to mean a "header block" as defined by SOAP 1.2. From the SOAP 1.2 specification section on Understanding SOAP headers, "A SOAP header block is said to be understood by a SOAP node if the software at that SOAP node has been written to fully conform to and implement the semantics conveyed by the combination of local name and namespace name of the outer-most element information item of that block."

Protocol

The SOAP Conversation Protocol defines the permitted sequence of messages and the erroneous messages in the SOAP Conversation state machine. The protocol is:
  1. The first message in a conversation from a sender MUST contain a StartHeader. A StartHeader MUST NOT be present on subsequent messages within the same conversation. A StartHeader MAY be omitted when sending a message to an operation that is marked in the WSDL as requesting a StartHeader. The effect MUST be as if a StartHeader were transmitted with no conversation ID and no callback location.
  2. The Receiver may use the conversationID in the StartConversation to start a conversation in an implementation specific manner. If a StartHeader does not contain a conversationID, this protocol defines no mechanism for informing the sender of any receiver defined conversationID and the sender will not be able to continue the conversation. The receiver MAY return a SOAP Fault after recieving a StartHeader if the conversation ID is unacceptable, such as non-uniqueness or too long for the receiver.
  3. If there is a callback location in the StartConversation, this location MUST be used for all asynchronous responses to the sender
  4. The CallbackHeader MUST be sent on any messages in a conversation that are sent from the sender to the receiver. The CallbackHeader MUST be sent on any messages to operations that are marked in the WSDL as requesting a CallbackHeader. The CallbackHeader MAY NOT be sent on any messages sent from the sender to the receiver. When a receiver sends callback message, it MUST send the callback message using the protocol binding that was used in the StartConversation message.
  5. Subsequent messages from the sender to the receiver in the same conversation MUST contain a ContinueHeader. The ContinueHeader MUST be sent on any messages to operations that are marked in the WSDL as requesting a ContinueHeader.
  6. Some subsequent message(s) will be labeled as finishing the conversation, and both sides SHOULD terminate their implementation specific conversation machinery.
Elements

The elements section defines the structures in the SOAP Conversation protocols, and their encoding characteristics.

Elements MAY be literal encoded or rpc encoded, as per the SOAP 1.1 specification. The header elements MUST be encoded in the same encoding style as the body. The message encoding for messages in a given conversation MAY use different encodings than other messages. That is, the encodings do not have to be uniform across the conversation. For example, a StartHeader could be literal encoded, and then a subsequent ContinueHeader could be rpc encoded.

Headers MAY have a mustUnderstand attribute set to true. Headers that do not have a mustUnderstand attribute set to true MAY be processed.

There are three header elements relating to conversations and asynchrony

StartHeader

The first header, StartHeader, starts a conversation. This header MAY contain a URL representing the callback location. This header MAY be present on any message from the initiator to the second party in a conversation. It MAY contains a conversationID that establishes an opaque string as the conversation ID. The string is established by the initiator of the conversation and MUST be echoed on every subsequent message in the conversation. The header MAY be empty, and such a header MUST be treated as the equivalent of a non-existant StartHeader.

Header sample #1: Initial message to receiver


  <xmp>
  <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
  <s:Header>
    <StartHeader xmlns="http://www.openuri.org/2002/04/soap/conversation/">
          <conversationID>[1018048628003]helloAsync:1018048628974</conversationID>
          <callbackLocation>http://original.com/foo/CallbackService</callbackLocation>
    </StartHeader>
  </s:Header>
  <s:Body>
  <!-- contents omitted -->
  </s:Body>
  </s:Envelope>
  </xmp>

ContinueHeader

The ContinueHeader header continues the conversation. It MUST contain a conversationID that the sender orginally sent in the StartHeader element.

Header sample #2: Another message to receiver


  <xmp>
  <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
  <s:Header>
    <ContinueHeader xmlns="http://www.openuri.org/2002/04/soap/conversation/">
          <conversationID>[1018048628003]helloAsync:1018048628974</conversationID>
    </ContinueHeader>
  </s:Header>
  <s:Body>
          <!-- contents omitted -->
  </s:Body>
  </s:Envelope>
  </xmp>

CallbackHeader

The CallbackHeader MUST contain a conversation ID that was previously submitted to the sender by the receiver.

Header sample #3: Callback to sender


  <xmp>
  <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
  <s:Header>
    <CallbackHeader xmlns="http://www.openuri.org/2002/04/soap/conversation/">
          <conversationID<[1018048628003]helloAsync:1018048628974</conversationID>
    </CallbackHeader>
  </s:Header>
  <s:Body>
    <!-- contents omitted -->
  </s:Body>
  </s:Envelope>
  </xmp>

Element Descriptions


The preceeding headers can contain the following elements.

CallbackLocation

callbackLocation is a URI that specifies the address that the sender is listening for callbacks. The callbackLocation MAY appear in any "start" header sent from the sender to the receiver.

The URL specified in the callbackLocation header MUST use a protocol that is compatible with the WSDL binding that is used by the sender on the incoming message. For example, if the sender is sending a message using SOAP over HTTP, the callback location must also be an HTTP URL, and the sender is expected to be listening for SOAP messages on that URL. If the sender is sending a message using JMS, the callback location must be a JMS URL; and so on.

When a sender issues a callback message, it MUST send the callback message using the protocol binding that was most recently used by the conversation. For example, if a message first arrives via HTTP; then over JMS; then a callback is sent, the callback will be sent via JMS.

If the WSDL indicate that a callback operation isn't supported over the desired binding, the message MUST be dropped and an exception MUST be thrown. Similarly, if a proper callback URL has not been established, the message MUST dropped and an exception MUST be thrown.

Rationale: the common case is that the callback URL never changes, and that the same protocol binding is used by the sender for every call as part of a conversation. (I.e., in the real world, we don't expect a single sender to change protocols in mid-conversation; different protocols arise because each individual sender speaks one protocol and there are a diversity of different senders.)

ConversationID

Conversation ID is an arbitrarily long string that is established by the sender (the originator of the first message).

The element MAY be present in any StartHeader and MUST be present in every ContinueHeader and CallbackHeader (I.e., it's not required on services that use primary keys for correlation.)

The header MUST be generated by the sender in a way that ensures that it is globally unique. Examples of such schemes are embedding a GUID, URL, secure random number, etc.

No other restrictions are placed on the format of the ConversationID string, so it MAY include any structure. For example, conversationID may include a concatenated primary key, clustering IP address and date stamp.

When "continue" or "finish" messages are sent as part of a conversation (either from the sender to receiver or from the receiver to the sender), the same conversationID that was established in the "start" message MUST be echoed verbatim.

WSDL definitions


The following information SHOULD be in WSDL documents related to conversational web services:

  1. The presence of SOAP headers and their types, SHOULD be declared on all SOAP conversation aware methods.
  2. The header elements StartHeader, ContinueHeader and CallbackHeader SHOULD be declared as types.
  3. All conversational methods from the sender to the receiver SHOULD be declared as accepting a conversationID element.
  4. All start conversation methods that are asynchronous going from the sender to the receiver SHOULD be declared as accepting a callbackLocation element.
  5. When SOAP Conversation headers are declared, the conversation phase of an operation (start/continue/finish) SHOULD be declared.
  6. The WSDL extensibility element cw:transition is part of the SOAP binding of an operation, and SHOULD be declared next to the conversational headers. This doesn't indicate that any additional information should flow on the wire, but it indicates the legal order in which the operations SHOULD be called and the semantics of the conversational headers. The "start" phase indicates that a new conversation ID SHOULD be established by the sender and saved by the receiver; the "continue" phase indicates that the conversationID SHOULD be found by the receiver - if it can't be found, it's an error; the "finish" phase indicates that the conversationID SHOULD be disposed by both the receiver and sender after completing the operation - if subsequent messages use the same conversationID, it's an error.
  7. When a WSDL contains conversational headers or callbacks, it should have a comment at the top of it that points to a URL at which there is a (human-readable) website explaining the conversational protocol.

The mustUnderstand attribute MAY be declared as required.

Note: This treats the conversation phase as part of the wire contract even though it's not concretely transmitted on the wire. This is in keeping with the philosophy that there is an implicit restriction on the legal order of messages that can be sent, and the semantics of when a new conversation ID needs to be established. senders are interested in this information: it is part of the contract, not the private implementation.

Sample WSDL

WSDL documents containing conversational services should look similar to the following:


  <xmp>
  <!--
    This WSDL file describes conversational bindings that may
    use conversation ID headers and callback messages. These
    bindings conform to the SOAP and WSDL conversations
    specification described at
    http://www.openuri.org/2002/04/wsdl/conversation/
    http://www.openuri.org/2002/04/soap/conversation/

  -->

  <definitions ....
    xmlns:s="http://www.w3.org/2001/XMLSchema"
    xmlns:conv="http://www.openuri.org/2002/04/soap/conversation/"
    xmlns:cw="http://www.openuri.org/2002/04/wsdl/conversation/"
    xmlns:s0="http://www.openuri.org"
    targetNamespace="http://www.openuri.org">

     <types>
     <s:schema elementFormDefault="qualified"
        targetNamespace="http://www.openuri.org/2002/04/soap/conversation/">
        <s:element name="StartHeader" type="conv:StartHeader" />
        <s:element name="ContinueHeader" type="conv:ContinueHeader" />
        <s:element name="CallbackHeader" type="conv:CallbackHeader" />
        <s:complexType name="StartHeader">
          <s:sequence>
            <s:element minOccurs="0" maxOccurs="1" name="conversationID"
            type="s:string" />
            <s:element minOccurs="0" maxOccurs="1" name="callbackLocation"
            type="s:string" />
          </s:sequence>
        </s:complexType>
        <s:complexType name="ContinueHeader">
          <s:sequence>
            <s:element minOccurs="1" maxOccurs="1" name="conversationID"
            type="s:string" />
          </s:sequence>
        </s:complexType>
        <s:complexType name="CallbackHeader">
        <s:sequence>
           <s:element minOccurs="1" maxOccurs="1" name="conversationID"
           type="s:string" />

          </s:sequence>
        </s:complexType>
      </s:schema>
      ....
     </types>

            <message name="StartHeader_literal">
                  <part name="StartHeader" element="conv:StartHeader"/>
          </message>
          <message name="StartHeader_rpc">
                  <part name="StartHeader" type="conv:StartHeader"/>
          </message>
          <message name="ContinueHeader_literal">
                  <part name="ContinueHeader" element="conv:ContinueHeader"/>
          </message>
          <message name="ContinueHeader_rpc">
                  <part name="ContinueHeader" type="conv:ContinueHeader"/>
          </message>
          <message name="CallbackHeader_literal">
                  <part name="CallbackHeader" element="conv:CallbackHeader"/>
          </message>
          <message name="CallbackHeader_rpc">
                  <part name="CallbackHeader" type="conv:CallbackHeader"/>
          </message>

     <binding .... >
       <operation name="startRpc">
              <soap:operation
                  soapAction="http://www.openuri.org/startRpc" style="rpc"/>
                  <cw:transition phase="start"/>
                  <input>
                          <soap:body use="encoded" namespace="http://www.openuri.org/"
                          encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
                          <soap:header wsdl:required="true"
                          message="s0:StartHeader_rpc"
                          part="StartHeader" use="encoded"
                          encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
                          xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"/>
                  </input>
                  <output>
                          <soap:body use="encoded" namespace="http://www.openuri.org/"
                          encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
                  </output>
          </operation>
          <operation name="startLit">
                  <soap:operation
                  soapAction="http://www.openuri.org/startLit" style="document"/>
                  <cw:transition phase="start"/>
                  <input>
                          <soap:body use="literal"/>
                          <soap:header wsdl:required="true"
                          message="s0:StartHeader_literal"
                          part="StartHeader" use="literal"
                          xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"/>
                  </input>
                  <output>
                          <soap:body use="literal"/>
                  </output>
                  </operation>
          <operation name="continueRpc">
                  <soap:operation
                  soapAction="http://www.openuri.org/continueRpc" style="rpc"/>
                  <cw:transition phase="continue"/>
                  <input>
                          <soap:body use="encoded" namespace="http://www.openuri.org/"
                          encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
                          <soap:header wsdl:required="true"
                          message="s0:ContinueHeader_rpc"
                          part="ContinueHeader" use="encoded"
                          encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
                          xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"/>
                  </input>
                  <output>
                          <soap:body use="encoded" namespace="http://www.openuri.org/"
                          encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
                  </output>
          </operation>
          <operation name="continueLit">
                  <soap:operation
                  soapAction="http://www.openuri.org/continueLit" style="document"/>
                  <cw:transition phase="continue"/>
                  <input>
                          <soap:body use="literal"/>
                          <soap:header wsdl:required="true"
                          message="s0:ContinueHeader_literal"
                          part="ContinueHeader" use="literal"
                          xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"/>
                  </input>
                  <output>
                      <soap:body use="literal"/>
                  </output>
          </operation>
          <operation name="finishRpc">
                  <soap:operation soapAction="http://www.openuri.org/finishRpc"
                  style="rpc"/>
                  <cw:transition phase="finish"/>
                  <input>
                          <soap:body use="encoded" namespace="http://www.openuri.org/"
                          encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
                          <soap:header wsdl:required="true"
                          message="s0:ContinueHeader_rpc"
                          part="ContinueHeader" use="encoded"
                          encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
                          xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"/>
                  </input>
                  <output>
                          <soap:body use="encoded" namespace="http://www.openuri.org/"
                          encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
                  </output>
          </operation>
          <operation name="finishLit">
                  <soap:operation
                  soapAction="http://www.openuri.org/finishLit" style="document"/>
                  <cw:transition phase="finish"/>
                  <input>
                          <soap:body use="literal"/>
                          <soap:header wsdl:required="true"
                          message="s0:ContinueHeader_literal"
                          part="ContinueHeader" use="literal"
                          xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"/>
                  </input>
                  <output>
                          <soap:body use="literal"/>
                  </output>
          </operation>
          <operation name="callbackRpc">
                  <soap:operation
                  soapAction="http://www.openuri.org/callbackRpc" style="rpc"/>
                  <cw:transition phase="continue"/>
                  <input>
                          <soap:body use="encoded" namespace="http://www.openuri.org/"
                          encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
                  </input>
                  <output>
                          <soap:body use="encoded" namespace="http://www.openuri.org/"
                          encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
                          <soap:header wsdl:required="true"
                          message="s0:CallbackHeader_rpc"
                          part="CallbackHeader" use="encoded"
                          encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
                          xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"/>
                  </output>
          </operation>
          <operation name="callbackLit">
                  <soap:operation
                  soapAction="http://www.openuri.org/callbackLit" style="document"/>
                  <cw:transition phase="continue"/>
                  <input>
                          <soap:body use="literal"/>
                  </input>
                  <output>
                          <soap:body use="literal"/>
                          <soap:header
                          wsdl:required="true" message="s0:CallbackHeader_literal"
                          part="CallbackHeader" use="literal"
                          xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"/>
                  </output>
          </operation>
  </binding>

  </definitions>
  </xmp>

Appendix


References (normative)

  1. RFC 2119: http://www.ietf.org/rfc/rfc2119.txt
  2. SOAP 1.1 Note: http://www.w3.org/TR/SOAP/
  3. XML 1.0: http://www.w3.org/TR/REC-xml
  4. Namespaces in XML: http://www.w3.org/TR/REC-xml-names/
  5. XML Schema Part 1: http://www.w3.org/TR/xmlschema-1/
  6. XML Schema Part 2: http://www.w3.org/TR/xmlschema-2/
  7. WSDL 1.1 Note: http://www.w3.org/TR/wsdl

Issues (normative)

SOAP Conversation does not define any SOAP Faults. For example, there is no defined FAULT for a continue message that preceeds a start. It is not expected that this specification will define faults in a subsequent version.

WS-Conversation (Web Services Conversation) is a more appropriate name. However, we wanted to allow for standardization of this work. We believe that any standardization efforts should use the name WS-Conversation.

Schema for Headers (non-normative)

This section contains a schema that is the equivalent of the normative SOAP header types in a WSDL document.


  <xmp>
  <s:schema targetNamespace="http://www.openuri.org/2002/04/soap/conversation/"
  xmlns:s="http://www.w3.org/2001/XMLSchema"
  xmlns:conv="http://www.openuri.org/2002/04/soap/conversation/"
  elementFormDefault="qualified">
          <s:element name="StartHeader" type="conv:StartHeader"/>
          <s:element name="ContinueHeader" type="conv:ContinueHeader"/>
          <s:element name="CallbackHeader" type="conv:CallbackHeader"/>
          <s:complexType name="StartHeader">
                  <s:sequence>
                          <s:element name="conversationID"
                          type="s:string" minOccurs="0"/>
                          <s:element name="callbackLocation"
                          type="s:string" minOccurs="0"/>
                  </s:sequence>
          </s:complexType>
          <s:complexType name="ContinueHeader">
                  <s:sequence>
                          <s:element name="conversationID" type="s:string"/>
                  </s:sequence>
          </s:complexType>
          <s:complexType name="CallbackHeader">
                  <s:sequence>
                          <s:element name="conversationID" type="s:string"/>
                  </s:sequence>
          </s:complexType>
  </s:schema>
  </xmp>

Schema for WSDL (non-normative)

This section contains a schema that defines the SOAP Conversation Transition element extensions to WSDL


  <xmp>
  <s:schema targetNamespace="http://www.openuri.org/2002/04/wsdl/conversation/"
  xmlns:s="http://www.w3.org/2001/XMLSchema"
  xmlns:cw="http://www.openuri.org/2002/04/wsdl/conversation/"
  elementFormDefault="qualified">
          <s:element name="transition" type="cw:Transition_t"/>
          <s:complexType name="Transition_t">
                  <s:complexContent>
                  <s:restriction base="s:anyType">
                          <s:attribute name="phase" type="cw:phase_t"/>
                  </s:restriction>
                  </s:complexContent>
          </s:complexType>
          <s:simpleType name="phase_t">
                  <s:restriction base="s:string">
                          <s:enumeration value="start"/>
                          <s:enumeration value="continue"/>
                          <s:enumeration value="finish"/>
                  </s:restriction>
          </s:simpleType>
  </s:schema>
  </xmp>

References (non-normative)
  1. SOAP 1.2 Part 1:http://www.w3.org/TR/2001/WD-soap12-part1-20011217/

Article Tools

Email E-mail
Print Print
Blog Blog

Related Technologies

Bookmark Article

del.icio.us del.icio.us
Digg Digg
DZone DZone
Furl Furl
Reddit Reddit