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 }