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;
}
}