1 17 package org.alfresco.repo.search; 18 19 import java.util.ArrayList ; 20 import java.util.HashSet ; 21 import java.util.List ; 22 import java.util.Set ; 23 import java.util.StringTokenizer ; 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 86 public class NodeServiceXPath extends BaseXPath 87 { 88 private static final long serialVersionUID = 3834032441789592882L; 89 90 private static String JCR_URI = "http://www.jcp.org/jcr/1.0"; 91 92 private static Log logger = LogFactory.getLog(NodeServiceXPath.class); 93 94 104 public NodeServiceXPath(String xpath, DocumentNavigator documentNavigator, QueryParameterDefinition[] paramDefs) 105 throws JaxenException 106 { 107 super(xpath, documentNavigator); 108 109 if (logger.isDebugEnabled()) 110 { 111 StringBuilder sb = new StringBuilder (); 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 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 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 prefix : documentNavigator.getNamespacePrefixResolver().getPrefixes()) 165 { 166 addNamespace(prefix, documentNavigator.getNamespacePrefixResolver().getNamespaceURI(prefix)); 167 } 168 } 169 170 176 @SuppressWarnings ("unchecked") 177 @Override 178 public List selectNodes(Object 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 <Object > resultsWithDuplicates = super.selectNodes(arg0); 188 189 Set <Object > set = new HashSet <Object >(resultsWithDuplicates); 190 191 List <Object > results = resultsWithDuplicates; 193 results.clear(); 194 results.addAll(set); 195 196 return results; 198 } 199 200 public static class FirstFunction implements Function 201 { 202 203 public Object call(Context context, List 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 evaluate(Context context) 214 { 215 return new Double (1); 216 } 217 } 218 219 223 static class SubTypeOf implements Function 224 { 225 public Object call(Context context, List 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 evaluate(List nodes, Object qnameObj, Navigator nav) 235 { 236 if (nodes.size() != 1) 237 { 238 return false; 239 } 240 String 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 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 call(Context context, List 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 evaluate(Object attributeName, Object pattern, Navigator nav) 287 { 288 List <Object > answer = new ArrayList <Object >(); 289 String attributeValue = StringFunction.evaluate(attributeName, nav); 290 String patternValue = StringFunction.evaluate(pattern, nav); 291 292 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 325 static class Like implements Function 326 { 327 public Object call(Context context, List 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 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 evaluate(List nodes, Object obj, Object patternObj, Object includeFtsObj, Navigator nav) 341 { 342 Object attribute = null; 343 if (obj instanceof List ) 344 { 345 List list = (List ) obj; 346 if (list.isEmpty()) 347 { 348 return false; 349 } 350 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 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 return dNav.like(car.getChildRef(), qname, pattern, includeFts); 374 375 } 376 } 377 378 static class Contains implements Function 379 { 380 381 public Object call(Context context, List 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 evaluate(List nodes, Object 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; 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 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 call(Context context, List 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 evaluate(List nodes, Object identifier, Object 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 target = identifier; 447 448 if (identifier instanceof List ) 449 { 450 List list = (List ) identifier; 451 if (list.isEmpty()) 452 { 453 return false; 454 } 455 target = list.get(0); 457 } 458 459 if (nav.isElement(target)) 460 { 461 qname = null; 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 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 one = new Double (1); 483 484 public Object call(Context context, List args) throws FunctionCallException 485 { 486 return evaluate(context.getNodeSet(), context.getNavigator()); 487 } 488 489 public Object evaluate(List 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 506 private static class Singleton 507 { 508 511 private static XPathFunctionContext instance = new XPathFunctionContext(); 512 } 513 514 519 public static FunctionContext getInstance() 520 { 521 return Singleton.instance; 522 } 523 524 531 public XPathFunctionContext() 532 { 533 registerFunction("", "boolean", new BooleanFunction()); 536 537 registerFunction("", "ceiling", new CeilingFunction()); 539 540 registerFunction("", "concat", new ConcatFunction()); 542 543 registerFunction("", "contains", new ContainsFunction()); 545 546 registerFunction("", "count", new CountFunction()); 548 549 registerFunction("", "document", new DocumentFunction()); 551 552 registerFunction("", "false", new FalseFunction()); 554 555 registerFunction("", "floor", new FloorFunction()); 557 558 registerFunction("", "id", new IdFunction()); 560 561 registerFunction("", "lang", new LangFunction()); 563 564 registerFunction("", "last", new LastFunction()); 566 567 registerFunction("", "local-name", new LocalNameFunction()); 569 570 registerFunction("", "name", new NameFunction()); 572 573 registerFunction("", "namespace-uri", new NamespaceUriFunction()); 575 576 registerFunction("", "normalize-space", new NormalizeSpaceFunction()); 578 579 registerFunction("", "not", new NotFunction()); 581 582 registerFunction("", "number", new NumberFunction()); 584 585 registerFunction("", "position", new PositionFunction()); 587 588 registerFunction("", "round", new RoundFunction()); 590 591 registerFunction("", "starts-with", new StartsWithFunction()); 593 594 registerFunction("", "string", new StringFunction()); 596 597 registerFunction("", "string-length", new StringLengthFunction()); 599 600 registerFunction("", "substring-after", new SubstringAfterFunction()); 602 603 registerFunction("", "substring-before", new SubstringBeforeFunction()); 605 606 registerFunction("", "substring", new SubstringFunction()); 608 609 registerFunction("", "sum", new SumFunction()); 611 612 registerFunction("", "true", new TrueFunction()); 614 615 registerFunction("", "translate", new TranslateFunction()); 617 618 622 registerFunction("", "matrix-concat", new MatrixConcatFunction()); 624 625 registerFunction("", "evaluate", new EvaluateFunction()); 627 628 registerFunction("", "lower-case", new LowerFunction()); 630 631 registerFunction("", "upper-case", new UpperFunction()); 633 634 registerFunction("", "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 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 <String > searches = new ArrayList <String >(); 657 658 private NamespacePrefixResolver resolver; 659 660 668 public JCRPatternMatch(String pattern, NamespacePrefixResolver resolver) 669 { 670 672 String regexPattern = pattern.replaceAll("\\*", ".*"); 674 675 StringTokenizer tokenizer = new StringTokenizer (regexPattern, "|", false); 677 while (tokenizer.hasMoreTokens()) 678 { 679 String disjunct = tokenizer.nextToken().trim(); 680 this.searches.add(disjunct); 681 } 682 683 this.resolver = resolver; 684 } 685 686 691 public boolean isMatch(QName qname) 692 { 693 String prefixedName = qname.toPrefixString(resolver); 694 for (String 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 |