001 package railo.runtime.tag; 002 003 import java.util.Iterator; 004 import java.util.Map; 005 006 import railo.commons.lang.StringUtil; 007 import railo.runtime.exp.ApplicationException; 008 import railo.runtime.exp.PageException; 009 import railo.runtime.ext.tag.TagImpl; 010 import railo.runtime.op.Caster; 011 import railo.runtime.op.Decision; 012 import railo.runtime.search.AddionalAttrs; 013 import railo.runtime.search.SearchCollection; 014 import railo.runtime.search.SearchData; 015 import railo.runtime.search.SearchDataImpl; 016 import railo.runtime.search.SearchEngine; 017 import railo.runtime.search.SearchException; 018 import railo.runtime.search.SuggestionItem; 019 import railo.runtime.tag.util.DeprecatedUtil; 020 import railo.runtime.type.KeyImpl; 021 import railo.runtime.type.List; 022 import railo.runtime.type.QueryImpl; 023 import railo.runtime.type.Struct; 024 import railo.runtime.type.StructImpl; 025 026 public final class Search extends TagImpl { 027 028 private static final String[] EMPTY = new String[0]; 029 030 private static final int SUGGESTIONS_ALWAYS = Integer.MAX_VALUE; 031 private static final int SUGGESTIONS_NEVER = -1; 032 033 private static final railo.runtime.type.Collection.Key FOUND = KeyImpl.intern("found"); 034 private static final railo.runtime.type.Collection.Key SEARCHED = KeyImpl.intern("searched"); 035 private static final railo.runtime.type.Collection.Key KEYWORDS = KeyImpl.intern("keywords"); 036 private static final railo.runtime.type.Collection.Key KEYWORD_SCORE = KeyImpl.intern("keywordScore"); 037 038 /** Specifies the criteria type for the search. */ 039 private short type=SearchCollection.SEARCH_TYPE_SIMPLE; 040 041 /** Specifies the maximum number of entries for index queries. If omitted, all rows are returned. */ 042 private int maxrows=-1; 043 044 /** Specifies the criteria for the search following the syntactic rules specified by type. */ 045 private String criteria=""; 046 047 /** Specifies the first row number to be retrieved. Default is 1. */ 048 private int startrow=1; 049 050 /** The logical collection name that is the target of the search operation or an external collection 051 ** with fully qualified path. */ 052 private SearchCollection[] collections; 053 054 /** A name for the search query. */ 055 private String name; 056 057 private String[] category=EMPTY; 058 private String categoryTree=""; 059 private String status; 060 private int suggestions=SUGGESTIONS_NEVER; 061 private int contextPassages=0; 062 private int contextBytes=300; 063 private String contextHighlightBegin="<b>"; 064 private String contextHighlightEnd="</b>"; 065 private String previousCriteria; 066 067 //private int spellCheckMaxLevel=10; 068 //private String result=null; 069 070 /** 071 * @see javax.servlet.jsp.tagext.Tag#release() 072 */ 073 public void release() { 074 super.release(); 075 type=SearchCollection.SEARCH_TYPE_SIMPLE; 076 maxrows=-1; 077 criteria=""; 078 startrow=1; 079 collections=null; 080 081 category=EMPTY; 082 categoryTree=""; 083 status=null; 084 suggestions=SUGGESTIONS_NEVER; 085 contextPassages=0; 086 contextBytes=300; 087 contextHighlightBegin="<b>"; 088 contextHighlightEnd="</b>"; 089 previousCriteria=null; 090 091 092 //spellCheckMaxLevel=10; 093 //result=null; 094 095 } 096 097 /** set the value type 098 * Specifies the criteria type for the search. 099 * @param type value to set 100 * @throws ApplicationException 101 **/ 102 public void setType(String type) throws ApplicationException { 103 if(type==null) return; 104 type=type.toLowerCase().trim(); 105 if(type.equals("simple"))this.type=SearchCollection.SEARCH_TYPE_SIMPLE; 106 else if(type.equals("explicit"))this.type=SearchCollection.SEARCH_TYPE_EXPLICIT; 107 else 108 throw new ApplicationException("attribute type of tag search has a invalid value, valid values are [simple,explicit] now is ["+type+"]"); 109 110 } 111 112 /** set the value maxrows 113 * Specifies the maximum number of entries for index queries. If omitted, all rows are returned. 114 * @param maxrows value to set 115 **/ 116 public void setMaxrows(double maxrows) { 117 this.maxrows=(int) maxrows; 118 } 119 120 /** set the value criteria 121 * Specifies the criteria for the search following the syntactic rules specified by type. 122 * @param criteria value to set 123 **/ 124 public void setCriteria(String criteria) { 125 this.criteria=criteria; 126 } 127 128 /** set the value startrow 129 * Specifies the first row number to be retrieved. Default is 1. 130 * @param startrow value to set 131 **/ 132 public void setStartrow(double startrow) { 133 this.startrow=(int) startrow; 134 } 135 136 /** set the value collection 137 * The logical collection name that is the target of the search operation or an external collection 138 * with fully qualified path. 139 * @param collection value to set 140 * @throws PageException 141 **/ 142 public void setCollection(String collection) throws PageException { 143 String[] collNames=List.toStringArrayTrim(List.listToArrayRemoveEmpty(collection,',')); 144 collections=new SearchCollection[collNames.length]; 145 SearchEngine se = pageContext.getConfig().getSearchEngine(); 146 try { 147 for(int i=0;i<collections.length;i++) { 148 collections[i]=se.getCollectionByName(collNames[i]); 149 } 150 } catch (SearchException e) { 151 collections=null; 152 throw Caster.toPageException(e); 153 } 154 } 155 156 /** set the value language 157 * @param language value to set 158 **/ 159 public void setLanguage(String language) { 160 DeprecatedUtil.tagAttribute(pageContext,"Search", "language"); 161 } 162 163 /** set the value external 164 * @param external value to set 165 * @throws ApplicationException 166 **/ 167 public void setExternal(boolean external) throws ApplicationException { 168 DeprecatedUtil.tagAttribute(pageContext,"Search", "external"); 169 } 170 171 /** set the value name 172 * A name for the search query. 173 * @param name value to set 174 **/ 175 public void setName(String name) { 176 this.name=name; 177 } 178 179 180 181 /** 182 * @param category the category to set 183 * @throws ApplicationException 184 */ 185 public void setCategory(String listCategories) { 186 if(StringUtil.isEmpty(listCategories)) return; 187 this.category = List.trimItems(List.listToStringArray(listCategories, ',')); 188 } 189 190 191 /** 192 * @param categoryTree the categoryTree to set 193 * @throws ApplicationException 194 */ 195 public void setCategorytree(String categoryTree) { 196 if(StringUtil.isEmpty(categoryTree)) return; 197 categoryTree=categoryTree.replace('\\', '/').trim(); 198 if(StringUtil.startsWith(categoryTree, '/'))categoryTree=categoryTree.substring(1); 199 if(!StringUtil.endsWith(categoryTree, '/') && categoryTree.length()>0)categoryTree+="/"; 200 this.categoryTree = categoryTree; 201 } 202 203 /** 204 * @param contextBytes the contextBytes to set 205 * @throws ApplicationException 206 */ 207 public void setContextbytes(double contextBytes) throws ApplicationException { 208 this.contextBytes = (int)contextBytes; 209 } 210 211 /** 212 * @param contextHighlightBegin the contextHighlightBegin to set 213 * @throws ApplicationException 214 */ 215 public void setContexthighlightbegin(String contextHighlightBegin) { 216 this.contextHighlightBegin = contextHighlightBegin; 217 } 218 219 /** 220 * @param contextHighlightEnd the contextHighlightEnd to set 221 * @throws ApplicationException 222 */ 223 public void setContexthighlightend(String contextHighlightEnd) { 224 this.contextHighlightEnd = contextHighlightEnd; 225 } 226 227 /** 228 * @param contextPassages the contextPassages to set 229 * @throws ApplicationException 230 */ 231 public void setContextpassages(double contextPassages) throws ApplicationException { 232 this.contextPassages = (int)contextPassages; 233 } 234 235 236 237 238 /** 239 * @param previousCriteria the previousCriteria to set 240 * @throws ApplicationException 241 */ 242 public void setPreviouscriteria(String previousCriteria) throws ApplicationException { 243 this.previousCriteria = previousCriteria; 244 throw new ApplicationException("attribute previousCriteria for tag search is not supported yet"); 245 // TODO impl tag attribute 246 } 247 248 /** 249 * @param status the status to set 250 * @throws ApplicationException 251 */ 252 public void setStatus(String status) { 253 if(!StringUtil.isEmpty(status))this.status = status; 254 } 255 256 /** 257 * @param suggestions the suggestions to set 258 * @throws ApplicationException 259 */ 260 public void setSuggestions(String suggestions) throws PageException { 261 if(StringUtil.isEmpty(suggestions))return; 262 suggestions = suggestions.trim().toLowerCase(); 263 if("always".equals(suggestions)) this.suggestions=SUGGESTIONS_ALWAYS; 264 else if("never".equals(suggestions)) this.suggestions=SUGGESTIONS_NEVER; 265 else if(Decision.isNumeric(suggestions)) { 266 this.suggestions=Caster.toIntValue(suggestions); 267 } 268 else 269 throw new ApplicationException("attribute suggestions has a invalid value ["+suggestions+"], valid values are [always,never,<positive numeric value>]"); 270 271 272 } 273 274 /** 275 * @see javax.servlet.jsp.tagext.Tag#doStartTag() 276 */ 277 public int doStartTag() throws PageException { 278 //SerialNumber sn = pageContext.getConfig().getSerialNumber(); 279 //if(sn.getVersion()==SerialNumber.VERSION_COMMUNITY) 280 // throw new SecurityException("no access to this functionality with the "+sn.getStringVersion()+" version of railo"); 281 final String v="VARCHAR",d="DOUBLE"; 282 String[] cols = new String[]{"title","url","summary","score","recordssearched","key","custom1","custom2","custom3","custom4", 283 "categoryTree","category","context","size","rank","author","type","collection"}; 284 285 // TODO support context 286 String[] types = new String[]{v,v,v,d,d,v,v,v,v,v,v,v,v,d,d,v,v,v}; 287 SearchData data=new SearchDataImpl(suggestions); 288 SuggestionItem item=null;// this is already here to make sure the classloader load this sinstance 289 290 291 railo.runtime.type.Query qry=new QueryImpl(cols,types,0,"query"); 292 293 SearchCollection collection; 294 long time=System.currentTimeMillis(); 295 AddionalAttrs.setAddionalAttrs(contextBytes,contextPassages,contextHighlightBegin,contextHighlightEnd); 296 try { 297 for(int i=0;i<collections.length;i++) { 298 collection=collections[i]; 299 startrow=collection.search(data,qry,criteria,collection.getLanguage(),type,startrow,maxrows,categoryTree,category); 300 301 if(maxrows>=0 && qry.getRecordcount()>=maxrows) break; 302 } 303 pageContext.setVariable(name,qry); 304 } 305 catch(SearchException se) { 306 throw Caster.toPageException(se); 307 } 308 finally{ 309 AddionalAttrs.removeAddionalAttrs(); 310 } 311 312 time=System.currentTimeMillis()-time; 313 Double recSearched=new Double(data.getRecordsSearched()); 314 int len=qry.getRecordcount(); 315 for(int i=1;i<=len;i++) { 316 qry.setAt("recordssearched",i,recSearched); 317 } 318 319 // status 320 if(status!=null) { 321 Struct sct=new StructImpl(); 322 pageContext.setVariable(status, sct); 323 sct.set(FOUND, new Double(qry.getRecordcount())); 324 sct.set(SEARCHED, recSearched); 325 sct.set(KeyImpl.TIME, new Double(time)); 326 327 // TODO impl this values 328 329 Map s = data.getSuggestion(); 330 if(s.size()>0) { 331 String key; 332 333 Iterator it = s.keySet().iterator(); 334 Struct keywords=new StructImpl(); 335 Struct keywordScore=new StructImpl(); 336 sct.set(KEYWORDS, keywords); 337 sct.set(KEYWORD_SCORE, keywordScore); 338 Object obj; 339 340 while(it.hasNext()) { 341 key=(String) it.next(); 342 // FUTURE add SuggestionItem as interface to public interface 343 344 // the problem is a conflict between the SuggestionItem version from core and extension 345 obj=s.get(key); 346 if(obj instanceof SuggestionItem){ 347 item=(SuggestionItem)obj; 348 keywords.set(key, item.getKeywords()); 349 keywordScore.set(key, item.getKeywordScore()); 350 } 351 else { 352 Class clazz = obj.getClass(); 353 try { 354 keywords.set(key, clazz.getMethod("getKeywords", new Class[0]).invoke(obj, new Object[0])); 355 keywordScore.set(key, clazz.getMethod("getKeywordScore", new Class[0]).invoke(obj, new Object[0])); 356 } 357 catch (Exception e) {} 358 } 359 360 361 362 } 363 364 365 String query = data.getSuggestionQuery(); 366 if(query!=null) { 367 String html = StringUtil.replace(query, "<suggestion>", "<b>", false); 368 html = StringUtil.replace(html, "</suggestion>", "</b>", false); 369 sct.set("suggestedQueryHTML", html); 370 371 String plain = StringUtil.replace(query, "<suggestion>", "", false); 372 plain = StringUtil.replace(plain, "</suggestion>", "", false); 373 sct.set("suggestedQuery", plain); 374 } 375 376 377 } 378 379 //if(suggestions!=SUGGESTIONS_NEVER)sct.set("suggestedQuery", ""); 380 //sct.set("keywords", ""); 381 //sct.set("keywordScore", ""); 382 383 384 } 385 386 387 return SKIP_BODY; 388 } 389 390 /** 391 * @see javax.servlet.jsp.tagext.Tag#doEndTag() 392 */ 393 public int doEndTag() { 394 return EVAL_PAGE; 395 } 396 397 398 }