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