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