1 17 package org.alfresco.web.bean; 18 19 import java.io.Serializable ; 20 import java.io.StringReader ; 21 import java.io.StringWriter ; 22 import java.util.ArrayList ; 23 import java.util.Collection ; 24 import java.util.HashMap ; 25 import java.util.Iterator ; 26 import java.util.List ; 27 import java.util.Map ; 28 import java.util.StringTokenizer ; 29 30 import javax.faces.context.FacesContext; 31 32 import org.alfresco.error.AlfrescoRuntimeException; 33 import org.alfresco.model.ContentModel; 34 import org.alfresco.repo.search.impl.lucene.QueryParser; 35 import org.alfresco.service.cmr.repository.ChildAssociationRef; 36 import org.alfresco.service.cmr.repository.NodeRef; 37 import org.alfresco.service.cmr.repository.Path; 38 import org.alfresco.service.namespace.NamespaceService; 39 import org.alfresco.service.namespace.QName; 40 import org.alfresco.util.ISO9075; 41 import org.alfresco.web.bean.repository.Repository; 42 import org.apache.commons.logging.Log; 43 import org.apache.commons.logging.LogFactory; 44 import org.dom4j.Document; 45 import org.dom4j.DocumentHelper; 46 import org.dom4j.Element; 47 import org.dom4j.io.OutputFormat; 48 import org.dom4j.io.SAXReader; 49 import org.dom4j.io.XMLWriter; 50 51 59 public final class SearchContext implements Serializable 60 { 61 private static final long serialVersionUID = 6730844584074229969L; 62 63 64 private static final String ELEMENT_VALUE = "value"; 65 private static final String ELEMENT_FIXED_VALUES = "fixed-values"; 66 private static final String ELEMENT_INCLUSIVE = "inclusive"; 67 private static final String ELEMENT_UPPER = "upper"; 68 private static final String ELEMENT_LOWER = "lower"; 69 private static final String ELEMENT_RANGE = "range"; 70 private static final String ELEMENT_RANGES = "ranges"; 71 private static final String ELEMENT_NAME = "name"; 72 private static final String ELEMENT_ATTRIBUTE = "attribute"; 73 private static final String ELEMENT_ATTRIBUTES = "attributes"; 74 private static final String ELEMENT_MIMETYPE = "mimetype"; 75 private static final String ELEMENT_CONTENT_TYPE = "content-type"; 76 private static final String ELEMENT_CATEGORY = "category"; 77 private static final String ELEMENT_CATEGORIES = "categories"; 78 private static final String ELEMENT_LOCATION = "location"; 79 private static final String ELEMENT_MODE = "mode"; 80 private static final String ELEMENT_TEXT = "text"; 81 private static final String ELEMENT_SEARCH = "search"; 82 private static final String ELEMENT_QUERY = "query"; 83 84 85 private static final char OP_WILDCARD = '*'; 86 private static final char OP_AND = '+'; 87 private static final char OP_NOT = '-'; 88 89 90 public final static int SEARCH_ALL = 0; 91 public final static int SEARCH_FILE_NAMES_CONTENTS = 1; 92 public final static int SEARCH_FILE_NAMES = 2; 93 public final static int SEARCH_SPACE_NAMES = 3; 94 95 96 private String text = ""; 97 98 99 private int mode = SearchContext.SEARCH_ALL; 100 101 102 private String location = null; 103 104 105 private String [] categories = new String [0]; 106 107 108 private String contentType = null; 109 110 111 private String mimeType = null; 112 113 114 private Map <QName, String > queryAttributes = new HashMap <QName, String >(5, 1.0f); 115 116 117 private Map <QName, RangeProperties> rangeAttributes = new HashMap <QName, RangeProperties>(5, 1.0f); 118 119 120 private Map <QName, String > queryFixedValues = new HashMap <QName, String >(5, 1.0f); 121 122 123 private boolean forceAndTerms = false; 124 125 126 private static Log logger = LogFactory.getLog(SearchContext.class); 127 128 129 137 public String buildQuery(int minimum) 138 { 139 String query; 140 boolean validQuery = false; 141 142 String nameAttr = Repository.escapeQName(QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, ELEMENT_NAME)); 144 145 String text = this.text.trim(); 147 148 StringBuilder fullTextBuf = new StringBuilder (64); 149 StringBuilder nameAttrBuf = new StringBuilder (64); 150 151 if (text.length() != 0 && text.length() >= minimum) 152 { 153 if (text.indexOf(' ') == -1 && text.charAt(0) != '"') 154 { 155 boolean operatorAND = (text.charAt(0) == OP_AND); 157 boolean operatorNOT = (text.charAt(0) == OP_NOT); 158 if (operatorAND || operatorNOT) 160 { 161 text = text.substring(1); 162 } 163 164 if (operatorNOT) 166 { 167 fullTextBuf.append(OP_NOT); 168 nameAttrBuf.append(OP_NOT); 169 } 170 171 if (text.charAt(0) != OP_WILDCARD) 173 { 174 String safeText = QueryParser.escape(text); 176 fullTextBuf.append("TEXT:").append(safeText).append(OP_WILDCARD); 177 nameAttrBuf.append("@").append(nameAttr).append(":").append(safeText).append(OP_WILDCARD); 178 } 179 else 180 { 181 String safeText = QueryParser.escape(text.substring(1)); 183 fullTextBuf.append("TEXT:*").append(safeText).append(OP_WILDCARD); 184 nameAttrBuf.append("@").append(nameAttr).append(":*").append(safeText).append(OP_WILDCARD); 185 } 186 } 187 else 188 { 189 if (text.charAt(0) == '"' && text.charAt(text.length() - 1) == '"') 191 { 192 String quotedSafeText = '"' + QueryParser.escape(text.substring(1, text.length() - 1)) + '"'; 194 fullTextBuf.append("TEXT:").append(quotedSafeText); 195 nameAttrBuf.append("@").append(nameAttr).append(":").append(quotedSafeText); 196 } 197 else 198 { 199 StringTokenizer t = new StringTokenizer (text, " "); 201 202 fullTextBuf.append('('); 203 nameAttrBuf.append('('); 204 int tokenCount = t.countTokens(); 205 for (int i=0; i<tokenCount; i++) 206 { 207 String term = t.nextToken(); 208 209 boolean operatorAND = (term.charAt(0) == OP_AND); 211 boolean operatorNOT = (term.charAt(0) == OP_NOT); 212 if (operatorAND || operatorNOT) 214 { 215 term = term.substring(1); 216 } 217 218 operatorAND = operatorAND | this.forceAndTerms; 220 221 if (term.length() != 0) 222 { 223 if (i != 0 && !operatorAND) 225 { 226 fullTextBuf.append("OR "); 227 nameAttrBuf.append("OR "); 228 } 229 230 if (operatorNOT) 232 { 233 fullTextBuf.append(OP_NOT); 234 nameAttrBuf.append(OP_NOT); 235 } 236 if (operatorAND) 238 { 239 fullTextBuf.append(OP_AND); 240 nameAttrBuf.append(OP_AND); 241 } 242 243 if (term.charAt(0) != OP_WILDCARD) 244 { 245 String safeTerm = QueryParser.escape(term); 246 fullTextBuf.append("TEXT:").append(safeTerm).append(OP_WILDCARD); 247 nameAttrBuf.append("@").append(nameAttr).append(":").append(safeTerm).append(OP_WILDCARD); 248 } 249 else 250 { 251 String safeTerm = QueryParser.escape(term.substring(1)); 252 fullTextBuf.append("TEXT:*").append(safeTerm).append(OP_WILDCARD); 253 nameAttrBuf.append("@").append(nameAttr).append(":*").append(safeTerm).append(OP_WILDCARD); 254 } 255 fullTextBuf.append(' '); 256 nameAttrBuf.append(' '); 257 } 258 } 259 fullTextBuf.append(')'); 260 nameAttrBuf.append(')'); 261 } 262 } 263 264 validQuery = true; 265 } 266 267 StringBuilder pathQuery = null; 269 if (location != null || (categories != null && categories.length !=0)) 270 { 271 pathQuery = new StringBuilder (128); 272 if (location != null) 273 { 274 pathQuery.append(" PATH:\"").append(location).append("\" "); 275 if (categories != null && categories.length != 0) 276 { 277 pathQuery.append("AND ("); 278 } 279 } 280 if (categories != null && categories.length != 0) 281 { 282 for (int i=0; i<categories.length; i++) 283 { 284 if (i > 0) 285 { 286 pathQuery.append("OR"); 287 } 288 pathQuery.append(" PATH:\"").append(categories[i]).append("\" "); 289 } 290 if (location != null) 291 { 292 pathQuery.append(") "); 293 } 294 } 295 } 296 297 StringBuilder attributeQuery = null; 299 if (queryAttributes.size() != 0) 300 { 301 attributeQuery = new StringBuilder (queryAttributes.size() << 6); 302 for (QName qname : queryAttributes.keySet()) 303 { 304 String value = queryAttributes.get(qname).trim(); 305 if (value.length() != 0 && value.length() >= minimum) 306 { 307 String escapedName = Repository.escapeQName(qname); 308 attributeQuery.append(" +@").append(escapedName) 309 .append(":").append(QueryParser.escape(value)).append(OP_WILDCARD); 310 } 311 } 312 313 if (attributeQuery.length() == 0) 315 { 316 attributeQuery = null; 317 } 318 } 319 320 if (queryFixedValues.size() != 0) 322 { 323 if (attributeQuery == null) 324 { 325 attributeQuery = new StringBuilder (queryFixedValues.size() << 6); 326 } 327 for (QName qname : queryFixedValues.keySet()) 328 { 329 String escapedName = Repository.escapeQName(qname); 330 String value = queryFixedValues.get(qname); 331 attributeQuery.append(" +@").append(escapedName) 332 .append(":\"").append(value).append('"'); 333 } 334 } 335 336 if (rangeAttributes.size() != 0) 338 { 339 if (attributeQuery == null) 340 { 341 attributeQuery = new StringBuilder (rangeAttributes.size() << 6); 342 } 343 for (QName qname : rangeAttributes.keySet()) 344 { 345 String escapedName = Repository.escapeQName(qname); 346 RangeProperties rp = rangeAttributes.get(qname); 347 String value1 = QueryParser.escape(rp.lower); 348 String value2 = QueryParser.escape(rp.upper); 349 attributeQuery.append(" +@").append(escapedName) 350 .append(":").append(rp.inclusive ? "[" : "{").append(value1) 351 .append(" TO ").append(value2).append(rp.inclusive ? "]" : "}"); 352 } 353 } 354 355 if (mimeType != null && mimeType.length() != 0) 358 { 359 if (attributeQuery == null) 360 { 361 attributeQuery = new StringBuilder (64); 362 } 363 String escapedName = Repository.escapeQName(QName.createQName(ContentModel.PROP_CONTENT + ".mimetype")); 364 attributeQuery.append(" +@").append(escapedName) 365 .append(":").append(mimeType); 366 } 367 368 String fileTypeQuery; 370 if (contentType != null) 371 { 372 fileTypeQuery = " TYPE:\"" + contentType + "\" "; 373 } 374 else 375 { 376 fileTypeQuery = " TYPE:\"{" + NamespaceService.CONTENT_MODEL_1_0_URI + "}content\" "; 378 } 379 380 String folderTypeQuery = " TYPE:\"{" + NamespaceService.CONTENT_MODEL_1_0_URI + "}folder\" "; 382 383 String fullTextQuery = fullTextBuf.toString(); 384 String nameAttrQuery = nameAttrBuf.toString(); 385 if (text.length() != 0 && text.length() >= minimum) 386 { 387 switch (mode) 389 { 390 case SearchContext.SEARCH_ALL: 391 query = '(' + fileTypeQuery + " AND " + '(' + nameAttrQuery + ' ' + fullTextQuery + ')' + ')' + " OR " + 392 '(' + folderTypeQuery + " AND " + nameAttrQuery + ')'; 393 break; 394 395 case SearchContext.SEARCH_FILE_NAMES: 396 query = fileTypeQuery + " AND " + nameAttrQuery; 397 break; 398 399 case SearchContext.SEARCH_FILE_NAMES_CONTENTS: 400 query = fileTypeQuery + " AND " + '(' + nameAttrQuery + ' ' + fullTextQuery + ')'; 401 break; 402 403 case SearchContext.SEARCH_SPACE_NAMES: 404 query = folderTypeQuery + " AND " + nameAttrQuery; 405 break; 406 407 default: 408 throw new IllegalStateException ("Unknown search mode specified: " + mode); 409 } 410 } 411 else 412 { 413 switch (mode) 415 { 416 case SearchContext.SEARCH_ALL: 417 query = '(' + fileTypeQuery + " OR " + folderTypeQuery + ')'; 418 break; 419 420 case SearchContext.SEARCH_FILE_NAMES: 421 case SearchContext.SEARCH_FILE_NAMES_CONTENTS: 422 query = fileTypeQuery; 423 break; 424 425 case SearchContext.SEARCH_SPACE_NAMES: 426 query = folderTypeQuery; 427 break; 428 429 default: 430 throw new IllegalStateException ("Unknown search mode specified: " + mode); 431 } 432 } 433 434 if (attributeQuery != null) 436 { 437 query = attributeQuery + " AND (" + query + ')'; 438 } 439 440 if (pathQuery != null) 442 { 443 query = "(" + pathQuery + ") AND (" + query + ')'; 444 } 445 446 validQuery = validQuery | (attributeQuery != null) | (pathQuery != null); 449 if (validQuery == false) 450 { 451 query = null; 452 } 453 454 if (logger.isDebugEnabled()) 455 logger.debug("Query: " + query); 456 457 return query; 458 } 459 460 469 static String getPathFromSpaceRef(NodeRef ref, boolean children) 470 { 471 FacesContext context = FacesContext.getCurrentInstance(); 472 Path path = Repository.getServiceRegistry(context).getNodeService().getPath(ref); 473 NamespaceService ns = Repository.getServiceRegistry(context).getNamespaceService(); 474 StringBuilder buf = new StringBuilder (64); 475 for (int i=0; i<path.size(); i++) 476 { 477 String elementString = ""; 478 Path.Element element = path.get(i); 479 if (element instanceof Path.ChildAssocElement) 480 { 481 ChildAssociationRef elementRef = ((Path.ChildAssocElement)element).getRef(); 482 if (elementRef.getParentRef() != null) 483 { 484 Collection prefixes = ns.getPrefixes(elementRef.getQName().getNamespaceURI()); 485 if (prefixes.size() >0) 486 { 487 elementString = '/' + (String )prefixes.iterator().next() + ':' + ISO9075.encode(elementRef.getQName().getLocalName()); 488 } 489 } 490 } 491 492 buf.append(elementString); 493 } 494 if (children == true) 495 { 496 buf.append("//*"); 498 } 499 else 500 { 501 buf.append("/*"); 503 } 504 505 return buf.toString(); 506 } 507 508 511 public String [] getCategories() 512 { 513 return this.categories; 514 } 515 516 519 public void setCategories(String [] categories) 520 { 521 if (categories != null) 522 { 523 this.categories = categories; 524 } 525 } 526 527 530 public String getLocation() 531 { 532 return this.location; 533 } 534 535 538 public void setLocation(String location) 539 { 540 this.location = location; 541 } 542 543 546 public int getMode() 547 { 548 return this.mode; 549 } 550 551 554 public void setMode(int mode) 555 { 556 this.mode = mode; 557 } 558 559 562 public String getText() 563 { 564 return this.text; 565 } 566 567 570 public void setText(String text) 571 { 572 this.text = text; 573 } 574 575 578 public String getContentType() 579 { 580 return this.contentType; 581 } 582 583 586 public void setContentType(String contentType) 587 { 588 this.contentType = contentType; 589 } 590 591 594 public String getMimeType() 595 { 596 return this.mimeType; 597 } 598 601 public void setMimeType(String mimeType) 602 { 603 this.mimeType = mimeType; 604 } 605 606 612 public void addAttributeQuery(QName qname, String value) 613 { 614 this.queryAttributes.put(qname, value); 615 } 616 617 public String getAttributeQuery(QName qname) 618 { 619 return this.queryAttributes.get(qname); 620 } 621 622 630 public void addRangeQuery(QName qname, String lower, String upper, boolean inclusive) 631 { 632 this.rangeAttributes.put(qname, new RangeProperties(qname, lower, upper, inclusive)); 633 } 634 635 public RangeProperties getRangeProperty(QName qname) 636 { 637 return this.rangeAttributes.get(qname); 638 } 639 640 646 public void addFixedValueQuery(QName qname, String value) 647 { 648 this.queryFixedValues.put(qname, value); 649 } 650 651 public String getFixedValueQuery(QName qname) 652 { 653 return this.queryFixedValues.get(qname); 654 } 655 656 659 public boolean getForceAndTerms() 660 { 661 return this.forceAndTerms; 662 } 663 664 667 public void setForceAndTerms(boolean forceAndTerms) 668 { 669 this.forceAndTerms = forceAndTerms; 670 } 671 672 704 public String toXML() 705 { 706 try 707 { 708 NamespaceService ns = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getNamespaceService(); 709 710 Document doc = DocumentHelper.createDocument(); 711 712 Element root = doc.addElement(ELEMENT_SEARCH); 713 714 root.addElement(ELEMENT_TEXT).addCDATA(this.text); 715 root.addElement(ELEMENT_MODE).addText(Integer.toString(this.mode)); 716 if (this.location != null) 717 { 718 root.addElement(ELEMENT_LOCATION).addText(this.location); 719 } 720 721 Element categories = root.addElement(ELEMENT_CATEGORIES); 722 for (String path : this.categories) 723 { 724 categories.addElement(ELEMENT_CATEGORY).addText(path); 725 } 726 727 if (this.contentType != null) 728 { 729 root.addElement(ELEMENT_CONTENT_TYPE).addText(this.contentType); 730 } 731 if (this.mimeType != null && this.mimeType.length() != 0) 732 { 733 root.addElement(ELEMENT_MIMETYPE).addText(this.mimeType); 734 } 735 736 Element attributes = root.addElement(ELEMENT_ATTRIBUTES); 737 for (QName attrName : this.queryAttributes.keySet()) 738 { 739 attributes.addElement(ELEMENT_ATTRIBUTE) 740 .addAttribute(ELEMENT_NAME, attrName.toPrefixString(ns)) 741 .addCDATA(this.queryAttributes.get(attrName)); 742 } 743 744 Element ranges = root.addElement(ELEMENT_RANGES); 745 for (QName rangeName : this.rangeAttributes.keySet()) 746 { 747 RangeProperties rangeProps = this.rangeAttributes.get(rangeName); 748 Element range = ranges.addElement(ELEMENT_RANGE); 749 range.addAttribute(ELEMENT_NAME, rangeName.toPrefixString(ns)); 750 range.addElement(ELEMENT_LOWER).addText(rangeProps.lower); 751 range.addElement(ELEMENT_UPPER).addText(rangeProps.upper); 752 range.addElement(ELEMENT_INCLUSIVE).addText(Boolean.toString(rangeProps.inclusive)); 753 } 754 755 Element values = root.addElement(ELEMENT_FIXED_VALUES); 756 for (QName valueName : this.queryFixedValues.keySet()) 757 { 758 values.addElement(ELEMENT_VALUE) 759 .addAttribute(ELEMENT_NAME, valueName.toPrefixString(ns)) 760 .addCDATA(this.queryFixedValues.get(valueName)); 761 } 762 763 Element query = root.addElement(ELEMENT_QUERY); 765 String queryString = buildQuery(0); 766 if (queryString != null) 767 { 768 query.addCDATA(queryString); 769 } 770 771 StringWriter out = new StringWriter (1024); 772 XMLWriter writer = new XMLWriter(OutputFormat.createPrettyPrint()); 773 writer.setWriter(out); 774 writer.write(doc); 775 776 return out.toString(); 777 } 778 catch (Throwable err) 779 { 780 throw new AlfrescoRuntimeException("Failed to export SearchContext to XML.", err); 781 } 782 } 783 784 789 public SearchContext fromXML(String xml) 790 { 791 try 792 { 793 NamespaceService ns = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getNamespaceService(); 794 795 SAXReader reader = new SAXReader(); 797 Document document = reader.read(new StringReader (xml)); 798 Element rootElement = document.getRootElement(); 799 Element textElement = rootElement.element(ELEMENT_TEXT); 800 if (textElement != null) 801 { 802 this.text = textElement.getText(); 803 } 804 Element modeElement = rootElement.element(ELEMENT_MODE); 805 if (modeElement != null) 806 { 807 this.mode = Integer.parseInt(modeElement.getText()); 808 } 809 Element locationElement = rootElement.element(ELEMENT_LOCATION); 810 if (locationElement != null) 811 { 812 this.location = locationElement.getText(); 813 } 814 Element categoriesElement = rootElement.element(ELEMENT_CATEGORIES); 815 if (categoriesElement != null) 816 { 817 List <String > categories = new ArrayList <String >(4); 818 for (Iterator i=categoriesElement.elementIterator(ELEMENT_CATEGORY); i.hasNext(); ) 819 { 820 Element categoryElement = (Element)i.next(); 821 categories.add(categoryElement.getText()); 822 } 823 this.categories = categories.toArray(this.categories); 824 } 825 Element contentTypeElement = rootElement.element(ELEMENT_CONTENT_TYPE); 826 if (contentTypeElement != null) 827 { 828 this.contentType = contentTypeElement.getText(); 829 } 830 Element mimetypeElement = rootElement.element(ELEMENT_MIMETYPE); 831 if (mimetypeElement != null) 832 { 833 this.mimeType = mimetypeElement.getText(); 834 } 835 Element attributesElement = rootElement.element(ELEMENT_ATTRIBUTES); 836 if (attributesElement != null) 837 { 838 for (Iterator i=attributesElement.elementIterator(ELEMENT_ATTRIBUTE); i.hasNext(); ) 839 { 840 Element attrElement = (Element)i.next(); 841 QName qname = QName.createQName(attrElement.attributeValue(ELEMENT_NAME), ns); 842 addAttributeQuery(qname, attrElement.getText()); 843 } 844 } 845 Element rangesElement = rootElement.element(ELEMENT_RANGES); 846 if (rangesElement != null) 847 { 848 for (Iterator i=rangesElement.elementIterator(ELEMENT_RANGE); i.hasNext(); ) 849 { 850 Element rangeElement = (Element)i.next(); 851 Element lowerElement = rangeElement.element(ELEMENT_LOWER); 852 Element upperElement = rangeElement.element(ELEMENT_UPPER); 853 Element incElement = rangeElement.element(ELEMENT_INCLUSIVE); 854 if (lowerElement != null && upperElement != null && incElement != null) 855 { 856 QName qname = QName.createQName(rangeElement.attributeValue(ELEMENT_NAME), ns); 857 addRangeQuery(qname, 858 lowerElement.getText(), upperElement.getText(), 859 Boolean.parseBoolean(incElement.getText())); 860 } 861 } 862 } 863 864 Element valuesElement = rootElement.element(ELEMENT_FIXED_VALUES); 865 if (valuesElement != null) 866 { 867 for (Iterator i=valuesElement.elementIterator(ELEMENT_VALUE); i.hasNext(); ) 868 { 869 Element valueElement = (Element)i.next(); 870 QName qname = QName.createQName(valueElement.attributeValue(ELEMENT_NAME), ns); 871 addFixedValueQuery(qname, valueElement.getText()); 872 } 873 } 874 } 875 catch (Throwable err) 876 { 877 throw new AlfrescoRuntimeException("Failed to import SearchContext from XML.", err); 878 } 879 return this; 880 } 881 882 885 static class RangeProperties 886 { 887 QName qname; 888 String lower; 889 String upper; 890 boolean inclusive; 891 892 RangeProperties(QName qname, String lower, String upper, boolean inclusive) 893 { 894 this.qname = qname; 895 this.lower = lower; 896 this.upper = upper; 897 this.inclusive = inclusive; 898 } 899 } 900 } 901 | Popular Tags |