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

          People

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

            Dates

            • Created:
              Updated:
              Resolved:

              Development