package railo.runtime.search; import java.net.MalformedURLException; import java.net.URL; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import org.w3c.dom.Element; import railo.commons.collection.MapFactory; import railo.commons.io.FileUtil; import railo.commons.io.log.Log; import railo.commons.io.res.Resource; import railo.commons.io.res.util.ResourceUtil; import railo.commons.lang.StringUtil; import railo.commons.lock.KeyLock; import railo.commons.lock.Lock; import railo.commons.net.HTTPUtil; import railo.runtime.PageContext; import railo.runtime.exp.DatabaseException; import railo.runtime.exp.PageException; import railo.runtime.op.Caster; import railo.runtime.type.ArrayImpl; import railo.runtime.type.Query; import railo.runtime.type.QueryColumn; import railo.runtime.type.QueryImpl; import railo.runtime.type.dt.DateTime; import railo.runtime.type.dt.DateTimeImpl; import railo.runtime.type.util.ArrayUtil; import railo.runtime.type.util.KeyConstants; import railo.runtime.type.util.ListUtil; /** * represent a single Collection */ public abstract class SearchCollectionSupport2 implements SearchCollectionPlus { private static final int LOCK_TIMEOUT = 10*60*1000; // ten minutes private String name; private Resource path; private String language; private DateTime lastUpdate; private SearchEngineSupport searchEngine; //TODO change visibility to private protected Map<String,SearchIndex> indexes=MapFactory.<String,SearchIndex>getConcurrentMap(); private DateTime created; private Log log; //private static LockManager manager=Lock Manager Impl.getInstance(); private KeyLock<String> lock=new KeyLock<String>(); /** * constructor of the class * @param searchEngine * @param name name of the Collection * @param path * @param language * @param count total count of documents in the collection * @param lastUpdate * @param created */ public SearchCollectionSupport2(SearchEngineSupport searchEngine, String name,Resource path, String language, DateTime lastUpdate, DateTime created) { this.searchEngine=searchEngine; this.name=name; this.path=path; this.language=SearchUtil.translateLanguage(language); this.lastUpdate=lastUpdate; this.created=created; this.log = searchEngine.getLogger(); } @Override public final void create() throws SearchException { Lock l = lock(); try { _create(); } finally { unlock(l); } } /** * create a collection * @throws SearchException */ protected abstract void _create() throws SearchException; @Override public final void optimize() throws SearchException { Lock l = lock(); try { _optimize(); changeLastUpdate(); } finally { unlock(l); } } /** * optimize a Collection * @throws SearchException */ protected abstract void _optimize() throws SearchException ; @Override public final void map(Resource path) throws SearchException { Lock l = lock(); try { _map(path); changeLastUpdate(); } finally { unlock(l); } } /** * map a Collection * @param path * @throws SearchException */ protected abstract void _map(Resource path) throws SearchException ; @Override public final void repair() throws SearchException { Lock l = lock(); try { _repair(); changeLastUpdate(); } finally { unlock(l); } } /** * repair a Collection * @throws SearchException */ protected abstract void _repair() throws SearchException ; @Override public IndexResult index(PageContext pc, String key, short type, String urlpath, String title, String body, String language, String[] extensions, String query, boolean recurse,String categoryTree, String[] categories, String custom1, String custom2, String custom3, String custom4) throws PageException, MalformedURLException, SearchException { return index(pc, key, type, urlpath, title, body, language, extensions, query, recurse, categoryTree, categories, 10000, custom1, custom2, custom3, custom4); } // FUTURE add this to interface public IndexResult index(PageContext pc, String key, short type, String urlpath, String title, String body, String language, String[] extensions, String query, boolean recurse,String categoryTree, String[] categories, long timeout, String custom1, String custom2, String custom3, String custom4) throws PageException, MalformedURLException, SearchException { language=SearchUtil.translateLanguage(language); Lock l = lock(); try { SearchIndex si = new SearchIndex(title,key,type,query,extensions,language,urlpath,categoryTree,categories, custom1,custom2,custom3,custom4); //String id=si.getId(); IndexResult ir=IndexResultImpl.EMPTY; if(type==SearchIndex.TYPE_FILE){ Resource file=ResourceUtil.toResourceNotExisting(pc,key); if(!file.isFile())throw new SearchException("value of attribute key must specify a existing file, ["+key+"] is invalid"); ir=indexFile(si,file); //ir=indexFile(id,title,file,language); } else if(type==SearchIndex.TYPE_PATH){ Resource dir=ResourceUtil.toResourceNotExisting(pc,key); if(!dir.isDirectory())throw new SearchException("value of attribute key must specify a existing directory, ["+key+"] is invalid"); ir=indexPath(si,dir,recurse); } else if(type==SearchIndex.TYPE_URL) { ir=indexURL(si,new URL(key),recurse,timeout); } else if(type==SearchIndex.TYPE_CUSTOM) { Query qv; if(StringUtil.isEmpty(query)){ // set columns railo.runtime.type.Array columns=new ArrayImpl(); columns.append("key"); columns.append("body"); if(!StringUtil.isEmpty(title))columns.append("title"); if(!StringUtil.isEmpty(urlpath))columns.append("urlpath"); if(!StringUtil.isEmpty(custom1))columns.append("custom1"); if(!StringUtil.isEmpty(custom2))columns.append("custom2"); if(!StringUtil.isEmpty(custom3))columns.append("custom3"); if(!StringUtil.isEmpty(custom4))columns.append("custom4"); // populate query with a single row qv=new QueryImpl(columns,1,"query"); // body qv.setAt(KeyConstants._key, 1, key); key="key"; // body qv.setAt(KeyConstants._body, 1, body); body="body"; // title if(!StringUtil.isEmpty(title)){ qv.setAt(KeyConstants._title, 1, title); title="title"; } // custom1 if(!StringUtil.isEmpty(urlpath)){ qv.setAt("urlpath", 1, urlpath); custom1="urlpath"; } // custom1 if(!StringUtil.isEmpty(custom1)){ qv.setAt(KeyConstants._custom1, 1, custom1); custom1="custom1"; } // custom2 if(!StringUtil.isEmpty(custom2)){ qv.setAt(KeyConstants._custom2, 1, custom2); custom2="custom2"; } // custom3 if(!StringUtil.isEmpty(custom3)){ qv.setAt(KeyConstants._custom3, 1, custom3); custom3="custom3"; } // custom4 if(!StringUtil.isEmpty(custom4)){ qv.setAt(KeyConstants._custom4, 1, custom4); custom4="custom4"; } } else qv = Caster.toQuery(pc.getVariable(query)); QueryColumn keyColumn=qv.getColumn(key); String[] strBodies=ListUtil.toStringArrayTrim(ListUtil.listToArrayRemoveEmpty(body,',')); QueryColumn[] bodyColumns=new QueryColumn[strBodies.length]; for(int i=0;i<bodyColumns.length;i++) { bodyColumns[i]=qv.getColumn(strBodies[i]); } ir= indexCustom(si, getColumnEL(qv,title), keyColumn, bodyColumns, getColumnEL(qv,urlpath), getColumnEL(qv,custom1), getColumnEL(qv,custom2), getColumnEL(qv,custom3), getColumnEL(qv,custom4)); } createIndex(si); return ir; } finally { unlock(l); } } private QueryColumn getColumnEL(Query query, String column) { if(column==null || column.length()==0) return null; return query.getColumn(column,null); } @Override public final IndexResult indexFile(String id,String title, Resource res, String language) throws SearchException { throw new SearchException("method indexFile(...) no longer supported use index(...) instead"); } public final IndexResult indexFile(SearchIndex si, Resource file) throws SearchException { IndexResult ir=_indexFile(si,file); changeLastUpdate(); return ir; } protected abstract IndexResult _indexFile(SearchIndex si, Resource file) throws SearchException; @Override public final IndexResult indexPath(String id, String title, Resource dir, String[] extensions, boolean recurse, String language) throws SearchException { throw new SearchException("method indexPath(...) no longer supported use index(...) instead"); } public final IndexResult indexPath(SearchIndex si, Resource dir, boolean recurse) throws SearchException { IndexResult ir=_indexPath(si,dir,recurse); changeLastUpdate(); return ir; } /** * updates a collection with a path * @param dir * @param id * @param title * @param dir * @param recurse * @param recurse * @param extensions * @param language * @throws SearchException */ protected abstract IndexResult _indexPath(SearchIndex si, Resource dir, boolean recurse) throws SearchException; @Override public final IndexResult indexURL(String id,String title, URL url, String[] extensions, boolean recurse, String language) throws SearchException { return indexURL(id, title, url, extensions, recurse, language, 10000); } // FUTURE replace this in interface with method above public final IndexResult indexURL(String id,String title, URL url, String[] extensions, boolean recurse, String language,long timeout) throws SearchException { throw new SearchException("method indexURL(...) no longer supported use index(...) instead"); } public final IndexResult indexURL(SearchIndex si, URL url, boolean recurse,long timeout) throws SearchException { IndexResult ir=_indexURL(si,url,recurse,timeout); changeLastUpdate(); return ir; } protected abstract IndexResult _indexURL(SearchIndex si, URL url, boolean recurse, long timeout) throws SearchException ; @Override public final IndexResult indexCustom(String id, QueryColumn title, QueryColumn keyColumn, QueryColumn[] bodyColumns, String language, QueryColumn custom1, QueryColumn custom2, QueryColumn custom3, QueryColumn custom4) throws SearchException { throw new SearchException("method indexCustom(...) no longer supported use index(...) instead"); } public final IndexResult indexCustom(SearchIndex si, QueryColumn colTitle, QueryColumn keyColumn, QueryColumn[] bodyColumns, QueryColumn ct1Column, QueryColumn ct2Column, QueryColumn ct3Column, QueryColumn ct4Column) throws SearchException { IndexResult ir=_indexCustom(si, colTitle, keyColumn, bodyColumns, null,ct1Column, ct2Column, ct3Column, ct4Column); changeLastUpdate(); return ir; } public final IndexResult indexCustom(SearchIndex si, QueryColumn colTitle, QueryColumn keyColumn, QueryColumn[] bodyColumns, QueryColumn urlpath, QueryColumn ct1Column, QueryColumn ct2Column, QueryColumn ct3Column, QueryColumn ct4Column) throws SearchException { IndexResult ir=_indexCustom(si, colTitle, keyColumn, bodyColumns, urlpath, ct1Column, ct2Column, ct3Column, ct4Column); changeLastUpdate(); return ir; } /** * updates a collection with a custom * @param id * @param title Title for the Index * @param keyColumn Key Column * @param bodyColumns Body Column Array * @param language Language for index * @param custom1 * @param custom2 * @param custom3 * @param custom4 * @throws SearchException */ //protected abstract IndexResult _indexCustom(SearchIndex si, QueryColumn colTitle, QueryColumn keyColumn, QueryColumn[] bodyColumns, QueryColumn ct1Column, QueryColumn ct2Column, QueryColumn ct3Column, QueryColumn ct4Column) throws SearchException; protected abstract IndexResult _indexCustom(SearchIndex si, QueryColumn colTitle, QueryColumn keyColumn, QueryColumn[] bodyColumns, QueryColumn urlpath, QueryColumn ct1Column, QueryColumn ct2Column, QueryColumn ct3Column, QueryColumn ct4Column) throws SearchException; /** * @param index * @throws SearchException */ private void createIndex(SearchIndex index) throws SearchException { Iterator<String> it = indexes.keySet().iterator(); SearchIndex otherIndex=null; while(it.hasNext()) { Object key=it.next(); if(key.equals(index.getId())) { otherIndex=indexes.get(key); break; } } Element collElement=searchEngine.getCollectionElement(name); // Insert if(otherIndex==null) { addIndex(index); collElement.appendChild(searchEngine.toElement(index)); } // Update else { addIndex(index); Element el=searchEngine.getIndexElement(collElement,index.getId()); searchEngine.setAttributes(el,index); } changeLastUpdate(); } /** * @param index */ public void addIndex(SearchIndex index) { indexes.put(index.getId(),index); } @Override public final String getLanguage() { return language; } @Override public final IndexResult purge() throws SearchException { Lock l = lock(); try { indexes.clear(); IndexResult ir=_purge(); searchEngine.purgeCollection(this); changeLastUpdate(); return ir; } finally { unlock(l); } } /** * purge a collection * @throws SearchException */ protected abstract IndexResult _purge() throws SearchException; @Override public final IndexResult delete() throws SearchException { Lock l = lock(); try { IndexResult ir=_delete(); searchEngine.removeCollection(this); return ir; } finally { unlock(l); } } /** * delete the collection from a file * @throws SearchException */ protected abstract IndexResult _delete() throws SearchException; @Override public final IndexResult deleteIndex(PageContext pc,String key,short type,String queryName) throws SearchException { Iterator it = indexes.keySet().iterator(); while(it.hasNext()) { Object id = it.next(); if(id.equals(SearchIndex.toId(type,key,queryName))) { SearchIndex index = indexes.get(id); IndexResult ir=_deleteIndex(index.getId()); Element indexEl=searchEngine.getIndexElement(searchEngine.getCollectionElement(name),index.getId()); if(indexEl!=null)indexEl.getParentNode().removeChild(indexEl); changeLastUpdate(); return ir; } } return new IndexResultImpl(0,0,0); } /** * delete a Index from collection * @param id id ofthe Index to delete * @throws SearchException */ protected abstract IndexResult _deleteIndex(String id) throws SearchException; @Override public final Resource getPath() { return path; } @Override public DateTime getCreated() { return created; } @Override public final DateTime getLastUpdate() { return lastUpdate; } @Override public final String getName() { return name; } @Override public final Log getLogger() { return log; } @Override public final SearchEngine getSearchEngine() { return searchEngine; } /** * change the last update attribute and store it * @throws SearchException */ private void changeLastUpdate() throws SearchException { lastUpdate=new DateTimeImpl(); searchEngine.store(); } @Override public Object created() { return created; } @Override public final int search(SearchData data, Query qry,String criteria, String language, short type,int startrow,int maxrow,String categoryTree, String[] categories) throws SearchException, PageException { int len=qry.getRecordcount(); SearchResulItem[] records; AddionalAttrs aa = AddionalAttrs.getAddionlAttrs(); boolean hasRowHandling=false; aa.setStartrow(startrow); if(maxrow!=-1)aa.setMaxrows(maxrow-len); Lock l = lock(); try { records = _search(data, criteria,language,type,categoryTree,categories); } finally { unlock(l); if(hasRowHandling=aa.hasRowHandling()) startrow = aa.getStartrow(); } // Startrow if(!hasRowHandling && startrow>1) { if(startrow>records.length) { return startrow-records.length; } int start=startrow-1; SearchResulItem[] tmpRecords=new SearchResulItem[records.length-start]; for(int i=start;i<records.length;i++) { tmpRecords[i-start]=records[i]; } records=tmpRecords; startrow=1; } if(!ArrayUtil.isEmpty(records)) { int to=(!hasRowHandling && maxrow>-1 && len+records.length>maxrow)?maxrow-len:records.length; qry.addRow(to); String title; String custom1; String custom2; String custom3; String custom4; String url; SearchResulItem record; SearchIndex si; for(int y=0;y<to;y++) { int row=len+y+1; record = records[y]; si=indexes.get(record.getId()); title=record.getTitle(); custom1=record.getCustom1(); custom2=record.getCustom2(); custom3=record.getCustom3(); custom4=record.getCustom4(); url=record.getUrl(); qry.setAt(KeyConstants._title,row,title); qry.setAt(KeyConstants._custom1,row,custom1); qry.setAt(KeyConstants._custom2,row,custom2); qry.setAt(KeyConstants._custom3,row,custom3); qry.setAt(KeyConstants._custom4,row,custom4); qry.setAt("categoryTree",row,record.getCategoryTree()); qry.setAt(KeyConstants._category,row,record.getCategory()); qry.setAt(KeyConstants._type,row,record.getMimeType()); qry.setAt(KeyConstants._author,row,record.getAuthor()); qry.setAt(KeyConstants._size,row,record.getSize()); qry.setAt(KeyConstants._summary,row,record.getSummary()); qry.setAt(KeyConstants._context,row,record.getContextSummary()); qry.setAt(KeyConstants._score,row,new Float(record.getScore())); qry.setAt(KeyConstants._key,row,record.getKey()); qry.setAt(KeyConstants._url,row,url); qry.setAt(KeyConstants._collection,row,getName()); qry.setAt(KeyConstants._rank,row,new Double(row)); String rootPath,file; String urlPath; if(si!=null) { switch(si.getType()){ case SearchIndex.TYPE_PATH: rootPath = si.getKey(); rootPath=rootPath.replace(FileUtil.FILE_ANTI_SEPERATOR,FileUtil.FILE_SEPERATOR); file=record.getKey(); file=file.replace(FileUtil.FILE_ANTI_SEPERATOR,FileUtil.FILE_SEPERATOR); qry.setAt(KeyConstants._url,row,toURL(si.getUrlpath(),StringUtil.replace(file, rootPath, "", true))); break; case SearchIndex.TYPE_URL: rootPath = si.getKey(); urlPath = si.getUrlpath(); try { rootPath = getDirectory(si.getKey()); } catch (MalformedURLException e) {} if(StringUtil.isEmpty(urlPath))urlPath=rootPath; file=record.getKey(); qry.setAt(KeyConstants._url,row,toURL(urlPath,StringUtil.replace(file, rootPath, "", true))); break; default: qry.setAt(KeyConstants._url,row,toURL(si.getUrlpath(),url)); break; } if(StringUtil.isEmpty(title)) qry.setAt(KeyConstants._title,row,si.getTitle()); if(StringUtil.isEmpty(custom1)) qry.setAt(KeyConstants._custom1,row,si.getCustom1()); if(StringUtil.isEmpty(custom2)) qry.setAt(KeyConstants._custom2,row,si.getCustom2()); if(StringUtil.isEmpty(custom3)) qry.setAt(KeyConstants._custom3,row,si.getCustom3()); if(StringUtil.isEmpty(custom4)) qry.setAt(KeyConstants._custom4,row,si.getCustom4()); } } } return startrow; } public static String getDirectory(String strUrl) throws MalformedURLException { URL url = new URL(strUrl); String path=url.getPath(); int slashIndex = path.lastIndexOf('/'); int dotIndex = path.lastIndexOf('.'); // no dot if(dotIndex==-1){ if(path.endsWith("/"))return HTTPUtil.removeRef(url).toExternalForm(); return HTTPUtil.removeRef(new URL( url.getProtocol(), url.getHost(), url.getPort(),path+"/")).toExternalForm(); } if(slashIndex>dotIndex){ path=path.substring(0,dotIndex); slashIndex = path.lastIndexOf('/'); } return HTTPUtil.removeRef(new URL( url.getProtocol(), url.getHost(), url.getPort(),path.substring(0,slashIndex+1))).toExternalForm(); } private static String toURL(String url, String path) { if(StringUtil.isEmpty(url)) return path; if(StringUtil.isEmpty(path)) return url; url=url.replace('\\','/'); path=path.replace('\\','/'); if(StringUtil.startsWith(path, '/'))path=path.substring(1); if(StringUtil.endsWith(url, '/'))url=url.substring(0,url.length()-1); if(StringUtil.startsWithIgnoreCase(path, url)) return path; return url+"/"+path; } protected SearchIndex[] getIndexes() { //Iterator<Entry<String, SearchIndex>> it = indexes.entrySet().iterator(); Iterator<SearchIndex> it = indexes.values().iterator(); int len=indexes.size(); SearchIndex[] rtn=new SearchIndex[len]; int count=0; while(it.hasNext()) { rtn[count++]=it.next(); } return rtn; } private Lock lock() throws SearchException { try { return lock.lock(getId(),LOCK_TIMEOUT); //manager.lock(LockManager.TYPE_EXCLUSIVE,getId(),LOCK_TIMEOUT,ThreadLocalPageContext.get().getId()); } catch (Exception e) { throw new SearchException(e); } } private void unlock(Lock l) { lock.unlock(l); //manager.unlock(ThreadLocalPageContext.get().getId()); } private String getId() { return path.getRealResource(name).getAbsolutePath(); } // FUTURE public Object getIndexesAsQuery() { Iterator<Entry<String, SearchIndex>> it = indexes.entrySet().iterator(); final String v="VARCHAR"; Query query=null; String[] cols = new String[]{ "categories","categoryTree","custom1","custom2","custom3","custom4","extensions", "key","language","query","title","urlpath","type"}; String[] types = new String[]{ v,v,v,v,v,v,v, v,v,v,v,v,v}; try { query=new QueryImpl(cols,types, 0,"query"); } catch (DatabaseException e) { query=new QueryImpl(cols, 0,"query"); } Entry<String, SearchIndex> entry; SearchIndex index; int row=0; while(it.hasNext()) { query.addRow(); row++; entry = it.next(); index=entry.getValue(); if(index==null)continue; try { query.setAt("categories",row,ListUtil.arrayToList(index.getCategories(),"")); query.setAt("categoryTree",row,index.getCategoryTree()); query.setAt(KeyConstants._custom1,row,index.getCustom1()); query.setAt(KeyConstants._custom2,row,index.getCustom2()); query.setAt(KeyConstants._custom3,row,index.getCustom3()); query.setAt(KeyConstants._custom4,row,index.getCustom4()); query.setAt(KeyConstants._extensions,row,ListUtil.arrayToList(index.getExtensions(),",")); query.setAt(KeyConstants._key,row,index.getKey()); query.setAt(KeyConstants._language,row,index.getLanguage()); query.setAt(KeyConstants._query,row,index.getQuery()); query.setAt(KeyConstants._title,row,index.getTitle()); query.setAt("urlpath",row,index.getUrlpath()); query.setAt(KeyConstants._type,row,SearchIndex.toStringTypeEL(index.getType())); } catch(PageException pe) {} } return query; } }