lucee.runtime.search.lucene2.LuceneSearchCollection.java Source code

Java tutorial

Introduction

Here is the source code for lucee.runtime.search.lucene2.LuceneSearchCollection.java

Source

/**
 *
 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public 
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 * 
 **/
package lucee.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 lucee.commons.io.SystemUtil;
import lucee.commons.io.log.Log;
import lucee.commons.io.res.Resource;
import lucee.commons.io.res.ResourcesImpl;
import lucee.commons.io.res.filter.DirectoryResourceFilter;
import lucee.commons.io.res.filter.ResourceFilter;
import lucee.commons.io.res.filter.ResourceNameFilter;
import lucee.commons.io.res.util.FileWrapper;
import lucee.commons.io.res.util.ResourceUtil;
import lucee.commons.lang.SerializableObject;
import lucee.commons.lang.StringUtil;
import lucee.runtime.config.ConfigImpl;
import lucee.runtime.op.Caster;
import lucee.runtime.search.AddionalAttrs;
import lucee.runtime.search.IndexResult;
import lucee.runtime.search.IndexResultImpl;
import lucee.runtime.search.SearchCollectionSupport;
import lucee.runtime.search.SearchData;
import lucee.runtime.search.SearchEngineSupport;
import lucee.runtime.search.SearchException;
import lucee.runtime.search.SearchIndex;
import lucee.runtime.search.SearchResulItem;
import lucee.runtime.search.SearchResulItemImpl;
import lucee.runtime.search.SuggestionItem;
import lucee.runtime.search.lucene2.docs.CustomDocument;
import lucee.runtime.search.lucene2.highlight.Highlight;
import lucee.runtime.search.lucene2.net.WebCrawler;
import lucee.runtime.search.lucene2.query.Literal;
import lucee.runtime.search.lucene2.query.Op;
import lucee.runtime.type.QueryColumn;
import lucee.runtime.type.Struct;
import lucee.runtime.type.StructImpl;
import lucee.runtime.type.dt.DateTime;
import lucee.runtime.type.util.ListUtil;

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;

/**
 * 
 */
public final class LuceneSearchCollection extends SearchCollectionSupport {

    private static final long serialVersionUID = 3430238280421965781L;

    private Resource collectionDir;
    private boolean spellcheck;
    private Log 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 = ((ConfigImpl) searchEngine.getConfig()).getLog("search");
    }

    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;
            lucee.runtime.search.lucene2.query.QueryParser queryParser = new lucee.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.log(Log.LEVEL_INFO, "Collection:" + getName(), "indexing " + doc);
    }

    public class SpellDirFilter implements ResourceNameFilter {

        @Override
        public boolean accept(Resource parent, String name) {
            return name.endsWith("_spell");
        }

    }
}