axiom.objectmodel.dom.LuceneManager.java Source code

Java tutorial

Introduction

Here is the source code for axiom.objectmodel.dom.LuceneManager.java

Source

/*
 * Axiom Stack Web Application Framework
 * Copyright (C) 2008  Axiom Software Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Axiom Software Inc., 11480 Commerce Park Drive, Third Floor, Reston, VA 20191 USA
 * email: info@axiomsoftwareinc.com
 */
package axiom.objectmodel.dom;

import java.util.*;
import java.io.*;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import org.apache.lucene.document.*;
import org.apache.lucene.index.*;
import org.apache.lucene.store.*;
import org.apache.lucene.search.*;
import org.apache.lucene.analysis.*;
import org.apache.lucene.analysis.standard.*;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.ScriptRuntime;

import axiom.extensions.trans.TransactionException;
import axiom.framework.ErrorReporter;
import axiom.framework.IPathElement;
import axiom.framework.core.Application;
import axiom.framework.core.Prototype;
import axiom.framework.core.RequestEvaluator;
import axiom.objectmodel.*;
import axiom.objectmodel.db.*;
import axiom.scripting.ScriptingEngine;
import axiom.scripting.rhino.FileObject;
import axiom.scripting.rhino.MultiValue;
import axiom.scripting.rhino.Reference;
import axiom.scripting.rhino.XhtmlUtils;
import axiom.scripting.rhino.extensions.XMLSerializer;
import axiom.util.FileUtils;
import axiom.util.ResourceProperties;
import axiom.util.UrlEncoded;
import axiom.util.XmlUtils;

public class LuceneManager {

    private static HashMap _lmMap = new HashMap();

    private Application app;
    private Directory directory;
    private IndexWriterManager writerManager;
    private LuceneDataFormatter dataFormatter;
    private IndexOptimizingRunner/*TODO:LuceneOptimizer*/ optimizerThread;
    private IndexSearcher searcher;
    private volatile boolean isSearcherValid = true;

    public static final String ID = "_id";
    public static final String PROTOTYPE = "_prototype";
    public static final String NAME = "_name";
    public static final String CREATED = "_created";
    public static final String LASTMODIFIED = "_lastmodified";
    public static final String PARENTID = "_parentid";
    public static final String PARENTPROTOTYPE = "_parentproto";
    public static final String ISCHILD = "_ischild";
    public static final String LAYER_OF_SAVE = "_mode";
    public static final String REF_LIST_FIELD = "_REFS";
    public static final String RELATIONAL_CHILDREN = "_relch";
    public static final String LOCATION = "_location";
    public static final String ACCESSNAME = "_accessname";
    public static final String IDENTITY = "_d";

    public static final String DOCTYPE = "doctype";
    public static final String TYPE_PROPS = "PropertiesTypes";

    public static final String NULL_DELIM = "\u0000"; // the null character

    public static final String NODE_MARKER = "__ROBJ__";
    static final String NULL_PARENT = "_NULL_";

    protected static final Field.Store DEFAULT_STORE = Field.Store.YES;
    protected static final Field.Index DEFAULT_INDEX = Field.Index.TOKENIZED;

    protected static final String YES_STORE = "YES";
    protected static final String NO_STORE = "NO";
    protected static final String COMPRESSED_STORE = "COMPRESS";

    public static final String NO_INDEX = "NO";
    public static final String NO_NORMS_INDEX = "NO_NORMS";
    public static final String TOKENIZED_INDEX = "TOKENIZED";
    public static final String UNTOKENIZED_INDEX = "UNTOKENIZED";

    public static final String BOOLEAN_FIELD = "boolean";
    public static final String DATE_FIELD = "date";
    public static final String TIME_FIELD = "time";
    public static final String TIMESTAMP_FIELD = "timestamp";
    public static final String FLOAT_FIELD = "float";
    public static final String SMALL_FLOAT_FIELD = "smallfloat";
    public static final String SMALL_INT_FIELD = "smallint";
    public static final String INTEGER_FIELD = "integer";
    public static final String NUMBER_FIELD = "number";
    public static final String NODE_FIELD = "node";
    public static final String REL_FIELD = "reference";
    public static final String JAVAOBJ_FIELD = "javaobject";
    public static final String STRING_FIELD = "string";
    public static final String MULTI_FIELD = "multivalue";
    public static final String FILE_FIELD = "file";
    public static final String IMAGE_FIELD = "image";
    public static final String XML_FIELD = "xml";
    public static final String XHTML_FIELD = "xhtml";

    private static final int LIVE_MODE = DbKey.LIVE_LAYER;

    public static final int LUCENE_VERSION = 8;
    public static final String MIGRATION_CLASS = "axiom.objectmodel.dom.H2Migrator";

    public static synchronized LuceneManager getInstance(Application app) throws Exception {
        if (!_lmMap.containsKey(app.getName())) {
            LuceneManager lm = new LuceneManager(app);
            _lmMap.put(app.getName(), lm);
        }
        LuceneManager lm = (LuceneManager) _lmMap.get(app.getName());
        return lm;
    }

    public static synchronized void clearLuceneInstances() {
        _lmMap.clear();
    }

    public synchronized void shutdown() {
        if (this.optimizerThread != null) {
            //TODO:this.optimizerThread.stopOptimizer();
        }

        if (this.searcher != null) {
            try {
                this.searcher.close();
            } catch (Exception ex) {
                this.app.logError(ErrorReporter.errorMsg(this.getClass(), "shutdown"), ex);
            }
            this.searcher = null;
        }

        IndexObjectsFactory.removeDeletedInfos(this.directory);
        //TODO:IndexObjectsFactory.removeFSSegmentsInfos(this.directory);

        if (this.directory != null) {
            try {
                this.directory.close();
            } catch (Exception ignore) {
                ignore.printStackTrace();
            }
            this.directory = null;
        }

        _lmMap.remove(app.getName());
    }

    public LuceneManager(Application app, boolean checkVersion, boolean initDelInfos) throws Exception {
        this.app = app;
        this.writerManager = new IndexWriterManager(app, buildAnalyzer(), checkVersion, initDelInfos);
        this.dataFormatter = new LuceneDataFormatter(app.getAppDir());
        this.directory = this.writerManager.getDirectory();
    }

    private LuceneManager(Application app) throws Exception {
        this.app = app;
        this.writerManager = new IndexWriterManager(app, buildAnalyzer(), true, true);
        this.dataFormatter = new LuceneDataFormatter(app.getAppDir());
        this.directory = this.writerManager.getDirectory();
        try {
            this.searcher = new IndexSearcher(this.directory);
        } catch (IOException ignore) {
            // db doesn't exist yet, so create the searcher the first time a search is done
        }
    }

    public Directory getDirectory() {
        return this.directory;
    }

    public static PerFieldAnalyzerWrapper buildAnalyzer() {
        PerFieldAnalyzerWrapper analyzer = new PerFieldAnalyzerWrapper(new StandardAnalyzer());
        analyzer.addAnalyzer(REF_LIST_FIELD, new ReferenceAnalyzer());
        return analyzer;
    }

    public IndexWriter updateLucene(File fsdir, ArrayList sNodes, ArrayList uNodes, ArrayList dNodes,
            final int currentMode) throws DatabaseException {
        IndexWriter fsWriter = null;

        try {
            int savesize = sNodes.size();
            ArrayList docids = new ArrayList(savesize);
            ArrayList doclist = new ArrayList(savesize);

            HashMap analyzerMap = new HashMap();
            for (int i = 0; i < savesize; i++) {
                INode node = (INode) sNodes.get(i);
                // have to save the file/image objects first b/c certain properties are 
                // set on these nodes at save time before the Lucene document is 
                // created out of them
                createDocumentFromNode(docids, doclist, node, analyzerMap);
                node = null;
            }

            int updatesize = uNodes.size();
            ArrayList updateids = new ArrayList(updatesize);
            ArrayList updatelist = new ArrayList(updatesize);
            int[] modes = new int[updatesize];
            for (int i = 0; i < updatesize; i++) {
                INode node = (INode) uNodes.get(i);
                createDocumentFromNode(updateids, updatelist, node, analyzerMap);
                if (node instanceof Node) {
                    modes[i] = ((Node) node).getKey().getLayer();
                } else {
                    modes[i] = DbKey.LIVE_LAYER;
                }
                node = null;
            }

            savesize = doclist.size();
            updatesize = updatelist.size();
            int delsize = dNodes.size();

            if (savesize + updatesize + delsize > 0) {
                try {
                    fsWriter = writerManager.getWriter();
                    for (int i = 0; i < savesize; i++) {
                        String docid = (String) docids.get(i);
                        Analyzer analyzer = (Analyzer) analyzerMap.get(docid);
                        if (analyzer != null) {
                            fsWriter.addDocument(docid, (Document) doclist.get(i), analyzer);
                        } else {
                            fsWriter.addDocument(docid, (Document) doclist.get(i));
                        }
                    }
                    doclist = null;
                    docids = null;

                    for (int i = 0; i < updatesize; i++) {
                        String docid = (String) updateids.get(i);
                        Analyzer analyzer = (Analyzer) analyzerMap.get(docid);
                        try {
                            if (analyzer != null) {
                                fsWriter.update(docid, (Document) updatelist.get(i), analyzer);
                            } else {
                                fsWriter.update(docid, (Document) updatelist.get(i));
                            }
                        } catch (IOException ioex) {
                            if (currentMode > LIVE_MODE || modes[i] > LIVE_MODE) {
                                if (analyzer != null) {
                                    fsWriter.addDocument(docid, (Document) updatelist.get(i), analyzer);
                                } else {
                                    fsWriter.addDocument(docid, (Document) updatelist.get(i));
                                }
                            } else {
                                throw ioex;
                            }
                        }
                    }

                    analyzerMap.clear();
                    analyzerMap = null;
                    updatelist = null;
                    updateids = null;

                    for (int i = 0; i < delsize; i++) {
                        String id = dNodes.get(i).toString();
                        try {
                            fsWriter.delete(id);
                        } catch (Exception delex) {
                            if (currentMode != LIVE_MODE && getLayerPart(id) != LIVE_MODE) {
                                throw delex; // only throw if we arent in preview
                            }
                        }
                    }

                    fsWriter = writerManager.releaseWriter(fsWriter);
                } catch (Exception ex) {
                    fsWriter = writerManager.abort(fsWriter);
                    throw ex;
                }
            }
        } catch (Exception ex) {
            app.logError(ErrorReporter.errorMsg(this.getClass(), "updateLucene"), ex);
            throw new DatabaseException("Error writing the new/modified axiom objects to the Lucene Index");
        }

        return fsWriter;
    }

    public Node retrieveFromLuceneIndex(String key, final int mode) throws IOException, ObjectNotFoundException {
        return retrieveFromIndex(ID, key, mode);
    }

    public Node retrieveFromIndex(String field, String key, final int mode)
            throws IOException, ObjectNotFoundException {
        IndexSearcher searcher = null;
        Document doc = null;
        int layerInStorage = mode;

        BooleanQuery query = null;
        try {
            searcher = this.getIndexSearcher();

            query = new BooleanQuery();
            Query mainQuery = new TermQuery(new Term(field, key));
            query.add(mainQuery, BooleanClause.Occur.MUST);

            for (int i = mode; i >= LIVE_MODE; i--) {
                if (i != LIVE_MODE && !isDraftNode(key, i)) {
                    continue;
                }
                query = new BooleanQuery();
                query.add(mainQuery, BooleanClause.Occur.MUST);
                query.add(new TermQuery(new Term(LuceneManager.LAYER_OF_SAVE, i + "")), BooleanClause.Occur.MUST);
                Hits hits = searcher.search(query);
                if (hits.length() > 0) {
                    doc = hits.doc(0);
                    layerInStorage = i;
                    break;
                }
            }
        } catch (IOException ioex) {
            if (!("_id".equals(field) && ("0".equals(key) || "1".equals(key) || "2".equals(key)))) {
                app.logError(
                        ErrorReporter.errorMsg(this.getClass(), "retrieveFromIndex") + "Occured on query " + query,
                        ioex);
            }
            doc = null;
        } catch (Exception ex) {
            if (!("_id".equals(field) && ("0".equals(key) || "1".equals(key) || "2".equals(key)))) {
                app.logError(
                        ErrorReporter.errorMsg(this.getClass(), "retrieveFromIndex") + "Occured on query " + query,
                        ex);
            }
        } finally {
            this.releaseIndexSearcher(searcher);
        }

        if (doc == null) {
            if (this.app.debug())
                app.logEvent("LuceneManager.retrieveFromIndex(" + field + "," + key + "," + mode
                        + ") executed query [" + query + "] and retrieved 0 documents");

            throw new ObjectNotFoundException("No documents exist for key '" + key + "'");
        }

        /*if (this.app.debug()) 
           app.logEvent("LuceneManager.retrieveFromIndex(" + field + "," + key + "," 
           + mode + ") executed query [" + query + "] and got document on layer " 
           + layerInStorage);*/

        Node node = null;
        try {
            node = docToNode(doc, mode, layerInStorage);
        } catch (IOException ioex) {
            throw ioex;
        } catch (ObjectNotFoundException onfe) {
            throw onfe;
        } catch (Exception ex) {
            throw new ObjectNotFoundException("Error searching for children of '" + key + "'");
        }

        return node;

    }

    public Key retrieveKeyFromIndex(String field, String key, String parentid, final int mode)
            throws IOException, ObjectNotFoundException {
        IndexSearcher searcher = null;
        Document doc = null;
        BooleanQuery query = null;

        try {
            searcher = this.getIndexSearcher();
            Query t1 = new TermQuery(new Term(field, key));
            Query t2 = new TermQuery(new Term(LuceneManager.PARENTID, parentid));
            for (int i = mode; i >= LIVE_MODE; i--) {
                query = new BooleanQuery();
                query.add(t1, BooleanClause.Occur.MUST);
                query.add(t2, BooleanClause.Occur.MUST);
                query.add(new TermQuery(new Term(LuceneManager.LAYER_OF_SAVE, i + "")), BooleanClause.Occur.MUST);
                Hits hits = searcher.search(query);
                int length = hits.length();
                if (length > 0) {
                    for (int j = 0; j < length; j++) {
                        Document d = hits.doc(j);
                        String id = d.get(LuceneManager.ID);
                        if ((i == LIVE_MODE || isDraftNode(id, i)) && !idExistsOnHigherLayers(id, i, mode)) {
                            doc = d;
                            break;
                        }
                    }
                }
                if (doc != null) {
                    break;
                }
            }
        } catch (Exception ex) {
            app.logError(
                    ErrorReporter.errorMsg(this.getClass(), "retrieveKeyFromIndex") + "Occured on query " + query,
                    ex);
        } finally {
            this.releaseIndexSearcher(searcher);
        }

        if (doc == null) {
            if (this.app.debug())
                app.logEvent("LuceneManager.retrieveKeyFromIndex(" + field + "," + key + "," + parentid + "," + mode
                        + ") executed query [" + query + "] and didnt find a key");

            throw new ObjectNotFoundException("No documents exist for key '" + key + "'");
        }

        DbMapping dbmap = this.app.getNodeManager().getDbMapping(doc.get(PROTOTYPE));
        String id = doc.get(ID);
        DbKey dbkey = new DbKey(dbmap, id, mode);

        /*if (this.app.debug()) 
           app.logEvent("LuceneManager.retrieveKeyFromIndex(" + field + "," + key + "," 
             + parentid + "," + mode + ") executed query [" + query 
             + "] and got key = " + dbkey);*/

        return dbkey;
    }

    public Node retrieveFromIndexFixedMode(String key, final int mode) throws IOException, ObjectNotFoundException {
        return this.retrieveFromIndexFixedMode(key, mode, false);
    }

    public Node retrieveFromIndexFixedMode(String key, final int mode, final boolean ignoreDraftSettings)
            throws IOException, ObjectNotFoundException {
        IndexSearcher searcher = null;
        Document doc = null;
        int layerInStorage = mode;

        BooleanQuery query = null;
        try {
            final boolean isDraftOn = isDraftNode(key, mode);
            if (isDraftOn || ignoreDraftSettings) {
                query = new BooleanQuery();
                Query mainQuery = new TermQuery(new Term(ID, key));
                query.add(mainQuery, BooleanClause.Occur.MUST);
                query.add(new TermQuery(new Term(LuceneManager.LAYER_OF_SAVE, mode + "")),
                        BooleanClause.Occur.MUST);

                searcher = this.getIndexSearcher();
                Hits hits = searcher.search(query);

                if (hits.length() > 0) {
                    doc = hits.doc(0);
                }
            }
        } catch (IOException ioex) {
            if (!("_id".equals(ID) && ("0".equals(key) || "1".equals(key) || "2".equals(key)))) {
                app.logError(ErrorReporter.errorMsg(this.getClass(), "retrieveNodeFromIndexFixedMode")
                        + "Occured on query " + query, ioex);
            }
            doc = null;
        } catch (Exception ex) {
            if (!("_id".equals(ID) && ("0".equals(key) || "1".equals(key) || "2".equals(key)))) {
                app.logError(ErrorReporter.errorMsg(this.getClass(), "retrieveNodeFromIndexFixedMode")
                        + "Occured on query " + query, ex);
            }
        } finally {
            this.releaseIndexSearcher(searcher);
        }

        if (doc == null) {
            if (this.app.debug())
                app.logEvent("LuceneManager.retrieveFromIndexFixedMode(" + key + "," + mode + ","
                        + ignoreDraftSettings + ") executed query [" + query + "] and retrieved 0 documents");

            throw new ObjectNotFoundException("No documents exist for key '" + key + "'");
        }

        /*if (this.app.debug()) 
           app.logEvent("LuceneManager.retrieveFromIndexFixedMode(" + key + "," + mode 
           + "," + ignoreDraftSettings + ") executed query [" + query 
           + "] and got document on layer " + layerInStorage);*/

        Node node = null;
        try {
            node = docToNode(doc, mode, layerInStorage);
        } catch (IOException ioex) {
            throw ioex;
        } catch (ObjectNotFoundException onfe) {
            throw onfe;
        } catch (Exception ex) {
            throw new ObjectNotFoundException("Error searching for children of '" + key + "'");
        }

        return node;
    }

    private boolean idExistsOnHigherLayers(final String id, final int layer, final int highest) {
        for (int i = layer + 1; i <= highest; i++) {
            try {
                if (this.retrieveFromIndexFixedMode(id, i) != null) {
                    return true;
                }
            } catch (Exception ex) {
                continue;
            }
        }
        return false;
    }

    protected boolean isDraftNode(String id, final int mode) {
        RequestEvaluator reqeval = this.app.getCurrentRequestEvaluator();
        if (reqeval == null || reqeval.getRequest() == null || reqeval.getRequest().getServletRequest() == null) {
            return false;
        }
        String server = reqeval.getRequest().getServletRequest().getServerName();
        if (mode > LIVE_MODE && !isSpecialNode(id) && reqeval.getSession().isDraftIdOn(id, server, mode)) {
            return true;
        }
        return false;
    }

    public static HashMap luceneDocumentToMap(Document doc) {
        HashMap map = new HashMap();

        Enumeration e = doc.fields();
        while (e.hasMoreElements()) {
            Field f = (Field) e.nextElement();
            String fieldname = f.name();
            if (LuceneManager.isSearchOnlyField(fieldname)) {
                map.put(fieldname, undoFields(doc.getFields(fieldname)));
            } else {
                map.put(fieldname, f.stringValue());
            }
        }

        return map;
    }

    public static Object[] undoFields(Field[] fields) {
        final int length = fields.length;
        Object[] arr = new Object[length];
        for (int i = 0; i < length; i++) {
            String[] fielddata = new String[2];
            fielddata[0] = fields[i].name();
            fielddata[1] = fields[i].stringValue();
            arr[i] = fielddata;
        }
        return arr;
    }

    public Node docToNode(final Document doc, final int mode, final int layerInStorage) throws Exception {
        return this.mapToNode(luceneDocumentToMap(doc), mode, layerInStorage);
    }

    public Node mapToNode(final HashMap map) throws Exception {
        return mapToNode(map, DbKey.LIVE_LAYER, DbKey.LIVE_LAYER);
    }

    public Node mapToNode(final HashMap map, final int mode, final int layerInStorage) throws Exception {
        Node node = null;
        String docid = null, name = null, prototype = null, refid = null, refprototype = null;
        long created = -1L, lastModified = -1L;
        Hashtable propMap = null;
        final NodeManager nmgr = this.app.getNodeManager();

        docid = (String) map.remove(ID);
        name = (String) map.remove(NAME);
        if (name == null)
            name = docid;
        prototype = (String) map.remove(PROTOTYPE);

        try {
            created = Long.parseLong((String) map.remove(CREATED));
        } catch (Exception ex) {
            created = -1L;
        }

        try {
            lastModified = Long.parseLong((String) map.remove(LASTMODIFIED));
        } catch (Exception ex) {
            lastModified = -1L;
        }

        refid = (String) map.remove(PARENTID);
        refprototype = (String) map.remove(PARENTPROTOTYPE);

        if (docid != null && name != null && prototype != null) {
            if (created != -1 && lastModified != -1) {
                node = new Node(name, docid, prototype, nmgr.safe, created, lastModified);
            } else {
                node = new Node(name, docid, prototype, nmgr.safe);
            }

            if (refid != null && refprototype != null && !NULL_PARENT.equals(refid)) {
                node.setParentHandle(makeNodeHandle(nmgr, refid, refprototype, mode));
            }

            getTheChildren(node, (String) map.remove(RELATIONAL_CHILDREN), nmgr, mode, layerInStorage);

            node.setLayer(mode);
            node.setLayerInStorage(layerInStorage);
        }

        if (node == null)
            throw new IOException("Could not create Node object from Lucene Document");

        Iterator iter = map.keySet().iterator();
        Prototype proto = app.typemgr.getPrototype(prototype);

        while (iter.hasNext()) {
            String fieldname = (String) iter.next();
            // if this field has already been read by some other property that needed it,
            // then dont repeat over it
            if (isSearchOnlyField(fieldname)) {
                continue;
            }

            String fieldvalue = (String) map.get(fieldname);
            axiom.objectmodel.db.Property prop = new axiom.objectmodel.db.Property(fieldname, node);
            boolean add = true;
            String type = getDataType(proto, fieldname);

            switch (stringToType(type)) {

            case IProperty.BOOLEAN:
                prop.setBooleanValue(Boolean.parseBoolean(fieldvalue));
                break;

            case IProperty.DATE:
                prop.setDateValue(strToDate(fieldvalue));
                break;

            case IProperty.TIME:
                prop.setDateValue(strToTime(fieldvalue));
                break;

            case IProperty.TIMESTAMP:
                prop.setDateValue(strToTimestamp(fieldvalue));
                break;

            case IProperty.FLOAT:
            case IProperty.SMALLFLOAT:
                double d = 0.0;
                try {
                    d = Double.parseDouble(fieldvalue);
                } catch (Exception nfe) {
                    this.app.logError(ErrorReporter.errorMsg(this.getClass(), "mapToNode") + "Could not assign "
                            + fieldname + " to " + fieldvalue + " because could not be parsed into a double", nfe);
                    d = 0.0;
                }
                prop.setFloatValue(d);
                break;

            case IProperty.INTEGER:
            case IProperty.SMALLINT:
                long l = 0L;
                try {
                    l = Long.parseLong(fieldvalue);
                } catch (Exception nfe) {
                    this.app.logError(ErrorReporter.errorMsg(this.getClass(), "mapToNode") + "Could not assign "
                            + fieldname + " to " + fieldvalue + " because could not be parsed into a long", nfe);
                    l = 0L;
                }
                prop.setIntegerValue(l);
                break;

            case IProperty.NODE:
                NodeHandle handle = generateNodeProp(fieldvalue, nmgr, mode);
                if (handle != null) {
                    prop.setNodeHandle(handle);
                } else {
                    add = false;
                }
                break;

            case IProperty.STRING:
                prop.setStringValue(fieldvalue);
                break;

            case IProperty.REFERENCE:
                Reference rel = generateRefProp(fieldvalue, mode);
                if (rel != null) {
                    prop.setReferenceValue(rel);
                } else {
                    add = false;
                }
                break;

            case IProperty.MULTI_VALUE:
                MultiValue mv = generateMultiValueProp(proto, fieldname, fieldvalue, mode);
                if (mv != null) {
                    prop.setMultiValue(mv);
                } else {
                    add = false;
                }
                break;
            case IProperty.XML:
                prop.setXMLValue(fieldvalue);
                break;
            case IProperty.XHTML:
                prop.setXHTMLValue(fieldvalue);
                break;

            case IProperty.JAVAOBJECT:
                break;

            default:
                prop.setStringValue(fieldvalue);
                break;
            }

            if (propMap == null) {
                propMap = new Hashtable();
                node.setPropMap(propMap);
            }

            if (add) {
                propMap.put(fieldname, prop);
            }
        }

        return node;
    }

    public void setPropertiesOnNode(Node node, HashMap props) throws Exception {
        Prototype proto = app.typemgr.getPrototype(node.getPrototype());
        NodeManager nmgr = app.getNodeManager();
        final int mode;
        if (app.getCurrentRequestEvaluator() != null) {
            mode = app.getCurrentRequestEvaluator().getLayer();
        } else {
            mode = DbKey.LIVE_LAYER;
        }

        Iterator iter = props.keySet().iterator();

        while (iter.hasNext()) {
            final String fieldname = (String) iter.next();
            if (isSearchOnlyField(fieldname) || isIgnoredOnSet(fieldname)) {
                continue;
            }

            String fieldvalue = (String) props.get(fieldname);
            String type = getDataType(proto, fieldname);

            switch (stringToType(type)) {
            case IProperty.BOOLEAN:
                node.setBoolean(fieldname, Boolean.parseBoolean(fieldvalue));
                break;
            case IProperty.DATE:
                node.setDate(fieldname, strToDate(fieldvalue));
                break;
            case IProperty.TIME:
                node.setDate(fieldname, strToTime(fieldvalue));
                break;
            case IProperty.TIMESTAMP:
                node.setDate(fieldname, strToTimestamp(fieldvalue));
                break;
            case IProperty.FLOAT:
                node.setFloat(fieldname, Double.parseDouble(fieldvalue));
                break;
            case IProperty.INTEGER:
                node.setInteger(fieldname, Long.parseLong(fieldvalue));
                break;
            case IProperty.JAVAOBJECT:
                break; // lucene doesnt store java object types
            case IProperty.NODE:
                Node n = generateNode(fieldvalue, nmgr, mode);
                if (n != null) {
                    node.setNode(fieldname, n);
                }
                break;
            case IProperty.STRING:
                node.setString(fieldname, fieldvalue);
                break;
            case IProperty.REFERENCE:
                Reference rel = generateRefProp(fieldvalue, mode);
                if (rel != null) {
                    node.setReference(fieldname, rel);
                }
                break;
            case IProperty.MULTI_VALUE:
                MultiValue mv = generateMultiValueProp(proto, fieldname, fieldvalue, mode);
                if (mv != null) {
                    node.setMultiValue(fieldname, mv);
                }
                break;
            case IProperty.XML:
                node.setXML(fieldname, fieldvalue);
                break;
            case IProperty.XHTML:
                node.setXHTML(fieldname, fieldvalue);
                break;
            default:
                node.setString(fieldname, fieldvalue);
                break;
            }
        }
    }

    public boolean isSpecialNode(String id) {
        return "0".equals(id) || "1".equals(id);
    }

    public void runOptimizer() throws Exception {
        String optimizeSwitch = this.app.getProperty("lucene.optimizer", "on");
        if ("off".equalsIgnoreCase(optimizeSwitch)) {
            return;
        }
        /*TODO:this.optimizerThread = new LuceneOptimizer(writerManager.getDirectory(), app, this);
          this.writerManager.setOptimizer(this.optimizerThread.getOptimizer());
          this.optimizerThread.runOptimizingThread();*/
        this.optimizerThread = new IndexOptimizingRunner(this.directory, this.app, this);
        this.optimizerThread.runOptimizingThread();
    }

    public static String[] nodePropEval(String value) {
        try {
            String remainder = value.substring(NODE_MARKER.length());
            return remainder.split(NULL_DELIM);
        } catch (Exception ex) {
            return new String[0];
        }
    }

    private Node generateNode(String nodePropId, NodeManager nmgr, int mode) throws ObjectNotFoundException {
        Node node = null;
        String[] nodeDesc = nodePropEval(nodePropId);

        if (nodeDesc.length == 2) {
            try {
                //node = retrieveFromLuceneIndex(actualId, nmgr, mode);
                DbMapping dbmap = app.typemgr.getPrototype(nodeDesc[1]).getDbMapping();
                node = nmgr.getNode(new DbKey(dbmap, nodeDesc[0], mode));
            } catch (Exception ex) {
                throw new ObjectNotFoundException("No documents exist for key '" + nodeDesc[0] + "'");
            }
        }

        return node;
    }

    private NodeHandle generateNodeProp(String nodePropId, NodeManager nmgr, int mode)
            throws ObjectNotFoundException {
        NodeHandle handle = null;
        String[] nodeDesc = nodePropEval(nodePropId);
        if (nodeDesc.length == 2) {
            handle = this.makeNodeHandle(nmgr, nodeDesc[0], nodeDesc[1], mode);
        }
        return handle;
    }

    public static boolean isSearchOnlyField(String fieldname) {
        if (fieldname != null) {
            if (fieldname.equalsIgnoreCase(REF_LIST_FIELD) || fieldname.equalsIgnoreCase(ISCHILD)
                    || fieldname.equalsIgnoreCase(IDENTITY) || fieldname.equalsIgnoreCase(LAYER_OF_SAVE)) {
                return true;
            }
        }
        return false;
    }

    public static boolean isIgnoredOnSet(String fieldname) {
        if (fieldname != null) {
            if (fieldname.equals(ID) || fieldname.equals(CREATED) || fieldname.equals(PROTOTYPE)) {
                return true;
            }
        }
        return false;
    }

    private Reference generateRefProp(final String id, final int mode) throws Exception {
        return new Reference(null, new Object[] { new DbKey(null, id, mode) });
    }

    private MultiValue generateMultiValueProp(Prototype proto, final String fieldname, final String fieldvalue,
            final int mode) throws Exception {
        String typeStr = "string";
        while (proto != null) {
            ResourceProperties props = proto.getTypeProperties();
            typeStr = props.getProperty(fieldname + ".type");
            if (typeStr != null) {
                typeStr = typeStr.substring(typeStr.indexOf("(") + 1, typeStr.indexOf(")")).trim();
                break;
            }
            proto = proto.getParentPrototype();
        }
        if (typeStr == null) {
            typeStr = STRING_FIELD;
        }
        int type = stringToType(typeStr);

        String[] values = fieldvalue.split(NULL_DELIM);
        int len = values.length;
        ArrayList list = new ArrayList();
        for (int i = 0; i < len; i++) {
            values[i] = values[i].trim();
            if (values[i].length() == 0) {
                continue;
            }

            switch (type) {
            case IProperty.BOOLEAN:
                list.add(new Boolean(values[i]));
                break;
            case IProperty.DATE:
                list.add(strToDate(values[i]));
                break;
            case IProperty.TIME:
                list.add(strToTime(values[i]));
                break;
            case IProperty.TIMESTAMP:
                list.add(strToTimestamp(values[i]));
                break;
            case IProperty.FLOAT:
                list.add(new Double(values[i]));
                break;
            case IProperty.INTEGER:
                list.add(new Integer(values[i]));
                break;
            case IProperty.REFERENCE:
                Reference r = generateRefProp(values[i], mode);
                /*if (ref_fields != null) {
                int length = ref_fields.length;
                for (int j = 0; j < length; j++) {
                    String[] fdata = (String[]) ref_fields[j];
                    String[] ref_info = fdata[1].split(NULL_DELIM);
                    if (ref_info.length == 4 && values[i].equals(ref_info[0]) 
                            && fieldname.equals(ref_info[1])) {
                        r.setSourceXPath(ref_info[3]);
                        break;
                    }
                }
                    }*/
                list.add(r);
                break;
            case IProperty.STRING:
                list.add(values[i]);
            default:
                break;
            }
        }

        MultiValue mv = null;
        try {
            mv = new MultiValue(list.toArray());
            mv.setValueType(type);
        } catch (Exception ex) {
            mv = null;
        }

        return mv;
    }

    private NodeHandle makeNodeHandle(NodeManager nmgr, String refid, String refprototype, int mode) {
        DbMapping dbmap = null;
        if (refprototype != null) {
            dbmap = nmgr.getDbMapping(refprototype);
        }
        return new NodeHandle(new DbKey(dbmap, refid, mode));

    }

    private static String getFieldValue(String fieldname, Document doc, HashSet unchecked) {
        String value = null;
        Field field = doc.getField(fieldname);
        if (field != null) {
            value = field.stringValue();
            unchecked.remove(field);
        }
        return value;
    }

    private void getTheChildren(Node parent, String relChildren, NodeManager nmgr, int mode, int layerInStorage)
            throws Exception {

        IndexSearcher searcher = null;
        BooleanQuery bq = null;

        try {
            searcher = this.getIndexSearcher();
            String pid = parent.getID();

            HashMap ids = new HashMap();
            int length;
            Query query1 = new TermQuery(new Term(ISCHILD, "true"));
            Query query2 = new TermQuery(new Term(PARENTID, pid));
            for (int i = LIVE_MODE; i <= mode; i++) {
                bq = new BooleanQuery();
                bq.add(query1, BooleanClause.Occur.MUST);
                bq.add(query2, BooleanClause.Occur.MUST);
                bq.add(new TermQuery(new Term(LAYER_OF_SAVE, i + "")), BooleanClause.Occur.MUST);
                Hits hits = searcher.search(bq);
                length = hits.length();

                /*if (app.debug()) 
                   app.logEvent("LuceneManager.getTheChildren(), parent = " + parent.getKey() 
                 + ", layer = " + mode + ", layerInStorage = " + layerInStorage 
                 + ", executed query " + bq + " which produced " + length 
                 + " results");*/

                for (int j = 0; j < length; j++) {
                    Document doc = hits.doc(j);
                    ids.put(doc.getField(ID).stringValue(), doc.getField(PROTOTYPE).stringValue());
                }
            }

            Collection<NodeHandle> subnodes = null;
            length = ids.size();
            if (length > 0) {
                subnodes = parent.createSubnodeList();
                Iterator iter = ids.keySet().iterator();
                while (iter.hasNext()) {
                    String id = (String) iter.next();
                    NodeHandle handle = makeNodeHandle(nmgr, id, (String) ids.get(id), mode);
                    if (subnodes instanceof SubnodeList) {
                        ((SubnodeList) subnodes).addSorted(handle);
                    } else {
                        subnodes.add(handle);
                    }
                }
            }
            ids.clear();
            ids = null;

            if (relChildren != null) {
                String[] charr = relChildren.split(NULL_DELIM);
                if (charr.length > 0 && charr.length % 2 == 0) {
                    if (subnodes == null) {
                        subnodes = parent.createSubnodeList();
                    }
                    for (int i = 0; i < charr.length; i += 2) {
                        if (subnodes instanceof SubnodeList) {
                            ((SubnodeList) subnodes).addSorted(makeNodeHandle(nmgr, charr[i], charr[i + 1], mode));
                        } else {
                            subnodes.add(makeNodeHandle(nmgr, charr[i], charr[i + 1], mode));
                        }
                    }
                }
            }
        } catch (IOException ioe) {
            throw new Exception("Searcher failed when attempting to retrieve children of " + "id '" + parent.getID()
                    + "', query = " + bq);
        } finally {
            this.releaseIndexSearcher(searcher);
        }
    }

    private void saveFile(INode node) throws Exception {
        // its a file or image, commit the contents to storage
        Object val = node.getJavaObject(FileObject.SELF);
        if (val instanceof FileObject) {
            FileObject fobj = (FileObject) val;
            if (node instanceof Node) {
                ((Node) node).unsetInternalProperty(FileObject.SELF);
            }
            final String tmpPath = fobj == null ? null : fobj.getTmpPath();
            if (tmpPath != null) {
                if (!commitToStorage(node, tmpPath)) {
                    throw new Exception("Could not write the File/Image to storage: " + tmpPath);
                }
            }
        }
    }

    public Document createDocument(INode node, HashMap analyzerMap) throws Exception {
        if (node == null)
            throw new Exception("The node to be saved is null");

        String prototype = node.getPrototype();
        if (prototype == null)
            prototype = "AxiomObject";

        if (prototype.equals("File") || prototype.equals("Image")) {
            saveFile(node);
        }

        Enumeration e = null;
        if (node instanceof axiom.objectmodel.db.Node) {
            // a newly constructed db.Node doesn't have a propMap,
            // but returns an enumeration of all it's db-mapped properties
            Hashtable props = ((Node) node).getPropMap();

            if (props != null)
                e = props.keys();
        } else {
            e = node.properties();
        }

        Prototype proto = app.typemgr.getPrototype(prototype);
        ResourceProperties rprops = new ResourceProperties();
        Stack protos = new Stack();
        while (proto != null) {
            protos.push(proto);
            proto = proto.getParentPrototype();
        }
        final int protoChainSize = protos.size();
        for (int i = 0; i < protoChainSize; i++) {
            proto = (Prototype) protos.pop();
            rprops.putAll(proto.getTypeProperties());
        }

        Field.Store store = DEFAULT_STORE;
        Field.Index index = Field.Index.UN_TOKENIZED;

        String idstr = node.getID();

        Document doc = new Document();

        doc.add(new Field(ID, idstr, store, index));
        doc.add(new Field(NAME, node.getName(), store, index));

        doc.add(new Field(PROTOTYPE, prototype, store, index));

        doc.add(new Field(CREATED, serializeInt(node.created()), store, index));
        doc.add(new Field(LASTMODIFIED, serializeInt(node.lastModified()), store, index));

        String parentid, parentprototype, accessprop = null;
        INode parent = node.getParent();
        boolean isChild;
        if (parent != null) {
            parentid = parent.getID();
            parentprototype = parent.getPrototype();
            if (parent.get(node.getName()) != null) {
                isChild = false;
            } else {
                isChild = true;
            }
            DbMapping dbmap = app.getPrototypeByName(parent.getPrototype()).getDbMapping();
            if (dbmap != null) {
                Relation rel = dbmap.getSubnodeRelation();
                if (rel != null) {
                    accessprop = rel.getAccessName();
                }
            }
        } else {
            parentid = NULL_PARENT;
            parentprototype = null;
            isChild = false;
        }

        doc.add(new Field(PARENTID, parentid, store, index));
        if (parentprototype != null) {
            doc.add(new Field(PARENTPROTOTYPE, parentprototype, store, index));
        }

        if (isChild) {
            doc.add(new Field(ISCHILD, "true", store, index));
        }

        int mode;
        if (node instanceof Node) {
            mode = ((Node) node).getKey().getLayer();
        } else {
            mode = LIVE_MODE;
        }
        doc.add(new Field(LAYER_OF_SAVE, mode + "", store, index));

        // this is kind of a hack - lucene requires any NOT query to include a must have
        // query term (e.g. you can search "X" NOT "Y" but you can not search NOT "Y" 
        // by itself).  to overcome this, every single object document in lucene will 
        // have the IDENTITY field set to 1, so any not filter can be proceeded by a 
        // search for IDENTITY equal to 1, since every lucene document in the system will have
        // the IDENTITY field set to 1.  
        doc.add(new Field(IDENTITY, "1", store, index));

        if (node instanceof axiom.objectmodel.db.Node) {
            axiom.objectmodel.db.Node n = (axiom.objectmodel.db.Node) node;
            if (n.relationalNodeAdded()) {
                addRelationalChildrenField(doc, node, store, index);
                n.resetRelationalNodeAdded();
            }
        }

        ArrayList ref_list = new ArrayList();
        if (e != null) {
            while (e.hasMoreElements()) {
                String key = (String) e.nextElement();
                IProperty prop = node.get(key);
                if (prop != null) {
                    addToDoc(doc, key, prop, rprops, ref_list, analyzerMap, accessprop);
                }
            }
        }

        int ref_list_size = ref_list.size();
        for (int i = 0; i < ref_list_size; i++) {
            doc.add(new Field(REF_LIST_FIELD, ref_list.get(i).toString(), store, Field.Index.TOKENIZED));
        }

        return doc;
    }

    private void addRelationalChildrenField(Document doc, INode node, Field.Store store, Field.Index index) {
        Enumeration e = node.getSubnodes();
        StringBuffer sb = new StringBuffer();
        while (e.hasMoreElements()) {
            Node n = (Node) e.nextElement();
            if (n.isRelational() && n.isAnonymous()) {
                sb.append(n.getID()).append(NULL_DELIM).append(n.getPrototype()).append(NULL_DELIM);
            }
        }
        if (sb.length() > 0) {
            doc.add(new Field(RELATIONAL_CHILDREN, sb.toString(), store, index));
        }
    }

    private void createDocumentFromNode(ArrayList ids, ArrayList docs, INode node, HashMap analyzerMap)
            throws Exception {
        Document doc = this.createDocument(node, analyzerMap);
        if (doc != null) {
            String key = doc.getField(ID).stringValue() + DeletedInfos.KEY_SEPERATOR
                    + doc.getField(LAYER_OF_SAVE).stringValue();
            ids.add(key);
            docs.add(doc);
        }
    }

    private void addToDoc(Document doc, String key, IProperty prop, ResourceProperties rprops, ArrayList ref_list,
            HashMap analyzerMap, String accessprop) throws Exception {
        if (prop.getValue() == null) {
            return;
        }

        final Field.Store store;
        final Field.Index index;
        if (accessprop != null && (key.equals(accessprop)
                || (this.app.isPropertyFilesIgnoreCase() && key.equalsIgnoreCase(accessprop)))) {
            store = Field.Store.YES;
            index = Field.Index.UN_TOKENIZED;
        } else if (key.startsWith("_") && !key.equals(FileObject.CONTENT)) {
            store = Field.Store.YES;
            index = Field.Index.UN_TOKENIZED;
        } else {
            store = getStore(rprops, key);
            index = getIndex(rprops, key);
        }

        int type = getType(rprops, key);
        if (type < 0) {
            type = prop.getType();
        }

        final Analyzer analyzer = (type == IProperty.STRING) ? getAnalyzer(rprops, key) : null;
        final float boost = getBoost(rprops, key);
        Field f;

        switch (type) {

        case IProperty.BOOLEAN:
            f = new Field(key, serializeBoolean(prop.getBooleanValue()), store, index);
            if (boost > -1f) {
                f.setBoost(boost);
            }
            doc.add(f);
            break;

        case IProperty.DATE:
            f = new Field(key, serializeDate(prop.getDateValue()), store, index);
            if (boost > -1f) {
                f.setBoost(boost);
            }
            doc.add(f);
            break;

        case IProperty.TIME:
            f = new Field(key, serializeTime(prop.getDateValue()), store, index);
            if (boost > -1f) {
                f.setBoost(boost);
            }
            doc.add(f);
            break;

        case IProperty.TIMESTAMP:
            f = new Field(key, serializeTimestamp(prop.getDateValue()), store, index);
            if (boost > -1f) {
                f.setBoost(boost);
            }
            doc.add(f);
            break;

        case IProperty.FLOAT:
            f = new Field(key, serializeFloat(prop.getFloatValue()), store, index);
            if (boost > -1f) {
                f.setBoost(boost);
            }
            doc.add(f);
            break;

        case IProperty.SMALLFLOAT:
            f = new Field(key, serializeSmallFloat(prop.getFloatValue()), store, index);
            if (boost > -1f) {
                f.setBoost(boost);
            }
            doc.add(f);
            break;

        case IProperty.INTEGER:
            f = new Field(key, serializeInt(prop.getIntegerValue()), store, index);
            if (boost > -1f) {
                f.setBoost(boost);
            }
            doc.add(f);
            break;

        case IProperty.SMALLINT:
            f = new Field(key, serializeSmallInt(prop.getIntegerValue()), store, index);
            if (boost > -1f) {
                f.setBoost(boost);
            }
            doc.add(f);
            break;

        case IProperty.STRING:
            f = new Field(key, prop.getStringValue(), store, index);
            if (boost > -1f) {
                f.setBoost(boost);
            }
            doc.add(f);
            if (analyzer != null) {
                String docid = doc.getField(ID).stringValue() + DeletedInfos.KEY_SEPERATOR
                        + doc.getField(LAYER_OF_SAVE).stringValue();
                PerFieldAnalyzerWrapper ret = (PerFieldAnalyzerWrapper) analyzerMap.get(docid);
                if (ret == null) {
                    ret = buildAnalyzer();
                    analyzerMap.put(docid, ret);
                }
                ret.addAnalyzer(key, analyzer);
            }
            break;

        case IProperty.NODE:
            INode propNode = prop.getNodeValue();
            if (propNode != null) {
                String value = serializeNodeProp(propNode.getID(), propNode.getPrototype());
                f = new Field(key, value, store, index);
                if (boost > -1f) {
                    f.setBoost(boost);
                }
                doc.add(f);
            }
            break;

        case IProperty.REFERENCE:
            if (prop instanceof axiom.objectmodel.db.Property) {
                axiom.objectmodel.db.Property p = (axiom.objectmodel.db.Property) prop;
                Reference relobj = p.getReferenceValue();
                if (relobj != null) {
                    String serialized_ref = serializeReference(relobj);
                    f = new Field(key, serialized_ref, store, index);
                    if (boost > -1f) {
                        f.setBoost(boost);
                    }
                    doc.add(f);

                    StringBuffer sb = new StringBuffer(serialized_ref);
                    sb.append(NULL_DELIM).append(key);
                    ref_list.add(sb.toString());
                }
            }
            break;

        case IProperty.MULTI_VALUE:
            if (prop instanceof axiom.objectmodel.db.Property) {
                axiom.objectmodel.db.Property p = (axiom.objectmodel.db.Property) prop;
                MultiValue mvobj = p.getMultiValue();
                if (mvobj != null) {
                    addMultiValueToDoc(doc, key, mvobj, store, index, ref_list);
                }
            }
            break;

        case IProperty.XML:
            if (prop instanceof axiom.objectmodel.db.Property) {
                axiom.objectmodel.db.Property p = (axiom.objectmodel.db.Property) prop;
                Object xml = p.getXMLValue();
                if (xml != null) {
                    f = new Field(key, XmlUtils.objectToXMLString(xml), store, index);
                    if (boost > -1f) {
                        f.setBoost(boost);
                    }
                    doc.add(f);
                }
            }
        case IProperty.XHTML:
            if (prop instanceof axiom.objectmodel.db.Property) {
                axiom.objectmodel.db.Property p = (axiom.objectmodel.db.Property) prop;
                Object xml = p.getXHTMLValue();
                if (xml != null) {
                    f = new Field(key, XmlUtils.objectToXMLString(xml), store, index);
                    if (boost > -1f) {
                        f.setBoost(boost);
                    }
                    doc.add(f);
                    addXhtmlRefs(key, xml, ref_list);
                }
            }
        default:
            break;
        }
    }

    private void addXhtmlRefs(String name, Object xml, ArrayList ref_list) {
        RequestEvaluator reqeval = this.app.getCurrentRequestEvaluator();
        if (reqeval != null) {
            ScriptingEngine se = reqeval.getScriptingEngine();
            final String DELIM = NULL_DELIM;

            String[] links = XhtmlUtils.getXhtmlLinks(xml);
            final int length = links.length;
            for (int i = 0; i < length; i++) {
                String link = links[i];
                if (link.indexOf("://") > 0) {
                    // TODO: Store external links in the relational db to be able to
                    // determine broken links to external sites
                    continue;
                }

                INode n = getNodeFromHref(link, se);
                if (n != null) {
                    StringBuffer sb = new StringBuffer(n.getID());
                    sb.append(DELIM).append(name);
                    ref_list.add(sb.toString());
                }
            }
        }
    }

    private INode getNodeFromHref(String link, ScriptingEngine se) {
        if (link == null) {
            return null;
        }

        link = this.app.resolveUrlToPath(link);

        String[] pathItems = link.split("/");
        final int length = pathItems.length;

        IPathElement currentElement = null;
        try {
            currentElement = app.getNodeManager().getRootNode();
        } catch (Exception ex) {
            currentElement = null;
        }

        for (int i = 0; i < length; i++) {
            if (currentElement == null) {
                return null;
            }
            if (pathItems[i].length() == 0) {
                continue;
            }

            if ((i != length - 1) || !(se.hasFunction(currentElement, pathItems[i]))) {
                currentElement = currentElement.getChildElement(pathItems[i]);
            }
        }

        return currentElement instanceof INode ? (INode) currentElement : null;
    }

    private String getPathFromLink(String link) {
        StringBuffer sb = new StringBuffer();
        String charset = this.app.getCharset();
        String[] tokens = link.split("/");

        int base_uri_tokens = 0;
        String base_uri = this.app.getBaseURI();
        if (base_uri != null && !base_uri.equals("/")) {
            base_uri_tokens = base_uri.split("/").length;
        }

        for (int i = 0; i < tokens.length; i++) {
            if (i < base_uri_tokens) {
                continue;
            }
            if (tokens[i].equals("")) {
                continue;
            }

            String path_elem;
            try {
                path_elem = UrlEncoded.decode(tokens[i], charset);
            } catch (Exception ex) {
                path_elem = tokens[i];
            }

            sb.append("/").append(path_elem);
        }

        // append trailing "/" if it is contained in original URI
        if (link.endsWith("/")) {
            sb.append('/');
        }

        return sb.toString();
    }

    public String serializeBoolean(boolean bool) {
        return bool ? "true" : "false";
    }

    public String serializeDate(Date date) {
        if (date == null) {
            date = new Date();
        }
        return dataFormatter.formatDate(date);
    }

    public String serializeTime(Date date) {
        if (date == null) {
            date = new Date();
        }
        return dataFormatter.formatTime(date);
    }

    public String serializeTimestamp(Date date) {
        if (date == null) {
            date = new Date();
        }
        return dataFormatter.formatTimestamp(date);
    }

    public String serializeFloat(double fl) {
        return dataFormatter.formatFloat(fl);
    }

    public String serializeInt(long l) {
        return dataFormatter.formatInt(l);
    }

    public String serializeSmallFloat(double fl) {
        return dataFormatter.formatSmallFloat(fl);
    }

    public String serializeSmallInt(long l) {
        return dataFormatter.formatSmallInt(l);
    }

    public String serializeReference(Reference relobj) {
        return relobj.getTargetKey().getID();
    }

    public static String serializeNodeProp(final String id, final String proto) {
        return NODE_MARKER + id + NULL_DELIM + proto;
    }

    private Date strToDate(String str) {
        try {
            return dataFormatter.strToDate(str);
        } catch (Exception ex) {
            return null;
        }
    }

    private Date strToTime(String str) {
        try {
            return dataFormatter.strToTime(str);
        } catch (Exception ex) {
            return null;
        }
    }

    private Date strToTimestamp(String str) {
        try {
            return dataFormatter.strToTimestamp(str);
        } catch (Exception ex) {
            return null;
        }
    }

    public void addMultiValueToDoc(Document doc, String key, MultiValue mvobj, Field.Store store, Field.Index index,
            ArrayList ref_list) {
        final int type = mvobj.getValueType();
        final Object[] values = mvobj.getValues();
        final int len = values.length;
        StringBuffer valueBuffer = new StringBuffer();
        final String DELIM = NULL_DELIM;

        for (int i = 0; i < len; i++) {
            if (values[i] == null) {
                continue;
            }

            switch (type) {
            case IProperty.BOOLEAN:
                valueBuffer.append(serializeBoolean(((Boolean) values[i]).booleanValue()));
                valueBuffer.append(DELIM);
                break;
            case IProperty.DATE:
                valueBuffer.append(serializeDate((Date) Context.jsToJava(values[i], Date.class)));
                valueBuffer.append(DELIM);
                break;
            case IProperty.TIME:
                valueBuffer.append(serializeTime((Date) Context.jsToJava(values[i], Date.class)));
                valueBuffer.append(DELIM);
                break;
            case IProperty.TIMESTAMP:
                valueBuffer.append(serializeTimestamp((Date) Context.jsToJava(values[i], Date.class)));
                valueBuffer.append(DELIM);
                break;
            case IProperty.FLOAT:
                if (values[i] instanceof Double) {
                    valueBuffer.append(serializeFloat(((Double) values[i]).doubleValue()));
                } else if (values[i] instanceof Float) {
                    valueBuffer.append(serializeFloat(((Float) values[i]).floatValue()));
                } else {
                    valueBuffer.append(serializeFloat(((Integer) values[i]).intValue()));
                }
                valueBuffer.append(DELIM);
                break;
            case IProperty.INTEGER:
                valueBuffer.append(serializeInt(((Integer) values[i]).intValue()));
                valueBuffer.append(DELIM);
                break;
            case IProperty.STRING:
                valueBuffer.append((String) values[i]).append(DELIM);
                break;
            case IProperty.REFERENCE:
                Reference r = (Reference) values[i];
                String serialized_ref = serializeReference(r);
                valueBuffer.append(serialized_ref).append(DELIM);

                StringBuffer sb = new StringBuffer(serialized_ref);
                sb.append(DELIM).append(key).append(DELIM).append(i);
                String xpath;
                if ((xpath = r.getSourceXPath()) != null) {
                    sb.append(DELIM).append(xpath);
                }
                ref_list.add(sb.toString());
                break;
            default:
                break;
            }
        }

        int endBuff = valueBuffer.length();
        if (valueBuffer.length() > 0) {
            endBuff -= NULL_DELIM.length();
        }

        String v;
        if (endBuff == 0) {
            v = "";
        } else {
            v = valueBuffer.substring(0, endBuff);
        }
        doc.add(new Field(key, v, store, Field.Index.TOKENIZED));
    }

    public Key[] getTargetNodeIds(final String id, final int mode, ArrayList protos, BooleanQuery append, Sort sort)
            throws Exception {
        IndexSearcher searcher = null;
        Document doc = null;
        BooleanQuery query = null;

        try {
            searcher = this.getIndexSearcher();
            String idvalue = id;
            Query id_query = new TermQuery(new Term(ID, idvalue));

            for (int i = mode; i >= LIVE_MODE; i--) {
                if (i != LIVE_MODE && !isDraftNode(id, mode)) {
                    continue;
                }
                query = new BooleanQuery();
                query.add(id_query, BooleanClause.Occur.MUST);
                query.add(new TermQuery(new Term(LAYER_OF_SAVE, i + "")), BooleanClause.Occur.MUST);
                Hits hits = searcher.search(query);

                /*if (app.debug()) 
                   app.logEvent("LuceneManager.getTargetNodeIds(): id=" + id 
                    + ",layer=" + mode + " executed query [" + query 
                    + "] which resulted in " + hits.length() + " hits");*/

                if (hits.length() > 0) {
                    doc = hits.doc(0);
                    break;
                }
            }
        } catch (Exception ex) {
            app.logError(
                    ErrorReporter.errorMsg(this.getClass(), "getSourceReferences") + "Could not retrieve document "
                            + id + " from Lucene index with query = " + (query != null ? query : "null"),
                    ex);
            throw ex;
        } finally {
            this.releaseIndexSearcher(searcher);
        }

        if (doc == null) {
            return new Key[0];
        }

        Field[] fields = doc.getFields(REF_LIST_FIELD);
        int len;
        if ((fields == null) || ((len = fields.length) == 0)) {
            return new Key[0];
        }

        ArrayList<Key> keys = new ArrayList<Key>();
        doc = null;
        for (int i = 0; i < len; i++) {
            doc = null;
            query = new BooleanQuery();
            String refid = getIdFromRefListField(fields[i]);
            query.add(new TermQuery(new Term(ID, refid)), BooleanClause.Occur.MUST);
            BooleanQuery proto_query = null;
            final int sizeOfProtos;
            if ((sizeOfProtos = protos.size()) > 0) {
                proto_query = new BooleanQuery();
                for (int j = 0; j < sizeOfProtos; j++) {
                    proto_query.add(new TermQuery(new Term(PROTOTYPE, (String) protos.get(j))),
                            BooleanClause.Occur.SHOULD);
                }
                query.add(proto_query, BooleanClause.Occur.MUST);
            }
            if (append != null && append.getClauses().length > 0) {
                query.add(append, BooleanClause.Occur.MUST);
            }
            for (int j = mode; j >= LIVE_MODE; j--) {
                if (j != LIVE_MODE && !isDraftNode(refid, mode)) {
                    continue;
                }
                query.add(new TermQuery(new Term(LAYER_OF_SAVE, j + "")), BooleanClause.Occur.MUST);
                Hits hits = searcher.search(query);

                /*if (app.debug()) 
                   app.logEvent("LuceneManager.getTargetNodeIds() [for retrieving target " +
                    "keys]: id=" + id + ",layer=" + mode + " executed query [" + query 
                    + "] which resulted in " + hits.length() + " hits");*/

                if (hits.length() > 0) {
                    doc = hits.doc(0);
                    break;
                }
            }
            if (doc != null) {
                keys.add(new DbKey(this.app.getDbMapping(doc.get(PROTOTYPE)), doc.get(ID), mode));
            }
        }

        Key[] key_arr = new Key[keys.size()];
        return keys.toArray(key_arr);
    }

    public static String getIdFromRefListField(Field f) {
        String value = f.stringValue();
        return value.substring(0, value.indexOf(NULL_DELIM));
    }

    public Key[] getSourceNodeIds(final String id, final int mode, ArrayList protos, BooleanQuery append, Sort sort)
            throws Exception {
        IndexSearcher searcher = null;
        Hits hits = null;
        Key[] keys = null;
        BooleanQuery query = null;

        try {
            searcher = this.getIndexSearcher();
            query = new BooleanQuery();
            final int sizeOfProtos;
            if ((sizeOfProtos = protos.size()) > 0) {
                BooleanQuery proto_query = new BooleanQuery();
                for (int i = 0; i < sizeOfProtos; i++) {
                    proto_query.add(new TermQuery(new Term(PROTOTYPE, (String) protos.get(i))),
                            BooleanClause.Occur.SHOULD);
                }
                query.add(proto_query, BooleanClause.Occur.MUST);
            }

            query.add(new TermQuery(new Term(REF_LIST_FIELD, id)), BooleanClause.Occur.MUST);

            if (append != null && append.getClauses().length > 0) {
                query.add(append, BooleanClause.Occur.MUST);
            }

            hits = searcher.search(query, sort);

            /*if (app.debug())
               app.logEvent("LuceneManager.getSourceNodeIds(): id=" + id + ",layer=" + mode
              + " executed query [" + query + " which resulted in " 
              + hits.length() + " hits");*/

            int size = hits.length();
            ArrayList<Key> list = new ArrayList<Key>();
            for (int i = 0; i < size; i++) {
                Document doc = hits.doc(i);

                if (!isIdInDocumentRefs(doc, id)) {
                    continue;
                }

                Field id_field = doc.getField(ID);
                Field proto_field = doc.getField(PROTOTYPE);
                Field layer_field = doc.getField(LAYER_OF_SAVE);
                if (layer_field != null) {
                    try {
                        if (mode < Integer.parseInt(layer_field.stringValue())) {
                            continue;
                        }
                    } catch (Exception nfe) {
                    }
                }
                if (id_field != null && proto_field != null) {
                    list.add(new DbKey(this.app.getDbMapping(proto_field.stringValue()), id_field.stringValue(),
                            mode));
                }
            }

            keys = new Key[list.size()];
            list.toArray(keys);
        } catch (Exception ex) {
            app.logError(
                    ErrorReporter.errorMsg(this.getClass(), "getSourceNodeIds") + "Could not retrieve document "
                            + id + " from Lucene index with query = " + (query != null ? query : "null"),
                    ex);
            throw ex;
        } finally {
            this.releaseIndexSearcher(searcher);
        }

        return keys;
    }

    public static boolean isIdInDocumentRefs(Document doc, String id) {
        Field[] ref_fields = doc.getFields(REF_LIST_FIELD);
        final int ref_length = ref_fields != null ? ref_fields.length : 0;
        for (int i = 0; i < ref_length; i++) {
            String[] values = ref_fields[i].stringValue().split(NULL_DELIM);
            if (id.equals(values[0])) {
                return true;
            }
        }
        return false;
    }

    public static String getDataType(Prototype proto, String fieldname) {
        if (ID.equalsIgnoreCase(fieldname) || PROTOTYPE.equalsIgnoreCase(fieldname)
                || NAME.equalsIgnoreCase(fieldname) || PARENTID.equalsIgnoreCase(fieldname)
                || PARENTPROTOTYPE.equalsIgnoreCase(fieldname)) {

            return STRING_FIELD;
        } else if (CREATED.equalsIgnoreCase(fieldname) || LASTMODIFIED.equalsIgnoreCase(fieldname)) {
            return INTEGER_FIELD;
        } else {
            while (proto != null) {
                ResourceProperties props = proto.getTypeProperties();
                String type = props.getProperty(fieldname + ".type");
                if (type != null) {
                    type = type.toLowerCase();
                    if (type.startsWith("object") || type.startsWith("collection")) {
                        return NODE_FIELD;
                    } else if (type.startsWith("multivalue")) {
                        return MULTI_FIELD;
                    }

                    return type;
                }

                proto = proto.getParentPrototype();
            }
        }

        return STRING_FIELD;
    }

    private Field.Store getStore(ResourceProperties rprops, String prop) {
        if (FileObject.CONTENT.equals(prop)) {
            return Field.Store.YES;
        }

        if (rprops == null)
            return DEFAULT_STORE;

        String store = rprops.getProperty(prop + ".store");
        if (store != null) {
            store = store.toUpperCase();
            if (store.equals(YES_STORE))
                return Field.Store.YES;
            else if (store.equals(NO_STORE))
                return Field.Store.NO;
            else if (store.equals(COMPRESSED_STORE))
                return Field.Store.COMPRESS;
        }

        return DEFAULT_STORE;
    }

    private Analyzer getAnalyzer(ResourceProperties rprops, String prop) {
        if (rprops == null) {
            return null;
        }

        String analyzer = rprops.getProperty(prop + ".analyzer");
        if (analyzer != null) {
            return this.getAnalyzer(analyzer);
        }

        return null;
    }

    private float getBoost(ResourceProperties rprops, String prop) {
        if (rprops == null) {
            return -1f;
        }

        String analyzer = rprops.getProperty(prop + ".boost");
        if (analyzer != null) {
            try {
                return Float.parseFloat(analyzer);
            } catch (Exception ex) {
                return -1f;
            }
        }

        return -1f;
    }

    private Field.Index getIndex(ResourceProperties rprops, String prop) {
        if (FileObject.CONTENT.equals(prop)) {
            return Field.Index.TOKENIZED;
        }

        if (rprops == null)
            return DEFAULT_INDEX;

        String index = rprops.getProperty(prop + ".index");
        if (index != null) {
            index = index.toUpperCase();
            if (index.equals(NO_INDEX))
                return Field.Index.NO;
            else if (index.equals(NO_NORMS_INDEX))
                return Field.Index.NO_NORMS;
            else if (index.equals(TOKENIZED_INDEX))
                return Field.Index.TOKENIZED;
            else if (index.equals(UNTOKENIZED_INDEX))
                return Field.Index.UN_TOKENIZED;
        }

        return DEFAULT_INDEX;
    }

    private int getType(ResourceProperties rprops, String prop) {
        if (rprops == null)
            return -1;

        String type = rprops.getProperty(prop + ".type");
        if (type != null) {
            type = type.toLowerCase();
            if (type.equals(BOOLEAN_FIELD))
                return IProperty.BOOLEAN;
            else if (type.equals(DATE_FIELD))
                return IProperty.DATE;
            else if (type.equals(TIME_FIELD))
                return IProperty.TIME;
            else if (type.equals(TIMESTAMP_FIELD))
                return IProperty.TIMESTAMP;
            else if (type.equals(FLOAT_FIELD))
                return IProperty.FLOAT;
            else if (type.equals(NUMBER_FIELD))
                return IProperty.FLOAT;
            else if (type.equals(INTEGER_FIELD))
                return IProperty.INTEGER;
            else if (type.equals(JAVAOBJ_FIELD))
                return IProperty.JAVAOBJECT;
            else if (type.equals(NODE_FIELD))
                return IProperty.NODE;
            else if (type.equals(STRING_FIELD))
                return IProperty.STRING;
            else if (type.equals(XML_FIELD))
                return IProperty.XML;
            else if (type.equals(XHTML_FIELD))
                return IProperty.XHTML;
            else if (type.equals(SMALL_FLOAT_FIELD))
                return IProperty.SMALLFLOAT;
            else if (type.equals(SMALL_INT_FIELD))
                return IProperty.SMALLINT;
        }

        return -1;
    }

    public static int stringToType(String strType) {
        int type = IProperty.STRING;

        if (strType != null) {
            strType = strType.toLowerCase();
            if (strType.equals(BOOLEAN_FIELD)) {
                type = IProperty.BOOLEAN;
            } else if (strType.equals(DATE_FIELD)) {
                type = IProperty.DATE;
            } else if (strType.equals(TIME_FIELD)) {
                type = IProperty.TIME;
            } else if (strType.equals(TIMESTAMP_FIELD)) {
                type = IProperty.TIMESTAMP;
            } else if (strType.equals(FLOAT_FIELD)) {
                type = IProperty.FLOAT;
            } else if (strType.equals(NUMBER_FIELD)) {
                type = IProperty.FLOAT;
            } else if (strType.equals(INTEGER_FIELD)) {
                type = IProperty.INTEGER;
            } else if (strType.equals(JAVAOBJ_FIELD)) {
                type = IProperty.JAVAOBJECT;
            } else if (strType.equals(NODE_FIELD)) {
                type = IProperty.NODE;
            } else if (strType.equals(REL_FIELD)) {
                type = IProperty.REFERENCE;
            } else if (strType.startsWith(MULTI_FIELD)) {
                type = IProperty.MULTI_VALUE;
            } else if (strType.equals(XML_FIELD)) {
                type = IProperty.XML;
            } else if (strType.equals(XHTML_FIELD)) {
                type = IProperty.XHTML;
            } else if (strType.equals(FILE_FIELD)) {
                return IProperty.NODE;
            } else if (strType.equals(IMAGE_FIELD)) {
                return IProperty.NODE;
            } else if (strType.equals(SMALL_FLOAT_FIELD)) {
                return IProperty.SMALLFLOAT;
            } else if (strType.equals(SMALL_INT_FIELD)) {
                return IProperty.SMALLINT;
            }
        }

        return type;
    }

    public final static String intToStr(int type) {
        switch (type) {
        case IProperty.BOOLEAN:
            return LuceneManager.BOOLEAN_FIELD;
        case IProperty.DATE:
            return LuceneManager.DATE_FIELD;
        case IProperty.TIME:
            return LuceneManager.TIME_FIELD;
        case IProperty.TIMESTAMP:
            return LuceneManager.TIMESTAMP_FIELD;
        case IProperty.FLOAT:
            return LuceneManager.FLOAT_FIELD;
        case IProperty.INTEGER:
            return LuceneManager.INTEGER_FIELD;
        case IProperty.JAVAOBJECT:
            return LuceneManager.JAVAOBJ_FIELD;
        case IProperty.MULTI_VALUE:
            return LuceneManager.MULTI_FIELD;
        case IProperty.NODE:
            return LuceneManager.NODE_FIELD;
        case IProperty.REFERENCE:
            return LuceneManager.REL_FIELD;
        case IProperty.STRING:
            return LuceneManager.STRING_FIELD;
        case IProperty.XML:
            return LuceneManager.XML_FIELD;
        case IProperty.XHTML:
            return LuceneManager.XHTML_FIELD;
        case IProperty.SMALLINT:
            return LuceneManager.SMALL_INT_FIELD;
        case IProperty.SMALLFLOAT:
            return LuceneManager.SMALL_FLOAT_FIELD;
        }
        return null;
    }

    public boolean commitToStorage(final INode node, final String tmpPath) {
        String oldRelPath = tmpPath;
        String repos = this.app.getBlobDir();

        if (oldRelPath != null) {
            String newPath = repos;
            if (newPath != null && !newPath.endsWith(File.separator)) {
                newPath += File.separator;
            }
            if (node instanceof Node) {
                newPath += node.getID();
            } else {
                return false;
            }

            oldRelPath = normalizePath(oldRelPath);
            newPath = normalizePath(newPath);
            createDirectories(newPath.substring(0, newPath.lastIndexOf(File.separator)));

            File oldFile = new File(oldRelPath);
            File newFile = new File(newPath);

            if (oldFile.equals(newFile)) {
                return true;
            }

            if (newFile.exists()) {
                newFile.delete();
            }

            String upload = node.getString(FileObject.FILE_UPLOAD);
            if (upload != null && "false".equals(upload)) {
                return FileUtils.copy(oldFile, newFile);
            } else {
                if (oldFile.renameTo(newFile)) {
                    return true;
                } else {
                    return FileUtils.copy(oldFile, newFile);
                }
            }
        }

        return false;
    }

    private void createDirectories(String path) {
        File dir = new File(path);
        if (dir.exists() && dir.isDirectory()) {
            return;
        }

        char separator = File.separatorChar;
        StringBuffer walker = new StringBuffer(path.length());

        String[] pathItems;
        int count = 0;
        if (separator == '\\') {
            pathItems = path.split("\\" + separator);
            walker.append(pathItems[count++]).append(separator);
        } else {
            pathItems = path.split("" + separator);
            walker.append(separator);
        }

        int length = pathItems.length;

        do {
            walker.append(pathItems[count++]).append(separator);
            dir = new File(walker.toString());
            if (!dir.exists()) {
                dir.mkdir();
            }
        } while (count < length);
    }

    private String normalizePath(String path) {
        String separator = File.separator;
        String wrong;
        if (separator.equals("\\")) {
            wrong = "/";
        } else {
            wrong = "\\";
        }

        StringBuffer pathBuffer = new StringBuffer(path);
        int index = -1;
        while ((index = pathBuffer.indexOf(wrong, index)) > -1) {
            pathBuffer.replace(index, index + 1, separator);
        }

        return pathBuffer.toString();
    }

    protected boolean moveFile(File src, File dst) {
        boolean success = false;
        java.io.InputStream in = null;
        java.io.OutputStream out = null;

        try {
            in = new FileInputStream(src);
            out = new FileOutputStream(dst);

            byte[] buf = new byte[2048];
            int len;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
            }

            success = true;
        } catch (Exception ex) {
            app.logError(ErrorReporter.errorMsg(this.getClass(), "moveFile") + "Could not move file "
                    + src.getName() + " to " + dst.getName(), ex);
            success = false;
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (Exception e) {
                    app.logError(ErrorReporter.errorMsg(this.getClass(), "moveFile"), e);
                }
                in = null;
            }
            if (out != null) {
                try {
                    out.close();
                } catch (Exception e) {
                    app.logError(ErrorReporter.errorMsg(this.getClass(), "moveFile"), e);
                }
                out = null;
            }
        }

        src.delete();

        return success;
    }

    public boolean deleteFromStorage(INode node) {
        StringBuffer path = new StringBuffer(35);

        ResourceProperties rprops = app.getProperties();
        final String repos = rprops.getProperty(node.getPrototype().toLowerCase() + ".repository");
        path.append(repos);
        path.append(node.getID());

        File file = new File(path.toString());
        return file.exists() ? file.delete() : false;
    }

    public static void commitSegments(Connection conn, Application app, Directory dir) {
        commitSegments(null, conn, app, dir);
    }

    public static void commitSegments(String segmentsNew, Connection conn, Application app, Directory dir) {
        byte[] segmentContents = null;
        if (segmentsNew == null) {
            segmentsNew = TransFSDirectory.SEGMENTS_NEW;//TODO:IndexFileNames.getSegmentsNewFileName();
        }
        IndexInput input = null;
        try {
            input = dir.openInput(segmentsNew);
            int length = (int) input.length();
            segmentContents = new byte[length];
            try {
                input.readBytes(segmentContents, 0, length);
            } catch (IOException ioe) {
                segmentContents = null;
            }
        } catch (Exception ex) {
            app.logError(ErrorReporter.errorMsg(LuceneManager.class, "commitSegments"), ex);
            throw new TransactionException("LuceneTransaction.executeSubTransaction(): " + ex.getMessage());
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (Exception ignore) {
                }
                input = null;
            }
        }

        if (segmentContents == null || segmentContents.length == 0) {
            throw new TransactionException("LuceneTransaction.executeSubTransaction(): "
                    + "The segments.new file does not contain any data to save.");
        }

        PreparedStatement pstmt = null;
        ByteArrayInputStream bais = null;
        boolean exceptionOccured = false;

        try {
            String sql = "UPDATE Lucene SET valid = ?, version = ? " + "WHERE valid = ? AND db_home = ?";
            pstmt = conn.prepareStatement(sql);
            int count = 1;
            pstmt.setBoolean(count++, false);
            pstmt.setInt(count++, getLuceneVersion());
            pstmt.setBoolean(count++, true);
            pstmt.setString(count++, app.getDbDir().getName());
            pstmt.executeUpdate();
            pstmt.close();
            pstmt = null;

            sql = "INSERT INTO Lucene (valid, db_home, segments, version) " + "VALUES (?,?,?,?)";
            pstmt = conn.prepareStatement(sql);
            count = 1;
            pstmt.setBoolean(count++, true);
            pstmt.setString(count++, app.getDbDir().getName());
            bais = new ByteArrayInputStream(segmentContents);
            pstmt.setBinaryStream(count++, bais, segmentContents.length);
            pstmt.setInt(count++, getLuceneVersion());
            int rows = pstmt.executeUpdate();
            if (rows < 1) {
                throw new Exception(
                        "LuceneTransactionManager.executeTransaction(): update didn't affect any rows in the database");
            }
        } catch (Exception ex) {
            exceptionOccured = true;
            throw new TransactionException(ex.getMessage());
        } finally {
            try {
                dir.deleteFile(segmentsNew);
            } catch (IOException ioex) {
                // i guess its okay if a random segments.new file is lying around, itll 
                // get overwritten on the next lucene write operation anyway
                app.logEvent(ErrorReporter.warningMsg(LuceneManager.class, "commitSegments") + "Could not delete "
                        + segmentsNew);
            }

            if (bais != null) {
                try {
                    bais.close();
                } catch (Exception ignoreit) {
                }
                bais = null;
            }
            segmentContents = null;

            if (pstmt != null) {
                try {
                    pstmt.close();
                } catch (SQLException sqle) {
                    if (!exceptionOccured) {
                        throw new TransactionException(sqle.getMessage());
                    }
                }
                pstmt = null;
            }
        }
    }

    public static void commitSegments(String segmentsNew, Connection conn, File dbhome, Directory dir) {
        byte[] segmentContents = null;
        if (segmentsNew == null) {
            segmentsNew = TransFSDirectory.SEGMENTS_NEW;//TODO:IndexFileNames.getSegmentsNewFileName();
        }
        IndexInput input = null;
        try {
            input = dir.openInput(segmentsNew);
            int length = (int) input.length();
            segmentContents = new byte[length];
            try {
                input.readBytes(segmentContents, 0, length);
            } catch (IOException ioe) {
                segmentContents = null;
            }
        } catch (Exception ex) {
            throw new TransactionException("LuceneTransaction.executeSubTransaction(): " + ex.getMessage());
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (Exception ignore) {
                }
                input = null;
            }
        }

        if (segmentContents == null || segmentContents.length == 0) {
            throw new TransactionException("LuceneTransaction.executeSubTransaction(): "
                    + "The segments.new file does not contain any data to save.");
        }

        PreparedStatement pstmt = null;
        ByteArrayInputStream bais = null;
        boolean exceptionOccured = false;

        try {
            String sql = "UPDATE Lucene SET valid = ?, version = ? " + "WHERE valid = ? AND db_home = ?";
            pstmt = conn.prepareStatement(sql);
            int count = 1;
            pstmt.setBoolean(count++, false);
            pstmt.setInt(count++, getLuceneVersion());
            pstmt.setBoolean(count++, true);
            pstmt.setString(count++, dbhome.getName());
            pstmt.executeUpdate();
            pstmt.close();
            pstmt = null;

            sql = "INSERT INTO Lucene (valid, db_home, segments, version) " + "VALUES (?,?,?,?)";
            pstmt = conn.prepareStatement(sql);
            count = 1;
            pstmt.setBoolean(count++, true);
            pstmt.setString(count++, dbhome.getName());
            bais = new ByteArrayInputStream(segmentContents);
            pstmt.setBinaryStream(count++, bais, segmentContents.length);
            pstmt.setInt(count++, getLuceneVersion());
            int rows = pstmt.executeUpdate();
            System.out.println("EXECUTE update was a SUCCESS!!");
            if (rows < 1) {
                throw new Exception(
                        "LuceneTransactionManager.executeTransaction(): update didn't affect any rows in the database");
            }
        } catch (Exception ex) {
            exceptionOccured = true;
            throw new TransactionException(ex.getMessage());
        } finally {
            try {
                dir.deleteFile(segmentsNew);
            } catch (IOException ioex) {
                // i guess its okay if a random segments.new file is lying around, itll 
                // get overwritten on the next lucene write operation anyway
            }

            if (bais != null) {
                try {
                    bais.close();
                } catch (Exception ignoreit) {
                }
                bais = null;
            }
            segmentContents = null;

            if (pstmt != null) {
                try {
                    pstmt.close();
                } catch (SQLException sqle) {
                    if (!exceptionOccured) {
                        throw new TransactionException(sqle.getMessage());
                    }
                }
                pstmt = null;
            }
        }
    }

    public static Analyzer getAnalyzer(String analyzerName) {
        if (analyzerName == null) {
            return null;
        }

        String analyzer = analyzerName.toLowerCase();
        if ("whitespaceanalyzer".equals(analyzer)) {
            return new WhitespaceAnalyzer();
        } else if ("simpleanalyzer".equals(analyzer)) {
            return new SimpleAnalyzer();
        } else if ("stopanalyzer".equals(analyzer)) {
            return new StopAnalyzer();
        } else if ("standardanalyzer".equals(analyzer)) {
            return new StandardAnalyzer();
        } else {
            try {
                return (Analyzer) Class.forName(analyzerName).newInstance();
            } catch (InstantiationException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        return null;
    }

    public static int getLuceneVersion() {
        return LUCENE_VERSION;
    }

    public int getCurrentLuceneVersion() {
        return this.writerManager.getCurrentVersion();
    }

    public ArrayList getChildrenIds(INode node) throws Exception {
        ArrayList childrenIds = new ArrayList();
        IndexSearcher searcher = null;
        BooleanQuery bq = new BooleanQuery();

        try {
            searcher = this.getIndexSearcher();
            String id = node.getID();
            final Query query1 = new TermQuery(new Term(PARENTID, isSpecialNode(id) ? id : node.getID()));
            final Query query2 = new TermQuery(new Term(ISCHILD, "true"));
            bq.add(query1, BooleanClause.Occur.MUST);
            bq.add(query2, BooleanClause.Occur.MUST);

            final Hits hits = searcher.search(bq);

            /*if (app.debug())
               app.logEvent("LuceneManager.getChildrenIds() executed query [" + bq 
                 + " which resulted in " + hits.length() + " hits");*/

            final int length = hits.length();
            for (int i = 0; i < length; i++) {
                Document doc = hits.doc(i);
                childrenIds.add(doc.getField(ID).stringValue());
            }
        } catch (IOException ioe) {
            throw new Exception("Searcher failed when attempting to retrieve children of " + "id '" + node.getID()
                    + "', query = " + bq);
        } finally {
            this.releaseIndexSearcher(searcher);
        }

        return childrenIds;
    }

    public void logSegInfo() throws Exception {
        IndexWriter iw = this.writerManager.getWriter();
        SegmentInfosWrapper sisw = iw.getSegmentInfosWrapper();
        StringBuffer logStr = new StringBuffer();
        logStr.append("Start Lucene Segments Log\n");
        for (int i = 0; i < sisw.size(); i++) {
            logStr.append(sisw.getSegmentInfos(i));
        }
        logStr.append("\nEnd Lucene Segments Log\n");
        app.logEvent(logStr.toString());
    }

    public void printSegInfo() throws Exception {
        IndexWriter iw = this.writerManager.getWriter();
        SegmentInfosWrapper sisw = iw.getSegmentInfosWrapper();
        StringBuffer logStr = new StringBuffer();
        logStr.append("Start Lucene Segments Log\n");
        for (int i = 0; i < sisw.size(); i++) {
            logStr.append(sisw.getSegmentInfos(i));
        }
        logStr.append("\nEnd Lucene Segments Log\n");
        System.out.println(logStr.toString());
    }

    public synchronized IndexSearcher getIndexSearcher() throws IOException {
        if (!this.isSearcherValid || this.searcher == null) {
            this.isSearcherValid = true;
            try {
                this.searcher = new IndexSearcher(this.directory);
            } catch (Exception ex) {
                throw new IOException(
                        "FATAL ERROR::LuceneManager.getIndexSearcher(), Could not create IndexSearcher");
            }
        }
        this.searcher.refCount++;
        return this.searcher;
    }

    public synchronized void releaseIndexSearcher(IndexSearcher searcher) {
        if (searcher == null) {
            return;
        }
        searcher.refCount--;
        if (this.searcher != searcher && searcher.refCount <= 0) {
            try {
                searcher.close();
            } catch (Exception ex) {
                this.app.logError(ErrorReporter.errorMsg(this.getClass(), "releaseIndexSearcher")
                        + "Could not close " + searcher, ex);
            }
            searcher = null;
        }
    }

    public synchronized void setSearcherDirty() {
        this.isSearcherValid = false;
        if (this.searcher != null && this.searcher.refCount <= 0) {
            try {
                this.searcher.close();
            } catch (Exception ex) {
                this.app.logError(
                        ErrorReporter.errorMsg(this.getClass(), "setSearcherDirty") + "Could not close " + searcher,
                        ex);
            }
            this.searcher = null;
        }
    }

    public void logIndexContents() {
        /*TODO:try {
        synchronized (this.directory) {
            SegmentInfos sinfos = IndexObjectsFactory.getFSSegmentInfos(this.directory);
            DeletedInfos delInfos = IndexObjectsFactory.getDeletedInfos(this.directory);
            BitSet bs = delInfos.getBitSet();
            IndexObjectsFactory.setDeletedInfos(this.directory, new DeletedInfos());
            IndexReader reader = IndexReader.open(this.directory);
            final int numdocs = reader.numDocsTotal();
            StringBuffer sb = new StringBuffer("\n");
            sb.append("TOTAL DOCS: ").append(numdocs).append("\n");
            sb.append("SEGMENTS: ");
            final int sinfossize = sinfos.size();
            for (int i = 0; i < sinfossize; i++) {
                SegmentInfo si = sinfos.info(i);
                sb.append(si.name).append("(").append(si.docCount).append(") ");
            }
            sb.append("\n");
            sb.append("ID\tPrototype\tPID\tParent Prototype\tObsolete?\tDeleted?\tStatus\n");
            sb.append("------------------------------------------------------------------\n");
            for (int i = 0; i < numdocs; i++) {
                Document d = reader.document(i);
                String id = d.get(ID);
                String prototype = d.get(PROTOTYPE);
                String parentid = d.get(PARENTID);
                String parentproto = d.get(PARENTPROTOTYPE);
                boolean isObsolete = bs.get(i);
                boolean isDeleted = d.get(DeletedInfos.DELETED) != null;
                String status = d.get(STATUS);
                sb.append(id).append("\t").append(prototype).append("\t");
                sb.append(parentid).append("\t").append(parentproto).append("\t");
                sb.append(isObsolete).append("\t").append(isDeleted).append("\t");
                sb.append(status).append("\n");
            }
            this.app.logEvent(sb.toString());
            IndexObjectsFactory.setDeletedInfos(this.directory, delInfos);
        }
        } catch (Exception ex) {
        ex.printStackTrace();
        }*/
    }

    /*TODO:public LuceneOptimizer getOptimizer() {
    return this.optimizerThread;
    }*/

    public ArrayList<Node> getPreviewNodes(String id, final int mode) throws Exception {
        ArrayList<Node> list = new ArrayList<Node>();
        IndexSearcher searcher = null;
        final int highestMode = app.getHighestPreviewLayer();

        try {
            for (int cmode = mode + 1; cmode <= highestMode; cmode++) {
                try {
                    Node n = this.retrieveFromIndexFixedMode(id, cmode);
                    if (n != null) {
                        list.add(n);
                    }
                } catch (Exception x) {
                }
            }
        } catch (Exception ex) {
            app.logError(ErrorReporter.errorMsg(this.getClass(), "getPreviewNodes"), ex);
            throw ex;
        } finally {
            this.releaseIndexSearcher(searcher);
        }

        return list;
    }

    private static final String getIDPart(String id) {
        return id.substring(0, id.indexOf(DeletedInfos.KEY_SEPERATOR));
    }

    private static final int getLayerPart(String id) {
        final int idx = id.indexOf(DeletedInfos.KEY_SEPERATOR);
        return Integer.parseInt(id.substring(idx + DeletedInfos.KEY_SEPERATOR.length()));
    }

    public LuceneDataFormatter getDataFormatter() {
        return dataFormatter;
    }

}