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