Commons JXPath
  1. Commons JXPath
  2. JXPATH-10

JXPath 1.1 code using custom functions failing when run in 1.2 onwards

    Details

    • Type: Bug Bug
    • Status: Resolved
    • Priority: Blocker Blocker
    • Resolution: Fixed
    • Affects Version/s: 1.2 Final
    • Fix Version/s: 1.3
    • Labels:
      None
    • Environment:

      Operating System: other
      Platform: PC

      Description

      We have recently attempted to upgrade from a 1.1 release of jxpath
      to 1.2 and found a great deal of our jxpath code fails to run correctly.

      We isolated the problem to relating to the use of Custom Extension
      Functions and have included sample junit test case snippets that should
      demonstrate the issue clearly.

      The background on what we are trying to do with jxpath is as follows
      (included so its clear on what we are trying to use jxpath to achieve):

      Within our project, we make extensive use of Custom Extension Functions in JXPath.

      We also use JXPath variables significantly, in combination with these functions,
      that often take a JXPath variable as an argument(s) to the function.

      We now rely heavily on the use of the ExpressionContext interface as the first
      argument to many of our functions. The reason for this is that we need a
      convienient way to obtain access to 'original' object references within the
      context invoked by the function (as you would expect).

      However, we have also begun using a very useful combination of features, which
      the API supports in version 1.1, where the first argument always defines the
      ExpressionContext interface (which isn't really part of the method signature -
      from a caller perspective), and a 2nd argument as 'Object' type. Within body of
      the function, we then cast the object of the 2nd argument as a NodeSet (or
      define the NodeSet type within the method signature - either appears to work),
      which provides us with access to the pointers for the object.

      As previously mentioned, a jxpath variable is passed from the caller of the
      function (received via the 2nd argument in the method signature), which is
      automatically resolved, by jxpath, to the object itself.

      The benefit of casting the object to NodeSet (interface) enables us to retrieve
      the first pointer in the NodeSet list. The first pointer concerns us, as it
      refers to the String value passed to the argument of the function which we need
      access to. The object reference itself is of little concern in this case, as
      once we have access to the variable name sent to the function, we can access the
      object via the ExpressionContext.

      This all works fine in jxpath 1.1, however, in version 1.2 this functionality is
      now broken, since our objects are no longer being cast to NodeSet (previously
      achieved via the internal implementation of jxpath NodeSet using a SimpleNodeSet

      • as per inspecting jxpath 1.1 source code).

      Note on the use of ExpressionContext: For those methods that declare the
      ExpressionContext interface (which must be the first argument, if used), as part
      of the argument list, the method signature from the caller perspective, excludes
      it from the argument list, as it is implied by jxpath. In other words, there
      will be one less argument from the caller when the interface is used. In the
      case where this interface was the only argument, which is common, then the
      caller would be invoking a zero-argument method. This is behaviour we expect.

      Here is a simplified example of one of our functions using the techniques
      discussed:-

      public static void deleteSomeObject(ExpressionContext expContext, Object obj) {

      // create a local jxpath context to retrieve values for jxpath variables
      JXPathContext localContext = expContext.getJXPathContext();

      // Nodeset of the object passed to method
      NodeSet nodeset = (NodeSet)obj;
      // Retrieve variable name passed to function
      String declaredVariable = nodeset.getPointers().get(0).toString();
      Object objectToDelete = null;
      // If this method was passed the $obj1 var to delete, then retrieve 'Object Type
      1' via $obj1 variable
      if (declaredVariable.equals("$obj1"))

      { objectToDelete = (ObjectType1)localContext.getValue("$obj1"); }

      // If this method was passed the $obj2 var to delete, then retrieve 'Object
      Type 2' via $obj2 variable
      if (declaredVariable.equals("$obj2"))

      { objectToDelete = (ObjectType2)localContext.getValue("$obj2"); }

      collectionOfSomeObjects.delete(objectToDelete);
      }

      Which would be used (called) in the following way:-
      ...
      // add to collection
      ObjectType1 objectType1 = new ObjectType1();
      ObjectType2 objectType2 = new ObjectType2();
      CollectionOfSomeObjects.add(objectType1);
      CollectionOfSomeObjects.add(objectType2);
      // add collection to jxpath context
      JXPathContext context = JXPathContext.newContext(collectionOfSomeObjects);
      // define jxpath variables
      context.getVariables().declareVariable("obj1", objectType1);
      context.getVariables().declareVariable("obj2", objectType2);

      // call jxpath Custom Extension Function

      String method2Invoke;
      method2Invoke = "ftn:deleteSomeObject($obj1)"
      context.getValue(method2Invoke); // ß invoke function & return value (for
      non-void methods)
      method2Invoke = "ftn:deleteSomeObject($obj2)"
      context.getValue(method2Invoke); // ß invoke function & return value (for
      non-void methods)

      In addition to the above example, I have prepared a suite of JUnit tests (&
      functions) that work against JXPath version 1.1,
      but fail against version 1.2. These code snippets can simply be dropped into
      the appropriate classes and executed.

      Also, I would like to note that the JXPath user guide is a little unclear with
      respect to the use of NodeSet's.
      After looking at many of the existing JUnit tests for JXPath, many un-documented
      features became evident & existing features became a little obscure, such as
      'Collections as NodeSets'.

      The code examples provided should give you our perspective on it's use.

      CODE SNIPPETS BELOW TO INSERT INTO EXISTING JXPATH TEST SUITE:
      Below you will find two sections:-
      (1) JUnit Tests to test Extension Functions
      (2) Additional Extension Functions for these new tests

      ---------------------------------------------------------------------------------------------------------
      3 JUnit Tests to add to 'ExtensionFunctionTest.java" (in package
      org.apache.commons.jxpath.ri.compiler) :

      /**

      • To test the use of NodeSet to retrieve String value passed to argument of
        method
        */
        public void testMyTestNodeSetOnly() { context.getVariables().declareVariable("obj1", new String("$12345.00")); assertXPathValue( context, "test:myTestNodeSetOnly($obj1)", "$obj1"); }

      /**

      • To test the use of ContextExpression & NodeSet Combined
        */
        public void testMyTestContextExpressionAndNodeSetCombined() { HashMap map = new HashMap(); map.put("1",new String("Item 1")); map.put("2",new String("Item 2")); map.put("3",new String("Item 3")); context.getVariables().declareVariable("obj2", map); assertXPathValue( context, "test:myTestContextExpressionAndNodeSetCombined($obj2)", "$obj2"); }

      /**

      • To test the use of ContextExpression & NodeSet Combined with additional
        arguments
        */
        public void testMyTestContextExpressionAndNodeSetCombinedExtraArgs() { HashMap map = new HashMap(); map.put("1",new String("Item 1")); map.put("2",new String("Item 2")); map.put("3",new String("Item 3")); HashMap map2 = new HashMap(); map2.put("another collection",map); map2.put("a bean",new TestBean()); TestBean testBean = new TestBean(); Map beanMap = testBean.getMap(); context.getVariables().declareVariable("obj1", beanMap); context.getVariables().declareVariable("obj2", map2); context.getVariables().declareVariable("obj3", testBean); context.getVariables().declareVariable("obj4", new Integer(10)); assertXPathValue( context, "test:myTestContextExpressionAndNodeSetCombinedExtraArgs($obj1, $obj2, $obj3, $obj4)", "$obj1"); }

      ---------------------------------------------------------------------------------------------------------
      3 Additional Functions to add to 'TestFunctions.java' (in package
      org.apache.commons.jxpath.ri.compiler) :

      public static String myTestNodeSetOnly(NodeSet obj) {

      // Nodeset of the object passed to method
      NodeSet nodeset = (NodeSet)obj;

      // Retrieve variable name passed to function
      String declaredVariable = "";
      declaredVariable = nodeset.getPointers().get(0).toString();

      // for (int i=0; i < nodeset.getPointers().size();i++ )

      { // declaredVariable = nodeset.getPointers().get(i).toString(); // System.out.println(" declaredVariable Name = "+declaredVariable+" value ="+nodeset.getNodes().get(i)); // // }

      // System.out.println(" declaredVariable Name = "+declaredVariable);

      return declaredVariable;
      }

      public static String
      myTestContextExpressionAndNodeSetCombined(ExpressionContext expContext, Object
      obj)

      { // create a local jxpath context to retrieve values for jxpath variables JXPathContext localContext = expContext.getJXPathContext(); // Nodeset of the object passed to method NodeSet nodeset = (NodeSet)obj; // Retrieve variable name passed to function String declaredVariable = nodeset.getPointers().get(0).toString(); // System.out.println(" declaredVariable Name = "+declaredVariable); HashMap object = (HashMap)localContext.getValue(declaredVariable); // System.out.println(" declaredVariable reference to object via context = "+object); return declaredVariable; }

      public static String
      myTestContextExpressionAndNodeSetCombinedExtraArgs(ExpressionContext expContext,
      Object obj1, Object obj2, Object obj3, Object obj4 )

      { // create a local jxpath context to retrieve values for jxpath variables JXPathContext localContext = expContext.getJXPathContext(); // Nodesets of the object(s) passed to method NodeSet nodeset1 = (NodeSet)obj1; NodeSet nodeset2 = (NodeSet)obj2; NodeSet nodeset3 = (NodeSet)obj3; NodeSet nodeset4 = (NodeSet)obj4; // Retrieve variable name passed to function String declaredVariable = nodeset1.getPointers().get(0).toString(); // System.out.println(" declaredVariable 1 Name = "+declaredVariable); HashMap object = (HashMap)localContext.getValue(declaredVariable); // System.out.println(" declaredVariable reference to object via context = "+object); return declaredVariable; }

        Activity

        Hide
        Jörg Heinicke added a comment -
            • COM-2593 has been marked as a duplicate of this bug. ***
        Show
        Jörg Heinicke added a comment - COM-2593 has been marked as a duplicate of this bug. ***
        Hide
        Matt Benson added a comment -

        Okay... this bug is a little more than a year old, so I'm not sure how the original reporter has fared since its origination, but anyway:

        As you may have realized, this regression is an artifact of the feature added with JXPath 1.2 whereby an Object argument signifies that Pointers and NodeSets should be decoded to the objects they represent. I hope to have a fairly easy workaround completed soon. More later...

        Show
        Matt Benson added a comment - Okay... this bug is a little more than a year old, so I'm not sure how the original reporter has fared since its origination, but anyway: As you may have realized, this regression is an artifact of the feature added with JXPath 1.2 whereby an Object argument signifies that Pointers and NodeSets should be decoded to the objects they represent. I hope to have a fairly easy workaround completed soon. More later...
        Hide
        Paul Parisi added a comment -
        Show
        Paul Parisi added a comment -
        Hide
        Paul Parisi added a comment -

        Hello Matt,
        We originally logged this defected some time back. I note you are interested in knowing our
        progress from your comments.

        Since logging this defect we stopped using jxpath 1.2 and commented out all the unit tests we had
        developed that depending on its functionality (we cannot run two versions of jxpath in our
        build system) So essentially we have not been able to upgrade jxpath, however we still would like
        to do this as we eventually. If you are able to provide a workaround we would be keen to try it out.

        regards,
        Paul Parisi

        Show
        Paul Parisi added a comment - Hello Matt, We originally logged this defected some time back. I note you are interested in knowing our progress from your comments. Since logging this defect we stopped using jxpath 1.2 and commented out all the unit tests we had developed that depending on its functionality (we cannot run two versions of jxpath in our build system) So essentially we have not been able to upgrade jxpath, however we still would like to do this as we eventually. If you are able to provide a workaround we would be keen to try it out. regards, Paul Parisi
        Hide
        Matt Benson added a comment -

        Hi Paul, I have committed to SVN HEAD a TypeConverter implementation that bypasses the Pointer conversions introduced in version 1.2 . In order to use it, you must first execute the following:

        TypeUtils.setTypeConverter(new JXPath11CompatibleTypeConverter());

        And I think that's the best I'll be able to do for you.

        Show
        Matt Benson added a comment - Hi Paul, I have committed to SVN HEAD a TypeConverter implementation that bypasses the Pointer conversions introduced in version 1.2 . In order to use it, you must first execute the following: TypeUtils.setTypeConverter(new JXPath11CompatibleTypeConverter()); And I think that's the best I'll be able to do for you.

          People

          • Assignee:
            Unassigned
            Reporter:
            Paul Parisi
          • Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development