KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > alfresco > repo > search > NodeServiceXPath


1 /*
2  * Copyright (C) 2005 Alfresco, Inc.
3  *
4  * Licensed under the Mozilla Public License version 1.1
5  * with a permitted attribution clause. You may obtain a
6  * copy of the License at
7  *
8  * http://www.alfresco.org/legal/license.txt
9  *
10  * Unless required by applicable law or agreed to in writing,
11  * software distributed under the License is distributed on an
12  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13  * either express or implied. See the License for the specific
14  * language governing permissions and limitations under the
15  * License.
16  */

17 package org.alfresco.repo.search;
18
19 import java.util.ArrayList JavaDoc;
20 import java.util.HashSet JavaDoc;
21 import java.util.List JavaDoc;
22 import java.util.Set JavaDoc;
23 import java.util.StringTokenizer JavaDoc;
24
25 import org.alfresco.error.AlfrescoRuntimeException;
26 import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
27 import org.alfresco.service.cmr.repository.ChildAssociationRef;
28 import org.alfresco.service.cmr.repository.NodeRef;
29 import org.alfresco.service.cmr.search.QueryParameterDefinition;
30 import org.alfresco.service.cmr.search.SearchParameters;
31 import org.alfresco.service.namespace.NamespacePrefixResolver;
32 import org.alfresco.service.namespace.QName;
33 import org.alfresco.service.namespace.QNamePattern;
34 import org.alfresco.util.ISO9075;
35 import org.apache.commons.logging.Log;
36 import org.apache.commons.logging.LogFactory;
37 import org.jaxen.BaseXPath;
38 import org.jaxen.Context;
39 import org.jaxen.Function;
40 import org.jaxen.FunctionCallException;
41 import org.jaxen.FunctionContext;
42 import org.jaxen.JaxenException;
43 import org.jaxen.Navigator;
44 import org.jaxen.SimpleFunctionContext;
45 import org.jaxen.SimpleVariableContext;
46 import org.jaxen.function.BooleanFunction;
47 import org.jaxen.function.CeilingFunction;
48 import org.jaxen.function.ConcatFunction;
49 import org.jaxen.function.ContainsFunction;
50 import org.jaxen.function.CountFunction;
51 import org.jaxen.function.FalseFunction;
52 import org.jaxen.function.FloorFunction;
53 import org.jaxen.function.IdFunction;
54 import org.jaxen.function.LangFunction;
55 import org.jaxen.function.LastFunction;
56 import org.jaxen.function.LocalNameFunction;
57 import org.jaxen.function.NameFunction;
58 import org.jaxen.function.NamespaceUriFunction;
59 import org.jaxen.function.NormalizeSpaceFunction;
60 import org.jaxen.function.NotFunction;
61 import org.jaxen.function.NumberFunction;
62 import org.jaxen.function.PositionFunction;
63 import org.jaxen.function.RoundFunction;
64 import org.jaxen.function.StartsWithFunction;
65 import org.jaxen.function.StringFunction;
66 import org.jaxen.function.StringLengthFunction;
67 import org.jaxen.function.SubstringAfterFunction;
68 import org.jaxen.function.SubstringBeforeFunction;
69 import org.jaxen.function.SubstringFunction;
70 import org.jaxen.function.SumFunction;
71 import org.jaxen.function.TranslateFunction;
72 import org.jaxen.function.TrueFunction;
73 import org.jaxen.function.ext.EndsWithFunction;
74 import org.jaxen.function.ext.EvaluateFunction;
75 import org.jaxen.function.ext.LowerFunction;
76 import org.jaxen.function.ext.MatrixConcatFunction;
77 import org.jaxen.function.ext.UpperFunction;
78 import org.jaxen.function.xslt.DocumentFunction;
79
80 /**
81  * Represents an xpath statement that resolves against a
82  * <code>NodeService</code>
83  *
84  * @author Andy Hind
85  */

86 public class NodeServiceXPath extends BaseXPath
87 {
88     private static final long serialVersionUID = 3834032441789592882L;
89
90     private static String JavaDoc JCR_URI = "http://www.jcp.org/jcr/1.0";
91     
92     private static Log logger = LogFactory.getLog(NodeServiceXPath.class);
93
94     /**
95      *
96      * @param xpath
97      * the xpath statement
98      * @param documentNavigator
99      * the navigator that will allow the xpath to be resolved
100      * @param paramDefs
101      * parameters to resolve variables required by xpath
102      * @throws JaxenException
103      */

104     public NodeServiceXPath(String JavaDoc xpath, DocumentNavigator documentNavigator, QueryParameterDefinition[] paramDefs)
105             throws JaxenException
106     {
107         super(xpath, documentNavigator);
108
109         if (logger.isDebugEnabled())
110         {
111             StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
112             sb.append("Created XPath: \n")
113               .append(" XPath: ").append(xpath).append("\n")
114               .append(" Parameters: \n");
115             for (int i = 0; paramDefs != null && i < paramDefs.length; i++)
116             {
117                 sb.append(" Parameter: \n")
118                   .append(" name: ").append(paramDefs[i].getQName()).append("\n")
119                   .append(" value: ").append(paramDefs[i].getDefault()).append("\n");
120             }
121             logger.debug(sb.toString());
122         }
123         
124         // Add support for parameters
125
if (paramDefs != null)
126         {
127             SimpleVariableContext svc = (SimpleVariableContext) this.getVariableContext();
128             for (int i = 0; i < paramDefs.length; i++)
129             {
130                 if (!paramDefs[i].hasDefaultValue())
131                 {
132                     throw new AlfrescoRuntimeException("Parameter must have default value");
133                 }
134                 Object JavaDoc value = null;
135                 if (paramDefs[i].getDataTypeDefinition().getName().equals(DataTypeDefinition.BOOLEAN))
136                 {
137                     value = Boolean.valueOf(paramDefs[i].getDefault());
138                 }
139                 else if (paramDefs[i].getDataTypeDefinition().getName().equals(DataTypeDefinition.DOUBLE))
140                 {
141                     value = Double.valueOf(paramDefs[i].getDefault());
142                 }
143                 else if (paramDefs[i].getDataTypeDefinition().getName().equals(DataTypeDefinition.FLOAT))
144                 {
145                     value = Float.valueOf(paramDefs[i].getDefault());
146                 }
147                 else if (paramDefs[i].getDataTypeDefinition().getName().equals(DataTypeDefinition.INT))
148                 {
149                     value = Integer.valueOf(paramDefs[i].getDefault());
150                 }
151                 else if (paramDefs[i].getDataTypeDefinition().getName().equals(DataTypeDefinition.LONG))
152                 {
153                     value = Long.valueOf(paramDefs[i].getDefault());
154                 }
155                 else
156                 {
157                     value = paramDefs[i].getDefault();
158                 }
159                 svc.setVariableValue(paramDefs[i].getQName().getNamespaceURI(), paramDefs[i].getQName().getLocalName(),
160                         value);
161             }
162         }
163
164         for (String JavaDoc prefix : documentNavigator.getNamespacePrefixResolver().getPrefixes())
165         {
166             addNamespace(prefix, documentNavigator.getNamespacePrefixResolver().getNamespaceURI(prefix));
167         }
168     }
169     
170     /**
171      * Jaxen has some magic with its IdentitySet, which means that we can get different results
172      * depending on whether we cache {@link ChildAssociationRef } instances or not.
173      * <p>
174      * So, duplicates are eliminated here before the results are returned.
175      */

176     @SuppressWarnings JavaDoc("unchecked")
177     @Override JavaDoc
178     public List JavaDoc selectNodes(Object JavaDoc arg0) throws JaxenException
179     {
180         if (logger.isDebugEnabled())
181         {
182             logger.debug("Selecting using XPath: \n" +
183                     " XPath: " + this + "\n" +
184                     " starting at: " + arg0);
185         }
186         
187         List JavaDoc<Object JavaDoc> resultsWithDuplicates = super.selectNodes(arg0);
188         
189         Set JavaDoc<Object JavaDoc> set = new HashSet JavaDoc<Object JavaDoc>(resultsWithDuplicates);
190         
191         // now return as a list again
192
List JavaDoc<Object JavaDoc> results = resultsWithDuplicates;
193         results.clear();
194         results.addAll(set);
195         
196         // done
197
return results;
198     }
199
200     public static class FirstFunction implements Function
201     {
202
203         public Object JavaDoc call(Context context, List JavaDoc args) throws FunctionCallException
204         {
205             if (args.size() == 0)
206             {
207                 return evaluate(context);
208             }
209
210             throw new FunctionCallException("first() requires no arguments.");
211         }
212
213         public static Double JavaDoc evaluate(Context context)
214         {
215             return new Double JavaDoc(1);
216         }
217     }
218
219     /**
220      * A boolean function to determine if a node type is a subtype of another
221      * type
222      */

223     static class SubTypeOf implements Function
224     {
225         public Object JavaDoc call(Context context, List JavaDoc args) throws FunctionCallException
226         {
227             if (args.size() != 1)
228             {
229                 throw new FunctionCallException("subtypeOf() requires one argument: subtypeOf(QName typeQName)");
230             }
231             return evaluate(context.getNodeSet(), args.get(0), context.getNavigator());
232         }
233
234         public Object JavaDoc evaluate(List JavaDoc nodes, Object JavaDoc qnameObj, Navigator nav)
235         {
236             if (nodes.size() != 1)
237             {
238                 return false;
239             }
240             // resolve the qname of the type we are checking for
241
String JavaDoc qnameStr = StringFunction.evaluate(qnameObj, nav);
242             if (qnameStr.equals("*"))
243             {
244                 return true;
245             }
246             QName typeQName;
247
248             if (qnameStr.startsWith("{"))
249             {
250                 typeQName = QName.createQName(qnameStr);
251             }
252             else
253             {
254                 typeQName = QName.createQName(qnameStr, ((DocumentNavigator) nav).getNamespacePrefixResolver());
255             }
256             // resolve the noderef
257
NodeRef nodeRef = null;
258             if (nav.isElement(nodes.get(0)))
259             {
260                 nodeRef = ((ChildAssociationRef) nodes.get(0)).getChildRef();
261             }
262             else if (nav.isAttribute(nodes.get(0)))
263             {
264                 nodeRef = ((DocumentNavigator.Property) nodes.get(0)).parent;
265             }
266
267             DocumentNavigator dNav = (DocumentNavigator) nav;
268             boolean result = dNav.isSubtypeOf(nodeRef, typeQName);
269             return result;
270         }
271     }
272
273     static class Deref implements Function
274     {
275
276         public Object JavaDoc call(Context context, List JavaDoc args) throws FunctionCallException
277         {
278             if (args.size() == 2)
279             {
280                 return evaluate(args.get(0), args.get(1), context.getNavigator());
281             }
282
283             throw new FunctionCallException("deref() requires two arguments.");
284         }
285
286         public Object JavaDoc evaluate(Object JavaDoc attributeName, Object JavaDoc pattern, Navigator nav)
287         {
288             List JavaDoc<Object JavaDoc> answer = new ArrayList JavaDoc<Object JavaDoc>();
289             String JavaDoc attributeValue = StringFunction.evaluate(attributeName, nav);
290             String JavaDoc patternValue = StringFunction.evaluate(pattern, nav);
291
292             // TODO: Ignore the pattern for now
293
// Should do a type pattern test
294
if ((attributeValue != null) && (attributeValue.length() > 0))
295             {
296                 DocumentNavigator dNav = (DocumentNavigator) nav;
297                 NodeRef nodeRef = new NodeRef(attributeValue);
298                 if (patternValue.equals("*"))
299                 {
300                     answer.add(dNav.getNode(nodeRef));
301                 }
302                 else
303                 {
304                     QNamePattern qNamePattern = new JCRPatternMatch(patternValue, dNav.getNamespacePrefixResolver());
305                     answer.addAll(dNav.getNode(nodeRef, qNamePattern));
306                 }
307
308             }
309             return answer;
310
311         }
312     }
313
314     /**
315      * A boolean function to determine if a node property matches a pattern
316      * and/or the node text matches the pattern.
317      * <p>
318      * The default is JSR170 compliant. The optional boolean allows searching
319      * only against the property value itself.
320      * <p>
321      * The search is always case-insensitive.
322      *
323      * @author Derek Hulley
324      */

325     static class Like implements Function
326     {
327         public Object JavaDoc call(Context context, List JavaDoc args) throws FunctionCallException
328         {
329             if (args.size() < 2 || args.size() > 3)
330             {
331                 throw new FunctionCallException("like() usage: like(@attr, 'pattern' [, includeFTS]) \n"
332                         + " - includeFTS can be 'true' or 'false' \n"
333                         + " - search is case-insensitive");
334             }
335             // default includeFTS to true
336
return evaluate(context.getNodeSet(), args.get(0), args.get(1), args.size() == 2 ? Boolean.toString(true)
337                     : args.get(2), context.getNavigator());
338         }
339
340         public Object JavaDoc evaluate(List JavaDoc nodes, Object JavaDoc obj, Object JavaDoc patternObj, Object JavaDoc includeFtsObj, Navigator nav)
341         {
342             Object JavaDoc attribute = null;
343             if (obj instanceof List JavaDoc)
344             {
345                 List JavaDoc list = (List JavaDoc) obj;
346                 if (list.isEmpty())
347                 {
348                     return false;
349                 }
350                 // do not recurse: only first list should unwrap
351
attribute = list.get(0);
352             }
353             if ((attribute == null) || !nav.isAttribute(attribute))
354             {
355                 return false;
356             }
357             if (nodes.size() != 1)
358             {
359                 return false;
360             }
361             if (!nav.isElement(nodes.get(0)))
362             {
363                 return false;
364             }
365             ChildAssociationRef car = (ChildAssociationRef) nodes.get(0);
366             String JavaDoc pattern = StringFunction.evaluate(patternObj, nav);
367             boolean includeFts = BooleanFunction.evaluate(includeFtsObj, nav);
368             QName qname = QName.createQName(nav.getAttributeNamespaceUri(attribute), ISO9075.decode(nav
369                     .getAttributeName(attribute)));
370
371             DocumentNavigator dNav = (DocumentNavigator) nav;
372             // JSR 170 includes full text matches
373
return dNav.like(car.getChildRef(), qname, pattern, includeFts);
374
375         }
376     }
377
378     static class Contains implements Function
379     {
380
381         public Object JavaDoc call(Context context, List JavaDoc args) throws FunctionCallException
382         {
383             if (args.size() != 1)
384             {
385                 throw new FunctionCallException("contains() usage: contains('pattern')");
386             }
387             return evaluate(context.getNodeSet(), args.get(0), context.getNavigator());
388         }
389
390         public Object JavaDoc evaluate(List JavaDoc nodes, Object JavaDoc pattern, Navigator nav)
391         {
392             if (nodes.size() != 1)
393             {
394                 return false;
395             }
396             QName qname = null;
397             NodeRef nodeRef = null;
398             if (nav.isElement(nodes.get(0)))
399             {
400                 qname = null; // should use all attributes and full text index
401
nodeRef = ((ChildAssociationRef) nodes.get(0)).getChildRef();
402             }
403             else if (nav.isAttribute(nodes.get(0)))
404             {
405                 qname = QName.createQName(
406                         nav.getAttributeNamespaceUri(nodes.get(0)),
407                         ISO9075.decode(nav.getAttributeName(nodes.get(0))));
408                 nodeRef = ((DocumentNavigator.Property) nodes.get(0)).parent;
409             }
410
411             String JavaDoc patternValue = StringFunction.evaluate(pattern, nav);
412             DocumentNavigator dNav = (DocumentNavigator) nav;
413
414             return dNav.contains(nodeRef, qname, patternValue, SearchParameters.OR);
415
416         }
417     }
418
419     static class JCRContains implements Function
420     {
421
422         public Object JavaDoc call(Context context, List JavaDoc args) throws FunctionCallException
423         {
424             if (args.size() == 2)
425             {
426                 if (context.getNavigator().isAttribute(context.getNodeSet().get(0)))
427                 {
428                     throw new FunctionCallException("jcr:contains() does not apply to an attribute context.");
429                 }
430                 return evaluate(context.getNodeSet(), args.get(0), args.get(1), context.getNavigator());
431             }
432
433             throw new FunctionCallException("contains() requires two argument.");
434         }
435
436         public Object JavaDoc evaluate(List JavaDoc nodes, Object JavaDoc identifier, Object JavaDoc pattern, Navigator nav)
437         {
438             if (nodes.size() != 1)
439             {
440                 return false;
441             }
442
443             QName qname = null;
444             NodeRef nodeRef = null;
445
446             Object JavaDoc target = identifier;
447
448             if (identifier instanceof List JavaDoc)
449             {
450                 List JavaDoc list = (List JavaDoc) identifier;
451                 if (list.isEmpty())
452                 {
453                     return false;
454                 }
455                 // do not recurse: only first list should unwrap
456
target = list.get(0);
457             }
458
459             if (nav.isElement(target))
460             {
461                 qname = null; // should use all attributes and full text index
462
nodeRef = ((ChildAssociationRef) target).getChildRef();
463             }
464             else if (nav.isAttribute(target))
465             {
466                 qname = QName.createQName(
467                         nav.getAttributeNamespaceUri(target),
468                         ISO9075.decode(nav.getAttributeName(target)));
469                 nodeRef = ((DocumentNavigator.Property) target).parent;
470             }
471
472             String JavaDoc patternValue = StringFunction.evaluate(pattern, nav);
473             DocumentNavigator dNav = (DocumentNavigator) nav;
474
475             return dNav.contains(nodeRef, qname, patternValue, SearchParameters.AND);
476
477         }
478     }
479
480     static class Score implements Function
481     {
482         private Double JavaDoc one = new Double JavaDoc(1);
483
484         public Object JavaDoc call(Context context, List JavaDoc args) throws FunctionCallException
485         {
486             return evaluate(context.getNodeSet(), context.getNavigator());
487         }
488
489         public Object JavaDoc evaluate(List JavaDoc nodes, Navigator nav)
490         {
491             return one;
492
493         }
494     }
495
496     protected FunctionContext createFunctionContext()
497     {
498         return XPathFunctionContext.getInstance();
499     }
500
501     public static class XPathFunctionContext extends SimpleFunctionContext
502     {
503         /**
504          * Singleton implementation.
505          */

506         private static class Singleton
507         {
508             /**
509              * Singleton instance.
510              */

511             private static XPathFunctionContext instance = new XPathFunctionContext();
512         }
513
514         /**
515          * Retrieve the singleton instance.
516          *
517          * @return the singleton instance
518          */

519         public static FunctionContext getInstance()
520         {
521             return Singleton.instance;
522         }
523
524         /**
525          * Construct.
526          *
527          * <p>
528          * Construct with all core XPath functions registered.
529          * </p>
530          */

531         public XPathFunctionContext()
532         {
533             // XXX could this be a HotSpot????
534
registerFunction("", // namespace URI
535
"boolean", new BooleanFunction());
536
537             registerFunction("", // namespace URI
538
"ceiling", new CeilingFunction());
539
540             registerFunction("", // namespace URI
541
"concat", new ConcatFunction());
542
543             registerFunction("", // namespace URI
544
"contains", new ContainsFunction());
545
546             registerFunction("", // namespace URI
547
"count", new CountFunction());
548
549             registerFunction("", // namespace URI
550
"document", new DocumentFunction());
551
552             registerFunction("", // namespace URI
553
"false", new FalseFunction());
554
555             registerFunction("", // namespace URI
556
"floor", new FloorFunction());
557
558             registerFunction("", // namespace URI
559
"id", new IdFunction());
560
561             registerFunction("", // namespace URI
562
"lang", new LangFunction());
563
564             registerFunction("", // namespace URI
565
"last", new LastFunction());
566
567             registerFunction("", // namespace URI
568
"local-name", new LocalNameFunction());
569
570             registerFunction("", // namespace URI
571
"name", new NameFunction());
572
573             registerFunction("", // namespace URI
574
"namespace-uri", new NamespaceUriFunction());
575
576             registerFunction("", // namespace URI
577
"normalize-space", new NormalizeSpaceFunction());
578
579             registerFunction("", // namespace URI
580
"not", new NotFunction());
581
582             registerFunction("", // namespace URI
583
"number", new NumberFunction());
584
585             registerFunction("", // namespace URI
586
"position", new PositionFunction());
587
588             registerFunction("", // namespace URI
589
"round", new RoundFunction());
590
591             registerFunction("", // namespace URI
592
"starts-with", new StartsWithFunction());
593
594             registerFunction("", // namespace URI
595
"string", new StringFunction());
596
597             registerFunction("", // namespace URI
598
"string-length", new StringLengthFunction());
599
600             registerFunction("", // namespace URI
601
"substring-after", new SubstringAfterFunction());
602
603             registerFunction("", // namespace URI
604
"substring-before", new SubstringBeforeFunction());
605
606             registerFunction("", // namespace URI
607
"substring", new SubstringFunction());
608
609             registerFunction("", // namespace URI
610
"sum", new SumFunction());
611
612             registerFunction("", // namespace URI
613
"true", new TrueFunction());
614
615             registerFunction("", // namespace URI
616
"translate", new TranslateFunction());
617
618             // register extension functions
619
// extension functions should go into a namespace, but which one?
620
// for now, keep them in default namespace to not break any code
621

622             registerFunction("", // namespace URI
623
"matrix-concat", new MatrixConcatFunction());
624
625             registerFunction("", // namespace URI
626
"evaluate", new EvaluateFunction());
627
628             registerFunction("", // namespace URI
629
"lower-case", new LowerFunction());
630
631             registerFunction("", // namespace URI
632
"upper-case", new UpperFunction());
633
634             registerFunction("", // namespace URI
635
"ends-with", new EndsWithFunction());
636
637             registerFunction("", "subtypeOf", new SubTypeOf());
638             registerFunction("", "deref", new Deref());
639             registerFunction("", "like", new Like());
640             registerFunction("", "contains", new Contains());
641
642             registerFunction("", "first", new FirstFunction());
643
644             // 170 functions
645

646             registerFunction(JCR_URI, "like", new Like());
647             registerFunction(JCR_URI, "score", new Score());
648             registerFunction(JCR_URI, "contains", new JCRContains());
649             registerFunction(JCR_URI, "deref", new Deref());
650
651         }
652     }
653
654     public static class JCRPatternMatch implements QNamePattern
655     {
656         private List JavaDoc<String JavaDoc> searches = new ArrayList JavaDoc<String JavaDoc>();
657
658         private NamespacePrefixResolver resolver;
659
660         /**
661          * Construct
662          *
663          * @param pattern
664          * JCR Pattern
665          * @param resolver
666          * Namespace Prefix Resolver
667          */

668         public JCRPatternMatch(String JavaDoc pattern, NamespacePrefixResolver resolver)
669         {
670             // TODO: Check for valid pattern
671

672             // Convert to regular expression
673
String JavaDoc regexPattern = pattern.replaceAll("\\*", ".*");
674
675             // Split into independent search strings
676
StringTokenizer JavaDoc tokenizer = new StringTokenizer JavaDoc(regexPattern, "|", false);
677             while (tokenizer.hasMoreTokens())
678             {
679                 String JavaDoc disjunct = tokenizer.nextToken().trim();
680                 this.searches.add(disjunct);
681             }
682
683             this.resolver = resolver;
684         }
685
686         /*
687          * (non-Javadoc)
688          *
689          * @see org.alfresco.service.namespace.QNamePattern#isMatch(org.alfresco.service.namespace.QName)
690          */

691         public boolean isMatch(QName qname)
692         {
693             String JavaDoc prefixedName = qname.toPrefixString(resolver);
694             for (String JavaDoc search : searches)
695             {
696                 if (prefixedName.matches(search))
697                 {
698                     return true;
699                 }
700             }
701             return false;
702         }
703
704     }
705
706 }
707
Popular Tags