package railo.runtime.search.lucene2; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.document.Document; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.queryParser.ParseException; import org.apache.lucene.queryParser.QueryParser; import org.apache.lucene.search.Hits; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.Searcher; import org.apache.lucene.search.spell.Dictionary; import org.apache.lucene.search.spell.LuceneDictionary; import org.apache.lucene.search.spell.SpellChecker; import org.apache.lucene.store.FSDirectory; import railo.commons.io.SystemUtil; import railo.commons.io.log.LogAndSource; import railo.commons.io.res.Resource; import railo.commons.io.res.ResourcesImpl; import railo.commons.io.res.filter.DirectoryResourceFilter; import railo.commons.io.res.filter.ResourceFilter; import railo.commons.io.res.filter.ResourceNameFilter; import railo.commons.io.res.util.FileWrapper; import railo.commons.io.res.util.ResourceUtil; import railo.commons.lang.SerializableObject; import railo.commons.lang.StringUtil; import railo.runtime.op.Caster; import railo.runtime.search.AddionalAttrs; import railo.runtime.search.IndexResult; import railo.runtime.search.IndexResultImpl; import railo.runtime.search.SearchCollectionSupport; import railo.runtime.search.SearchData; import railo.runtime.search.SearchEngineSupport; import railo.runtime.search.SearchException; import railo.runtime.search.SearchIndex; import railo.runtime.search.SearchResulItem; import railo.runtime.search.SearchResulItemImpl; import railo.runtime.search.SuggestionItem; import railo.runtime.search.lucene2.docs.CustomDocument; import railo.runtime.search.lucene2.highlight.Highlight; import railo.runtime.search.lucene2.net.WebCrawler; import railo.runtime.search.lucene2.query.Literal; import railo.runtime.search.lucene2.query.Op; import railo.runtime.type.QueryColumn; import railo.runtime.type.Struct; import railo.runtime.type.StructImpl; import railo.runtime.type.dt.DateTime; import railo.runtime.type.util.ListUtil; /** * */ public final class LuceneSearchCollection extends SearchCollectionSupport { private static final long serialVersionUID = 3430238280421965781L; private Resource collectionDir; private boolean spellcheck; private LogAndSource log; private static final SerializableObject token=new SerializableObject(); /** * @param searchEngine * @param name * @param path * @param language * @param lastUpdate * @param created */ public LuceneSearchCollection(SearchEngineSupport searchEngine, String name, Resource path, String language, //int count, DateTime lastUpdate, DateTime created,boolean spellcheck) { super(searchEngine, name, path, language, lastUpdate,created); this.spellcheck=spellcheck; collectionDir=getPath().getRealResource(StringUtil.toIdentityVariableName(getName())); log=searchEngine.getLogger(); } public LuceneSearchCollection(SearchEngineSupport searchEngine, String name, Resource path, String language, //int count, DateTime lastUpdate, DateTime created) { this(searchEngine, name, path, language, lastUpdate, created, true); } @Override protected void _create() throws SearchException { try { if(!collectionDir.exists())collectionDir.createDirectory(true); } catch (IOException e) {} } @Override protected void _optimize() throws SearchException { IndexWriter[] writers=_getWriters(false); for(int i=0;i<writers.length;i++) { try { optimizeEL(writers[i]); } finally { close(writers[i]); } } } @Override protected void _map(Resource path) throws SearchException { throw new SearchException("mapping of existing Collection for file ["+path+"] not supported"); } @Override protected void _repair() throws SearchException { //throw new SearchException("repair of existing Collection not supported"); } @Override protected IndexResult _indexFile(String id, String title, Resource res,String language) throws SearchException { info(res.getAbsolutePath()); _checkLanguage(language); int before=getDocumentCount(id); IndexWriter writer=null; synchronized(token){ try { writer = _getWriter(id,true); _index(writer,res,res.getName()); writer.optimize(); } catch (Exception e) { throw new SearchException(e); } finally { close(writer); } indexSpellCheck(id); } if(getDocumentCount(id)==before) return new IndexResultImpl(0,0,1); return new IndexResultImpl(0,1,0); } @Override protected IndexResult _indexPath(String id, String title, Resource dir,String[] extensions, boolean recurse, String language) throws SearchException { info(dir.getAbsolutePath()); _checkLanguage(language); int doccount=0; IndexWriter writer=null; synchronized(token){ try { writer = _getWriter(id,true); doccount=_list(0,writer,dir,new LuceneExtensionFileFilter(extensions,recurse),""); //optimizeEL(writer); writer.optimize(); } catch (IOException e) { throw new SearchException(e); } finally { close(writer); } indexSpellCheck(id); } return new IndexResultImpl(0,0,doccount); } private void optimizeEL(IndexWriter writer) { if(writer==null)return; try { writer.optimize(); } catch (Throwable t) { //print.printST(t); } } private void indexSpellCheck(String id) throws SearchException { if(!spellcheck) return; IndexReader reader=null; FSDirectory spellDir=null; Resource dir = _createSpellDirectory(id); try { File spellFile = FileWrapper.toFile(dir); spellDir = FSDirectory.getDirectory(spellFile); reader = _getReader(id,false); Dictionary dictionary = new LuceneDictionary(reader,"contents"); SpellChecker spellChecker = new SpellChecker(spellDir); spellChecker.indexDictionary(dictionary); } catch(IOException ioe) { throw new SearchException(ioe); } finally { flushEL(reader); closeEL(reader); } } private void close(IndexWriter writer) throws SearchException { if(writer!=null){ //print.out("w-close"); try { writer.close(); } catch (IOException e) { throw new SearchException(e); } } } private static void close(IndexReader reader) throws SearchException { if(reader!=null){ try { reader.close(); } catch (IOException e) { throw new SearchException(e); } } } private static void close(Searcher searcher) throws SearchException { if(searcher!=null){ try { searcher.close(); } catch (IOException e) { throw new SearchException(e); } } } private static void flushEL(IndexReader reader) { //print.out("r-closeEL"); if(reader!=null){ try { reader.flush(); } catch (Throwable t) { //throw new SearchException(t); } } } private static void closeEL(IndexReader reader) { //print.out("r-closeEL"); if(reader!=null){ try { reader.close(); } catch (Throwable t) { //throw new SearchException(t); } } } @Override protected IndexResult _indexURL(String id, String title, URL url,String[] extensions, boolean recurse, String language)throws SearchException { //timeout=ThreadLocalPageContext.getConfig().getRequestTimeout().getMillis(); return _indexURL(id, title, url, extensions, recurse, language,50000L); } public IndexResult _indexURL(String id, String title, URL url,String[] extensions, boolean recurse, String language, long timeout)throws SearchException { _checkLanguage(language); info(url.toExternalForm()); int before=getDocumentCount(id); IndexWriter writer=null; synchronized(token){ try { writer = _getWriter(id,true); new WebCrawler(log).parse(writer, url, extensions, recurse,timeout); writer.optimize(); } catch (Exception e) { throw new SearchException(e); } finally { close(writer); } indexSpellCheck(id); } if(getDocumentCount(id)==before) return new IndexResultImpl(0,0,1); return new IndexResultImpl(0,1,0); //throw new SearchException("url indexing not supported"); } /** * @param id * @param title * @param keyColumn * @param bodyColumns * @param language * @param custom1 * @param custom2 * @param custom3 * @param custom4 * @return * @throws SearchException */ protected IndexResult _deleteCustom(String id,QueryColumn keyColumn) throws SearchException { int countBefore=0; int countAfter=0; Map<String,Document> docs=new HashMap<String,Document>(); Set<String> keys=toSet(keyColumn); IndexWriter writer=null; String key; IndexReader reader=null; Document doc; synchronized(token){ try { try { reader=_getReader(id,false); countBefore=reader.maxDoc(); for(int i=0;i<countBefore;i++) { doc=reader.document(i); key=doc.getField("key").stringValue(); if(!keys.contains(key)) docs.put(key,doc); } } catch(Exception e) {} finally { close(reader); } countAfter=docs.size(); writer = _getWriter(id,true); Iterator<Entry<String, Document>> it = docs.entrySet().iterator(); while(it.hasNext()) { writer.addDocument(it.next().getValue()); } optimizeEL(writer); } catch (IOException e) { throw new SearchException(e); } finally { close(writer); } indexSpellCheck(id); } int removes=countBefore-countAfter; return new IndexResultImpl(removes,0,0); } private Set<String> toSet(QueryColumn column) { Set<String> set=new HashSet<String>(); Iterator it = column.valueIterator(); while(it.hasNext()){ set.add(Caster.toString(it.next(),null)); } return set; } /** * @param id * @param title * @param keyColumn * @param bodyColumns * @param language * @param custom1 * @param custom2 * @param custom3 * @param custom4 * @return * @throws SearchException */ protected IndexResult _indexCustom(String id, Object title, QueryColumn keyColumn, QueryColumn[] bodyColumns, String language, Object urlpath,Object custom1,Object custom2,Object custom3,Object custom4) throws SearchException { _checkLanguage(language); String t; String url; String c1; String c2; String c3; String c4; int countExisting=0; int countAdd=keyColumn.size(); int countNew=0; Map<String,Document> docs=new HashMap<String,Document>(); IndexWriter writer=null; synchronized(token){ try { // read existing reader IndexReader reader=null; try { reader=_getReader(id,false); int len=reader.maxDoc(); Document doc; for(int i=0;i<len;i++) { doc=reader.document(i); docs.put(doc.getField("key").stringValue(),doc); } } catch(Exception e) {} finally { close(reader); } countExisting=docs.size(); writer = _getWriter(id,true); int len = keyColumn.size(); String key; for(int i=1;i<=len;i++) { key=Caster.toString(keyColumn.get(i,null),null); if(key==null) continue; StringBuilder body=new StringBuilder(); for(int y=0;y<bodyColumns.length;y++) { Object tmp=bodyColumns[y].get(i,null); if(tmp!=null){ body.append(tmp.toString()); body.append(' '); } } //t=(title==null)?null:Caster.toString(title.get(i,null),null); //url=(urlpath==null)?null:Caster.toString(urlpath.get(i,null),null); t=getRow(title,i); url=getRow(urlpath,i); c1=getRow(custom1,i); c2=getRow(custom2,i); c3=getRow(custom3,i); c4=getRow(custom4,i); docs.put(key,CustomDocument.getDocument(t,key,body.toString(),url,c1,c2,c3,c4)); } countNew=docs.size(); Iterator<Entry<String, Document>> it = docs.entrySet().iterator(); Entry<String, Document> entry; Document doc; while(it.hasNext()) { entry = it.next(); doc = entry.getValue(); writer.addDocument(doc); } optimizeEL(writer); //writer.optimize(); } catch(IOException ioe) { throw new SearchException(ioe); } finally { close(writer); } indexSpellCheck(id); } int inserts=countNew-countExisting; return new IndexResultImpl(0,inserts,countAdd-inserts); } private String getRow(Object column, int row) { if(column instanceof QueryColumn){ return Caster.toString(((QueryColumn)column).get(row,null),null); } if(column!=null) return Caster.toString(column,null); return null; } @Override protected IndexResult _purge() throws SearchException { SearchIndex[] indexes=getIndexes(); int count=0; for(int i=0;i<indexes.length;i++) { count+=getDocumentCount(indexes[i].getId()); } ResourceUtil.removeChildrenEL(collectionDir); return new IndexResultImpl(count,0,0); } @Override protected IndexResult _delete() throws SearchException { SearchIndex[] indexes=getIndexes(); int count=0; for(int i=0;i<indexes.length;i++) { count+=getDocumentCount(indexes[i].getId()); } ResourceUtil.removeEL(collectionDir, true); return new IndexResultImpl(count,0,0); } @Override protected IndexResult _deleteIndex(String id) throws SearchException { int count=getDocumentCount(id); ResourceUtil.removeEL(_getIndexDirectory(id,true), true); return new IndexResultImpl(count,0,0); } @Override public SearchResulItem[] _search(SearchData data, String criteria, String language,short type, String categoryTree, String[] category) throws SearchException { try { if(type!=SEARCH_TYPE_SIMPLE) throw new SearchException("search type explicit not supported"); Analyzer analyzer = SearchUtil.getAnalyzer(language); Query query=null; Op op=null; Object highlighter=null; railo.runtime.search.lucene2.query.QueryParser queryParser=new railo.runtime.search.lucene2.query.QueryParser(); AddionalAttrs aa = AddionalAttrs.getAddionlAttrs(); aa.setHasRowHandling(true); int startrow=aa.getStartrow(); int maxrows=aa.getMaxrows(); if(!criteria.equals("*")) { // FUTURE take this data from calling parameters op=queryParser.parseOp(criteria); if(op==null) criteria="*"; else criteria=op.toString(); try { query = new QueryParser("contents",analyzer ).parse(criteria); highlighter = Highlight.createHighlighter(query,aa.getContextHighlightBegin(),aa.getContextHighlightEnd()); } catch (ParseException e) { throw new SearchException(e); } } Resource[] files = _getIndexDirectories(); if(files==null) return new SearchResulItem[0]; ArrayList<SearchResulItem> list=new ArrayList<SearchResulItem>(); String ct,c; ArrayList<String> spellCheckIndex=spellcheck?new ArrayList<String>():null; int count=0; IndexReader reader = null; Searcher searcher = null; try { outer:for(int i=0;i<files.length;i++) { if(removeCorrupt(files[i]))continue; String strFile=files[i].toString(); SearchIndex si = indexes.get(files[i].getName()); if(si==null)continue; ct=si.getCategoryTree(); c=ListUtil.arrayToList(si.getCategories(), ","); // check category tree if(!matchCategoryTree(ct,categoryTree))continue; if(!matchCategories(si.getCategories(),category))continue; Document doc; String id=files[i].getName(); data.addRecordsSearched(_countDocs(strFile)); reader = _getReader(id,false); if(query==null && "*".equals(criteria)) { int len=reader.numDocs(); for(int y=0;y<len;y++) { if(startrow>++count)continue; if(maxrows>-1 && list.size()>=maxrows) break outer; doc = reader.document(y); list.add(createSearchResulItem(highlighter,analyzer,doc,id,1,ct,c,aa.getContextPassages(),aa.getContextBytes())); } } else { if(spellcheck)spellCheckIndex.add(id); // search searcher = new IndexSearcher(reader); Hits hits = searcher.search(query); int len=hits.length(); for (int y=0; y<len; y++) { if(startrow>++count)continue; if(maxrows>-1 && list.size()>=maxrows) break outer; //list.add(new SearchResulItemHits(hits,y,highlighter,analyzer,id,ct,c,aa.getContextPassages(),aa.getContextBytes())); doc = hits.doc(y); list.add(createSearchResulItem(highlighter,analyzer,doc,id,hits.score(y),ct,c,aa.getContextPassages(),aa.getContextBytes())); } } } } finally { close(reader); close(searcher); } // spellcheck //SearchData data=ThreadLocalSearchData.get(); if(spellcheck && data!=null) { if(data.getSuggestionMax()>=list.size()) { Map suggestions = data.getSuggestion(); Iterator it = spellCheckIndex.iterator(); String id; Literal[] literals = queryParser.getLiteralSearchedTerms(); String[] strLiterals = queryParser.getStringSearchedTerms(); boolean setSuggestionQuery=false; while(it.hasNext()) { id=(String) it.next(); // add to set to remove duplicate values SuggestionItem si; SpellChecker sc = getSpellChecker(id); for(int i=0;i<strLiterals.length;i++) { String[] arr = sc.suggestSimilar(strLiterals[i], 1000); if(arr.length>0){ literals[i].set("<suggestion>"+arr[0]+"</suggestion>"); setSuggestionQuery=true; si=(SuggestionItem) suggestions.get(strLiterals[i]); if(si==null)suggestions.put(strLiterals[i],new SuggestionItem(arr)); else si.add(arr); } } } if(setSuggestionQuery)data.setSuggestionQuery(op.toString()); } } return list.toArray(new SearchResulItem[list.size()]); } catch (IOException e) { throw new SearchException(e); } } private SpellChecker getSpellChecker(String id) throws IOException { FSDirectory siDir = FSDirectory.getDirectory(FileWrapper.toFile(_getSpellDirectory(id))); SpellChecker spellChecker = new SpellChecker(siDir); return spellChecker; } private boolean removeCorrupt(Resource dir) { if(ResourceUtil.isEmptyFile(dir)) { ResourceUtil.removeEL(dir, true); return true; } return false; } private static SearchResulItem createSearchResulItem(Object highlighter,Analyzer a,Document doc, String name, float score, String ct, String c,int maxNumFragments, int maxLength) { String contextSummary=""; if(maxNumFragments>0) contextSummary=Highlight.createContextSummary(highlighter,a,doc.get("contents"),maxNumFragments,maxLength,doc.get("summary")); String summary = doc.get("summary"); return new SearchResulItemImpl( name, doc.get("title"), score, doc.get("key"), doc.get("url"), summary,contextSummary, ct,c, doc.get("custom1"), doc.get("custom2"), doc.get("custom3"), doc.get("custom4"), doc.get("mime-type"), doc.get("author"), doc.get("size")); } private boolean matchCategories(String[] categoryIndex, String[] categorySearch) { if(categorySearch==null ||categorySearch.length==0) return true; String search; for(int s=0;s<categorySearch.length;s++) { search=categorySearch[s]; for(int i=0;i<categoryIndex.length;i++) { if(search.equals(categoryIndex[i]))return true; } } return false; } private boolean matchCategoryTree(String categoryTreeIndex, String categoryTreeSearch) { //if(StringUtil.isEmpty(categoryTreeIndex) || categoryTreeIndex.equals("/")) return true; //if(StringUtil.isEmpty(categoryTreeSearch) || categoryTreeSearch.equals("/")) return true; return categoryTreeIndex.startsWith(categoryTreeSearch); } /** * list a directory and call every file * @param writer * @param res * @param filter * @param url * @throws IOException * @throws InterruptedException */ private int _list(int doccount,IndexWriter writer, Resource res,ResourceFilter filter,String url) { if (res.isReadable()) { if (res.exists() && res.isDirectory()) { Resource[] files = (filter==null)?res.listResources():res.listResources(filter); if (files != null) { for (int i = 0; i < files.length; i++) { if(removeCorrupt(files[i])){ continue; } doccount=_list(doccount,writer, files[i],filter,url+"/"+files[i].getName()); } } } else { try { info(res.getAbsolutePath()); _index(writer,res,url); doccount++; } catch (Exception e) {} } } return doccount; } /** * index a single file * @param writer * @param file * @param url * @throws IOException * @throws InterruptedException */ private void _index(IndexWriter writer, Resource file,String url) throws IOException { if(!file.exists()) return; writer.addDocument(DocumentUtil.toDocument(file,url,SystemUtil.getCharset().name())); } /** * @param id * @return returns the Index Directory */ private Resource _getIndexDirectory(String id, boolean createIfNotExists) { Resource indexDir=collectionDir.getRealResource(id); if(createIfNotExists && !indexDir.exists())indexDir.mkdirs(); return indexDir; } /** * get writer to id * @param id * @return returns the Writer * @throws IOException * @throws SearchException * @throws IOException */ private IndexWriter _getWriter(String id,boolean create) throws SearchException, IOException { // FUTURE support for none file -> Directory Object Resource dir = _getIndexDirectory(id,true); return new IndexWriter(FileWrapper.toFile(dir), SearchUtil.getAnalyzer(getLanguage()), create); //return new ResourceIndexWriter(dir, SearchUtil.getAnalyzer(getLanguage()), create); /*try { return new ResourceIndexWriter(dir, SearchUtil.getAnalyzer(getLanguage()), true); } catch (IOException e) { ResourceUtil.removeChildrenEL(dir); dir.getResourceProvider().unlock(dir); return new ResourceIndexWriter(dir, SearchUtil.getAnalyzer(getLanguage()),true); }*/ } private IndexReader _getReader(String id,boolean absolute) throws IOException { return _getReader(_getFile(id, absolute)); } private IndexReader _getReader(File file) throws IOException { if(!IndexReader.indexExists(file))throw new IOException("there is no index in ["+file+"]"); return IndexReader.open(file); } private File _getFile(String id,boolean absolute) throws IOException { Resource res = absolute?ResourcesImpl.getFileResourceProvider().getResource(id):_getIndexDirectory(id,true); res.getResourceProvider().read(res); return FileWrapper.toFile(res); } /** * @return returns all existing IndexWriter */ private Resource[] _getIndexDirectories() { Resource[] files = collectionDir.listResources(new DirectoryResourceFilter()); return files; } /** * @return returns all existing IndexWriter * @throws SearchException */ private IndexWriter[] _getWriters(boolean create) throws SearchException { Resource[] files = _getIndexDirectories(); if(files==null) return new IndexWriter[0]; IndexWriter[] writers=new IndexWriter[files.length]; for(int i=0;i<files.length;i++) { try { writers[i]=_getWriter(files[i].getName(),create); } catch (IOException e) {} } return writers; } private int _countDocs(String col) { // FUTURE add support for none file resources int totalDocs; IndexReader reader=null; try { reader=_getReader(col,true); totalDocs = reader.numDocs(); } catch(Exception e) { return 0; } finally { closeEL(reader); } return totalDocs; } /** * @deprecated see SearchUtil.getAnalyzer(String language); * @param language * @return returns language matching Analyzer * @throws SearchException */ public static Analyzer _getAnalyzer(String language) throws SearchException { return SearchUtil.getAnalyzer(language); } /** * check given language against collection language * @param language * @throws SearchException */ private void _checkLanguage(String language) throws SearchException { if(language!=null && !language.trim().equalsIgnoreCase(getLanguage())) { throw new SearchException("collection Language and Index Language must be of same type, but collection language is of type ["+getLanguage()+"] and index language is of type ["+language+"]"); } } @Override public int getDocumentCount(String id) { try { if(!_getIndexDirectory(id,false).exists()) return 0; IndexReader r=null; int num=0; try { r = _getReader(id,false); num=r.numDocs(); } finally { close(r); } return num; } catch (Exception e) {} return 0; } @Override public int getDocumentCount() { int count=0; SearchIndex[] _indexes = getIndexes(); for(int i=0;i<_indexes.length;i++) { count+=getDocumentCount(_indexes[i].getId()); } return count; } @Override public long getSize() { return ResourceUtil.getRealSize(collectionDir)/1024; } public Object getCategoryInfo() { Struct categories=new StructImpl(); Struct categorytrees=new StructImpl(); Struct info=new StructImpl(); info.setEL("categories", categories); info.setEL("categorytrees", categorytrees); Iterator it = indexes.keySet().iterator(); String[] cats; String catTree; Double tmp; while(it.hasNext()) { SearchIndex index=indexes.get(it.next()); // category tree catTree = index.getCategoryTree(); tmp=(Double) categorytrees.get(catTree,null); if(tmp==null) categorytrees.setEL(catTree,Caster.toDouble(1)); else categorytrees.setEL(catTree,Caster.toDouble(tmp.doubleValue()+1)); // categories cats = index.getCategories(); for(int i=0;i<cats.length;i++) { tmp=(Double) categories.get(cats[i],null); if(tmp==null) categories.setEL(cats[i],Caster.toDouble(1)); else categories.setEL(cats[i],Caster.toDouble(tmp.doubleValue()+1)); } } return info; } class ResourceIndexWriter extends IndexWriter { private Resource dir; public ResourceIndexWriter(Resource dir, Analyzer analyzer, boolean create) throws IOException { super(FileWrapper.toFile(dir), analyzer, create); this.dir=dir; dir.getResourceProvider().lock(dir); } @Override public synchronized void close() throws IOException { super.close(); dir.getResourceProvider().unlock(dir); } } private Resource _createSpellDirectory(String id) { Resource indexDir=collectionDir.getRealResource(id+"_"+(_getMax(true)+1)+"_spell"); //print.out("create:"+indexDir); indexDir.mkdirs(); return indexDir; } private Resource _getSpellDirectory(String id) { Resource indexDir=collectionDir.getRealResource(id+"_"+_getMax(false)+"_spell"); //print.out("get:"+indexDir); return indexDir; } private long _getMax(boolean delete) { Resource[] children = collectionDir.listResources(new SpellDirFilter()); long max=0, nbr; String name; for(int i=0;i<children.length;i++) { name=children[i].getName(); name=name.substring(0,name.length()-6); nbr=Caster.toLongValue(name.substring(name.lastIndexOf('_')+1),0); if(delete){ try { children[i].remove(true); continue; } catch (Throwable t) {} } if(nbr>max)max=nbr; } return max; } private void info(String doc) { if(log==null) return; log.info("Collection:"+getName(), "indexing "+doc); } public class SpellDirFilter implements ResourceNameFilter { @Override public boolean accept(Resource parent, String name) { return name.endsWith("_spell"); } } }