package railo.runtime.tag; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import railo.commons.io.res.Resource; import railo.commons.io.res.util.ResourceUtil; import railo.commons.lang.StringUtil; import railo.runtime.exp.ApplicationException; import railo.runtime.exp.ExpressionException; import railo.runtime.exp.PageException; import railo.runtime.ext.tag.TagImpl; import railo.runtime.op.Caster; import railo.runtime.search.IndexResult; import railo.runtime.search.SearchCollection; import railo.runtime.search.SearchCollectionSupport; import railo.runtime.search.SearchException; import railo.runtime.search.SearchIndex; import railo.runtime.search.lucene2.LuceneSearchCollection; import railo.runtime.tag.util.DeprecatedUtil; import railo.runtime.type.Struct; import railo.runtime.type.StructImpl; import railo.runtime.type.util.ListUtil; /** * Populates collections with indexed data. **/ public final class Index extends TagImpl { private static final String[] EMPTY = new String[0]; public static String[] EXTENSIONS=new String[]{"htm","html","cfm","cfml","dbm","dbml"}; /** Specifies the index action. */ private String action; /** Specifies the URL path for files if type = "file" and type = "path". When the collection is ** searched with cfsearch, the pathname is automatically be prepended to filenames and returned as ** the url attribute. */ private String urlpath; /** Specifies the type of entity being indexed. Default is CUSTOM. */ private short type=-1; /** Title for collection; ** Query column name for type and a valid query name; ** Permits searching collections by title or displaying a separate title from the key */ private String title; private String language; /** Specifies the comma-separated list of file extensions that CFML uses to index files if ** type = "Path". Default is HTM, HTML, CFM, CFML, DBM, DBML. ** An entry of "*." returns files with no extension */ private String[] extensions=EXTENSIONS; /** */ private String key; /** A custom field you can use to store data during an indexing operation. Specify a query column ** name for type and a query name. */ private String custom1; private long timeout=10000; /** A custom field you can use to store data during an indexing operation. Usage is the same as ** for custom1. */ private String custom2; /** A custom field you can use to store data during an indexing operation. Usage is the same as ** for custom1. */ private String custom3; /** A custom field you can use to store data during an indexing operation. Usage is the same as ** for custom1. */ private String custom4; /** Specifies the name of the query against which the collection is generated. */ private String query; /** Specifies a collection name. If you are indexing an external collection external = "Yes", ** specify the collection name, including fully qualified path. */ private SearchCollection collection; /** Yes or No. Yes specifies, if type = "Path", that directories below the path specified in ** key are included in the indexing operation. */ private boolean recurse; /** */ private String body; private String name; private String[] category=EMPTY; private String categoryTree=""; private String status; private String prefix; private boolean throwontimeout=false; @Override public void release() { super.release(); action=null; urlpath=null; type=-1; title=null; language=null; extensions=EXTENSIONS; key=null; custom1=null; custom2=null; custom3=null; custom4=null; query=null; collection=null; recurse=false; body=null; name=null; category=EMPTY; categoryTree=""; status=null; prefix=null; timeout=10000; throwontimeout=false; } /** set the value action * Specifies the index action. * @param action value to set **/ public void setAction(String action) { this.action=action.toLowerCase().trim(); } /** set the value urlpath * Specifies the URL path for files if type = "file" and type = "path". When the collection is * searched with cfsearch, the pathname is automatically be prepended to filenames and returned as * the url attribute. * @param urlpath value to set **/ public void setUrlpath(String urlpath) { if(StringUtil.isEmpty(urlpath))return; this.urlpath=urlpath.toLowerCase().trim(); } /** set the value type * Specifies the type of entity being indexed. Default is CUSTOM. * @param type value to set * @throws PageException **/ public void setType(String type) throws PageException { if(type==null)return; try { this.type=SearchIndex.toType(type); } catch (SearchException e) { throw Caster.toPageException(e); } } /** * @param timeout the timeout in seconds * @throws ApplicationException */ public void setTimeout(double timeout) throws ApplicationException { this.timeout = (long)(timeout*1000D); if(this.timeout<0) throw new ApplicationException("attribute timeout must contain a positive number"); if(timeout==0)timeout=1; } /** set the value throwontimeout * Yes or No. Specifies how timeout conditions are handled. If the value is Yes, an exception is * generated to provide notification of the timeout. If the value is No, execution continues. Default is Yes. * @param throwontimeout value to set **/ public void setThrowontimeout(boolean throwontimeout) { this.throwontimeout = throwontimeout; } public void setName(String name){ this.name=name; } /** set the value title * Title for collection; * Query column name for type and a valid query name; * Permits searching collections by title or displaying a separate title from the key * @param title value to set **/ public void setTitle(String title) { this.title=title; } /** set the value custom1 * A custom field you can use to store data during an indexing operation. Specify a query column * name for type and a query name. * @param custom1 value to set **/ public void setCustom1(String custom1) { this.custom1=custom1; } /** set the value language * @param language value to set **/ public void setLanguage(String language) { if(StringUtil.isEmpty(language)) return; this.language=Collection.validateLanguage(language); } /** set the value external * @param external value to set * @throws ApplicationException **/ public void setExternal(boolean external) { DeprecatedUtil.tagAttribute(pageContext,"Index", "external"); } /** set the value extensions * @param extensions value to set * @throws PageException **/ public void setExtensions(String extensions) throws PageException { if(extensions==null) return; this.extensions=ListUtil.toStringArrayTrim(ListUtil.listToArray(extensions,',')); } /** set the value key * * @param key value to set **/ public void setKey(String key) { this.key=key; } /** set the value custom2 * A custom field you can use to store data during an indexing operation. Usage is the same as * for custom1. * @param custom2 value to set **/ public void setCustom2(String custom2) { this.custom2=custom2; } /** * @param custom3 The custom3 to set. */ public void setCustom3(String custom3) { this.custom3 = custom3; } /** * @param custom4 The custom4 to set. */ public void setCustom4(String custom4) { this.custom4 = custom4; } /** set the value query * Specifies the name of the query against which the collection is generated. * @param query value to set **/ public void setQuery(String query) { this.query=query; } /** set the value collection * Specifies a collection name. If you are indexing an external collection external = "Yes", * specify the collection name, including fully qualified path. * @param collection value to set * @throws PageException **/ public void setCollection(String collection) throws PageException { try { this.collection=pageContext.getConfig().getSearchEngine().getCollectionByName(collection.toLowerCase().trim()); } catch (SearchException e) { throw Caster.toPageException(e); } } /** set the value recurse * Yes or No. Yes specifies, if type = "Path", that directories below the path specified in * key are included in the indexing operation. * @param recurse value to set **/ public void setRecurse(boolean recurse) { this.recurse=recurse; } /** set the value body * * @param body value to set **/ public void setBody(String body) { this.body=body; } /** * @param category the category to set * @throws ApplicationException */ public void setCategory(String listCategories) { if(listCategories==null) return; this.category = ListUtil.trimItems(ListUtil.listToStringArray(listCategories, ',')); } /** * @param categoryTree the categoryTree to set * @throws ApplicationException */ public void setCategorytree(String categoryTree) { if(categoryTree==null) return; categoryTree=categoryTree.replace('\\', '/').trim(); if(StringUtil.startsWith(categoryTree, '/'))categoryTree=categoryTree.substring(1); if(!StringUtil.endsWith(categoryTree, '/') && categoryTree.length()>0)categoryTree+="/"; this.categoryTree = categoryTree; } /** * @param prefix the prefix to set */ public void setPrefix(String prefix) { this.prefix = prefix; } /** * @param status the status to set * @throws ApplicationException */ public void setStatus(String status) { this.status = status; } @Override public int doStartTag() throws PageException { // SerialNumber sn = pageContext.getConfig().getSerialNumber(); //if(sn.getVersion()==SerialNumber.VERSION_COMMUNITY) // throw new SecurityException("no access to this functionality with the "+sn.getStringVersion()+" version of railo"); try { if(action.equals("purge")) doPurge(); else if(action.equals("update")) doUpdate(); else if(action.equals("delete")) doDelete(); else if(action.equals("refresh")) doRefresh(); else if(action.equals("list")) doList(); else throw new ApplicationException("invalid action name [" + action + "]","valid action names are [list,update, delete, purge, refresh]"); } catch (Exception e) { throw Caster.toPageException(e); } return SKIP_BODY; } /** * @throws PageException * @throws SearchException * @throws IOException * */ private void doRefresh() throws PageException, SearchException, IOException { doPurge(); doUpdate(); } private void doList() throws ApplicationException, PageException { required("index",action,"name",name); pageContext.setVariable(name,((SearchCollectionSupport)collection).getIndexesAsQuery()); } /** * delete a collection * @throws PageException * @throws SearchException */ private void doDelete() throws PageException, SearchException { required("index",action,"collection",collection); if(type!=SearchIndex.TYPE_CUSTOM)required("index",action,"key",key); // no type defined if(type==-1) { if(query!=null) { type=SearchIndex.TYPE_CUSTOM; } else { Resource file=null; try { file=ResourceUtil.toResourceExisting(pageContext,key); pageContext.getConfig().getSecurityManager().checkFileLocation(file); } catch (ExpressionException e) {} if(file!=null && file.exists() && file.isFile()) type=SearchIndex.TYPE_FILE; else if(file!=null && file.exists() && file.isDirectory()) type=SearchIndex.TYPE_PATH; else { try { new URL(key); type=SearchIndex.TYPE_URL; } catch (MalformedURLException e) {} } } } collection.deleteIndex(pageContext,key,type,query); } /** * purge a collection * @throws PageException * @throws SearchException */ private void doPurge() throws PageException, SearchException { required("index",action,"collection",collection); collection.purge(); } /** * update a collection * @throws PageException * @throws SearchException * @throws IOException */ private void doUpdate() throws PageException, SearchException, IOException { // check attributes required("index",action,"collection",collection); required("index",action,"key",key); if(type==-1) type=(query==null)?SearchIndex.TYPE_FILE:SearchIndex.TYPE_CUSTOM; if(type==SearchIndex.TYPE_CUSTOM) { required("index",action,"body",body); //required("index",action,"query",query); } IndexResult result; // FUTURE remove this condition if(collection instanceof LuceneSearchCollection) result = ((LuceneSearchCollection)collection).index(pageContext,key,type,urlpath,title,body,language,extensions,query,recurse,categoryTree,category,timeout,custom1,custom2,custom3,custom4); else result = collection.index(pageContext,key,type,urlpath,title,body,language,extensions,query,recurse,categoryTree,category,custom1,custom2,custom3,custom4); if(!StringUtil.isEmpty(status))pageContext.setVariable(status,toStruct(result)); } @Override public int doEndTag() { return EVAL_PAGE; } private Struct toStruct(IndexResult result) { Struct sct=new StructImpl(); sct.setEL("deleted",new Double(result.getCountDeleted())); sct.setEL("inserted",new Double(result.getCountInserted())); sct.setEL("updated",new Double(result.getCountUpdated())); return sct; } }