001 package railo.runtime.tag; 002 003 import java.io.IOException; 004 import java.net.MalformedURLException; 005 import java.net.URL; 006 007 import railo.commons.io.res.Resource; 008 import railo.commons.io.res.util.ResourceUtil; 009 import railo.commons.lang.StringUtil; 010 import railo.runtime.exp.ApplicationException; 011 import railo.runtime.exp.ExpressionException; 012 import railo.runtime.exp.PageException; 013 import railo.runtime.ext.tag.TagImpl; 014 import railo.runtime.op.Caster; 015 import railo.runtime.search.IndexResult; 016 import railo.runtime.search.SearchCollection; 017 import railo.runtime.search.SearchCollectionSupport; 018 import railo.runtime.search.SearchException; 019 import railo.runtime.search.SearchIndex; 020 import railo.runtime.search.lucene2.LuceneSearchCollection; 021 import railo.runtime.tag.util.DeprecatedUtil; 022 import railo.runtime.type.Struct; 023 import railo.runtime.type.StructImpl; 024 import railo.runtime.type.util.ListUtil; 025 026 /** 027 * Populates collections with indexed data. 028 **/ 029 public final class Index extends TagImpl { 030 031 032 private static final String[] EMPTY = new String[0]; 033 034 035 public static String[] EXTENSIONS=new String[]{"htm","html","cfm","cfml","dbm","dbml"}; 036 037 038 /** Specifies the index action. */ 039 private String action; 040 041 /** Specifies the URL path for files if type = "file" and type = "path". When the collection is 042 ** searched with cfsearch, the pathname is automatically be prepended to filenames and returned as 043 ** the url attribute. */ 044 private String urlpath; 045 046 /** Specifies the type of entity being indexed. Default is CUSTOM. */ 047 private short type=-1; 048 049 /** Title for collection; 050 ** Query column name for type and a valid query name; 051 ** Permits searching collections by title or displaying a separate title from the key */ 052 private String title; 053 054 private String language; 055 056 057 /** Specifies the comma-separated list of file extensions that CFML uses to index files if 058 ** type = "Path". Default is HTM, HTML, CFM, CFML, DBM, DBML. 059 ** An entry of "*." returns files with no extension */ 060 private String[] extensions=EXTENSIONS; 061 062 /** */ 063 private String key; 064 065 /** A custom field you can use to store data during an indexing operation. Specify a query column 066 ** name for type and a query name. */ 067 private String custom1; 068 private long timeout=10000; 069 070 /** A custom field you can use to store data during an indexing operation. Usage is the same as 071 ** for custom1. */ 072 private String custom2; 073 074 /** A custom field you can use to store data during an indexing operation. Usage is the same as 075 ** for custom1. */ 076 private String custom3; 077 078 /** A custom field you can use to store data during an indexing operation. Usage is the same as 079 ** for custom1. */ 080 private String custom4; 081 082 /** Specifies the name of the query against which the collection is generated. */ 083 private String query; 084 085 /** Specifies a collection name. If you are indexing an external collection external = "Yes", 086 ** specify the collection name, including fully qualified path. */ 087 private SearchCollection collection; 088 089 /** Yes or No. Yes specifies, if type = "Path", that directories below the path specified in 090 ** key are included in the indexing operation. */ 091 private boolean recurse; 092 093 /** */ 094 private String body; 095 private String name; 096 097 098 private String[] category=EMPTY; 099 private String categoryTree=""; 100 private String status; 101 private String prefix; 102 103 104 private boolean throwontimeout=false; 105 106 @Override 107 public void release() { 108 super.release(); 109 action=null; 110 urlpath=null; 111 type=-1; 112 title=null; 113 language=null; 114 extensions=EXTENSIONS; 115 key=null; 116 117 custom1=null; 118 custom2=null; 119 custom3=null; 120 custom4=null; 121 122 query=null; 123 collection=null; 124 recurse=false; 125 body=null; 126 name=null; 127 128 category=EMPTY; 129 categoryTree=""; 130 status=null; 131 prefix=null; 132 timeout=10000; 133 throwontimeout=false; 134 } 135 136 137 /** set the value action 138 * Specifies the index action. 139 * @param action value to set 140 **/ 141 public void setAction(String action) { 142 this.action=action.toLowerCase().trim(); 143 } 144 145 /** set the value urlpath 146 * Specifies the URL path for files if type = "file" and type = "path". When the collection is 147 * searched with cfsearch, the pathname is automatically be prepended to filenames and returned as 148 * the url attribute. 149 * @param urlpath value to set 150 **/ 151 public void setUrlpath(String urlpath) { 152 if(StringUtil.isEmpty(urlpath))return; 153 this.urlpath=urlpath.toLowerCase().trim(); 154 } 155 156 /** set the value type 157 * Specifies the type of entity being indexed. Default is CUSTOM. 158 * @param type value to set 159 * @throws PageException 160 **/ 161 public void setType(String type) throws PageException { 162 if(type==null)return; 163 try { 164 this.type=SearchIndex.toType(type); 165 } 166 catch (SearchException e) { 167 throw Caster.toPageException(e); 168 } 169 } 170 171 /** 172 * @param timeout the timeout in seconds 173 * @throws ApplicationException 174 */ 175 public void setTimeout(double timeout) throws ApplicationException { 176 177 this.timeout = (long)(timeout*1000D); 178 if(this.timeout<0) 179 throw new ApplicationException("attribute timeout must contain a positive number"); 180 if(timeout==0)timeout=1; 181 } 182 183 /** set the value throwontimeout 184 * Yes or No. Specifies how timeout conditions are handled. If the value is Yes, an exception is 185 * generated to provide notification of the timeout. If the value is No, execution continues. Default is Yes. 186 * @param throwontimeout value to set 187 **/ 188 public void setThrowontimeout(boolean throwontimeout) { 189 this.throwontimeout = throwontimeout; 190 } 191 192 193 194 public void setName(String name){ 195 this.name=name; 196 } 197 198 /** set the value title 199 * Title for collection; 200 * Query column name for type and a valid query name; 201 * Permits searching collections by title or displaying a separate title from the key 202 * @param title value to set 203 **/ 204 public void setTitle(String title) { 205 this.title=title; 206 } 207 208 /** set the value custom1 209 * A custom field you can use to store data during an indexing operation. Specify a query column 210 * name for type and a query name. 211 * @param custom1 value to set 212 **/ 213 public void setCustom1(String custom1) { 214 this.custom1=custom1; 215 } 216 217 /** set the value language 218 * @param language value to set 219 **/ 220 public void setLanguage(String language) { 221 if(StringUtil.isEmpty(language)) return; 222 this.language=Collection.validateLanguage(language); 223 } 224 225 /** set the value external 226 * @param external value to set 227 * @throws ApplicationException 228 **/ 229 public void setExternal(boolean external) { 230 DeprecatedUtil.tagAttribute(pageContext,"Index", "external"); 231 } 232 233 /** set the value extensions 234 * @param extensions value to set 235 * @throws PageException 236 **/ 237 public void setExtensions(String extensions) throws PageException { 238 if(extensions==null) return; 239 this.extensions=ListUtil.toStringArrayTrim(ListUtil.listToArray(extensions,',')); 240 } 241 242 /** set the value key 243 * 244 * @param key value to set 245 **/ 246 public void setKey(String key) { 247 this.key=key; 248 } 249 250 /** set the value custom2 251 * A custom field you can use to store data during an indexing operation. Usage is the same as 252 * for custom1. 253 * @param custom2 value to set 254 **/ 255 public void setCustom2(String custom2) { 256 this.custom2=custom2; 257 } 258 259 /** 260 * @param custom3 The custom3 to set. 261 */ 262 public void setCustom3(String custom3) { 263 this.custom3 = custom3; 264 } 265 266 267 /** 268 * @param custom4 The custom4 to set. 269 */ 270 public void setCustom4(String custom4) { 271 this.custom4 = custom4; 272 } 273 274 /** set the value query 275 * Specifies the name of the query against which the collection is generated. 276 * @param query value to set 277 **/ 278 public void setQuery(String query) { 279 this.query=query; 280 } 281 282 /** set the value collection 283 * Specifies a collection name. If you are indexing an external collection external = "Yes", 284 * specify the collection name, including fully qualified path. 285 * @param collection value to set 286 * @throws PageException 287 **/ 288 public void setCollection(String collection) throws PageException { 289 try { 290 this.collection=pageContext.getConfig().getSearchEngine().getCollectionByName(collection.toLowerCase().trim()); 291 } 292 catch (SearchException e) { 293 throw Caster.toPageException(e); 294 } 295 296 } 297 298 /** set the value recurse 299 * Yes or No. Yes specifies, if type = "Path", that directories below the path specified in 300 * key are included in the indexing operation. 301 * @param recurse value to set 302 **/ 303 public void setRecurse(boolean recurse) { 304 this.recurse=recurse; 305 } 306 307 /** set the value body 308 * 309 * @param body value to set 310 **/ 311 public void setBody(String body) { 312 this.body=body; 313 } 314 315 /** 316 * @param category the category to set 317 * @throws ApplicationException 318 */ 319 public void setCategory(String listCategories) { 320 if(listCategories==null) return; 321 this.category = ListUtil.trimItems(ListUtil.listToStringArray(listCategories, ',')); 322 } 323 324 325 /** 326 * @param categoryTree the categoryTree to set 327 * @throws ApplicationException 328 */ 329 public void setCategorytree(String categoryTree) { 330 if(categoryTree==null) return; 331 categoryTree=categoryTree.replace('\\', '/').trim(); 332 if(StringUtil.startsWith(categoryTree, '/'))categoryTree=categoryTree.substring(1); 333 if(!StringUtil.endsWith(categoryTree, '/') && categoryTree.length()>0)categoryTree+="/"; 334 this.categoryTree = categoryTree; 335 } 336 337 338 /** 339 * @param prefix the prefix to set 340 */ 341 public void setPrefix(String prefix) { 342 this.prefix = prefix; 343 } 344 345 346 /** 347 * @param status the status to set 348 * @throws ApplicationException 349 */ 350 public void setStatus(String status) { 351 this.status = status; 352 } 353 354 355 @Override 356 public int doStartTag() throws PageException { 357 // SerialNumber sn = pageContext.getConfig().getSerialNumber(); 358 //if(sn.getVersion()==SerialNumber.VERSION_COMMUNITY) 359 // throw new SecurityException("no access to this functionality with the "+sn.getStringVersion()+" version of railo"); 360 361 try { 362 if(action.equals("purge")) doPurge(); 363 else if(action.equals("update")) doUpdate(); 364 else if(action.equals("delete")) doDelete(); 365 else if(action.equals("refresh")) doRefresh(); 366 else if(action.equals("list")) doList(); 367 368 else throw new ApplicationException("invalid action name [" + action + "]","valid action names are [list,update, delete, purge, refresh]"); 369 } catch (Exception e) { 370 throw Caster.toPageException(e); 371 } 372 return SKIP_BODY; 373 } 374 375 376 /** 377 * @throws PageException 378 * @throws SearchException 379 * @throws IOException 380 * 381 */ 382 private void doRefresh() throws PageException, SearchException, IOException { 383 doPurge(); 384 doUpdate(); 385 } 386 387 private void doList() throws ApplicationException, PageException { 388 required("index",action,"name",name); 389 pageContext.setVariable(name,((SearchCollectionSupport)collection).getIndexesAsQuery()); 390 } 391 392 393 /** 394 * delete a collection 395 * @throws PageException 396 * @throws SearchException 397 */ 398 private void doDelete() throws PageException, SearchException { 399 required("index",action,"collection",collection); 400 if(type!=SearchIndex.TYPE_CUSTOM)required("index",action,"key",key); 401 402 // no type defined 403 if(type==-1) { 404 if(query!=null) { 405 type=SearchIndex.TYPE_CUSTOM; 406 } 407 else { 408 Resource file=null; 409 try { 410 file=ResourceUtil.toResourceExisting(pageContext,key); 411 pageContext.getConfig().getSecurityManager().checkFileLocation(file); 412 } 413 catch (ExpressionException e) {} 414 415 416 if(file!=null && file.exists() && file.isFile()) type=SearchIndex.TYPE_FILE; 417 else if(file!=null && file.exists() && file.isDirectory()) type=SearchIndex.TYPE_PATH; 418 else { 419 try { 420 new URL(key); 421 type=SearchIndex.TYPE_URL; 422 } catch (MalformedURLException e) {} 423 } 424 } 425 } 426 427 collection.deleteIndex(pageContext,key,type,query); 428 } 429 430 /** 431 * purge a collection 432 * @throws PageException 433 * @throws SearchException 434 */ 435 private void doPurge() throws PageException, SearchException { 436 required("index",action,"collection",collection); 437 collection.purge(); 438 } 439 440 /** 441 * update a collection 442 * @throws PageException 443 * @throws SearchException 444 * @throws IOException 445 */ 446 private void doUpdate() throws PageException, SearchException, IOException { 447 // check attributes 448 required("index",action,"collection",collection); 449 required("index",action,"key",key); 450 451 if(type==-1) type=(query==null)?SearchIndex.TYPE_FILE:SearchIndex.TYPE_CUSTOM; 452 453 if(type==SearchIndex.TYPE_CUSTOM) { 454 required("index",action,"body",body); 455 //required("index",action,"query",query); 456 } 457 IndexResult result; 458 459 // FUTURE remove this condition 460 if(collection instanceof LuceneSearchCollection) 461 result = ((LuceneSearchCollection)collection).index(pageContext,key,type,urlpath,title,body,language,extensions,query,recurse,categoryTree,category,timeout,custom1,custom2,custom3,custom4); 462 else 463 result = collection.index(pageContext,key,type,urlpath,title,body,language,extensions,query,recurse,categoryTree,category,custom1,custom2,custom3,custom4); 464 if(!StringUtil.isEmpty(status))pageContext.setVariable(status,toStruct(result)); 465 } 466 467 468 @Override 469 public int doEndTag() { 470 return EVAL_PAGE; 471 } 472 473 private Struct toStruct(IndexResult result) { 474 Struct sct=new StructImpl(); 475 sct.setEL("deleted",new Double(result.getCountDeleted())); 476 sct.setEL("inserted",new Double(result.getCountInserted())); 477 sct.setEL("updated",new Double(result.getCountUpdated())); 478 return sct; 479 } 480 481 }