/*
Copyright (C) 2000-2010  Ministere de la culture et de la communication (France), AJLSM
See LICENCE file
 */

package fr.gouv.culture.sdx.application;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.Hashtable;

import javax.xml.parsers.ParserConfigurationException;

import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.configuration.DefaultConfiguration;
import org.apache.avalon.framework.configuration.DefaultConfigurationSerializer;
import org.apache.avalon.framework.context.ContextException;
import org.apache.avalon.framework.context.DefaultContext;
import org.apache.avalon.framework.logger.Logger;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.cocoon.Constants;
import org.apache.cocoon.ProcessingException;
import org.apache.excalibur.source.SourceValidity;
import org.apache.excalibur.xml.EntityResolver;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

import fr.gouv.culture.sdx.document.Document;
import fr.gouv.culture.sdx.document.XMLDocument;
import fr.gouv.culture.sdx.documentbase.DocumentBase;
import fr.gouv.culture.sdx.documentbase.IndexParameters;
import fr.gouv.culture.sdx.documentbase.LuceneDocumentBase;
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.pipeline.Transformation;
import fr.gouv.culture.sdx.repository.Repository;
import fr.gouv.culture.sdx.resolver.entity.SDXResolver;
import fr.gouv.culture.sdx.search.Searchable;
import fr.gouv.culture.sdx.search.lucene.FieldList;
import fr.gouv.culture.sdx.thesaurus.SDXThesaurus;
import fr.gouv.culture.sdx.thesaurus.Thesaurus;
import fr.gouv.culture.sdx.user.AnonymousUserInformation;
import fr.gouv.culture.sdx.user.Group;
import fr.gouv.culture.sdx.user.GroupInformationInserter;
import fr.gouv.culture.sdx.user.Identity;
import fr.gouv.culture.sdx.user.User;
import fr.gouv.culture.sdx.user.UserDatabase;
import fr.gouv.culture.sdx.user.UserInformation;
import fr.gouv.culture.sdx.utils.AbstractSdxObject;
import fr.gouv.culture.sdx.utils.Localizable;
import fr.gouv.culture.sdx.utils.SdxObject;
import fr.gouv.culture.sdx.utils.Utilities;
import fr.gouv.culture.sdx.utils.configuration.ConfigurationUtils;
import fr.gouv.culture.sdx.utils.constants.ContextKeys;
import fr.gouv.culture.sdx.utils.constants.Node;
import fr.gouv.culture.sdx.utils.database.Database;
import fr.gouv.culture.sdx.utils.io.IOWrapper;
import fr.gouv.culture.sdx.utils.logging.LoggingUtils;
import fr.gouv.culture.sdx.utils.rdbms.hsql.HSQLDB;
import fr.gouv.culture.sdx.utils.save.SaveParameters;
import fr.gouv.culture.sdx.utils.save.SaveParametersBuilder;
import fr.gouv.culture.sdx.utils.save.Saveable;
import fr.gouv.culture.sdx.utils.save.StringComparator;
import fr.gouv.culture.sdx.utils.zip.ZipWrapper;

/** An SDX application, from which searches can be done.
 *
 * <p>
 * This class represents an SDX 2 application. There can be many applications
 * within an SDX installation. The applications within the installation are referenced
 * and managed by the {@link fr.gouv.culture.sdx.framework.Framework}.
 * The aim of an application is to make documents available and searchable.
 * <p>
 * An application is created from an XML configuration file. This file and all other files
 * needed by the application must reside within one and only one directory hierarchy on the server.
 *
 * BTW, there is also a reference to the application in {TOMCAT_HOME}/webapps/sdx/WEB-INF/sdx/applications -pb
 *
 * This directory is called the base directory, a directory hierarchy, and must be directly within the SDX
 * installation directory.
 * <p>
 * All files created by SDX regarding the management of an application and it's users and
 * groups will be placed in subdirectories within the directory where the configuration
 * file is located. it could look like this (for an application whose id is 'sdxworld') :
 * <pre>
 *   -webapps
 *     -sdx
 *       -sdxworld
 *         -conf
 *           application.xconf
 *           -dbs ({@link fr.gouv.culture.sdx.documentbase databases} needed by SDX)
 *           -repos ({@link fr.gouv.culture.sdx.repository repositories} needed by SDX)
 *           -users
 *         index.xsp (or any valid 'welcome page')
 *         ...
 *         -xsl
 *           index.xsl
 *           ...
 *
 * </pre>
 * <p>
 * An application can be either public, private or restricted. A public application is
 * searchable from other applications. A private application is not searchable from other
 * applications. A restricted application may be searchable from other applications, if they
 * qualify. Qualification is done from hosts and public ids. This could change in the future.
 * <p>
 * An appplication has four identification properties :
 * <UL>
 * <LI>a {@link #getId id}, which should be unique amongst SDX applications anywhere as would be a Java package</LI>
 * <LI>a name, for humans</LI>
 * <LI>a description, also for humans</LI>
 * </UL>
 * <p>
 * Other than basic properties, an application manages {@link fr.gouv.culture.sdx.documentbase document bases},
 * {@link fr.gouv.culture.sdx.user.User users} and {@link fr.gouv.culture.sdx.user.Group groups}. It
 * keeps track of the objects using Database objects and a filesystem store.
 * <p>
 * An application can also be used to search document bases within other applications.
 * <p>
 * After creating an Application object, you must remember to :
 * 1) provide a super.getLog()
 * 2) configure the application
 * 3) initialize the application
 * The methods are {@link #enableLogging enableLogging()}, {@link #configure configure()} and {@link #init init()}.
 */
public class Application extends AbstractSdxObject implements Saveable{


	/************
     Class members
	 ************/

	/** The application's path. */
	private String path;
	/** The system dir */
	private String dataDir;
	/** The repositories owned by this application (i.e. application-level repositories). */
	private DefaultContext _repositories;
	/**The fieldList(s) defined at the application level*/
	private DefaultContext _fieldLists = null;
	/** The document bases owned by this application. */
	private Hashtable _documentBases;
	/** The default document base. */
	private DocumentBase _defaultDocumentBase = null;
	/** The user database. */
	private UserDatabase _userDatabase;
	/** The list of thesauri*/
	private Hashtable _thesauri = new Hashtable();
	/** The info relative to the users ; incrementally feeded as incoming users are identified. */
	private Hashtable _userInformations;
	/** The id of the default admin group. */
	private String _defaultAdminGroupId = null;
	/** The coniguration of the default admin group. */
	private Configuration _defaultAdminGroupConf = null;
	/** The id of the default admin user. */
	private String _defaultAdminUserId = null;
	/** The *fixed* id of the user document base for this application. */
	public static final String USER_DOCUMENT_BASE_ID = "sdxuserdb"; //TODO : move to document base ? -pb
	/** The *fixed* id of the user document base for this application. */
	public static final String USER_DATABASE_ID = "sdxuserdatabase"; //TODO : move to document base ? -pb
	/** Should this application use the internal SDX users database */
	private boolean _isUsingSDXUserDataBase = false;

	/***************************************
     Directory names to be provided to Lucene
	 ***************************************/

	/** The name for the directory that contains the application-level repositories. */
	private final String REPOSITORIES_DIR_NAME = "repos";
	/** The name for the directory that contains the document bases. */
	private final String DOCUMENTBASES_DIR_NAME = "dbs";
	/** The name for the directory that contains the user info. */
	private final String USERS_DIR_NAME = "users";
	/** The name for the directory that contains the users DB. */
	private final String USERS_DATABASE_DIR_NAME = "userDataBase";
	/** The name for the directory that contains the users document base. */
	private final String USERS_DOCUMENTBASE_DIR_NAME = "userDocBase";
	/**The name for the directory that contains the thesauri*/
	private final String THESAURI_DIR_NAME = "thesauri";

	/********************************************
    Directory names for configure the application
	 ********************************************/
	/** The directory name where application configuration files reside. */
	public static final String APPLICATION_CONFIGURATION_DIRECTORY = "conf";
	/** The configuration file name of an application. */
	public static final String APP_CONFIG_FILENAME = "application.xconf";

	/****************************************
     Directory names for dynamic class loading
	 ****************************************/

	/** Directory name for dynamic class loading of library files. */
	private final String LIB_DIR_NAME = "lib" + File.separator;
	/** Directory name for dynamic loading of class files. */
	private final String CLASSES_DIR_NAME = "classes" + File.separator;

	/**********************************************************************
     Attribute names for the configuration element in the configuration file
	 **********************************************************************/

	private final String ATTRIBUTE_SYSTEM_DIR = "dataDir";
	private final String ATTRIBUTE_SESSION_OBJECT_LIMIT = "sessionObjectLimit";
	private final int SESSION_OBJECT_LIMIT_DEFAULT = 5;
	private final String ATTRIBUTE_IS_DATADIR_SHARED = "is-datadir-shared";
	private final String ATTRIBUTE_INDEXATION_LOGGING_LEVEL = "indexation-logging-level";

	/*************************************************************************
     Child element names of the configuration element in the configuration file
	 *************************************************************************/

	/** The element used to define a user document base. */
	public static final String ELEMENT_NAME_USER_DOCUMENT_BASE = "userDocumentBase"; //TODO : move it to AbstractDocumentBase.java ? -pb
	/** The element used to define the default group for admin. */
	private String ELEMENT_NAME_ADMIN = "admin"; //TODO : use a dedicated configurable class here ? -pb
	/** The required attribute used for defining the id of the admin group. */
	private String ATTRIBUTE_GROUP_ID = "groupId"; //TODO : use a dedicated configurable class here ? -pb
	/** The required attribute used for defining the default user in the admin group. */
	private String ATTRIBUTE_USER_ID = "userId"; //TODO : use a dedicated configurable class here ? -pb
	/** The implied attribute used for defining the default user's password in the admin group. */
	private String ATTRIBUTE_USER_PASSWORD = "userPassword"; //TODO : use a dedicated configurable class here ? -pb
	/** The element used to define the catalogs. */
	private final String ELEMENT_NAME_CATALOGS = "catalogs";
	/** The element used to define a catalog. */
	private final String ELEMENT_NAME_CATALOG = "catalog"; //TODO : move to a catalog class ? -pb
	/** The required attribute used for defining the source of a catalog. */
	private final String ATTRIBUTE_CATALOG_SRC = "src"; //TODO : use a dedicated configurable class here ? -pb

	/** String representation for a default document base type in the config file. */
	private final String DEFAULT_DOCUMENTBASE_TYPE = "lucene"; //TODO : move to AbstractDocumentBase ? -pb
	/** String representation for a default thesaurus type in the config file. */
	private final String DEFAULT_THESAURUS_TYPE = "lucene"; //TODO : move to AbstractDocumentBase ? or an abstract thesaurus ? -pb

	/**The name for the directory that contains the databases*/
	public String DATABASES_DIR_NAME = "databases";
	/**The number of results and terms objects that will be stored in session before
	 * being subject our first-in first-out removal strategy*/
	protected int sessionObjectLimit = 5;	//defaulted
	public static final String CLASS_NAME_SUFFIX = "Application";
	/*Should this application log indexation process*/
	private int indexationLoggingLevel;


	/** Builds an application that must be configured afterwards. */
	public Application() {
	}


	/** Sets the configuration options to build the application.
	 *
	 * @param configuration The configuration object, previously populated in
	 * {@link fr.gouv.culture.sdx.framework.FrameworkImpl},
	 * that will allow the application to configure itself.
	 *
	 * @see #documented_application.xconf we should link to this in the future when we have better documentation capabilities
	 * @throws ConfigurationException
	 */
	public void configure(Configuration configuration) throws ConfigurationException {

		super.configure(configuration);

		DefaultContext l_context = super.getContext();
		//getting the path of the application provided by the FrameworkImpl

		try {
			path = (String) l_context.get(ContextKeys.SDX.Application.DIRECTORY_NAME);
			// TODO: enforce some constraints on the format of this name.
			// we will see
		} catch (ContextException e) {
			throw new ConfigurationException(e.getMessage(), e);
		}

		// setting the id in the application's properties
		l_context.put(ContextKeys.SDX.Application.ID, super.getId());

		// xml:lang attribute is now required for an application
		ConfigurationUtils.checkConfAttributeValue(Localizable.ConfigurationNode.XML_LANG, getXmlLang(), configuration.getLocation());

		// creating paths for the application's configuration directories
		// using the FrameworkImpl path context
		final String appConfPath = Utilities.getStringFromContext(ContextKeys.SDX.Application.CONFIGURATION_DIRECTORY_PATH, l_context);

		// attribut datadir
		String _attSysDir=null;
		try {
			_attSysDir = configuration.getAttribute(this.ATTRIBUTE_SYSTEM_DIR);
		} catch (ConfigurationException ce){
			// dataDir is optional
		}
		if( Utilities.checkString(_attSysDir) ) {
			try {
				dataDir = Utilities.resolveFile(null, configuration.getLocation(), getContext(), _attSysDir, true).getAbsolutePath();
				Utilities.checkDirectory( dataDir, getLogger() );
			} catch(Exception e){
				dataDir = null;
				throw new ConfigurationException(e.getMessage(), e);
			}
		}
		
		// is datadir shared
		boolean _isDatadirShared=configuration.getAttributeAsBoolean(this.ATTRIBUTE_IS_DATADIR_SHARED, false);
		
		indexationLoggingLevel = configuration.getAttributeAsInteger(this.ATTRIBUTE_INDEXATION_LOGGING_LEVEL, 0);
		
		String basepath = appConfPath;
		if ( Utilities.checkString(dataDir) )
				basepath = dataDir;
		// dataDir = appConfPath;
		if ( Utilities.checkString(basepath) && !basepath.endsWith(File.separator) )
				basepath += File.separator;
		final String dbsDirPath = basepath + DOCUMENTBASES_DIR_NAME + File.separator;
		final String reposDirPath = basepath + REPOSITORIES_DIR_NAME + File.separator;
		final String usersDirPath = basepath + USERS_DIR_NAME + File.separator;
		final String usersDocbaseDirPath = usersDirPath + USERS_DOCUMENTBASE_DIR_NAME + File.separator;
		final String thesauriDbsPath = basepath + THESAURI_DIR_NAME + File.separator;
		final String sdxDatabaseDir = basepath + DATABASES_DIR_NAME + File.separator;
		final String backup_location = basepath + "backup" + File.separator;
		// setting them in application's properties
		l_context.put(ContextKeys.SDX.Application.DATADIR_DIRECTORY_PATH, ((dataDir==null)?"":dataDir) );
		l_context.put(ContextKeys.SDX.Application.DOCUMENTBASES_DIRECTORY_PATH, dbsDirPath);
		l_context.put(ContextKeys.SDX.Application.REPOSITORIES_DIRECTORY_PATH, reposDirPath);
		l_context.put(ContextKeys.SDX.Application.USERS_DIRECTORY_PATH, usersDirPath);
		l_context.put(ContextKeys.SDX.Application.USERS_DOCUMENTBASE_DIRECTORY_PATH, usersDocbaseDirPath);
		l_context.put(ContextKeys.SDX.Application.THESAURI_DIRECTORY_PATH, thesauriDbsPath);
		l_context.put(ContextKeys.SDX.Application.DATABASE_DIRECTORY_PATH, sdxDatabaseDir);
		l_context.put(ContextKeys.SDX.Application.BACKUP_DIRECTORY_PATH, backup_location);
		l_context.put(ContextKeys.SDX.Application.IS_DATADIR_SHARED, new Boolean( _isDatadirShared ) );
		l_context.put(ContextKeys.SDX.Application.INDEXATION_LOGGING_LEVEL, new Integer( indexationLoggingLevel ) );
		try {
			l_context.put(ContextKeys.SDX.Application.HSQL_DATABASE_OBJECT, new HSQLDB());
		} catch (ClassNotFoundException e) {
			throw new ConfigurationException(e.getMessage(), e);
		}

		// do dynamic class loading
		loadClasses(appConfPath);
		configureDefaultDatabaseType(configuration);
		configureSessionObjectLimit(configuration);

		// global configuration objects
		configureRepositories(configuration);
		configureFieldLists(configuration);

		// configuring document base for user/group managements
		configureUserDocumentBase(configuration);

		// retaining the default admin group configuration if it exists
		// TODO : comment using XML elements when everything is in place -pb
		_defaultAdminGroupConf = configuration.getChild(ELEMENT_NAME_ADMIN, false);

		// getting entity catalogs if specified
		configureEntityCatalogs(configuration);

		// building and configuring the configuring the document bases
		configureDocumentBases(configuration);

		// configuring any thesauri
		configureThesauri(configuration);
	}


	/** Configures the entity catalogs of the application if needed.
	 * At this point, we could have a <sdx:catalogs> element from the configuration file
	 * wich contains <sdx:catalog> sub-elements.
	 *
	 * @param configuration
	 * @throws ConfigurationException
	 */
	protected void configureEntityCatalogs(Configuration configuration) throws ConfigurationException {
		// TODO : comment using XML elements when everything is in place -pb
		Configuration catalogs = configuration.getChild(ELEMENT_NAME_CATALOGS, false);
		if (catalogs != null) {
			Configuration[] catalogConfList = catalogs.getChildren(ELEMENT_NAME_CATALOG);
			if (catalogConfList != null && catalogConfList.length > 0) {
				try {
					// adding catalog files to the sdx entity resolver
					addEntityCatalogs(catalogConfList);
				} catch (SDXException e) {
					throw new ConfigurationException(e.getMessage(), e);
				}
			}
		}

	}

	/**
	 * Configures the fields lists of the application.
	 * At this point, we could have a <sdx:fieldLists> element containing
	 * a list of fields lists identified by their @id.
	 * These fields lists could be used by the application's documents bases.
	 *
	 * @param	configuration
	 * @throws	ConfigurationException
	 * */
	protected void configureFieldLists(Configuration configuration) throws ConfigurationException {
//		adding the fields lists to this documentbase, must be done before documentBases
//      are configured as global fields lists need to be provided to documentbases
		String elemNameFieldLists = DocumentBase.ConfigurationNode.FIELD_LIST + "s";
		Configuration[] fieldListConfList = new Configuration[configuration.getChild(elemNameFieldLists).getChildren(DocumentBase.ConfigurationNode.FIELD_LIST).length];
		// getting an array of configuration objects from each of the <sdx:repository> subElements of <sdx:repositories> element
		fieldListConfList = configuration.getChild(elemNameFieldLists).getChildren(DocumentBase.ConfigurationNode.FIELD_LIST);
		// testing if we have something
		if (fieldListConfList == null || fieldListConfList.length == 0) {
			String[] args = new String[1];
			args[0] = super.getId();
			SDXException sdxE = new SDXException(SDXExceptionCode.ERROR_NO_APP_FIELD_LISTS_DEFINED, args);
			LoggingUtils.logWarn(getLogger(), sdxE.getMessage());
			return;
		}

		// ensuring we have a hashtable to populate
		if (this._fieldLists == null) this._fieldLists = new DefaultContext();
		// iterating over the list and creating the repositories
		for (int i = 0; i < fieldListConfList.length; i++) {
			FieldList l_fieldList = null;
			try {

				// creating the repository
				l_fieldList = ConfigurationUtils.configureFieldList(getLogger(), getServiceManager(), Utilities.createNewReadOnlyContext(getContext()), null, fieldListConfList[i]);
				// TODO: all fieldLists are currently lucene specific this should be changed when another search implementation is added
				l_fieldList = ConfigurationUtils.configureLuceneFieldList(getLogger(), getContext(), l_fieldList);
				String id = l_fieldList.getId();
				ConfigurationUtils.checkConfAttributeValue("id", id, fieldListConfList[i].getLocation());
				// populating the hashtable
				Utilities.isObjectUnique(this._fieldLists, null, l_fieldList);
				this._fieldLists.put(l_fieldList.getId(), l_fieldList);

			} catch (ConfigurationException e) {
				LoggingUtils.logException(getLogger(), e);
				// we don't want all fieldList configurations to fail, so we won't throw this farther out
			} catch (SDXException e) {
				LoggingUtils.logException(getLogger(), e);
				// we don't want all fieldList configurations to fail, so we won't throw this farther out
			}

		}

		// adding the list of fieldlists to the properties object
		super.getContext().put(ContextKeys.SDX.Application.FIELD_LISTS, Utilities.createNewReadOnlyContext(_fieldLists));
	}

	/** Configures the SDX users document base if needed.
	 * At this point, we could have a <sdx:userDocumentBase> element.
	 *
	 * @param configuration
	 * @throws ConfigurationException
	 */
	protected void configureUserDocumentBase(Configuration configuration) throws ConfigurationException {

		// getting the <sdx:userDocumentBase> element from application.xconf
		Configuration userDbConf = configuration.getChild(ELEMENT_NAME_USER_DOCUMENT_BASE, false);
		// testing if we have something
		if (userDbConf == null) {
			// at this point, we have no user document base configuration to work with
			String[] args = new String[2];
			args[0] = configuration.getLocation();
			args[1] = configuration.getLocation();
			SDXException sdxE = new SDXException(SDXExceptionCode.ERROR_NO_USER_DOCUMENTBASE_CONFIG, args);
			// log a warn message here indicating that we dont have the configuration data in the application.xconf
			LoggingUtils.logWarn(getLogger(), sdxE.getMessage(), sdxE);
			return;
		}

		_isUsingSDXUserDataBase = true;	// flag to indicate that this application use the SDX user database

		// ensuring we have a hashtable in which to store the docBases
		if (_documentBases == null) _documentBases = new Hashtable();

		try {
			// creating a DB for user management
			// for now, it is a lucene document base
			LuceneDocumentBase userDb = new LuceneDocumentBase();
			// setting the DB's super.getLog()
			DefaultContext l_context = super.getContext();
			l_context = new DefaultContext(l_context);
			l_context.put(ContextKeys.SDX.Application.DOCUMENTBASES_DIRECTORY_PATH, Utilities.getStringFromContext(ContextKeys.SDX.Application.USERS_DOCUMENTBASE_DIRECTORY_PATH, l_context));
			userDb = (LuceneDocumentBase) Utilities.setUpSdxObject(userDb, super.getLog(), Utilities.createNewReadOnlyContext(l_context), super.getServiceManager());
			// configuring the DB
			userDb.setId(USER_DOCUMENT_BASE_ID);
			userDb.configure(userDbConf);
			// initializing the DB
			userDb.init();
			// adding the DB to the hashtable
			Utilities.isObjectUnique(this._documentBases, USER_DOCUMENT_BASE_ID, userDb);
			_documentBases.put(USER_DOCUMENT_BASE_ID, userDb);
		} catch (SDXException e) {
			throw new ConfigurationException(e.getMessage(), e.fillInStackTrace());
		}
	}

	/** Configures the maximum number of objects should be store in session.
	 *  At this point, we could have an attribute sdx:application/@sessionObjectLimit
	 *
	 *  @param	configuration
	 * */
	protected void configureSessionObjectLimit(Configuration configuration) {
		this.sessionObjectLimit = configuration.getAttributeAsInteger(ATTRIBUTE_SESSION_OBJECT_LIMIT, SESSION_OBJECT_LIMIT_DEFAULT);
	}

	/**Get's the global default database type
	 * for use in all database backed objects.
	 * <p>At this point, we cound have an element:
	 * <pre>
	 * &lt;sdx:database 
	 *         type="{MYSQL|POSTGRESQL|HSQL|ORACLE}" 
	 *         dsi="{jdbc connection id}"/>
	 * </pre>
	 * <ul>
	 * <li>By default, <code>type</code> is HSQL, even if this element does not
	 * exist in configuration file.</li>
	 * <li>The value of <code>dsi</code> is usually the value found in 
	 * the WEB-INF/cocoon.xconf file in the <code>cocoon/datasources/jdbc/@name</code>.
	 * </li> 
	 * </ul>
	 * </p> 
	 * @param configuration The application level configuration
	 */
	private void configureDefaultDatabaseType(Configuration configuration) {
		Configuration defaultDatabaseConf = configuration.getChild(Utilities.getElementName(Database.CLASS_NAME_SUFFIX), false);
		if (defaultDatabaseConf != null)
			super.getContext().put(ContextKeys.SDX.Application.DEFAULT_DATABASE_CONFIGURATION_OBJECT, defaultDatabaseConf);
	}

	/** Configures the repositories of the application if needed.
	 * These repositories could be use by the document bases.
	 * At this point, we could have a <sdx:repositories> element containing
	 * a list of repositories identified by their @id.
	 *
	 * @param configuration
	 */
	private void configureRepositories(Configuration configuration) {

		/* adding the repositories to this documentbase, must be done before documentBases
		 * are configured as global repos need to be provided to documentbases */
		String elementName = Utilities.getElementName(Repository.CLASS_NAME_SUFFIX);
		Configuration[] repoConfList = new Configuration[configuration.getChild(Repository.ConfigurationNode.REPOSITORIES).getChildren(elementName).length];
		// getting an array of configuration objects from each of the <sdx:repository> subElements of <sdx:repositories> element
		repoConfList = configuration.getChild(Repository.ConfigurationNode.REPOSITORIES, true).getChildren(elementName);
		// testing if we have something
		if (repoConfList == null || repoConfList.length == 0) {
			String[] args = new String[1];
			args[0] = super.getId();
			SDXException sdxE = new SDXException(SDXExceptionCode.ERROR_NO_APP_REPOS_DEFINED, args);
			LoggingUtils.logWarn(getLogger(), sdxE.getMessage());
			return;
		}

		// ensuring we have a hashtable to populate
		if (_repositories == null) _repositories = new DefaultContext();

		// iterating over the list and creating the repositories
		for (int i = 0; i < repoConfList.length; i++) {
			Repository repo = null;
			try {

				// creating the repository
				repo = ConfigurationUtils.createRepository(getLogger(), super.getContext(), super.getServiceManager(), repoConfList[i]);
				// populating the hashtable
				Utilities.isObjectUnique(this._repositories, null, repo);
				_repositories.put(repo.getId(), repo);

			} catch (SDXException e) {
				LoggingUtils.logException(getLogger(), e);
				// we don't want all repositories configurations to fail, so we won't throw this farther out
			} catch (ConfigurationException e) {
				LoggingUtils.logException(getLogger(), e);
				// we don't want all repository configurations to fail, so we won't throw this farther out
			}
		}

		// adding the list of repositories to the properties object
		super.getContext().put(ContextKeys.SDX.Application.REPOSITORIES, Utilities.createNewReadOnlyContext(_repositories));
	}

	/** Configures application's documents bases.
	 * At this point, we should have a <sdx:documentBases> element containing
	 * a list of documents bases
	 * @param configuration
	 * @throws ConfigurationException
	 */
	private void configureDocumentBases(Configuration configuration) throws ConfigurationException {
		DefaultContext l_context = super.getContext();
		// intializing the array
		String documentBaseElementName = Utilities.getElementName(DocumentBase.CLASS_NAME_SUFFIX);
		Configuration[] dbConfList = new Configuration[configuration.getChild(documentBaseElementName + "s").getChildren(documentBaseElementName).length];
		// getting an array of configuration objects from each of the <sdx:documentBase> subElements of <sdx:documentBases> element
		dbConfList = configuration.getChild(documentBaseElementName + "s").getChildren(documentBaseElementName);
		// testing if we have something
		if (dbConfList == null || dbConfList.length == 0) {
			String[] args = new String[1];
			// getting the location of the configuration file
			try {
				args[0] = ((Configuration) l_context.get(ContextKeys.SDX.Application.CONFIGURATION_OBJECT)).getLocation();
			} catch (ContextException e) {
				args[0] = "no file location available";
			}
			SDXException sdxE = new SDXException(getLogger(), SDXExceptionCode.ERROR_NO_DOCUMENTBASES_IN_CONFIG, args, null);
			throw new ConfigurationException(sdxE.getMessage(), sdxE);
		}

		// ensuring we have a hashtable in which to store the docBases
		if (_documentBases == null) _documentBases = new Hashtable();

		// a pointer to the first document base, for defaults
		DocumentBase firstDocumentBase = null;
		// iterating over the list of configuration objects to create the DocumentBase objects
		for (int i = 0; i < dbConfList.length; i++) {
			try {
				// reading the document base type attribute, if not we use the default, currently "lucene"
				String dbType = dbConfList[i].getAttribute(SdxObject.ConfigurationNode.TYPE, DEFAULT_DOCUMENTBASE_TYPE);
				// building a new instance of a DB object
				Object obj = Utilities.getObjectForClassName(getLog(), dbType, DocumentBase.PACKAGE_QUALNAME, dbType, DocumentBase.CLASS_NAME_SUFFIX);

				Class l_objClass = obj.getClass();
				// testing to see if the object implements the fr.gouv.culture.sdx.documentbase.DocumentBase Interface
				if (!(obj instanceof DocumentBase)) {
					// the object doesn't implement our interface
					String[] args = new String[3];
					args[0] = DocumentBase.CLASS_NAME_SUFFIX;
					if (l_objClass != null)
						args[1] = l_objClass.getName();
					args[2] = dbType;
					throw new SDXException(getLogger(), SDXExceptionCode.ERROR_CLASS_NOT_INSTANCEOF_SDX_INTERFACE, args, null);
				}
				// the object does implement our interface
				// casting it into a DocumentBase object
				DocumentBase db = (DocumentBase) obj;
				db = (DocumentBase) super.setUpSdxObject(db);
				// configuring the DB
				db.configure(dbConfList[i]);
				// initializing the DB
				db.init();
				// adding the DB to the hashtable
				Utilities.isObjectUnique(this._documentBases, null, db);
				_documentBases.put(db.getId(), db);
				// setting the default document base for this application
				// TODO : not compliant with SDX 1 behaviour ; first default one out of many was assumed -pb
				if (db.isDefault()) _defaultDocumentBase = db;
				// assigning a value to the pointer
				if (i == 0) firstDocumentBase = db;
			} catch (ConfigurationException e) {
				LoggingUtils.logException(getLogger(), e);
				// we don't want all document base configurations to fail so, we won't throw this farther out
			} catch (SDXException e) {
				// when create, exception should have been logged
				// we don't want all document base configurations to fail so, we won't throw this farther out
			}


			// if there isn't a default document base we set the default to the first one
			if (_defaultDocumentBase == null) _defaultDocumentBase = firstDocumentBase;
		}

	}

	private void addEntityCatalogs(Configuration[] catalogConfList) throws SDXException, ConfigurationException {
		SDXResolver resolver = null;
		String src = "";
		File srcFile = null;
		ServiceManager l_manager = super.getServiceManager();
		try {
			// getting our entity resolver
			Object c = l_manager.lookup(EntityResolver.ROLE);
			if (c != null && c instanceof SDXResolver)
				resolver = (SDXResolver) c;

			// verifying we have a resolver
			if (resolver == null) {
				SDXException sdxE = new SDXException(null, SDXExceptionCode.ERROR_ACQUIRE_RESOLVER, null, null);
				String[] args = new String[2];
				args[0] = src;
				args[1] = sdxE.getMessage();
				throw new SDXException(getLogger(), SDXExceptionCode.ERROR_ENTITY_CATALOG_ADD, args, sdxE);
			}
			// adding catalogs
			for (int i = 0; i < catalogConfList.length; i++) {
				Configuration conf = catalogConfList[i];
				if (conf != null) {
					// getting the file path
					src = conf.getAttribute(ATTRIBUTE_CATALOG_SRC);
					// verifying we have a good attribute
					ConfigurationUtils.checkConfAttributeValue(ATTRIBUTE_CATALOG_SRC, src, conf.getLocation());
					// creating a file from the path
					srcFile = Utilities.resolveFile(null, conf.getLocation(), getContext(), src, false);
					// adding the file to the entity resolver
					if (srcFile != null) resolver.addCatalog(srcFile.toURI().toURL());
				}
			}
		} catch (ServiceException e) {
			throw new SDXException(getLogger(), SDXExceptionCode.ERROR_ACQUIRE_RESOLVER, null, e);
		} catch (MalformedURLException e) {
			String[] args = new String[2];
			args[0] = src;
			args[1] = e.getMessage();
			throw new SDXException(getLogger(), SDXExceptionCode.ERROR_ENTITY_CATALOG_ADD, args, e);
		} finally {
			if (resolver != null) l_manager.release(resolver);
		}
	}

	/** Configures thesaurus of the application if needed.
	 * At this point, we could have a <sdx:thesauri> element containing a list of thesauri
	 * @param configuration
	 */
	private void configureThesauri(Configuration configuration) {
		String elementName = Utilities.getElementName(SDXThesaurus.CLASS_NAME_SUFFIX);
		//intializing the array
		Configuration[] thConfList = new Configuration[configuration.getChild(SDXThesaurus.ConfigurationNode.THESAURI, true).getChildren(elementName).length];
		// getting an array of configuration objects from each of the <sdx:thesaurus> subElements of <sdx:thesauri> element
		thConfList = configuration.getChild(SDXThesaurus.ConfigurationNode.THESAURI, true).getChildren(elementName);
		// a pointer to the first thesaurus, for defaults
		// Thesaurus firstThesaurus = null;
		// iterating over the list of configuration objects to create the thesauri objects
		for (int i = 0; i < thConfList.length; i++) {
			try {
				// reading the thesaurus type attribute, if not we use the default, currently "lucene"
				String thType = thConfList[i].getAttribute(SdxObject.ConfigurationNode.TYPE, DEFAULT_THESAURUS_TYPE);
				// building the fully qualified class name based on the type
				Object obj = Utilities.getObjectForClassName(getLog(), thType, SDXThesaurus.PACKAGE_QUALNAME, thType, SDXThesaurus.CLASS_NAME_SUFFIX);

				Class l_objClass = obj.getClass();
				// testing to see if the object implements the fr.gouv.culture.sdx.thesaurus.Thesaurus Interface
				if (!(obj instanceof Thesaurus)) {
					// the object doesn't implement our interface
					String[] args = new String[3];
					args[0] = SDXThesaurus.CLASS_NAME_SUFFIX;
					if (l_objClass != null)
						args[1] = l_objClass.getName();
					args[2] = thType;
					throw new SDXException(getLogger(), SDXExceptionCode.ERROR_CLASS_NOT_INSTANCEOF_SDX_INTERFACE, args, null);
				}
				// the object does implement our interface
				// casting it into a Thesaurus object
				SDXThesaurus th = (SDXThesaurus) obj;
				// setting the DB's super.getLog()
				th = (SDXThesaurus) super.setUpSdxObject(th, thConfList[i]);
				// initializing the thesaurus
				th.init();
				// adding the thesaurus to the hashtable
				Utilities.isObjectUnique(this._thesauri, null, th);
				_thesauri.put(th.getId(), th);
			} catch (ConfigurationException e) {
				String[] args = new String[2];
				args[0] = thConfList[i].getLocation();
				args[1] = e.getMessage();
				new SDXException(getLogger(), SDXExceptionCode.ERROR_CONFIGURE_THESAURUS, args, e);
				// we don't want all thesaurus configurations to fail so, we won't throw this farther out
			} catch (SDXException e) {
				// when create, exception should have been logged
				// we don't want all document base configurations to fail so, we won't throw this farther out
				// if there isn't a default document base we set the default to the first one
				// if (defaultThesaurus == null) defaultThesaurus = firstThesaurus;
			}


		}
	}

	/** Initializes the application and makes the necessary data structures available.
	 *
	 * @throws SDXException
	 */
	public void init() throws SDXException, ConfigurationException, ServiceException, ContextException {

		// testing the directory for the path for the base directory for the document bases
		// to ensure it is available and we have access to it
		// TODO: controler l'acces au repertoire systeme [MP]
		Utilities.checkDirectory(Utilities.getStringFromContext(ContextKeys.SDX.Application.DOCUMENTBASES_DIRECTORY_PATH, super.getContext()), getLogger());

		// Configure SDX user database if needed
		if ( _isUsingSDXUserDataBase ) {
			// creating the user database
			_userDatabase = new UserDatabase(USER_DATABASE_ID);
			// setting the super.getLog()
			_userDatabase = (UserDatabase) super.setUpSdxObject(_userDatabase);
			// hack configuration TODO: fix me
			_userDatabase.configure(new DefaultConfiguration("hack", ""));
			// initializing the userDatabase
			_userDatabase.init();
			// verifying the default admin group
			verifyDefaultAdminGroup();
		}
	}

	// TODO: javadoc [MP]
	private void loadClasses(String appConfPath) throws ConfigurationException {
		String libPath = appConfPath + LIB_DIR_NAME;
		File libDir = new File(libPath);
		String classesPath = appConfPath + CLASSES_DIR_NAME;
		File classesDir = new File(classesPath);
		String classpath = "";
		DefaultContext context = (DefaultContext) super.getContext();
		try {
			if (libDir.exists() || classesDir.exists()) {
				classpath = (String) context.get(Constants.CONTEXT_CLASSPATH);
				if (!classpath.endsWith(File.pathSeparator))
					classpath += File.pathSeparator;
				if (Utilities.checkString(classpath)) {
					if (libDir.exists()) {
						File[] libs = libDir.listFiles();
						for (int i = 0; i < libs.length; i++)

							classpath += libs[i].getAbsolutePath() + File.pathSeparator;
					}

					if (classesDir.exists())
						classpath += classesDir.getAbsolutePath() + File.pathSeparator;
				}
				context.put(Constants.CONTEXT_CLASSPATH, classpath);
			}

		} catch (ContextException e) {
			throw new ConfigurationException(e.getMessage(), e);
		}
	}

	/**
	 * Gets the application's super.getLog().
	 *
	 * @return  The super.getLog().
	 */
	public Logger getLogger() {
		return super.getLog();
	}

	/** Gets the name of the subdirectory in which this application's data resides.
	 * @return  The path. */
	public String getPath() {
		return path;
	}

	/**
	 * Gets the path of the directory wich contains system datas (ie,
	 * databases, Lucene indexes, SDX user database, SDX thesaurus, etc.)
	 * By default, it's the same path as the conf file path. This can be change
	 * with the sdx:application/@datadir attribute (cf. application.xconf)
	 * @return	The path to the datadir
	 */
	public String getDataDirPath() {
		return dataDir;
	}

	/** Gets a repository in this document base.
	 *
	 * @param id The repository's id, null for getting default repository
	 * @return The repository object
	 */
	public Repository getRepository(String id) throws SDXException {
		if (!Utilities.checkString(id))
			return null;
		else {
			Repository repo = (Repository) Utilities.getObjectFromContext(id, _repositories);
			if (repo == null) {
				String[] args = new String[2];
				args[0] = id;
				args[1] = this.getId();
				throw new SDXException(getLogger(), SDXExceptionCode.ERROR_UNKNOWN_REPOSITORY, args, null);
			} else
				return repo;
		}
	}

	/** Gets a DocumentBase in this application.
	 *
	 * @param dbId	The documentBase's id.
	 * @return	The documentBase object.
	 * @throws SDXException
	 */
	public DocumentBase getDocumentBase(String dbId) throws SDXException {
		if (!Utilities.checkString(dbId)) return null;
		DocumentBase db = null;
		db = (DocumentBase) _documentBases.get(dbId);
		if (db == null) {
			String[] args = new String[2];
			args[0] = dbId;
			args[1] = this.getId();
			throw new SDXException(getLogger(), SDXExceptionCode.ERROR_UNKNOWN_DOCUMENT_BASE, args, null);
		} else
			return db;
	}

	/** Gets an enumeration of DocumentBase ids in this application.
	 *  used to display information and open functionality
	 * for this application.
	 *
	 * @return	The Enumeration of documentBase object.
	 */
	public Enumeration getDocumentBasesIds() {
		if (this._documentBases != null) {
			return this._documentBases.keys();
		} else
			return null;
	}

	/**  Gets the default DocumentBase for this application.
	 *
	 * @return  The default documentBase, null if there is no default document base.
	 */
	public DocumentBase getDefaultDocumentBase() {
		return _defaultDocumentBase;
	}

	/**
	 * Gets the SDXThesaurus of an application identified by its id.
	 *
	 * @param id	The id of the SDXThesaurus
	 * @return	SDXThesaurus
	 * @throws SDXException
	 */
	public SDXThesaurus getThesaurus(String id) throws SDXException {
		if (!Utilities.checkString(id)) return null;
		SDXThesaurus th = null;
		th = (SDXThesaurus) _thesauri.get(id);
		if (th == null) {
			String[] args = new String[2];
			args[0] = id;
			args[1] = this.getId();
			throw new SDXException(getLogger(), SDXExceptionCode.ERROR_UNKNOWN_THESAURUS, args, null);
		} else
			return th;
	}

	// TODO: javadoc [MP]
	public Searchable getSearchable(String id) {
		Searchable searchable = null;
		if (!Utilities.checkString(id)) return null;
		searchable = (Searchable) _documentBases.get(id);
		if (searchable == null)
			searchable = (Searchable) _thesauri.get(id);
		return searchable;
	}

	/**	Indicates if this application uses the internal SDX users database.
	 */
	 public boolean isUsingSDXUserDatabase(){
		 return this._isUsingSDXUserDataBase;
	 }

	 /**
	  * Return userDatabase for special usages, should be unuseful
	  */
	 public UserDatabase getUserDatabase() {
		 return this._userDatabase;
	 }

	 /**Returns the id of the default admin group.
	  * The id will be <code>null</code> if the group has been deleted
	  * from the application after start-up
	  */
	 public String getDefaultAdminGroupId() {
		 String gid = null;
		 try {
			 if (this.getUserInformation(_defaultAdminGroupId) != null)
				 gid = _defaultAdminGroupId;
		 } catch (SDXException e) {
			 // the defaultAdminGroup has been deleted
			 LoggingUtils.logException(getLogger(), e);
			 gid = null;
		 }
		 return gid;
	 }

	 /**Returns the id of the default admin user.
	  * The id will be <code>null</code> if the user has been deleted
	  * from the application after start-up.
	  */
	 public String getDefaultAdminUserId() {
		 String uid = null;
		 try {
			 if (this.getUserInformation(_defaultAdminUserId) != null)
				 uid = _defaultAdminUserId;
		 } catch (SDXException e) {
			 // the defaultAdminUser has been deleted
			 uid = null;
		 }
		 return uid;
	 }

	 /**
	  * Control the validity of the SDX administration configuration.
	  * At this point, we could have a <sdx:admin> element in the configuration file.
	  * <pre>
	  * 	<sdx:admin groupId="admins"
	  *                userId="admin"
	  *                userPassword="somepassword may be empty"/>
	  * </pre>
	  * @throws SDXException
	  * @throws ConfigurationException
	  */
	 private void verifyDefaultAdminGroup() throws SDXException, ConfigurationException {
//		 <sdx:admin groupId="admins" userId="admin" userPassword="somepassword may be empty"/>
		 if (!_isUsingSDXUserDataBase) return;
		 UserDatabase udb = getUserDatabase();
		 if (udb != null) {
//			TODO:move this configuration under the declaration of a user document base as if the user document base is not specified
//            neither should this, as it is of no user
			 if (_defaultAdminGroupConf != null) {
				 String adminGroupId = _defaultAdminGroupConf.getAttribute(ATTRIBUTE_GROUP_ID);
				 ConfigurationUtils.checkConfAttributeValue(ATTRIBUTE_GROUP_ID, adminGroupId, _defaultAdminGroupConf.getLocation());
				 String adminUserId = _defaultAdminGroupConf.getAttribute(ATTRIBUTE_USER_ID);
				 ConfigurationUtils.checkConfAttributeValue(ATTRIBUTE_USER_ID, adminUserId, _defaultAdminGroupConf.getLocation());
				 String passwd = _defaultAdminGroupConf.getAttribute(ATTRIBUTE_USER_PASSWORD, null);
				 // create the group with the provided id if it doesn't exist yet
				 if (udb.getEntity(adminGroupId) == null) {
					 // for xml content
					 javax.xml.parsers.DocumentBuilderFactory factory = javax.xml.parsers.DocumentBuilderFactory.newInstance();
					 javax.xml.parsers.DocumentBuilder builder = null;
					 try {
						 builder = factory.newDocumentBuilder();

						 org.w3c.dom.Document doc = null;
						 org.w3c.dom.Element top = null;

						 // creating the default admin group
						 doc = builder.newDocument();
						 top = doc.createElementNS(Framework.SDXNamespaceURI, Framework.SDXNamespacePrefix + ":group");
						 doc.appendChild(top);
						 top.setAttribute("id", adminGroupId);

						 Group adminGroup = new Group();
						 adminGroup = (Group) super.setUpSdxObject(adminGroup);
						 adminGroup.setId(adminGroupId);
						 adminGroup.setPreferredFilename(adminGroupId + ".xml");
						 adminGroup.setContent(doc);
						 // adding the adminGroup
						 this.addIdentity(adminGroup, null, null, null, null);

						 // setting the class field
						 this._defaultAdminGroupId = adminGroupId;

						 // creating the user
						 doc = builder.newDocument();
						 top = doc.createElementNS(Framework.SDXNamespaceURI, Framework.SDXNamespacePrefix + ":user");
						 doc.appendChild(top);
						 top.setAttribute("id", adminUserId);

						 User adminUser = new User();
						 adminUser = (User) super.setUpSdxObject(adminUser);
						 adminUser.setId(adminUserId);
						 adminUser.setPreferredFilename(adminUserId + ".xml");
						 adminUser.setContent(doc);
						 // adding the adminUser
						 this.addIdentity(adminUser, passwd, null, null, null);
						 // setting the class field
						 this._defaultAdminUserId = adminUserId;

						 // adding the adminUser to the adminGroup
						 this.addMember(adminGroup, adminUserId);


					 } catch (ParserConfigurationException e) {
						 throw new ConfigurationException(e.getMessage(), e);
					 } catch (SAXException e) {
						 throw new ConfigurationException(e.getMessage(), e);
					 } catch (ProcessingException e) {
						 throw new ConfigurationException(e.getMessage(), e);
					 }

				 }
				 // setting the class field (FG:even if the document already exists)
				 if (udb.getEntity(adminGroupId) != null) this._defaultAdminGroupId = adminGroupId;
				 // setting the class field (FG:even if the document already exists)
				 if (udb.getEntity(adminUserId) != null) this._defaultAdminUserId = adminUserId;

			 }
		 }
	 }

	 /**
	  * Adds a member to a group. The member should be in the database.
	  *
	  * @param   group       The group.
	  * @param   id          Id of an identity to add as a member.
	  * @throws SDXException  */
	 public void addMember(Group group, String id) throws SDXException {
		 if (!_isUsingSDXUserDataBase) return;
		 if (group == null || group.getId() == null || id == null) return;
		 _userDatabase.addMember(group, id);
		 resetUserInformation(id); // to have modification of groups in userInformation
	 }

	 /**
	  * Adds members to a group. The members should be in the database.
	  *
	  * @param   group       The group.
	  * @param   ids         Array of ids to add as a member.
	  * @throws SDXException  */
	 public void addMembers(Group group, String[] ids) throws SDXException {
		 if (!_isUsingSDXUserDataBase) return;
		 if (group == null || group.getId() == null || ids == null || ids.length == 0) return;
		 for (int i = 0; i < ids.length; i++) addMember(group, ids[i]);
	 }

	 /**
	  * Replace members of a group. All members are deleted before add the ids.
	  *
	  * @param   group       The group.
	  * @param   ids         Array of ids to add as a member.
	  * @throws SDXException  */
	 public void replaceMembers(Group group, String[] ids) throws SDXException {
		 if (!_isUsingSDXUserDataBase) return;
		 String[] members = _userDatabase.getMembers(group.getId());
		 if (members != null && members.length != 0)
			 for (int i = 0; i < members.length; i++)
				 resetUserInformation(members[i]);
		 _userDatabase.deleteMembers(group);
		 addMembers(group, ids);
	 }

	 /** Returns the parents of an identity.
	  *
	  * @return A Hashtable of group object by id.
	  * @param  id  name of the user (or the group).
	  * @throws SDXException  */
	 public Hashtable getParents(String id) throws SDXException {
		 if (!_isUsingSDXUserDataBase) return null;
		 if (id == null) return null;
		 return _userDatabase.getParents(id);
	 }

	 /** Returns the members of a group.
	  *
	  * @return An array of strings containing the names of the members (users or groups) belonging to the group; the values are also the group name.
	  * @param groupname     The groupname.
	  * @throws SDXException  */
	 public String[] getMembers(String groupname) throws SDXException {
		 if (!_isUsingSDXUserDataBase) return null;
		 if (groupname == null) return null;
		 else return _userDatabase.getMembers(groupname);
	 }

	 /**
	  * Adds a user or group to this application.
	  *
	  * @param   identity        The user or group.
	  * @param   password        The unencoded password given to this user (may be null).
	  * @param   repository      The repository where the user document is stored (if null, default repository will be used).
	  * @param   params          The parameters of this adding process (may be null).
	  * @param   handler         A content handler where information on the adding process will be sent (may be null).
	  */
	 public void addIdentity(Identity identity, String password, Repository repository, IndexParameters params, ContentHandler handler) throws SDXException, SAXException, ProcessingException {
		 if (!_isUsingSDXUserDataBase) return;
		 if (identity == null) throw new SDXException(getLogger(), SDXExceptionCode.ERROR_IDENTITY_NULL, null, null);

		 DocumentBase userDB = (DocumentBase) _documentBases.get(USER_DOCUMENT_BASE_ID);
		 if (userDB == null) {
			 String[] args = new String[2];
			 args[0] = USER_DOCUMENT_BASE_ID;
			 args[1] = this.getId();
			 throw new SDXException(getLogger(), SDXExceptionCode.ERROR_UNKNOWN_DOCUMENT_BASE, args, null);
		 }
		 // TODO what to do for identical users ? -> params
		 userDB.index(identity, repository, params, handler);
		 // ensuring we have a good document
		 // Utilities.checkDocument(super.getLog(), (Document) identity);
		 // DEBUG: System.out.println("identity : " + identity.getId() + " ; " +  password);
		 _userDatabase.add(identity, encode(password));

		 // We must reset the user information object in case informations have changed
		 if (identity.getDocType() == Document.DOCTYPE_USER) resetUserInformation(identity.getId());
	 }

	 public boolean changePassword(String username, String oldPass, String newPass) throws SDXException {
		 if (!_isUsingSDXUserDataBase) return false;
		 if (this._userDatabase == null) return false;
		 else return this._userDatabase.changePassword(username, oldPass, newPass);
	 }

	 /**
	  * Removes an identity (user or group) from this application.
	  *
	  * @param   identity        The identity to remove (only it's name is needed).
	  */
	 public void deleteIdentity(Identity identity) throws SDXException {
		 if (!_isUsingSDXUserDataBase) return;
		 if (identity == null) throw new SDXException(getLogger(), SDXExceptionCode.ERROR_IDENTITY_NULL, null, null);
		 // ensuring we have a good document
		 Utilities.checkDocument(getLogger(), identity);

		 DocumentBase userDB = (DocumentBase) _documentBases.get(USER_DOCUMENT_BASE_ID);
		 try {
			 userDB.delete(identity, null);
		 } catch (SAXException e) {
			 // nothing here for the null handler provided
		 } catch (ProcessingException e) {
			 // nothing here for the null handler provided
		 }
		 _userDatabase.delete(identity);
		 // delete UserInformation
		 if (identity.getDocType() == Document.DOCTYPE_USER) resetUserInformation(identity.getId());
	 }

	 /**
	  * Checks if a user and a plain text password match.
	  *
	  * <p>
	  * If the password is null, there is a match if no password has been given
	  * to the user. If the password is an empty string, then an empty string must
	  * have been given as a password for this user at creation time.
	  * <p>
	  * Otherwise, there is a match if the password exactly matches (including case)
	  * the user password.
	  *
	  * @param   username    The username to check.
	  * @param   password    The password to check (may be null).
	  */
	 public boolean validateUser(String username, String password) throws SDXException {
		 if (!_isUsingSDXUserDataBase) return false;
		 if (username == null) return false;
		 // DEBUG: System.out.println("username : " + username + " password : " + password + " valid :" + userDatabase.checkPassword(username, encode(password)));
		 return _userDatabase.checkPassword(username, encode(password));
	 }

	 /**
	  * Checks if an identity (user or group) belongs to a group.
	  *
	  *
	  * @param   identity    The username to check.
	  * @param   groupName   The password to check (may be null).
	  */
	 public boolean isMember(Identity identity, String groupName) throws SDXException {
		 return _userDatabase.isMember(identity, groupName);
	 }

	 /**
	  * Encodes a password with this application private key.
	  *
	  * @param   password    The plain text password to encodeURL.
	  */
	 private String encode(String password) {
		 if (password == null)
			 return null;
		 else
			 return password;   //FIXME: encodeURL the password-MS, assume it should be similar to superuser encoding scheme-rbp
	 }

	 /** Returns information about a user.
	  *
	  * @param username The username (if null, anonymous user information is returned).
	  * @throws SDXException
	  * @return  The UserInformation object. */
	 public UserInformation getUserInformation(String username) throws SDXException {

		 if(!_isUsingSDXUserDataBase) return null;

		 // Check to see if we need anonymous user
		 if (username == null) username = UserInformation.ANONYMOUS_USERNAME;

		 // If we have the object, return it.
		 if (_userInformations != null && _userInformations.get(username) != null) return (UserInformation) _userInformations.get(username);

		 // If not, build it, store it and return it
		 UserInformation userInfo = _userDatabase.getUserInformation(super.getId(), username, super.getLocale(), this._defaultAdminGroupId);
		 if (userInfo == null) return null;

		 if (_userInformations == null) _userInformations = new Hashtable();
		 _userInformations.put(username, userInfo);

		 return userInfo;
	 }

	 /**
	  * Returns information about an anonymous user.
	  */
	 public UserInformation getUserInformation() throws SDXException {
		 return getUserInformation(null);
	 }


	 /**
	  * Returns the document where the user information is stored.
	  *
	  * @param   username    The username (if <code>null</code>, anonymous user information will be sent).
	  * @param   groups      The groups the user belongs to (may be <code>null</code>).
	  * @param   consumer     The XMLconsumer to feed with the information.
	  */
	 public void getUserDocument(String username, Hashtable groups, org.apache.cocoon.xml.XMLConsumer consumer) throws SDXException {
		 // verifying the consumer
		 if(!_isUsingSDXUserDataBase) return;
		 Utilities.checkXmlConsumer(getLogger(), consumer);
		 if (username == null || username.equals(UserInformation.ANONYMOUS_USERNAME)) {
			 AnonymousUserInformation anon = (AnonymousUserInformation) _userInformations.get(UserInformation.ANONYMOUS_USERNAME);
			 try {
				 if (anon != null) anon.toSAX(consumer);
			 } catch (SAXException e) {
				 // couldn't retrive the user document
				 String[] args = new String[2];
				 args[0] = UserInformation.ANONYMOUS_USERNAME;
				 args[1] = e.getMessage();
				 throw new SDXException(getLogger(), SDXExceptionCode.ERROR_USER_DOC_SAX_PARSE, args, e);
			 }
		 } else {
			 // Builds a filter if necessary
			 if (groups != null && groups.size() > 0) {
				 GroupInformationInserter gii = new GroupInformationInserter(groups, consumer);
				 gii.enableLogging(getLogger());
				 Transformation pipe = gii;
				 ((DocumentBase) _documentBases.get(USER_DOCUMENT_BASE_ID)).getDocument(new XMLDocument(username), pipe);
			 } else {
				 ((DocumentBase) _documentBases.get(USER_DOCUMENT_BASE_ID)).getDocument(new XMLDocument(username), consumer);
			 }
		 }
	 }

	 /**
	  * Resets a user information object.
	  *
	  * @param username	The username.
	  */
	 private void resetUserInformation(String username) {

		 // Make sure we have a valid username
		 if (!_isUsingSDXUserDataBase) return;
		 if (username == null) return; // Nothing to do

		 // ensuring the availability of data structures
		 if (_userInformations == null)
			 _userInformations = new Hashtable();

		 // removing the user
		 else if (_userInformations.containsKey(username)) _userInformations.remove(username);
	 }

	 public int getSessionObjectLimit() {
		 return sessionObjectLimit;
	 }

	 protected String getClassNameSuffix() {
		 return Application.CLASS_NAME_SUFFIX;
	 }


	 /* (non-Javadoc)
	  * @see fr.gouv.culture.sdx.utils.AbstractSdxObject#initToSax()
	  */
	 protected boolean initToSax() {

		 // TODO: get infos from context

		 String appConfPath = this.getPath()+ File.separator + "conf" + File.separator;

		 this._xmlizable_objects.put("ID", this.getId());
		 this._xmlizable_objects.put("Path", this.getPath());
		 this._xmlizable_objects.put("Encoding", this.getEncoding());
		 this._xmlizable_objects.put("XML-Lang", this.getXmlLang());
		 this._xmlizable_objects.put("Session_Object_Limit", Integer.toString(this.getSessionObjectLimit()));
		 this._xmlizable_objects.put("System_Dir", this.dataDir);

		 this._xmlizable_objects.put("Classes_Directory_Name", appConfPath + this.CLASSES_DIR_NAME);
		 this._xmlizable_objects.put("Libraries_Directory_Name", appConfPath + this.LIB_DIR_NAME);

		 this._xmlizable_objects.put("Databases_Directory_Name", appConfPath + this.DATABASES_DIR_NAME + File.separator);

		 if (_isUsingSDXUserDataBase)
			 this._xmlizable_objects.put("Users_Databases_Directory_Name", appConfPath +
					 this.USERS_DIR_NAME + File.separator +
					 "xml" + File.separator);

		 this._xmlizable_objects.put("Documentbases_Directory_Name", appConfPath + this.DOCUMENTBASES_DIR_NAME + File.separator);
		 this._xmlizable_objects.put("Default_Documentbase_Type", this.DEFAULT_DOCUMENTBASE_TYPE);
		 if (_isUsingSDXUserDataBase)
			 this._xmlizable_objects.put("Users_Documentbases_Directory_Name", appConfPath +
					 this.USERS_DIR_NAME + File.separator +
					 this.USERS_DOCUMENTBASE_DIR_NAME + File.separator);

		 this._xmlizable_objects.put("Repositories_Directory_Name", appConfPath + this.REPOSITORIES_DIR_NAME + File.separator);
		 this._xmlizable_objects.put("Thesauri_Directory_Name", appConfPath + this.THESAURI_DIR_NAME + File.separator);
		 this._xmlizable_objects.put("Default_Thesaurus_Type", this.DEFAULT_THESAURUS_TYPE);

		 this._xmlizable_objects.put("Default_Administrator_Group_ID", this.getDefaultAdminGroupId());
		 this._xmlizable_objects.put("Default_Administrator_User_ID", this.getDefaultAdminUserId());

		 this._xmlizable_objects.put("Default_DocumentBase", this.getDefaultDocumentBase().getId());
		 this._xmlizable_objects.put("Default_Repositories", this._repositories);
		 this._xmlizable_objects.put("Default_FieldLists", this._fieldLists);
		 this._xmlizable_objects.put("Document_Bases", this._documentBases);
		 this._xmlizable_objects.put("Thesauri", this._thesauri);


		 return true;
	 }


	 /**Init the LinkedHashMap _xmlizable_volatile_objects with the objects in order to describ them in XML
	  * Some objects need to be refresh each time a toSAX is called*/
	 protected void initVolatileObjectsToSax() {}

	 /**
	  * Optimize the application by optimizing all documentbases
	  */
	 public synchronized void optimize()
	 {
		 Enumeration dbIds = this.getDocumentBasesIds();
		 while(dbIds.hasMoreElements())
			 try {
				 this.getDocumentBase((String)dbIds.nextElement()).optimize();
			 } catch (SDXException e) {
				 LoggingUtils.logException(getLogger(), e);
			 }
	 }

	 /**
	  * Optimize the application by optimizing the documentbases sent in the array
	  */
	 public synchronized void optimize(String[] dbIds)
	 {
		 int i = 0;
		 while(dbIds[i] != null)
		 {
			 try {
				 this.getDocumentBase(dbIds[i]).optimize();
			 } catch (SDXException e) {
				 LoggingUtils.logException(getLogger(), e);
			 }
			 i++;
		 }
	 }

	 /**
	  * Optimize an application's documentbase
	  */
	 public synchronized void optimize(String dbId)
	 {
		 try {
			 this.getDocumentBase(dbId).optimize();
		 } catch (SDXException e) {
			 LoggingUtils.logException(getLogger(), e);
		 }
	 }

	 /**
	  * Check the application integrity
	  */
	 public synchronized void checkIntegrity()
	 {
		 // TODO: implement for internal database and repository
		 Enumeration dbIds = this.getDocumentBasesIds();
		 while(dbIds.hasMoreElements())
			 try {
				 this.getDocumentBase((String)dbIds.nextElement()).checkIntegrity();
			 } catch (SDXException e) {
				 LoggingUtils.logException(getLogger(), e);
			 }
	 }

	 /**
	  * Optimize the application by optimizing the documentbases sent in the array
	  */
	 public synchronized void checkIntegrity(String[] dbIds)
	 {
		 // TODO: implement for internal database and repository
		 int i = 0;
		 while(dbIds[i] != null)
		 {
			 try {
				 this.getDocumentBase(dbIds[i]).checkIntegrity();
			 } catch (SDXException e) {
				 LoggingUtils.logException(getLogger(), e);
			 }
			 i++;
		 }
	 }

	 /**
	  * Optimize an application's documentbase
	  */
	 public synchronized void checkIntegrity(String dbId)
	 {
		 // TODO: implement for internal database and repository
		 try {
			 this.getDocumentBase(dbId).checkIntegrity();
		 } catch (SDXException e) {
			 LoggingUtils.logException(getLogger(), e);
		 }
	 }

	 /**
	  * Save the application for backup or quick deployment purpose
	  */
	 public synchronized void saveApplication()
	 {
		 // where the backup will be stored
		 String backup_location = Utilities.getStringFromContext(ContextKeys.SDX.Application.BACKUP_DIRECTORY_PATH,this.getContext());

		 // create a temp dir where the objects will be saved before being zipped.
		 File save_dir = new File(backup_location+ File.separator+"temp");
		 if(!save_dir.exists())
			 save_dir.mkdirs();

		 // Create a SaveParameters object with only "application" as root element :
		 // the "all" attribute is automaticaly set to true
		 // --> all the application will saved
		 SaveParameters save_params = new SaveParameters("application", null, backup_location+ File.separator+"temp", null );
		 try{
			 // backup the application
			 this.backup(save_params);
			 // zip the temp directory
			 ZipWrapper zw = new ZipWrapper();
			 Calendar cal = Calendar.getInstance();
			 SimpleDateFormat date = new SimpleDateFormat("yyyy-MM-dd_hh-mm-ss");
			 String sar_name = getPath()+ "-" + date.format(cal.getTime()) +".sar";

			 try{
				 zw.zipDirectory(backup_location+File.separator+sar_name,backup_location+File.separator+"temp");
				 IOWrapper iow = new IOWrapper();
				 iow.deleteDirectory(backup_location+File.separator+"temp");
			 }catch(SDXException ee)
			 {
				 // Give the Exception a logger
				 String[] args = new String[2];
				 args[0] = this.getId();
				 args[1] = ee.getMessage();
				 throw new SDXException(getLog(), SDXExceptionCode.ERROR_BACKUP_REPOSITORY, args, ee);
			 }
		 }catch(SDXException e)
		 {
			 this.getLog().error(e.getMessage(),e.fillInStackTrace());
		 }
	 }

	 /**
	  * Load the application from a previous backup, restoring all functionnality
	  * If file path is null, get the most recent backup file found.
	  */
	 public synchronized void loadApplication(String file_path) throws SDXException
	 {
//		 where the backup is stored (if no file_path) and temp directory is created
		 String backup_location = Utilities.getStringFromContext(ContextKeys.SDX.Application.BACKUP_DIRECTORY_PATH,this.getContext());
		 String sar_path = file_path;

		 // If we haven't a valid sar_path get the most recent file in backup_location
		 if(sar_path == null || (!sar_path.endsWith(".sar")))
		 {
			 // Get the most recent backup file
			 File bk_dir= new File(backup_location);
			 String[] files = bk_dir.list();
			 if(files.length>0)
			 {
				 Arrays.sort(files,new StringComparator());
				 sar_path = backup_location + files[files.length-1];
			 }
		 }

		 if(sar_path != null && sar_path.endsWith(".sar"))
		 {
			 // create a temp dir where the objects will be unzipped before being restored.
			 File unzip_dir = new File(backup_location+ File.separator+"temp");
			 if(!unzip_dir.exists())
				 unzip_dir.mkdirs();

			 try{
				 // unzip the backup file
				 ZipWrapper zw = new ZipWrapper();
				 zw.unzipDirectory(sar_path, unzip_dir.getAbsolutePath());
			 }catch(SDXException ee)
			 {
				 // Give the Exception a logger
				 String[] args = new String[2];
				 args[0] = this.getId();
				 args[1] = ee.getMessage();
				 throw new SDXException(getLog(), SDXExceptionCode.ERROR_BACKUP_REPOSITORY, args, ee);
			 }
			 // build the application's SaveParameters tree
			 SaveParametersBuilder builder = new SaveParametersBuilder();
			 SaveParameters save_conf = (SaveParameters)builder.buildFromFile(backup_location+ File.separator+"temp", "info.xml");

			 // restore the application data from the backup file
			 this.restore(save_conf);

			 try{
				 // delete the temp directory
				 IOWrapper iow = new IOWrapper();
				 iow.deleteDirectory(unzip_dir.getAbsolutePath());
			 }catch(SDXException ee)
			 {
				 //Give the Exception a logger
				 String[] args = new String[2];
				 args[0] = this.getId();
				 args[1] = ee.getMessage();
				 throw new SDXException(getLog(), SDXExceptionCode.ERROR_BACKUP_REPOSITORY, args, ee);
			 }

		 }
	 }

	 /** Save the application data objects
	  * @see fr.gouv.culture.sdx.utils.save.Saveable#backup(fr.gouv.culture.sdx.utils.save.SaveParameters)
	  */
	 public void backup(SaveParameters save_config) throws SDXException{
		 if(save_config != null)
		 {
			 if(save_config.isAllElementSelected())
			 {
				 // add the application id
				 save_config.setAttribute(Node.Name.ID, this.getId());
				 SaveParameters saveparams = null;
				 if(this._userDatabase != null)
				 {
//					 add the userdatabase
					 saveparams = new SaveParameters("userDatabase", save_config);
					 backupUserDatabase(saveparams);
				 }

				 if((_documentBases!=null) && (_documentBases.size()>0))
				 {
					 // add the DocumentBases if any
					 saveparams = new SaveParameters(Utilities.getElementName(DocumentBase.CLASS_NAME_SUFFIX)+"s", save_config);
					 backupDocumentBases(saveparams);
				 }

				 if((_thesauri!=null) && (_thesauri.size()>0))
				 {
					 // add the Thesauri if any
					 saveparams = new SaveParameters(SDXThesaurus.ConfigurationNode.THESAURI, save_config);
					 backupThesauri(saveparams);
				 }
			 }

			 DefaultConfigurationSerializer DCserial = new DefaultConfigurationSerializer();
			 try{
				 // Create the info.xml file
				 String save_location = save_config.getStoreBasePath()+ save_config.getStorePath();
				 DCserial.serializeToFile(save_location+ File.separator + "info.xml",save_config);
			 }catch(IOException e){
				 throw new SDXException("Error accessing the save location.",e);
			 }
			 catch(SAXException e1){
				 throw new SDXException("Error editing the info.xml file",e1);
			 }
			 catch(ConfigurationException e2){
				 throw new SDXException("Error in the save configuration.",e2);
			 }
		 }
	 }

	 private void backupUserDatabase(SaveParameters save_config) throws SDXException{
		 if(save_config != null)
		 {
			 if(save_config.isAllElementSelected())
			 {
				 this._userDatabase.backup(save_config);
			 }
		 }
	 }

	 private void backupDocumentBases(SaveParameters save_config) throws SDXException{
		 if(save_config != null)
		 {
			 String dbs_path = save_config.getStoreCompletePath() + File.separator + save_config.getName();
			 if(save_config.isAllElementSelected())
			 {
				 // create the documentBases directory
				 File dbs_dir = new File(dbs_path);
				 if(!dbs_dir.exists())
					 dbs_dir.mkdir();
				 save_config.savePathInConfig(save_config.getName());
				 for(Enumeration m_enum = _documentBases.elements(); m_enum.hasMoreElements();)
				 {
					 DocumentBase db = (DocumentBase)m_enum.nextElement();
					 if(db != null){
						 // save each DocumentBase
						 SaveParameters dbsave = new SaveParameters(Utilities.getElementName(DocumentBase.CLASS_NAME_SUFFIX), save_config, save_config.getName());
						 dbsave.setUniqueID(save_config.getUniqueID());
						 db.backup(dbsave);
					 }
				 }
			 }
		 }
	 }

	 private void backupThesauri(SaveParameters save_config) throws SDXException{
		 if(save_config != null)
		 {
			 String thi_path = save_config.getStoreCompletePath() + File.separator + save_config.getName();
			 if(save_config.isAllElementSelected())
			 {
//				 create the thesauri directory
				 File thi_dir = new File(thi_path);
				 if(!thi_dir.exists())
					 thi_dir.mkdir();
				 save_config.savePathInConfig(save_config.getName());
				 for(Enumeration m_enum = _thesauri.elements(); m_enum.hasMoreElements();)
				 {
					 SDXThesaurus th = (SDXThesaurus)m_enum.nextElement();
					 if(th != null)
					 {
						 // Save each thesaurus
						 SaveParameters thsave = new SaveParameters(Utilities.getElementName(SDXThesaurus.CLASS_NAME_SUFFIX), save_config, save_config.getName());
						 thsave.setUniqueID(save_config.getUniqueID());
						 th.backup(thsave);
					 }
				 }
			 }
		 }
	 }
	 /** Restore the application data objects
	  * @see fr.gouv.culture.sdx.utils.save.Saveable#restore(fr.gouv.culture.sdx.utils.save.SaveParameters)
	  */
	 public void restore(SaveParameters save_config) throws SDXException{
		 if(save_config != null)
		 {
			 if(save_config.isAllElementSelected())
			 {
				 // check if the backup file was made with this apllication
				 if(!save_config.getAttribute(Node.Name.ID, "").equals(getId()))
					 throw new SDXException("This application id don't match the one in \"info.xml\" in the backup file.");

				 String elementName = Utilities.getElementName(DocumentBase.CLASS_NAME_SUFFIX)+"s";
				 SaveParameters saveparams = null;
				 if((_documentBases!=null) && (_documentBases.size()>0))
				 {
					 // add the DocumentBases if any
					 saveparams = (SaveParameters)save_config.getChild(elementName);
					 restoreDocumentBases(saveparams);
				 }

				 elementName = SDXThesaurus.ConfigurationNode.THESAURI;
				 if((_thesauri!=null) && (_thesauri.size()>0))
				 {
					 // add the Thesauri if any
					 saveparams = (SaveParameters)save_config.getChild(elementName);
					 restoreThesauri(saveparams);
				 }
			 }
		 }
	 }

	 private void restoreUserDatabase(SaveParameters save_config) throws SDXException{
		 if(save_config != null)
		 {
			 if(save_config.isAllElementSelected())
			 {
				 this._userDatabase.backup(save_config);
			 }
		 }
	 }

	 public void restoreDocumentBases(SaveParameters save_config) throws SDXException{
		 if(save_config != null)
		 {
			 String db_path = save_config.getStoreCompletePath();
			 if(save_config.isAllElementSelected())
			 {
//				 check the document bases directory
				 File db_dir = new File(db_path);
				 if(!db_dir.exists())
					 throw new SDXException(db_path + " not found.");

				 for(Enumeration m_enum = _documentBases.elements(); m_enum.hasMoreElements();)
				 {
					 DocumentBase db = (DocumentBase)m_enum.nextElement();
					 if(db != null)
					 {
						 //Restore each document bases
						 SaveParameters dbsave = save_config.getSaveParametersById(db.getId());
						 db.restore(dbsave);
					 }
				 }
			 }
		 }
	 }

	 public void restoreThesauri(SaveParameters save_config) throws SDXException{
		 if(save_config != null)
		 {
			 String thi_path = save_config.getStoreCompletePath();
			 if(save_config.isAllElementSelected())
			 {
//				 check the thesauri directory
				 File thi_dir = new File(thi_path);
				 if(!thi_dir.exists())
					 throw new SDXException(thi_path + " not found.");

				 for(Enumeration m_enum = _thesauri.elements(); m_enum.hasMoreElements();)
				 {
					 SDXThesaurus th = (SDXThesaurus)m_enum.nextElement();
					 if(th != null)
					 {
						 //Restore each thesaurus
						 SaveParameters thsave = save_config.getSaveParametersById(th.getId());
						 th.restore(thsave);
					 }
				 }
			 }
		 }
	 }

	 /**
	  * Gets the source validity of the application.
	  * Commonly used with Cocoon to cache data.
	  * @return	SourceValidity
	  */
	 public SourceValidity getSourceValidity() {
		 return new ApplicationSourceValidity(this);
	 }
}
