1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.jxpath.ri;
19
20 import java.lang.ref.SoftReference;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.Map;
27 import java.util.Map.Entry;
28 import java.util.Vector;
29
30 import org.apache.commons.jxpath.CompiledExpression;
31 import org.apache.commons.jxpath.ExceptionHandler;
32 import org.apache.commons.jxpath.Function;
33 import org.apache.commons.jxpath.Functions;
34 import org.apache.commons.jxpath.JXPathContext;
35 import org.apache.commons.jxpath.JXPathException;
36 import org.apache.commons.jxpath.JXPathFunctionNotFoundException;
37 import org.apache.commons.jxpath.JXPathInvalidSyntaxException;
38 import org.apache.commons.jxpath.JXPathNotFoundException;
39 import org.apache.commons.jxpath.JXPathTypeConversionException;
40 import org.apache.commons.jxpath.Pointer;
41 import org.apache.commons.jxpath.ri.axes.InitialContext;
42 import org.apache.commons.jxpath.ri.axes.RootContext;
43 import org.apache.commons.jxpath.ri.compiler.Expression;
44 import org.apache.commons.jxpath.ri.compiler.LocationPath;
45 import org.apache.commons.jxpath.ri.compiler.Path;
46 import org.apache.commons.jxpath.ri.compiler.TreeCompiler;
47 import org.apache.commons.jxpath.ri.model.NodePointer;
48 import org.apache.commons.jxpath.ri.model.NodePointerFactory;
49 import org.apache.commons.jxpath.ri.model.VariablePointerFactory;
50 import org.apache.commons.jxpath.ri.model.beans.BeanPointerFactory;
51 import org.apache.commons.jxpath.ri.model.beans.CollectionPointerFactory;
52 import org.apache.commons.jxpath.ri.model.container.ContainerPointerFactory;
53 import org.apache.commons.jxpath.ri.model.dynamic.DynamicPointerFactory;
54 import org.apache.commons.jxpath.util.ClassLoaderUtil;
55 import org.apache.commons.jxpath.util.ReverseComparator;
56 import org.apache.commons.jxpath.util.TypeUtils;
57
58
59
60
61 public class JXPathContextReferenceImpl extends JXPathContext {
62
63
64
65
66 public static final boolean USE_SOFT_CACHE = true;
67 private static final Compiler COMPILER = new TreeCompiler();
68 private static Map<String, Object> compiled = new HashMap<>();
69 private static int cleanupCount;
70 private static NodePointerFactory[] nodeFactoryArray;
71
72 private static final int CLEANUP_THRESHOLD = 500;
73 private static final Vector<NodePointerFactory> nodeFactories = new Vector<>();
74 static {
75 nodeFactories.add(new CollectionPointerFactory());
76 nodeFactories.add(new BeanPointerFactory());
77 nodeFactories.add(new DynamicPointerFactory());
78 nodeFactories.add(new VariablePointerFactory());
79
80 final NodePointerFactory domFactory = (NodePointerFactory) allocateConditionally("org.apache.commons.jxpath.ri.model.dom.DOMPointerFactory",
81 "org.w3c.dom.Node");
82 if (domFactory != null) {
83 nodeFactories.add(domFactory);
84 }
85
86 final NodePointerFactory jdomFactory = (NodePointerFactory) allocateConditionally("org.apache.commons.jxpath.ri.model.jdom.JDOMPointerFactory",
87 "org.jdom.Document");
88 if (jdomFactory != null) {
89 nodeFactories.add(jdomFactory);
90 }
91
92 final NodePointerFactory dynaBeanFactory = (NodePointerFactory) allocateConditionally(
93 "org.apache.commons.jxpath.ri.model.dynabeans." + "DynaBeanPointerFactory", "org.apache.commons.beanutils.DynaBean");
94 if (dynaBeanFactory != null) {
95 nodeFactories.add(dynaBeanFactory);
96 }
97 nodeFactories.add(new ContainerPointerFactory());
98 createNodeFactoryArray();
99 }
100
101
102
103
104
105
106
107 public static void addNodePointerFactory(final NodePointerFactory factory) {
108 synchronized (nodeFactories) {
109 nodeFactories.add(factory);
110 nodeFactoryArray = null;
111 }
112 }
113
114
115
116
117
118
119
120
121 public static Object allocateConditionally(final String className, final String existenceCheckClassName) {
122 try {
123 try {
124 ClassLoaderUtil.getClass(existenceCheckClassName, true);
125 } catch (final ClassNotFoundException ex) {
126 return null;
127 }
128 return ClassLoaderUtil.getClass(className, true).getConstructor().newInstance();
129 } catch (final Exception ex) {
130 throw new JXPathException("Cannot allocate " + className, ex);
131 }
132 }
133
134
135
136
137 private static synchronized void createNodeFactoryArray() {
138 if (nodeFactoryArray == null) {
139 nodeFactoryArray = nodeFactories.toArray(new NodePointerFactory[nodeFactories.size()]);
140 Arrays.sort(nodeFactoryArray, (a, b) -> {
141 final int orderA = a.getOrder();
142 final int orderB = b.getOrder();
143 return orderA - orderB;
144 });
145 }
146 }
147
148
149
150
151
152
153 public static NodePointerFactory[] getNodePointerFactories() {
154 return nodeFactoryArray;
155 }
156
157
158
159
160
161
162
163
164 public static boolean removeNodePointerFactory(final NodePointerFactory factory) {
165 synchronized (nodeFactories) {
166 final boolean remove = nodeFactories.remove(factory);
167 nodeFactoryArray = null;
168 return remove;
169 }
170 }
171
172
173 protected NamespaceResolver namespaceResolver;
174 private Pointer rootPointer;
175 private Pointer contextPointer;
176
177
178
179
180
181
182
183 protected JXPathContextReferenceImpl(final JXPathContext parentContext, final Object contextBean) {
184 this(parentContext, contextBean, null);
185 }
186
187
188
189
190
191
192
193
194 public JXPathContextReferenceImpl(final JXPathContext parentContext, final Object contextBean, final Pointer contextPointer) {
195 super(parentContext, contextBean);
196 synchronized (nodeFactories) {
197 createNodeFactoryArray();
198 }
199 if (contextPointer != null) {
200 this.contextPointer = contextPointer;
201 this.rootPointer = NodePointer.newNodePointer(new QName(null, "root"), contextPointer.getRootNode(), getLocale());
202 } else {
203 this.contextPointer = NodePointer.newNodePointer(new QName(null, "root"), contextBean, getLocale());
204 this.rootPointer = this.contextPointer;
205 }
206 NamespaceResolver parentNR = null;
207 if (parentContext instanceof JXPathContextReferenceImpl) {
208 parentNR = ((JXPathContextReferenceImpl) parentContext).getNamespaceResolver();
209 }
210 namespaceResolver = new NamespaceResolver(parentNR);
211 namespaceResolver.setNamespaceContextPointer((NodePointer) this.contextPointer);
212 }
213
214
215
216
217
218
219 private void checkSimplePath(final Expression expr) {
220 if (!(expr instanceof LocationPath) || !((LocationPath) expr).isSimplePath()) {
221 throw new JXPathInvalidSyntaxException(
222 "JXPath can only create a path if it uses exclusively " + "the child:: and attribute:: axes and has " + "no context-dependent predicates");
223 }
224 }
225
226
227
228
229
230
231
232 private Expression compileExpression(final String xpath) {
233 Expression expr;
234 synchronized (compiled) {
235 if (USE_SOFT_CACHE) {
236 expr = null;
237 final SoftReference<Expression> ref = (SoftReference) compiled.get(xpath);
238 if (ref != null) {
239 expr = ref.get();
240 }
241 } else {
242 expr = (Expression) compiled.get(xpath);
243 }
244 }
245 if (expr != null) {
246 return expr;
247 }
248 expr = (Expression) Parser.parseExpression(xpath, getCompiler());
249 synchronized (compiled) {
250 if (USE_SOFT_CACHE) {
251 if (cleanupCount++ >= CLEANUP_THRESHOLD) {
252 final Iterator<Entry<String, Object>> it = compiled.entrySet().iterator();
253 while (it.hasNext()) {
254 final Entry<String, ?> me = it.next();
255 if (((SoftReference<Expression>) me.getValue()).get() == null) {
256 it.remove();
257 }
258 }
259 cleanupCount = 0;
260 }
261 compiled.put(xpath, new SoftReference<>(expr));
262 } else {
263 compiled.put(xpath, expr);
264 }
265 }
266 return expr;
267 }
268
269 @Override
270 protected CompiledExpression compilePath(final String xpath) {
271 return new JXPathCompiledExpression(xpath, compileExpression(xpath));
272 }
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302 @Override
303 public Pointer createPath(final String xpath) {
304 return createPath(xpath, compileExpression(xpath));
305 }
306
307
308
309
310
311
312
313
314 public Pointer createPath(final String xpath, final Expression expr) {
315 try {
316 final Object result = expr.computeValue(getEvalContext());
317 Pointer pointer;
318 if (result instanceof Pointer) {
319 pointer = (Pointer) result;
320 } else if (result instanceof EvalContext) {
321 final EvalContext ctx = (EvalContext) result;
322 pointer = ctx.getSingleNodePointer();
323 } else {
324 checkSimplePath(expr);
325
326 throw new JXPathException("Cannot create path:" + xpath);
327 }
328 return ((NodePointer) pointer).createPath(this);
329 } catch (final Throwable ex) {
330 throw new JXPathException("Exception trying to create XPath " + xpath, ex);
331 }
332 }
333
334
335
336
337
338
339
340
341
342 public Pointer createPathAndSetValue(final String xpath, final Expression expr, final Object value) {
343 try {
344 return setValue(xpath, expr, value, true);
345 } catch (final Throwable ex) {
346 throw new JXPathException("Exception trying to create XPath " + xpath, ex);
347 }
348 }
349
350 @Override
351 public Pointer createPathAndSetValue(final String xpath, final Object value) {
352 return createPathAndSetValue(xpath, compileExpression(xpath), value);
353 }
354
355
356
357
358
359
360 public EvalContext getAbsoluteRootContext() {
361 return new InitialContext(new RootContext(this, getAbsoluteRootPointer()));
362 }
363
364
365
366
367
368
369 private NodePointer getAbsoluteRootPointer() {
370 return (NodePointer) rootPointer;
371 }
372
373
374
375
376
377
378
379
380 protected Compiler getCompiler() {
381 return COMPILER;
382 }
383
384 @Override
385 public Pointer getContextPointer() {
386 return contextPointer;
387 }
388
389
390
391
392
393
394 private EvalContext getEvalContext() {
395 return new InitialContext(new RootContext(this, (NodePointer) getContextPointer()));
396 }
397
398
399
400
401
402
403
404
405 public Function getFunction(final QName functionName, final Object[] parameters) {
406 final String namespace = functionName.getPrefix();
407 final String name = functionName.getName();
408 JXPathContext funcCtx = this;
409 Function func;
410 Functions funcs;
411 while (funcCtx != null) {
412 funcs = funcCtx.getFunctions();
413 if (funcs != null) {
414 func = funcs.getFunction(namespace, name, parameters);
415 if (func != null) {
416 return func;
417 }
418 }
419 funcCtx = funcCtx.getParentContext();
420 }
421 throw new JXPathFunctionNotFoundException("Undefined function: " + functionName.toString());
422 }
423
424 @Override
425 public Pointer getNamespaceContextPointer() {
426 return namespaceResolver.getNamespaceContextPointer();
427 }
428
429
430
431
432
433
434 public NamespaceResolver getNamespaceResolver() {
435 namespaceResolver.seal();
436 return namespaceResolver;
437 }
438
439 @Override
440 public String getNamespaceURI(final String prefix) {
441 return namespaceResolver.getNamespaceURI(prefix);
442 }
443
444 @Override
445 public Pointer getPointer(final String xpath) {
446 return getPointer(xpath, compileExpression(xpath));
447 }
448
449
450
451
452
453
454
455
456 public Pointer getPointer(final String xpath, final Expression expr) {
457 Object result = expr.computeValue(getEvalContext());
458 if (result instanceof EvalContext) {
459 result = ((EvalContext) result).getSingleNodePointer();
460 }
461 if (result instanceof Pointer) {
462 if (!isLenient() && !((NodePointer) result).isActual()) {
463 throw new JXPathNotFoundException("No pointer for xpath: " + xpath);
464 }
465 return (Pointer) result;
466 }
467 return NodePointer.newNodePointer(null, result, getLocale());
468 }
469
470
471
472
473
474
475 @Override
476 public String getPrefix(final String namespaceURI) {
477 return namespaceResolver.getPrefix(namespaceURI);
478 }
479
480 @Override
481 public JXPathContext getRelativeContext(final Pointer pointer) {
482 final Object contextBean = pointer.getNode();
483 if (contextBean == null) {
484 throw new JXPathException("Cannot create a relative context for a non-existent node: " + pointer);
485 }
486 return new JXPathContextReferenceImpl(this, contextBean, pointer);
487 }
488
489
490
491
492
493
494
495 @Override
496 public Object getValue(final String xpath) {
497 final Expression expression = compileExpression(xpath);
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529 return getValue(xpath, expression);
530 }
531
532
533
534
535
536
537
538
539 @Override
540 public Object getValue(final String xpath, final Class requiredType) {
541 final Expression expr = compileExpression(xpath);
542 return getValue(xpath, expr, requiredType);
543 }
544
545
546
547
548
549
550
551
552 public Object getValue(final String xpath, final Expression expr) {
553 Object result = expr.computeValue(getEvalContext());
554 if (result == null) {
555 if (expr instanceof Path && !isLenient()) {
556 throw new JXPathNotFoundException("No value for xpath: " + xpath);
557 }
558 return null;
559 }
560 if (result instanceof EvalContext) {
561 final EvalContext ctx = (EvalContext) result;
562 result = ctx.getSingleNodePointer();
563 if (!isLenient() && result == null) {
564 throw new JXPathNotFoundException("No value for xpath: " + xpath);
565 }
566 }
567 if (result instanceof NodePointer) {
568 result = ((NodePointer) result).getValuePointer();
569 if (!isLenient()) {
570 NodePointer.verify((NodePointer) result);
571 }
572 result = ((NodePointer) result).getValue();
573 }
574 return result;
575 }
576
577
578
579
580
581
582
583
584
585 public Object getValue(final String xpath, final Expression expr, final Class requiredType) {
586 Object value = getValue(xpath, expr);
587 if (value != null && requiredType != null) {
588 if (!TypeUtils.canConvert(value, requiredType)) {
589 throw new JXPathTypeConversionException("Invalid expression type. '" + xpath + "' returns " + value.getClass().getName()
590 + ". It cannot be converted to " + requiredType.getName());
591 }
592 value = TypeUtils.convert(value, requiredType);
593 }
594 return value;
595 }
596
597
598
599
600
601
602
603 public NodePointer getVariablePointer(final QName qName) {
604 return NodePointer.newNodePointer(qName, VariablePointerFactory.contextWrapper(this), getLocale());
605 }
606
607
608
609
610
611
612
613
614 @Override
615 public Iterator iterate(final String xpath) {
616 return iterate(xpath, compileExpression(xpath));
617 }
618
619
620
621
622
623
624
625
626
627 public Iterator iterate(final String xpath, final Expression expr) {
628 return expr.iterate(getEvalContext());
629 }
630
631
632
633
634
635
636
637
638 @Override
639 public Iterator<Pointer> iteratePointers(final String xpath) {
640 return iteratePointers(xpath, compileExpression(xpath));
641 }
642
643
644
645
646
647
648
649
650
651 public Iterator<Pointer> iteratePointers(final String xpath, final Expression expr) {
652 return expr.iteratePointers(getEvalContext());
653 }
654
655 @Override
656 public void registerNamespace(final String prefix, final String namespaceURI) {
657 if (namespaceResolver.isSealed()) {
658 namespaceResolver = (NamespaceResolver) namespaceResolver.clone();
659 }
660 namespaceResolver.registerNamespace(prefix, namespaceURI);
661 }
662
663 @Override
664 public void removeAll(final String xpath) {
665 removeAll(xpath, compileExpression(xpath));
666 }
667
668
669
670
671
672
673
674 public void removeAll(final String xpath, final Expression expr) {
675 try {
676 final ArrayList<NodePointer> list = new ArrayList<>();
677 Iterator<NodePointer> it = expr.iteratePointers(getEvalContext());
678 while (it.hasNext()) {
679 list.add(it.next());
680 }
681 Collections.sort(list, ReverseComparator.INSTANCE);
682 it = list.iterator();
683 if (it.hasNext()) {
684 final NodePointer pointer = it.next();
685 pointer.remove();
686 while (it.hasNext()) {
687 removePath(it.next().asPath());
688 }
689 }
690 } catch (final Throwable ex) {
691 throw new JXPathException("Exception trying to remove all for xpath " + xpath, ex);
692 }
693 }
694
695 @Override
696 public void removePath(final String xpath) {
697 removePath(xpath, compileExpression(xpath));
698 }
699
700
701
702
703
704
705
706 public void removePath(final String xpath, final Expression expr) {
707 try {
708 final NodePointer pointer = (NodePointer) getPointer(xpath, expr);
709 if (pointer != null) {
710 pointer.remove();
711 }
712 } catch (final Throwable ex) {
713 throw new JXPathException("Exception trying to remove XPath " + xpath, ex);
714 }
715 }
716
717
718
719
720 @Override
721 public void setExceptionHandler(final ExceptionHandler exceptionHandler) {
722 if (rootPointer instanceof NodePointer) {
723 ((NodePointer) rootPointer).setExceptionHandler(exceptionHandler);
724 }
725 }
726
727 @Override
728 public void setNamespaceContextPointer(final Pointer pointer) {
729 if (namespaceResolver.isSealed()) {
730 namespaceResolver = (NamespaceResolver) namespaceResolver.clone();
731 }
732 namespaceResolver.setNamespaceContextPointer((NodePointer) pointer);
733 }
734
735
736
737
738
739
740
741
742 public void setValue(final String xpath, final Expression expr, final Object value) {
743 try {
744 setValue(xpath, expr, value, false);
745 } catch (final Throwable ex) {
746 throw new JXPathException("Exception trying to set value with XPath " + xpath, ex);
747 }
748 }
749
750
751
752
753
754
755
756
757
758
759 private Pointer setValue(final String xpath, final Expression expr, final Object value, final boolean create) {
760 final Object result = expr.computeValue(getEvalContext());
761 Pointer pointer;
762 if (result instanceof Pointer) {
763 pointer = (Pointer) result;
764 } else if (result instanceof EvalContext) {
765 final EvalContext ctx = (EvalContext) result;
766 pointer = ctx.getSingleNodePointer();
767 } else {
768 if (create) {
769 checkSimplePath(expr);
770 }
771
772 throw new JXPathException("Cannot set value for xpath: " + xpath);
773 }
774 if (create) {
775 pointer = ((NodePointer) pointer).createPath(this, value);
776 } else {
777 pointer.setValue(value);
778 }
779 return pointer;
780 }
781
782 @Override
783 public void setValue(final String xpath, final Object value) {
784 setValue(xpath, compileExpression(xpath), value);
785 }
786 }