/*
Copyright (C) 2000-2010  Ministere de la culture et de la communication (France), AJLSM
See LICENCE file
*/
package fr.gouv.culture.sdx.document;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Vector;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;

import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.configuration.DefaultConfiguration;
import org.apache.avalon.framework.parameters.ParameterException;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.cocoon.serialization.XMLSerializer;
import org.apache.cocoon.xml.XMLConsumer;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.AttributesImpl;

import fr.gouv.culture.sdx.documentbase.DefaultIDGenerator;
import fr.gouv.culture.sdx.documentbase.IDGenerator;
import fr.gouv.culture.sdx.exception.SDXException;
import fr.gouv.culture.sdx.exception.SDXExceptionCode;
import fr.gouv.culture.sdx.framework.Framework;
import fr.gouv.culture.sdx.utils.Utilities;
import fr.gouv.culture.sdx.utils.constants.Node;
import fr.gouv.culture.sdx.utils.logging.LoggingUtils;

/**
 * An abstract class for indexable documents.
 *TODO: THIS SHOULD BE BETTER, IN MORE DETAILS OF THE BASIC INDEXATION ELEMENT CREATION,
 * should refer to some schema and it works from the permuatations of the schema.
 */
//TODO?:should this class implement the ParsableDocument interface as both subclasses do?-rbp

public abstract class AbstractIndexableDocument extends AbstractDocument implements IndexableDocument {

    /* These methods handle receiving of indexing data. */

    protected String DOC_NAMESPACE = Framework.SDXNamespaceURI;

    protected String DOC_ROOT_ELEMENT_NAME = Node.Name.DOCUMENT;

    protected String DOC_FIELD_ELEMENT_NAME = Node.Name.FIELD;

    protected String DOC_ATTACHEDOC_ELEMENT_NAME = Node.Name.ATTACHED_DOCUMENT;

    protected String DOC_MSG_ELEMENT_NAME = Node.Name.MESSAGE;

    protected Parameters nsTable = null;

    /** List of fields for indexing. */
    protected Vector properties;

    /** A character buffer for element content. */
    protected StringBuffer characterBuffer;

    /** The current field name */
    protected String currentFieldName;

    protected int openSdxDocElems = 0;

    /**A list of attached documents*/
    protected Vector attachedDocuments = null;

    /**A list of sub(Indexable) documents*/
    protected Vector subDocuments;

    /** Indicates wheter the documents must be refreshed. */
    private boolean updateAttachedDocuments = true;

    /**A document resulting from a transformation*/
    protected IndexableDocument transformedDoc = null;

    //protected Stack subDocs = new Stack();
    protected IndexableDocument subDoc = null;

    protected ByteArrayOutputStream subDocBytes = null;

    protected boolean withinSdxElement = false;

    protected boolean withinXmlField = false;

    protected ContentHandler _msgHandler = null;

    /**
     * @see org.apache.lucene.document.Document#boost
     */
    protected float _boost = 1.0f;//defaulted to value in lucene Document.class

    protected float _currentFieldBoost = 1.0f;

    /** list of fields with a a XML type*/
    protected HashMap _xmlFieldList = null;

    /** The transfomer used to parse the xml fields*/
    protected TransformerHandler _xmlFieldTransformer = null;

    /** The output of the parsed xml field*/
    protected ByteArrayOutputStream _xmlFieldOutput = null;

    public void startElement(String nsURI, String name, String qName, Attributes atts) throws SAXException {
    	if (DOC_NAMESPACE.equals(nsURI)) {
            this.withinSdxElement = true;

            if (DOC_FIELD_ELEMENT_NAME.equals(name)) {
                this._currentFieldBoost = 1.0f;//resetting for each new field
                if (this.openSdxDocElems > 1 && this.subDoc != null)
                    this.subDoc.startElement(nsURI, name, qName, atts);

                else if (atts != null) {
                    String boost = atts.getValue(Node.Name.BOOST);
                    if (Utilities.checkString(boost))
                        this._currentFieldBoost = Float.parseFloat(boost);
                    currentFieldName = atts.getValue(Node.Name.NAME);
                    //backward compatibility : try to use old attribute name
                    if (currentFieldName == null || "".equals(currentFieldName)) currentFieldName = atts.getValue(Node.Name.CODE);
                    if(_xmlFieldList.get(currentFieldName) != null){
                    	withinXmlField = true;
                    	_xmlFieldTransformer.startDocument();
                    	_xmlFieldTransformer.startPrefixMapping(Node.Prefix.SDX, Node.Uri.SDX);
                    	_xmlFieldTransformer.startElement(nsURI, Node.Name.FRAGMENT, Node.Prefix.SDX + ":" + Node.Name.FRAGMENT, new AttributesImpl());
                    }
                }

                characterBuffer = new StringBuffer();
            } else if (DOC_ATTACHEDOC_ELEMENT_NAME.equals(name)) {

                if (this.openSdxDocElems > 1 && this.subDoc != null)
                    this.subDoc.startElement(nsURI, name, qName, atts);

                else if (this.updateAttachedDocuments()) {
                    if (atts != null) {
                        String id = atts.getValue(Node.Name.ID);
                        String attid = atts.getValue(Node.Name.ATTID);
                        if (Utilities.checkString(attid)) {
                            if (!Utilities.checkString(id))
                                id = super.getId();//no base 'id' was passed, but an 'attId' was passed so we will use the id of the parent document
                            id = Utilities.attId(id, attid);
                        }
                        if (Utilities.checkString(id)) {
                            try {
                                this.addAttachedDocument(id, getURL(), atts.getValue(Node.Name.URL), atts.getValue(Node.Name.MIMETYPE), atts.getValue(Node.Name.REPO));
                            } catch (SDXException e) {
                                throw new SAXException(e.getMessage(), e);
                            }
                        }
                    }
                }
            }
/*
            else if ( name.equals("table") )
            {
                if ( atts != null )
                {
                    tableName = atts.getValue("name");
                    if ( tableName != null ) tableValues = new Hashtable();
                }
            }
            else if ( name.equals("column") )
            {
                if ( atts != null )
                {
                    columnName = atts.getValue("name");
                    content = new StringBuffer();
                }
            }
*/
            else if (DOC_ROOT_ELEMENT_NAME.equals(name)) {
                try {
                    this.openSdxDocElems++;
                    if (this.openSdxDocElems == 1) {
                    	this.handleDocumentId(atts);
                        String repo = atts.getValue(Node.Name.REPO);
                        if (Utilities.checkString(repo))
                            setRepositoryForStorage(repo);
                        String boost = atts.getValue(Node.Name.BOOST);
                        if (Utilities.checkString(boost))
                        	setBoost(Float.parseFloat(boost));

                    }
                    if (this.openSdxDocElems > 1) {
                        if (this.openSdxDocElems == 2) {
                            XMLSerializer sBytes = new XMLSerializer();
                            sBytes.enableLogging(super.getLog());
                            this.subDocBytes = new ByteArrayOutputStream();

                            //if we have a sub doc we build the appropriate consumer
                            try {
                                sBytes.configure(new DefaultConfiguration("hack", ""));
								sBytes.setOutputStream(subDocBytes);
                                this.subDoc = (IndexableDocument) this.getClass().newInstance();
                                this.subDoc.enableLogging(super.getLog());
                            } catch (IOException e) {
								//unable to parse
								String[] args = new String[1];
								args[0] = getId();
								throw new SAXException(e.getMessage(), e);
							} catch (InstantiationException e) {
                                throw new SAXException(e.getMessage(), e);
                            } catch (IllegalAccessException e) {
                                throw new SAXException(e.getMessage(), e);
                            } catch (ConfigurationException e) {
                                throw new SAXException(e.getMessage(), e);
                            }
                            //TODO: the below line should be handled in the constructors
                            ((AbstractIndexableDocument) this.subDoc).resetFields();
                            this.setConsumer(sBytes);
                            if (this.xmlConsumer != null) this.xmlConsumer.startDocument();
                            //adding our namespace prefix mappings from the parent document
                            startNamespaceMappings();
                        }
                        if (this.subDoc != null)
                        	/*the subdoc must also have the fieldlist*/
                        	((AbstractIndexableDocument) this.subDoc)._xmlFieldList = _xmlFieldList;
                        	((AbstractIndexableDocument) this.subDoc)._xmlFieldOutput = _xmlFieldOutput;
                        	((AbstractIndexableDocument) this.subDoc)._xmlFieldTransformer = _xmlFieldTransformer;
                            this.subDoc.startElement(nsURI, name, qName, atts);
                    }


                } catch (SDXException e) {
                    throw new SAXException(e.getMessage(), e);
                }
            } else if (DOC_MSG_ELEMENT_NAME.equals(name))
                characterBuffer = new StringBuffer();
        } else {
            this.withinSdxElement = false;
            if (this.openSdxDocElems == 2 && this.xmlConsumer != null)
                this.xmlConsumer.startElement(nsURI, name, qName, atts);
            else if (this.subDoc != null)
                this.subDoc.startElement(nsURI, name, qName, atts);
            else if (withinXmlField) this._xmlFieldTransformer.startElement(nsURI, name, qName, atts);
        }

    }

    /*
   private void handleDocumentId(org.xml.sax.Attributes atts) throws SDXException {
        if (atts != null) {
            // default id from <sdx:document id="my_id"/>
            String docId = atts.getValue("id");
            // overriding <sdx:document id="my_id" overrideId="true"/> (example: set of document with random possible not unique id)
            if ("false".equalsIgnoreCase(atts.getValue("overrideId"))) docId = null;
            // generate-id <sdx:document id="my_id" generateId="true"/>
            if ("true".equalsIgnoreCase(atts.getValue("generateId"))) {
                docId = generateId(atts.getValue("generator"));
            }
            if (docId != null) {
                setId(docId);
            }

            //we verify the document to see if we have at least a valid unique id for our
            //internal purposes, if so we all is well, otherwise we throw an SDXException
            Utilities.checkDocument(super.getLog(), this);
        }

    }
    */

    protected void handleDocumentId(org.xml.sax.Attributes atts) throws SDXException {
        // already setted id
        String id = getId();
        // override allowed by default
        boolean overrideId = true;
        // don't force generateId by default
        boolean generateId = false;
        if (atts != null) {
            // get boolean attributes
            if (atts.getValue(Node.Name.OVERRIDE_ID) != null)
                if (atts.getValue(Node.Name.OVERRIDE_ID).equalsIgnoreCase("false"))
                    overrideId = false;
            if (atts.getValue(Node.Name.GENERATE_ID) != null)
                if (atts.getValue(Node.Name.GENERATE_ID).equalsIgnoreCase("true"))
                    generateId = true;
            // indexation id
            if (Utilities.checkString(atts.getValue(Node.Name.ID)) && overrideId)
                id = atts.getValue(Node.Name.ID);
        }
        // if no id already available, or generateId wanted ; and overrideId allowed
        if ((!Utilities.checkString(id) || generateId) && overrideId)
        //TODO: pass a class name if there is one provided for id generation, in this case we can't verify against a documentbase??
            id = generateId();
        setId(id);

        /*
        //at this point we have the atts object of the <sdx:document> element
        //TODOException?:what if this att's object is null, i assume we need an exception here?-rbp
        if (atts != null) {
            String docId;
            //indicates whether the existing document id should be overridden, default true
            boolean overrideId = false;
            //if the att does not exist we keep the true setting, if it does and is equal to false we change it
            if (atts.getValue("overrideId") != null && !atts.getValue("overrideId").equalsIgnoreCase("false"))
                overrideId = true;

            // If we don't override and don't already have an id
            // we still want an id...
//            if ( getId() == null ) overrideId = true;
            if (!Utilities.checkString(getId())) overrideId = true;

            //indicates whether an id should be generated , default true
            boolean generateId = true;
            //if the att does not exist we keep the true setting, if it does and is equal to false we change it
            if (atts.getValue("generateId") != null && atts.getValue("generateId").equalsIgnoreCase("false"))
                generateId = false;

            //we generate if we have the appropriate value, this is an override, so we must check that too
            if (generateId && overrideId) {
                //generating an id, passing in the value of the generator attribute if it exists
                docId = generateId();
                if (Utilities.checkString(docId)) setId(docId); //if we have a good doc idea after generation we set it
            } else if (overrideId) {
                //we override any existing id's
                //getting the value generated from the xslt tranformation, if it is not null or an empty string? is this ok
                if (Utilities.checkString(atts.getValue(ConfigurationNode.Name.ID))) {
                    docId = atts.getValue(ConfigurationNode.Name.ID);
                    //setting the id's
                    setId(docId);
                }
            }

            // we verify the document to see if we have at least a valid unique id for our
            // internal purposes, if so we all is well, otherwise we throw an SDXException
            Utilities.checkDocument(super.getLog(), this);
        }
            */

    }

    protected String generateId() throws SDXException {
        //TODO: handle a IDGenerator from the attributes
        String newId = "";

        //we get the specified generator by id
        IDGenerator idGen = this.idGenerator;

        if (idGen == null) {
            //log a  warn message saying if generator specified, could not retrieve the specified generator and continue
            SDXException sdxE = new SDXException(super.getLog(), SDXExceptionCode.ERROR_USING_DEFAULT_GENERATOR, null, null);
            LoggingUtils.logWarn(super.getLog(), sdxE.getMessage(), sdxE);
            //we get the SDX default id generator
            idGen = this.idGenerator = new DefaultIDGenerator();
        }

        //could not get a generator, we throw an exception
        if (idGen == null) throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_NO_ID_GENERATOR, null, null);

        //if we have a specified prefix and suffix we use it
        if (Utilities.checkString(this.idPrefix) || Utilities.checkString(this.idSuffix))
            newId = idGen.generate(this.idPrefix, this.idSuffix);
        else//otherwise we use the prefix and suffix already set in the id generator
            newId = idGen.generate();

        return newId;


    }

    public void characters(char[] ch, int start, int length) throws SAXException {
        if (this.openSdxDocElems > 1) {
            if (this.withinSdxElement && this.subDoc != null)
                this.subDoc.characters(ch, start, length);
            else if (!this.withinSdxElement) {
                if (this.openSdxDocElems == 2 && this.xmlConsumer != null)
                    this.xmlConsumer.characters(ch, start, length);
                else if (this.subDoc != null) this.subDoc.characters(ch, start, length);
            }
        } else{
        	if(withinXmlField)
        		_xmlFieldTransformer.characters(ch, start, length);
        	else if (characterBuffer != null)
        		characterBuffer.append(ch, start, length);
        }
    }

    /**Currently has no function*/
    public void comment(char[] chars, int i, int i1) throws SAXException {
    }

    /**Currently has no function*/
    public void endCDATA() throws SAXException {
    }

    /**Currently has no function*/
    public void endDTD() throws SAXException {
    }

    /**Currently has no function*/
    public void endDocument() throws SAXException {
    }

    /**Currently has no function*/
    public void endEntity(String s) throws SAXException {
    }

    /**Currently has no function*/
    public void endPrefixMapping(String s) throws SAXException {
        if (this.openSdxDocElems > 1) {
            if (this.withinSdxElement && this.subDoc != null)
                this.subDoc.endPrefixMapping(s);
            else if (!this.withinSdxElement) {
                if (this.openSdxDocElems == 2 && this.xmlConsumer != null)
                    this.xmlConsumer.endPrefixMapping(s);
                else if (this.subDoc != null) this.subDoc.endPrefixMapping(s);
            }
        }
        if (this.nsTable != null)
            this.nsTable.removeParameter(s);

    }

    /**Currently has no function*/
    public void ignorableWhitespace(char[] chars, int i, int i1) throws SAXException {
    }

    /**Currently has no function*/
    public void processingInstruction(String s, String s1) throws SAXException {
    }

    /**Currently has no function*/
    public void setDocumentLocator(Locator locator) {
    }

    /**Currently has no function*/
    public void skippedEntity(String s) throws SAXException {
    }

    /**Currently has no function*/
    public void startCDATA() throws SAXException {
    }

    /**Currently has no function*/
    public void startDTD(String s, String s1, String s2) throws SAXException {
    }

    /**Currently has no function*/
    public void startDocument() throws SAXException {
    	if(this._xmlFieldOutput == null){
    		this._xmlFieldOutput = new ByteArrayOutputStream();
    		_xmlFieldTransformer.setResult(new StreamResult(_xmlFieldOutput));
    	}
    }

    /**Currently has no function*/
    public void startEntity(String s) throws SAXException {
    }

    /**Currently has no function*/
    public void startPrefixMapping(String s, String s1) throws SAXException {
        if (this.nsTable == null) this.nsTable = new Parameters();
        if (!s1.equals(Framework.SDXNamespaceURI))//if it isnt our sdx namespace mapping we add it to the table
            this.nsTable.setParameter(s, s1);
        if (this.openSdxDocElems > 1) {
            if (this.withinSdxElement && this.subDoc != null)
                this.subDoc.startPrefixMapping(s, s1);
            else if (!this.withinSdxElement) {
                if (this.openSdxDocElems == 2 && this.xmlConsumer != null)
                    this.xmlConsumer.startPrefixMapping(s, s1);
                else if (this.subDoc != null) this.subDoc.startPrefixMapping(s, s1);
            }
        }
    }

    public void endElement(String nsURI, String name, String qName) throws SAXException {
        if (DOC_NAMESPACE.equals(nsURI)) {

            if (DOC_FIELD_ELEMENT_NAME.equals(name)) {

                if (this.openSdxDocElems > 1 && this.subDoc != null)
                    this.subDoc.endElement(nsURI, name, qName);

                else {
                	if(withinXmlField){
                    	_xmlFieldTransformer.endElement(nsURI, Node.Name.FRAGMENT, Node.Prefix.SDX + ":" + Node.Name.FRAGMENT);
                    	_xmlFieldTransformer.endPrefixMapping(Node.Prefix.SDX);
                    	_xmlFieldTransformer.endDocument();
                	}
                	if (properties != null && currentFieldName != null && characterBuffer != null && this._xmlFieldOutput != null &&
                		(characterBuffer.length() > 0 || this._xmlFieldOutput.size() > 0) ) {
	                    IndexableFieldProperty currentFieldProperty = new IndexableFieldProperty();
	                    currentFieldProperty.enableLogging(super.getLog());
	                    currentFieldProperty.setName(currentFieldName);
	                    if(withinXmlField){
	                    	try {
                                currentFieldProperty.addValue(_xmlFieldOutput.toString("UTF-8"));
                            } catch (UnsupportedEncodingException e) {
                                // Use System default
                                currentFieldProperty.addValue(_xmlFieldOutput.toString());
                            }
	                    	_xmlFieldOutput.reset();
	                    	withinXmlField = false;
	                    }
	                    else
	                    	currentFieldProperty.addValue(characterBuffer.toString());
	                    currentFieldProperty.setBoost(this._currentFieldBoost);
	                    properties.add(currentFieldProperty);
                	}
                }

                currentFieldName = null;
            } else if (DOC_ATTACHEDOC_ELEMENT_NAME.equals(name)) {

                if (this.openSdxDocElems > 1 && this.subDoc != null)
                    this.subDoc.endElement(nsURI, name, qName);
            }
//            else if ( name.equals("column") )
//            {
//                if ( tableName != null && tableValues != null )
//                    if ( content != null ) tableValues.put(columnName, content.toString());
//            }
//            else if ( name.equals("table") )
//            {
//                if ( tableName != null && tableValues != null )
//                {
//                    try
//                    {
//                        addTableValue(new TableValue(tableName, tableValues));
//                    }
//                    catch ( SDXException e ) {}
//                }
//                tableName = null;
//            }
            else if (DOC_ROOT_ELEMENT_NAME.equals(name)) {
                this.openSdxDocElems--;

                if (this.openSdxDocElems >= 1) {
                    if (this.subDoc != null)
                        this.subDoc.endElement(nsURI, name, qName);

                    if (this.openSdxDocElems == 1) {
                        //ending the namespace mappings
                        endNamespaceMappings();

                        if (this.xmlConsumer != null)
                            this.xmlConsumer.endDocument();
                        //adding the subdocument to the attached docs vector
                        try {
                            if (this.subDocBytes != null && this.subDoc != null) {
                                this.subDocBytes.close();
                                this.subDoc.setContent(this.subDocBytes.toByteArray());
                                if (this.subDocuments == null) this.subDocuments = new Vector();
                                this.subDocuments.add(subDoc);
                                this.subDoc = null;
                                this.subDocBytes = null;
                            }
                        } catch (IOException e) {
                            throw new SAXException(e.getMessage(), e);
                        }
                        //sub document finished resetting the consumer
                        this.setConsumer(null);
                    }

                }


            } else if (DOC_MSG_ELEMENT_NAME.equals(name)) {
                if (super.getLog() != null && characterBuffer != null && characterBuffer.length() > 0) {
                    String qualElemName = Framework.SDXNamespacePrefix + ":" + Node.Name.MESSAGE;
                    String bufferContents = characterBuffer.toString();
                    char[] bufferChars = bufferContents.toCharArray();
                    //loggerName.?docId?.sdx-message should appear in the logs
                    LoggingUtils.logInfo(super.getLog().getChildLogger(qualElemName), bufferContents);
                    if (this._msgHandler != null) {
                        this._msgHandler.startElement(Framework.SDXNamespaceURI, Node.Name.MESSAGE, qualElemName, new AttributesImpl());
                        this._msgHandler.characters(bufferChars, 0, bufferChars.length);
                        this._msgHandler.endElement(Framework.SDXNamespaceURI, Node.Name.MESSAGE, qualElemName);
                    }
                }
            }
        } else {
            if (this.openSdxDocElems == 2 && this.xmlConsumer != null)
                this.xmlConsumer.endElement(nsURI, name, qName);
            else if (this.subDoc != null)
                this.subDoc.endElement(nsURI, name, qName);
            else if(withinXmlField)
            	this._xmlFieldTransformer.endElement(nsURI, name, qName);
        }

        characterBuffer = new StringBuffer();
    }

    private void startNamespaceMappings() throws SAXException {

        String[] prefixes = null;
        if (this.nsTable != null) prefixes = this.nsTable.getNames();
        if (prefixes != null) {
            for (int i = 0; i < prefixes.length; i++) {
                String prefix = prefixes[i];
                String uri = null;
                try {
                    uri = this.nsTable.getParameter(prefix);
                } catch (ParameterException e) {
                    throw new SAXException(e.getMessage(), e);
                }
                if (uri != null) {
                    if (this.subDoc != null)
                        this.subDoc.startPrefixMapping(prefix, uri);
                    if (this.xmlConsumer != null)
                        this.xmlConsumer.startPrefixMapping(prefix, uri);
                }
            }
        }

    }

    private void endNamespaceMappings() throws SAXException {

        String[] prefixes = null;
        if (this.nsTable != null) prefixes = this.nsTable.getNames();
        if (prefixes != null) {
            for (int i = 0; i < prefixes.length; i++) {
                String prefix = prefixes[i];
                if (this.subDoc != null)
                    this.subDoc.endPrefixMapping(prefix);
                if (this.xmlConsumer != null)
                    this.xmlConsumer.endPrefixMapping(prefix);
            }
        }

    }


    /** The <code>XMLConsumer</code> receiving SAX events. */
    protected XMLConsumer xmlConsumer;

    /** The <code>ContentHandler</code> receiving SAX events. */
    protected ContentHandler contentHandler;

    /** The <code>LexicalHandler</code> receiving SAX events. */
    protected LexicalHandler lexicalHandler;

    /**
     * Set the <code>XMLConsumer</code> that will receive XML data.
     * <br>
     * This method will simply call <code>setContentHandler(consumer)</code>
     * and <code>setLexicalHandler(consumer)</code>.
     */
    public void setConsumer(XMLConsumer consumer) {
        //verifying the consumer
        this.xmlConsumer = consumer;
        this.contentHandler = consumer;
        this.lexicalHandler = consumer;
    }

    /**
     * Set the <code>ContentHandler</code> that will receive XML data.
     * <br>
     * Subclasses may retrieve this <code>ContentHandler</code> instance
     * accessing the protected <code>super.contentHandler</code> field.
     */
    public void setContentHandler(ContentHandler handler) {
        this.contentHandler = handler;
    }

    /**
     * Set the <code>LexicalHandler</code> that will receive XML data.
     * <br>
     * Subclasses may retrieve this <code>LexicalHandler</code> instance
     * accessing the protected <code>super.lexicalHandler</code> field.
     *
     * @exception IllegalStateException If the <code>LexicalHandler</code> or
     *                                  the <code>XMLConsumer</code> were
     *                                  already set.
     */
    public void setLexicalHandler(LexicalHandler handler) {
        this.lexicalHandler = handler;
    }

    /**
     * Returns field values.
     */
    public Enumeration getFieldValues() {
        if (properties != null && properties.elements() != null)
            return properties.elements();
        else
            return null;
    }

    /**
     *	Add an attached document to the list for this document.
     *
     *	@param	id			The document id.
     *  @param  baseURL     The base URL, usually the parent document's URL
     *	@param	url			URL of the attached document.
     *	@param	mimetype	Mime type of the document, can be null.
     * @param repoId
     * @throws SDXException
     */
    public void addAttachedDocument(String id, URL baseURL, String url, String mimetype, String repoId) throws SDXException {
        if (id != null) {
            if (attachedDocuments == null) attachedDocuments = new Vector();
            try {
                BinaryDocument doc = new BinaryDocument();
                //setting the super.getLog()
                doc.enableLogging(super.getLog());
                //setting the id
                /*
                String bDocId = this.getId();
                if (Utilities.checkString(bDocId) && Utilities.checkString(id))
                    bDocId = bDocId + "_" + id;
                else
                    bDocId = id;
                doc.setId(bDocId); */
                doc.setId(id);
                //setting the document mimetype
                doc.setMimeType(mimetype);
                //setting the storage repository if provided
                if (Utilities.checkString(repoId))
                    doc.setRepositoryForStorage(repoId);
                // set content
//                doc.setContent(new URL(url));
                doc.setContent(Utilities.attUrl(baseURL, url));
                //adding the document to the vector
                attachedDocuments.add(doc);
//                attachedDocuments.add(new BinaryDocument(id, new URL(url), mimetype));
            } catch (MalformedURLException e) {
                //here we should really be adding both the stack traces to one SDXException and then throw it
                //we have a bad absolute path, when trying to build from only "url", sorry
                String[] args1 = new String[2];
                args1[0] = getId();
                args1[1] = url;
                //throwing the second exception
                throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_BUILD_DOC_URL, args1, e);

            }
        }
    }

    /**
     *	Set's the list of attached documents for this document.
     *
     *	@param	list	The list of attached documents.
     */
    public void setAttachedDocuments(Vector list) {
        attachedDocuments = list;
    }

    /**
     *   Indicates wheter the documents must be refreshed.
     * @return boolean
     */
    public boolean updateAttachedDocuments() {
        return updateAttachedDocuments;
    }

    /**
     *	Indicates wheter the list of attached documents must be refreshed.
     *
     *	@param	updateAttachedDocuments	    A boolean indicator.
     */
    public void setUpdateAttachedDocuments(boolean updateAttachedDocuments) {
        this.updateAttachedDocuments = updateAttachedDocuments;
    }


    /**Retrieves an Enumeration of attached documents
     *
     * @return  An Enumeration of "BinaryDocument" objects
     */
    public Enumeration getAttachedDocuments() {
        if (attachedDocuments != null)
            return attachedDocuments.elements();
        else
            return null;
    }
    
    public int getAttachedDocumentsSize() {
    	if (attachedDocuments != null)
    		return attachedDocuments.size();
    	else
    		return 0;
    }

    /**Resets the objects we need to store indexation data
     * or creates them if they do not exist
     *
     */
    protected void resetFields() {
        properties = new Vector();
    }

    /**Reinits the Vector of attached documents*/
    public void resetAttachedDocuments() {
        attachedDocuments = new Vector();
    }


    /**Returns the transformed document object or
     * <code>null</null> if no transformed document
     * during the indexation pipeline
     */
    public IndexableDocument getTransformedDocument() {
        return this.transformedDoc;
    }

    protected void setUpTransformedDocument() throws SDXException {
        //giving the transformed document the original filename and id
        this.transformedDoc.setId(this.getId());
        if (Utilities.checkString(this.getPreferredFilename())) this.transformedDoc.setPreferredFilename(this.getPreferredFilename());
        this.transformedDoc.setRepositoryForStorage(this.getRepositoryForStorage());

    }

    /**Retrieves an Enumeration of sub(Indexable) documents
     *
     * @return  An Enumeration of "XMLDocuments" objects
     */
    public Enumeration getSubDocuments() {
        if (this.subDocuments != null)
            return this.subDocuments.elements();
        else
            return null;
    }
    public int getSubDocumentsSize() {
    	if (this.subDocuments != null)
    		return this.subDocuments.size();
    	else
    		return 0;
    }
/*
    protected IndexableDocument peekTopSubDoc(){
        IndexableDocument subDoc = null;
        if (this.subDocs.size() > 0){
            subDoc = (IndexableDocument)subDocs.peek();
        }
        return subDoc;
    }

    protected IndexableDocument popTopSubDoc(){
        IndexableDocument subDoc = null;
        if (this.subDocs.size() > 0){
            subDoc = (IndexableDocument)subDocs.pop();
        }
        return subDoc;
    }
    */

    public StoreHandler getStoreHandler() {
        return this.storeHandler;
    }

    protected StoreHandler storeHandler = new StoreHandler();

    public class StoreHandler {
        ArrayList docsStored = new ArrayList();

        public void addDoc(Document doc) throws SDXException {
            Utilities.checkDocument(null, doc);
            docsStored.add(doc);
        }

        public Document[] getDocs() {
            return (Document[]) docsStored.toArray(new Document[0]);
        }


    }

    public void setMessageHandler(ContentHandler handler) {
        this._msgHandler = handler;
    }

    /** Sets a boost factor for scoring (currently Lucene specific)
     *
     *
     * @see org.apache.lucene.document.Document#setBoost
     */
    public void setBoost(float boost) {
        this._boost = boost;
    }

    /** Gets a boost factor for scoring (currently Lucene specific)
     *
     *
     * @see org.apache.lucene.document.Document#getBoost
     */
    public float getBoost() {
        return _boost;
    }

	/**
	 * @see fr.gouv.culture.sdx.document.IndexableDocument#setFieldList(java.util.HashMap)
	 */
	public void setXMLFieldList(HashMap fieldList) {
		this._xmlFieldList = fieldList;

	}

	/**
	 * @see fr.gouv.culture.sdx.document.IndexableDocument#setXMLTransformer(javax.xml.transform.Transformer)
	 */
	public void setXMLTransformerHandler(TransformerHandler xmlFieldTransformer) {
		_xmlFieldTransformer = xmlFieldTransformer;
		// Make sure we work with UTF-8 streams
		_xmlFieldTransformer.getTransformer().setOutputProperty(OutputKeys.ENCODING, "UTF-8");
	}
}
