View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.jxpath.ri.model;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertThrows;
22  
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Locale;
29  import java.util.Map;
30  import java.util.Vector;
31  
32  import org.apache.commons.jxpath.AbstractJXPathTest;
33  import org.apache.commons.jxpath.JXPathContext;
34  import org.apache.commons.jxpath.Pointer;
35  import org.apache.commons.jxpath.TestBean;
36  import org.apache.commons.jxpath.TestMixedModelBean;
37  import org.apache.commons.jxpath.TestNull;
38  import org.apache.commons.jxpath.Variables;
39  import org.junit.jupiter.api.BeforeEach;
40  import org.junit.jupiter.api.Test;
41  
42  /**
43   * Tests JXPath with mixed model: beans, maps, DOM etc.
44   */
45  public class MixedModelTest extends AbstractJXPathTest {
46  
47      private JXPathContext context;
48  
49      @Override
50      @BeforeEach
51      public void setUp() {
52          final TestMixedModelBean bean = new TestMixedModelBean();
53          context = JXPathContext.newContext(bean);
54          context.setFactory(new TestMixedModelFactory());
55          context.setLocale(Locale.US);
56          final Variables vars = context.getVariables();
57          vars.declareVariable("string", bean.getString());
58          vars.declareVariable("bean", bean.getBean());
59          vars.declareVariable("map", bean.getMap());
60          vars.declareVariable("list", bean.getList());
61          vars.declareVariable("document", bean.getDocument());
62          vars.declareVariable("element", bean.getElement());
63          vars.declareVariable("container", bean.getContainer());
64          vars.declareVariable("testnull", new TestNull());
65          final int[][] matrix = new int[1][];
66          matrix[0] = new int[1];
67          matrix[0][0] = 3;
68          vars.declareVariable("matrix", matrix);
69      }
70  
71      @Test
72      public void testBeanBean() {
73          assertXPathValueAndPointer(context, "bean/int", Integer.valueOf(1), "/bean/int");
74      }
75  
76      @Test
77      public void testBeanContainer() {
78          assertXPathValueAndPointer(context, "container/vendor/location/address/city", "Fruit Market", "/container/vendor[1]/location[2]/address[1]/city[1]");
79      }
80  
81      @Test
82      public void testBeanDocument() {
83          assertXPathValueAndPointer(context, "document/vendor/location/address/city", "Fruit Market", "/document/vendor[1]/location[2]/address[1]/city[1]");
84      }
85  
86      @Test
87      public void testBeanElement() {
88          assertXPathValueAndPointer(context, "element/location/address/city", "Fruit Market", "/element/location[2]/address[1]/city[1]");
89      }
90  
91      @Test
92      public void testBeanList() {
93          assertXPathValueAndPointer(context, "list[1]", "string", "/list[1]");
94      }
95  
96      @Test
97      public void testBeanMap() {
98          assertXPathValueAndPointer(context, "map/string", "string", "/map[@name='string']");
99      }
100 
101     @Test
102     public void testBeanPrimitive() {
103         assertXPathValueAndPointer(context, "string", "string", "/string");
104     }
105 
106     /**
107      * Scott Heaberlin's test - collection of collections
108      */
109     @Test
110     public void testCollectionPointer() {
111         final List list = new ArrayList();
112         final Map map = new HashMap();
113         map.put("KeyOne", "SomeStringOne");
114         map.put("KeyTwo", "SomeStringTwo");
115         final Map map2 = new HashMap();
116         map2.put("KeyA", "StringA");
117         map2.put("KeyB", "StringB");
118         map.put("KeyThree", map2);
119         list.add(map);
120         final List list2 = new ArrayList();
121         list2.add("foo");
122         list2.add(map);
123         list2.add(map);
124         list.add(list2);
125         context = JXPathContext.newContext(list);
126         assertEquals("SomeStringOne", context.getValue(".[1]/KeyOne"));
127         assertEquals("StringA", context.getValue(".[1]/KeyThree/KeyA"));
128         assertEquals(Integer.valueOf(3), context.getValue("size(.[1]/KeyThree)"));
129         assertEquals(Double.valueOf(6.0), context.getValue("count(.[1]/KeyThree/*)"));
130         assertEquals(Double.valueOf(3.0), context.getValue("count(.[1]/KeyThree/KeyA)"));
131     }
132 
133     @Test
134     public void testCreatePath() {
135         context = JXPathContext.newContext(new TestBean());
136         context.setFactory(new TestMixedModelFactory());
137         final TestBean bean = (TestBean) context.getContextBean();
138         bean.setMap(null);
139         assertXPathCreatePath(context, "/map[@name='TestKey5']/nestedBean/int", Integer.valueOf(1), "/map[@name='TestKey5']/nestedBean/int");
140         bean.setMap(null);
141         assertXPathCreatePath(context, "/map[@name='TestKey5']/beans[2]/int", Integer.valueOf(1), "/map[@name='TestKey5']/beans[2]/int");
142     }
143 
144     @Test
145     public void testCreatePathAndSetValueWithMatrix() {
146         context.setValue("matrix", null);
147         // Calls factory.createObject(..., TestMixedModelBean, "matrix")
148         // Calls factory.createObject(..., nestedBean, "strings", 2)
149         assertXPathCreatePathAndSetValue(context, "/matrix[1]/.[1]", Integer.valueOf(4), "/matrix[1]/.[1]");
150     }
151 
152     @Test
153     public void testErrorProperty() {
154         context.getVariables().declareVariable("e", new ExceptionPropertyTestBean());
155         assertThrows(Throwable.class, () -> assertXPathValue(context, "$e/errorString", null), "Legitimate exception accessing property");
156         assertXPathPointer(context, "$e/errorString", "$e/errorString");
157         assertXPathPointerLenient(context, "$e/errorStringArray[1]", "$e/errorStringArray[1]");
158         assertXPathPointerIterator(context, "$e/errorString", list("$e/errorString"));
159         assertXPathPointerIterator(context, "$e//error", Collections.EMPTY_LIST);
160     }
161 
162     /**
163      * Test JXPath.iterate() with map containing an array
164      */
165     @Test
166     public void testIterateArray() {
167         final Map map = new HashMap();
168         map.put("foo", new String[] { "a", "b", "c" });
169         final JXPathContext context = JXPathContext.newContext(map);
170         assertXPathValueIterator(context, "foo", list("a", "b", "c"));
171     }
172 
173     @Test
174     public void testIteratePointersArray() {
175         final Map map = new HashMap();
176         map.put("foo", new String[] { "a", "b", "c" });
177         final JXPathContext context = JXPathContext.newContext(map);
178         final Iterator<Pointer> it = context.iteratePointers("foo");
179         final List<Object> actual = new ArrayList<>();
180         while (it.hasNext()) {
181             final Pointer ptr = it.next();
182             actual.add(context.getValue(ptr.asPath()));
183         }
184         assertEquals(list("a", "b", "c"), actual, "Iterating pointers <" + "foo" + ">");
185     }
186 
187     @Test
188     public void testIteratePointersArrayElementWithVariable() {
189         final Map map = new HashMap();
190         map.put("foo", new String[] { "a", "b", "c" });
191         final JXPathContext context = JXPathContext.newContext(map);
192         context.getVariables().declareVariable("x", Integer.valueOf(2));
193         final Iterator<Pointer> it = context.iteratePointers("foo[$x]");
194         final List<Object> actual = new ArrayList<>();
195         while (it.hasNext()) {
196             final Pointer ptr = it.next();
197             actual.add(context.getValue(ptr.asPath()));
198         }
199         assertEquals(list("b"), actual, "Iterating pointers <" + "foo" + ">");
200     }
201 
202     @Test
203     public void testIterateVector() {
204         final Map map = new HashMap();
205         final Vector vec = new Vector();
206         vec.add(new HashMap());
207         vec.add(new HashMap());
208         map.put("vec", vec);
209         final JXPathContext context = JXPathContext.newContext(map);
210         assertXPathPointerIterator(context, "/vec", list("/.[@name='vec'][1]", "/.[@name='vec'][2]"));
211     }
212 
213     @Test
214     public void testListBean() {
215         assertXPathValueAndPointer(context, "list[2]/int", Integer.valueOf(1), "/list[2]/int");
216     }
217 
218     @Test
219     public void testListContainer() {
220         assertXPathValueAndPointer(context, "list[7]/vendor/location/address/city", "Fruit Market", "/list[7]/vendor[1]/location[2]/address[1]/city[1]");
221     }
222 
223     @Test
224     public void testListDocument() {
225         assertXPathValueAndPointer(context, "list[5]/vendor/location/address/city", "Fruit Market", "/list[5]/vendor[1]/location[2]/address[1]/city[1]");
226     }
227 
228     @Test
229     public void testListElement() {
230         assertXPathValueAndPointer(context, "list[6]/location/address/city", "Fruit Market", "/list[6]/location[2]/address[1]/city[1]");
231     }
232 
233     @Test
234     public void testListList() {
235         /**
236          * @todo: what is this supposed to do? Should we stick to XPath, in which case [1] is simply ignored, or Java, in which case it is supposed to extract
237          *        the first element from the list?
238          */
239 //        assertXPathValueAndPointer(context,
240 //                "list[4][1]",
241 //                "string2",
242 //                "/list[4][1]");
243         assertXPathValueAndPointer(context, "list[4]/.[1]", "string2", "/list[4]/.[1]");
244     }
245 
246     @Test
247     public void testListMap() {
248         assertXPathValueAndPointer(context, "list[3]/string", "string", "/list[3][@name='string']");
249     }
250 
251     @Test
252     public void testListPrimitive() {
253         assertXPathValueAndPointer(context, "list[1]", "string", "/list[1]");
254     }
255 
256     @Test
257     public void testMapBean() {
258         assertXPathValueAndPointer(context, "map/bean/int", Integer.valueOf(1), "/map[@name='bean']/int");
259     }
260 
261     @Test
262     public void testMapContainer() {
263         assertXPathValueAndPointer(context, "map/container/vendor/location/address/city", "Fruit Market",
264                 "/map[@name='container']" + "/vendor[1]/location[2]/address[1]/city[1]");
265     }
266 
267     @Test
268     public void testMapDocument() {
269         assertXPathValueAndPointer(context, "map/document/vendor/location/address/city", "Fruit Market",
270                 "/map[@name='document']" + "/vendor[1]/location[2]/address[1]/city[1]");
271     }
272 
273     @Test
274     public void testMapElement() {
275         assertXPathValueAndPointer(context, "map/element/location/address/city", "Fruit Market", "/map[@name='element']/location[2]/address[1]/city[1]");
276     }
277 
278     @Test
279     public void testMapList() {
280         assertXPathValueAndPointer(context, "map/list[1]", "string", "/map[@name='list'][1]");
281     }
282 
283     @Test
284     public void testMapMap() {
285         assertXPathValueAndPointer(context, "map/map/string", "string", "/map[@name='map'][@name='string']");
286     }
287 
288     @Test
289     public void testMapPrimitive() {
290         assertXPathValueAndPointer(context, "map/string", "string", "/map[@name='string']");
291     }
292 
293     @Test
294     public void testMatrix() {
295         assertXPathValueAndPointer(context, "$matrix[1]/.[1]", Integer.valueOf(3), "$matrix[1]/.[1]");
296         context.setValue("$matrix[1]/.[1]", Integer.valueOf(2));
297         assertXPathValueAndPointer(context, "matrix[1]/.[1]", Integer.valueOf(3), "/matrix[1]/.[1]");
298         context.setValue("matrix[1]/.[1]", "2");
299         assertXPathValue(context, "matrix[1]/.[1]", Integer.valueOf(2));
300         context.getVariables().declareVariable("wholebean", context.getContextBean());
301         assertXPathValueAndPointer(context, "$wholebean/matrix[1]/.[1]", Integer.valueOf(2), "$wholebean/matrix[1]/.[1]");
302         assertThrows(Exception.class, () -> context.setValue("$wholebean/matrix[1]/.[2]", "4"), "Exception setting value of non-existent element");
303         assertThrows(Exception.class, () -> context.setValue("$wholebean/matrix[2]/.[1]", "4"), "Exception setting value of non-existent element");
304     }
305 
306     @Test
307     public void testNull() {
308         assertXPathPointerLenient(context, "$null", "$null");
309         assertXPathPointerLenient(context, "$null[3]", "$null[3]");
310         assertXPathPointerLenient(context, "$testnull/nothing", "$testnull/nothing");
311         assertXPathPointerLenient(context, "$testnull/nothing[2]", "$testnull/nothing[2]");
312         assertXPathPointerLenient(context, "beans[8]/int", "/beans[8]/int");
313         assertXPathValueIterator(context, "$testnull/nothing[1]", list(null));
314         final JXPathContext ctx = JXPathContext.newContext(new TestNull());
315         assertXPathValue(ctx, "nothing", null);
316         assertXPathValue(ctx, "child/nothing", null);
317         assertXPathValue(ctx, "array[2]", null);
318         assertXPathValueLenient(ctx, "nothing/something", null);
319         assertXPathValueLenient(ctx, "array[2]/something", null);
320     }
321 
322     @Test
323     public void testRootAsCollection() {
324         assertXPathValue(context, ".[1]/string", "string");
325     }
326 
327     @Test
328     public void testVar() {
329         context.getVariables().declareVariable("foo:bar", "baz");
330         assertXPathValueAndPointer(context, "$foo:bar", "baz", "$foo:bar");
331     }
332 
333     @Test
334     public void testVarBean() {
335         assertXPathValueAndPointer(context, "$bean/int", Integer.valueOf(1), "$bean/int");
336     }
337 
338     @Test
339     public void testVarContainer() {
340         assertXPathValueAndPointer(context, "$container/vendor/location/address/city", "Fruit Market", "$container/vendor[1]/location[2]/address[1]/city[1]");
341     }
342 
343     @Test
344     public void testVarDocument() {
345         assertXPathValueAndPointer(context, "$document/vendor/location/address/city", "Fruit Market", "$document/vendor[1]/location[2]/address[1]/city[1]");
346     }
347 
348     @Test
349     public void testVarElement() {
350         assertXPathValueAndPointer(context, "$element/location/address/city", "Fruit Market", "$element/location[2]/address[1]/city[1]");
351     }
352 
353     @Test
354     public void testVarList() {
355         assertXPathValueAndPointer(context, "$list[1]", "string", "$list[1]");
356     }
357 
358     @Test
359     public void testVarMap() {
360         assertXPathValueAndPointer(context, "$map/string", "string", "$map[@name='string']");
361     }
362 
363     @Test
364     public void testVarPrimitive() {
365         assertXPathValueAndPointer(context, "$string", "string", "$string");
366     }
367 }