Axiom
  1. Axiom
  2. AXIOM-412

DOOM's OMFactory implementation should be stateless

    Details

    • Type: Improvement Improvement
    • Status: Resolved
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 1.2.12
    • Fix Version/s: 1.2.14
    • Component/s: DOOM
    • Labels:
      None

      Description

      There is a sort of "impedance mismatch" between the Axiom API and DOM because

      • the Axiom API is designed such that nodes are created using a factory (OMFactory or SOAPFactory) that is expected to be a singleton and stateless;
      • in the DOM API, the Document instance plays the role of node factory, and each node (explicitly or implicitly) keeps a reference to the Document instance from which it was created (the "owner document").

      The approach currently used by DOOM to solve that issue is to have a stateful OMFactory implementation which has a reference to a (OM)Document instance. That (OM)Document instance is then used as the owner document for nodes created using the Axiom API [Well, actually it is a bit more complicated and obscure than that...]. For this to work, the application code is required to request an OMFactory once and only once for each document created. This is one of the reasons why in general code written for the standard Axiom implementation (LLOM) doesn't work well when switching to DOOM. In addition, although the implementation of that design is relatively simple, it makes DOOM quite obscure from the user's perspective.

      There is an alternative approach to solve the Axiom/DOM impedance mismatch which doesn't require a stateful OMFactory implementation. It is based on the following set of rules that determine how the DOM owner document is handled when nodes are created and manipulated using the Axiom API:

      (1) Nodes created using the Axiom API and for which a parent node is specified will have as their owner document the owner document of the parent. This is simply a consequence of the fact that DOM is designed such that two nodes that are part of the same tree must have the same owner document.

      (2) Nodes created using the Axiom API and for which no parent node is specified will get a new owner document. That is unavoidable if one wants a stateless/singleton OMFactory.

      (3) When the Axiom API is used to add a node A as a child of another node B, then the owner document of B becomes the new owner document of A and all its descendants. In DOM parlance, this means that node A is automatically adopted by the owner document of B. This rule ensures that any operation that is valid with the LLOM implementation will also work with DOOM (without triggering a WRONG_DOCUMENT_ERR exception and without violating the rule that all nodes in a tree must have the same owner document).

      (4) When a node is detached from its parent using the Axiom API, it will get a new owner document. This rule is not strictly required to make the approach work; it merely exists for consistency because together with the other rules it implies that every tree has a distinct owner document (as long as only the Axiom API is used to manipulate the nodes).

      That rule applies to the following methods:

      • OMNode#detach
      • OMElement#removeAttribute
      • OMElement#setText (in the case where the side effect of the invocation is to detach preexisting nodes)
      • OMElement#addAttribute (in the case where the new attribute replaces an existing one, which will be removed from its owner)

      If implemented literally, these rules would obviously have a performance impact because (depending on the usage pattern) a large number of temporary Document instances may be needed. In addition, rules (3) and (4) require an efficient way to change the owner document of an entire tree (more efficient than to traverse the entire tree). Therefore these rules would be supplemented by the following design choices:

      (5) Owner documents are created lazily, namely when explicitly requested using DOM's Node#getOwnerDocument() API (or when DOOM needs to access data that it choses to store in the owner document).

      (6) Only the root node of a tree stores a reference to the owner document. As noted above, all nodes in a tree must have the same owner document. Therefore it is not necessary for a node to have references to both its parent and its owner document. Instead, a node should have a single attribute that stores the reference either to the parent (if it has a parent) or to the owner document (if it has no parent), as well as a flag that indicates the meaning of the reference (this is important if the reference points to a Document instance, which would otherwise be ambiguous). With this design, changing the owner document of a tree is O(1) instead of O(N) where N is the number of nodes in the tree. However, requesting the owner document of a node is O(M) instead of O(1) with M the depth of the tree. This is a good tradeoff considering that

      • Axiom methods never need to check the owner document;
      • DOM methods need to check the owner document more often (basically for every node addition) than the owner document of a tree is changed, but M << N.

      It should be noted that switching from the current design to the new design proposed here is not entirely transparent for application code. It implies a change in behavior if nodes are first created and manipulated using the Axiom API and then later passed to DOM APIs such as appendChild. In that situation it is likely that with the new design, the nodes have different owner documents, while this was not the case with the old design (because the same OMFactory instance was used). Such application code needs to be changed to use Document#adoptNode where appropriate. However, it is expected that in the Axis2 universe, the impact will be limited to a few places in the SAAJ implementation as well as Rampart. There are quite some examples where these two components depended on incorrect behavior in DOOM's DOM implementation or other implementation details, so that in general they can only be expected to work with the Axiom version for which they were built. One may therefore assume that the impact is acceptable.

        Activity

        Andreas Veithen made changes -
        Status In Progress [ 3 ] Resolved [ 5 ]
        Resolution Fixed [ 1 ]
        Andreas Veithen made changes -
        Status Open [ 1 ] In Progress [ 3 ]
        Andreas Veithen made changes -
        Assignee Andreas Veithen [ veithen ]
        Fix Version/s 1.2.14 [ 12318340 ]
        Andreas Veithen made changes -
        Field Original Value New Value
        Description There is a sort of "impedance mismatch" between the Axiom API and DOM because
        * the Axiom API is designed such that nodes are created using a factory (OMFactory or SOAPFactory) that is expected to be a singleton and stateless;
        * in the DOM API, the Document instance plays the role of node factory, and each node (explicitly or implicitly) keeps a reference to the Document instance from which it was created (the "owner document").

        The approach currently used by DOOM to solve that issue is to have a stateful OMFactory implementation which has a reference to a (OM)Document instance. That (OM)Document instance is then used as the owner document for nodes created using the Axiom API [Well, actually it is a bit more complicated and obscure than that...]. For this to work, the application code is required to request an OMFactory once and only once for each document created. This is one of the reasons why in general code written for the standard Axiom implementation (LLOM) doesn't work well when switching to DOOM. In addition, although the implementation of that design is relatively simple, it makes DOOM quite obscure from the user's perspective.

        There is an alternative approach to solve the Axiom/DOM impedance mismatch which doesn't require a stateful OMFactory implementation. It is based on the following set of rules that determine how the DOM owner document is handled when nodes are created and manipulated using the Axiom API:

        (1) Nodes created using the Axiom API and for which a parent node is specified will have as their owner document the owner document of the parent. This is simply a consequence of the fact that DOM is designed such that two nodes that are part of the same tree must have the same owner document.

        (2) Nodes created using the Axiom API and for which no parent node is specified will get a new owner document. That is unavoidable if one wants a stateless/singleton OMFactory.

        (3) When the Axiom API is used to add a node A as a child of another node B, then the owner document of B becomes the new owner document of A and all its descendants. In DOM parlance, this means that node A is automatically adopted by the owner document of B. This rule ensures that any operation that is valid with the LLOM implementation will also work with DOOM (without triggering a WRONG_DOCUMENT_ERR exception and without violating the rule that all nodes in a tree must have the same owner document).

        (4) When a node is detached from its parent using the Axiom API (i.e. using OMNode#detach), it will get a new owner document. This rule is not strictly required to make the approach work; it merely exists for consistency because together with the other rules it implies that every tree has a distinct owner document (as long as only the Axiom API is used to manipulate the nodes).

        If implemented literally, these rules would obviously have a performance impact because (depending on the usage pattern) a large number of temporary Document instances may be needed. In addition, rules (3) and (4) require an efficient way to change the owner document of an entire tree (more efficient than to traverse the entire tree). Therefore these rules would be supplemented by the following design choices:

        (5) Owner documents are created lazily, namely when explicitly requested using DOM's Node#getOwnerDocument() API (or when DOOM needs to access data that it choses to store in the owner document).

        (6) Only the root node of a tree stores a reference to the owner document. As noted above, all nodes in a tree must have the same owner document. Therefore it is not necessary for a node to have references to both its parent and its owner document. Instead, a node should have a single attribute that stores the reference either to the parent (if it has a parent) or to the owner document (if it has no parent), as well as a flag that indicates the meaning of the reference (this is important if the reference points to a Document instance, which would otherwise be ambiguous). With this design, changing the owner document of a tree is O(1) instead of O(N) where N is the number of nodes in the tree. However, requesting the owner document of a node is O(M) instead of O(1) with M the depth of the tree. This is a good tradeoff considering that
        * Axiom methods never need to check the owner document;
        * DOM methods need to check the owner document more often (basically for every node addition) than the owner document of a tree is changed, but M << N.

        It should be noted that switching from the current design to the new design proposed here is not entirely transparent for application code. It implies a change in behavior if nodes are first created and manipulated using the Axiom API and then later passed to DOM APIs such as appendChild. In that situation it is likely that with the new design, the nodes have different owner documents, while this was not the case with the old design (because the same OMFactory instance was used). Such application code needs to be changed to use Document#adoptNode where appropriate. However, it is expected that in the Axis2 universe, the impact will be limited to a few places in the SAAJ implementation as well as Rampart. There are quite some examples where these two components depended on incorrect behavior in DOOM's DOM implementation or other implementation details, so that in general they can only be expected to work with the Axiom version for which they were built. One may therefore assume that the impact is acceptable.
        There is a sort of "impedance mismatch" between the Axiom API and DOM because
        * the Axiom API is designed such that nodes are created using a factory (OMFactory or SOAPFactory) that is expected to be a singleton and stateless;
        * in the DOM API, the Document instance plays the role of node factory, and each node (explicitly or implicitly) keeps a reference to the Document instance from which it was created (the "owner document").

        The approach currently used by DOOM to solve that issue is to have a stateful OMFactory implementation which has a reference to a (OM)Document instance. That (OM)Document instance is then used as the owner document for nodes created using the Axiom API [Well, actually it is a bit more complicated and obscure than that...]. For this to work, the application code is required to request an OMFactory once and only once for each document created. This is one of the reasons why in general code written for the standard Axiom implementation (LLOM) doesn't work well when switching to DOOM. In addition, although the implementation of that design is relatively simple, it makes DOOM quite obscure from the user's perspective.

        There is an alternative approach to solve the Axiom/DOM impedance mismatch which doesn't require a stateful OMFactory implementation. It is based on the following set of rules that determine how the DOM owner document is handled when nodes are created and manipulated using the Axiom API:

        (1) Nodes created using the Axiom API and for which a parent node is specified will have as their owner document the owner document of the parent. This is simply a consequence of the fact that DOM is designed such that two nodes that are part of the same tree must have the same owner document.

        (2) Nodes created using the Axiom API and for which no parent node is specified will get a new owner document. That is unavoidable if one wants a stateless/singleton OMFactory.

        (3) When the Axiom API is used to add a node A as a child of another node B, then the owner document of B becomes the new owner document of A and all its descendants. In DOM parlance, this means that node A is automatically adopted by the owner document of B. This rule ensures that any operation that is valid with the LLOM implementation will also work with DOOM (without triggering a WRONG_DOCUMENT_ERR exception and without violating the rule that all nodes in a tree must have the same owner document).

        (4) When a node is detached from its parent using the Axiom API, it will get a new owner document. This rule is not strictly required to make the approach work; it merely exists for consistency because together with the other rules it implies that every tree has a distinct owner document (as long as only the Axiom API is used to manipulate the nodes).

        That rule applies to the following methods:
        * OMNode#detach
        * OMElement#removeAttribute
        * OMElement#setText (in the case where the side effect of the invocation is to detach preexisting nodes)
        * OMElement#addAttribute (in the case where the new attribute replaces an existing one, which will be removed from its owner)

        If implemented literally, these rules would obviously have a performance impact because (depending on the usage pattern) a large number of temporary Document instances may be needed. In addition, rules (3) and (4) require an efficient way to change the owner document of an entire tree (more efficient than to traverse the entire tree). Therefore these rules would be supplemented by the following design choices:

        (5) Owner documents are created lazily, namely when explicitly requested using DOM's Node#getOwnerDocument() API (or when DOOM needs to access data that it choses to store in the owner document).

        (6) Only the root node of a tree stores a reference to the owner document. As noted above, all nodes in a tree must have the same owner document. Therefore it is not necessary for a node to have references to both its parent and its owner document. Instead, a node should have a single attribute that stores the reference either to the parent (if it has a parent) or to the owner document (if it has no parent), as well as a flag that indicates the meaning of the reference (this is important if the reference points to a Document instance, which would otherwise be ambiguous). With this design, changing the owner document of a tree is O(1) instead of O(N) where N is the number of nodes in the tree. However, requesting the owner document of a node is O(M) instead of O(1) with M the depth of the tree. This is a good tradeoff considering that
        * Axiom methods never need to check the owner document;
        * DOM methods need to check the owner document more often (basically for every node addition) than the owner document of a tree is changed, but M << N.

        It should be noted that switching from the current design to the new design proposed here is not entirely transparent for application code. It implies a change in behavior if nodes are first created and manipulated using the Axiom API and then later passed to DOM APIs such as appendChild. In that situation it is likely that with the new design, the nodes have different owner documents, while this was not the case with the old design (because the same OMFactory instance was used). Such application code needs to be changed to use Document#adoptNode where appropriate. However, it is expected that in the Axis2 universe, the impact will be limited to a few places in the SAAJ implementation as well as Rampart. There are quite some examples where these two components depended on incorrect behavior in DOOM's DOM implementation or other implementation details, so that in general they can only be expected to work with the Axiom version for which they were built. One may therefore assume that the impact is acceptable.
        Andreas Veithen created issue -

          People

          • Assignee:
            Andreas Veithen
            Reporter:
            Andreas Veithen
          • Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development