/*
 * Copyright
 *   2008 axYus - www.axyus.com
 *   2008 C.Bosquet - charles.bosquet@axyus.com
 *
 * This file is part of XEMELIOS.
 *
 * XEMELIOS is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * XEMELIOS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with XEMELIOS; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package fr.gouv.finances.dgfip.xemelios.web.gwt.server.service;

import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.ConfigExportDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.DocumentsDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.ListResultatDTO;
import fr.gouv.finances.cp.utils.xml.marshal.XmlMarshallable;
import fr.gouv.finances.cp.utils.xml.marshal.XmlOutputter;
import fr.gouv.finances.dgfip.utils.Pair;
import fr.gouv.finances.dgfip.utils.xml.SAXWriter;
import fr.gouv.finances.dgfip.xemelios.auth.UnauthorizedException;
import fr.gouv.finances.dgfip.xemelios.auth.XemeliosUser;
import fr.gouv.finances.dgfip.xemelios.common.config.ChampModel;
import fr.gouv.finances.dgfip.xemelios.common.config.CritereModel;
import fr.gouv.finances.dgfip.xemelios.common.config.CritereRefModel;
import fr.gouv.finances.dgfip.xemelios.common.config.DocumentModel;
import fr.gouv.finances.dgfip.xemelios.common.config.DocumentsModel;
import fr.gouv.finances.dgfip.xemelios.common.config.ElementModel;
import fr.gouv.finances.dgfip.xemelios.common.config.EtatModel;
import fr.gouv.finances.dgfip.xemelios.common.config.HiddenModel;
import fr.gouv.finances.dgfip.xemelios.common.config.InputModel;
import fr.gouv.finances.dgfip.xemelios.common.config.ListeResultatModel;
import fr.gouv.finances.dgfip.xemelios.common.config.Loader;
import fr.gouv.finances.dgfip.xemelios.common.config.OptionModel;
import fr.gouv.finances.dgfip.xemelios.common.config.PJRefInfo;
import fr.gouv.finances.dgfip.xemelios.common.config.PjRefHandler;
import fr.gouv.finances.dgfip.xemelios.common.config.PluginModel;
import fr.gouv.finances.dgfip.xemelios.common.config.PropertyModel;
import fr.gouv.finances.dgfip.xemelios.common.config.RechercheModel;
import fr.gouv.finances.dgfip.xemelios.common.config.RecherchePaireModel;
import fr.gouv.finances.dgfip.xemelios.common.config.ResourceRefModel;
import fr.gouv.finances.dgfip.xemelios.common.config.SelectModel;
import fr.gouv.finances.dgfip.xemelios.common.config.SpecialKeyEntryModel;
import fr.gouv.finances.dgfip.xemelios.data.DataAccessException;
import fr.gouv.finances.dgfip.xemelios.data.DataConfigurationException;
import fr.gouv.finances.dgfip.xemelios.data.DataHandler;
import fr.gouv.finances.dgfip.xemelios.data.DataLayerManager;
import fr.gouv.finances.dgfip.xemelios.data.DataResultSet;
import fr.gouv.finances.dgfip.xemelios.export.ConfigModel;
import fr.gouv.finances.dgfip.xemelios.export.ExportChampModel;
import fr.gouv.finances.dgfip.xemelios.export.ExportElementModel;
import fr.gouv.finances.dgfip.xemelios.export.ExportJob;
import fr.gouv.finances.dgfip.xemelios.export.ExportModel;
import fr.gouv.finances.dgfip.xemelios.plugins.ExportPlugin;
import fr.gouv.finances.dgfip.xemelios.plugins.SearchPlugin;
import fr.gouv.finances.dgfip.xemelios.services.XemeliosHelper;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.InitParametersDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.CritereTypeDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.CritereItemDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.InputCritereItemDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.DateCritereItemDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.DisplayableFieldDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.DocumentDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.EtatDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.ExportChampDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.ExportElementDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.ExportModelDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.StoredSearchDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.TextCritereItemDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.LabelCritereItemDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.LabelValueDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.NumericCritereItemDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.PaginationInfoDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.SearchInitParametersDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.SearchParameterDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.SearchResultDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.SearchSessionDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.dto.SelectOneCritereItemDTO;
import fr.gouv.finances.dgfip.xemelios.web.gwt.client.service.XemeliosService;
import fr.gouv.finances.dgfip.xemelios.web.gwt.server.service.SearchSessionContainer.SearchSession;
import fr.gouv.finances.dgfip.xemelios.web.navigate.ViewAttachmentAction;
import fr.gouv.finances.dgfip.xemelios.web.utils.ParametreManager;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.reflect.Constructor;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Vector;
import javax.xml.XMLConstants;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

/**
 * @author CBO
 * @author chm
 */
public class XemeliosServiceImpl extends AbstractXemeliosService implements XemeliosService {
    public static final long serialVersionUID = 1230557266433L;

    private static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(XemeliosServiceImpl.class);

    private SearchSession getSearchSession(String searchSessionId) {
        return SearchSessionContainer.getInstance(getThreadLocalRequest().getSession()).getSearchSession(searchSessionId);
    }

    /**
     * Retourne les parametres necessaires a l'initialisation du widget d'elaboration des criteres de recherche
     * @param documentId
     * @param etatId
     * @param elementId
     * @return
     */
    public InitParametersDTO initSearchWindow(String documentId, String etatId, String elementId) {
        SearchSession searchSession = SearchSessionContainer.getInstance(getThreadLocalRequest().getSession()).createSearchSession(documentId, etatId, elementId);
        List<LabelValueDTO> collectivites = getCollectivites(searchSession.getId(), getXemeliosUser());
        List<LabelValueDTO> budgetsForFirstCollectivite = null;
        if (!collectivites.isEmpty()) {
            budgetsForFirstCollectivite = getBudgets(searchSession.getId(), collectivites.iterator().next().getValue());
        } else {
            budgetsForFirstCollectivite = new ArrayList<LabelValueDTO>();
        }
        // Creation ListeResultatModel et placement dans la session de recherche
        try {
            ElementModel elementModel = searchSession.getElementModel();
            ListeResultatModel lrm = elementModel.getListeResultat().clone();
            lrm.setParent(elementModel);
            searchSession.setLrm(lrm);
        } catch (Exception saxe) {
            throw new RuntimeException(saxe);
        }
        return new InitParametersDTO(
                searchSession.getId(),
                collectivites,
                budgetsForFirstCollectivite,
                new SearchInitParametersDTO(new ArrayList<CritereTypeDTO>(), getCriteres(searchSession.getId())),
                searchSession.getDocumentModel().getReferentielXsltFile()!=null);
    }

    public void destroySearchSession(String searchSessionId) {
        SearchSessionContainer.getInstance(getThreadLocalRequest().getSession()).destroySearchSession(searchSessionId);
    }

    private List<LabelValueDTO> getCollectivites(String searchSessionId, XemeliosUser user) {
        try {
            return pairsToLabelValueDTOs(XemeliosHelper.getCollectivites(user, getSearchSession(searchSessionId).getDocumentModel()));
        } catch (DataConfigurationException dce) {
            throw new RuntimeException(dce);
        } catch (DataAccessException dae) {
            throw new NoDataFoundException(getSearchSession(searchSessionId).getDocumentId());
        }
    }

    public List<LabelValueDTO> getBudgets(String searchSessionId, String collectiviteId) {
        try {
            return pairsToLabelValueDTOs(XemeliosHelper.getBudgets(getXemeliosUser(), getSearchSession(searchSessionId).getDocumentModel(), collectiviteId));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private List<CritereTypeDTO> getCriteres(String searchSessionId) {
        List<CritereTypeDTO> toReturn = new ArrayList<CritereTypeDTO>();
        for (CritereModel cm : getSearchSession(searchSessionId).getElementModel().getAvailableCriteres()) {
            toReturn.add(new CritereTypeDTO(cm.getId(), cm.getLibelle()));
        }
        return toReturn;
    }

    public SelectOneCritereItemDTO getUpdatedSelectOneCritereItemDTO(String searchSessionId, String collectiviteId, String budgetId, String critereId, String selectId, Map<String, String> inputValuesById, String pluginId) {
        String smId = selectId.substring(selectId.indexOf(":") + 1);
        CritereModel critereModel = getCritereModel(searchSessionId, critereId, pluginId);
        for (XmlMarshallable xm : critereModel.getInputs()) {
            if (xm instanceof SelectModel) {
                SelectModel sm = (SelectModel) xm;
                if (sm.getId().equals(smId)) {
                    // On a trouve le composant :
                    Collection<Pair> data = null;
                    data = sm.getNewRecipient();
                    if (sm.getOptions() != null) {
                        for (OptionModel om : sm.getOptions()) {
                            data.add(new Pair(om.getValue(), om.getLibelle()));
                        }
                    }
                    for (RecherchePaireModel rpm : sm.getRecherches()) {
                        if (rpm.getPath() != null) {
                            // on fait une requete dans le referentiel pour remplir la liste
                            try {
                                Collection<Pair> localData = null;
                                localData = rpm.getNewRecipient();

                                localData.addAll(DataLayerManager.getImplementation().queryRepository(getSearchSession(searchSessionId).getDocumentModel(), new Pair(collectiviteId, null), rpm, getXemeliosUser()));
                                data.addAll(localData);
                            } catch (Exception dbEx) {
                                throw new RuntimeException(dbEx);
                            }
                        }
                    }
                    // Il y a des dependances, on recherche les ##VALEUR:id##
                    for (SpecialKeyEntryModel skem : sm.getSpecialKeys()) {
                        if (skem.isDepends()) {
                            String path = skem.getKey1().getData();
                            String itemName = path.substring(2, path.length() - 2);
                            Collection<Pair> tmp = sm.getNewRecipient();
                            try {
                                if (skem.getPos() == 2) {
                                    tmp.addAll(DataLayerManager.getImplementation().getSpecialKeys2(getSearchSession(searchSessionId).getDocumentModel(), new Pair(collectiviteId, null), new Pair(budgetId, null), new Pair(inputValuesById.get(itemName), inputValuesById.get(itemName)), skem.isDistinct(), getXemeliosUser()));
                                } else {
                                    throw new RuntimeException("Not yet implemented");
                                /*id = cv.inputs.get(itemName);
                                if (id.getInputValue() != null && id.getInputValue().length() > 0) {
                                key2 = id.getInputValue();
                                }
                                tmp.addAll(DataLayerManager.getImplementation().getSpecialKeys3(dm,collectivite,budget,new Pair(key1,key1),new Pair(key2,key2),skem.isDistinct()));*/
                                }
                            } catch(UnauthorizedException ex) {
                                // on ajoute rien, et voilà !
                            } catch (DataConfigurationException ex) {
                                throw new RuntimeException(ex);
                            } catch (DataAccessException ex) {
                                throw new RuntimeException(ex);
                            }
                            data.addAll(tmp);
                        }
                    }
                    // Il y a des dependances, on recherche les ##VALEUR:id##
                    for (SpecialKeyEntryModel skem : sm.getSpecialKeys()) {
                        if (!skem.isDepends()) {// Si pas de dependance, on peut remplir maintenant :
                            try {
                                data.addAll(DataLayerManager.getImplementation().getSpecialKeys1(getSearchSession(searchSessionId).getDocumentModel(), new Pair(collectiviteId, null), new Pair(budgetId, null), skem.isDistinct(), getXemeliosUser()));
                            } catch (Exception dbEx) {
                                throw new RuntimeException(dbEx);
                            }
                        }
                    }
                    for(ResourceRefModel rrm:sm.getResources()) {
                        for(XmlMarshallable it:getSearchSession(searchSessionId).getDocumentModel().getResource(rrm.getRefId()).getData()) {
                            if(it instanceof OptionModel) {
                                OptionModel om=(OptionModel)it;
                                data.add(new Pair(om.getValue(), om.getLibelle()));
                            }
                        }
                    }
                    SelectOneCritereItemDTO selectOneCritereItem = new SelectOneCritereItemDTO("VALEUR:" + sm.getId());
                    for (Pair pair : data) {
                        LabelValueDTO labelValue = new LabelValueDTO(pair.key, pair.libelle);
                        selectOneCritereItem.addItem(labelValue);
                        if (sm.getValue() != null && sm.getValue().equals(pair.key)) {
                            selectOneCritereItem.setSelectedItem(labelValue);
                        }
                    }
                    return selectOneCritereItem;
                }
            }
        }
        return null;
    }

    public List<CritereItemDTO> getCritereItems(String searchSessionId, String collectiviteId, String budgetId, String critereId, String pluginId) {
        Map<String, SelectOneCritereItemDTO> selectOneCritereItemByKey1 = new HashMap<String, SelectOneCritereItemDTO>();
        List<CritereItemDTO> toReturn = new ArrayList<CritereItemDTO>();

        CritereModel critereModel = getCritereModel(searchSessionId, critereId, pluginId);

        for (XmlMarshallable xm : critereModel.getInputs()) {
            if (xm instanceof InputModel) {
                InputModel im = (InputModel) xm;
                if (im.getLibelle() != null) {
                    LabelCritereItemDTO labelCritereItem = new LabelCritereItemDTO();
                    labelCritereItem.setLibelle(im.getLibelle());
                    toReturn.add(labelCritereItem);
                }
                String datatype = im.getDatatype();
                if (datatype != null) {
                    Pair pairs[] = null;
                    try {
                        pairs = DataLayerManager.getImplementation().getOperators(datatype);
                        String lastInputValue = DataLayerManager.getImplementation().getLastInputValue(im.getId()+".OPERATEUR", getXemeliosUser());
                        if (pairs.length > 0) {
                            SelectOneCritereItemDTO operatorsSelect = new SelectOneCritereItemDTO("OPERATEUR:" + im.getId(),lastInputValue);
                            for (Pair pair : pairs) {
                                operatorsSelect.addItem(new LabelValueDTO(pair.key, pair.libelle));
                            }
                            toReturn.add(operatorsSelect);
                        }

                    } catch (Exception dbEx) {
                        throw new RuntimeException(dbEx);
                    }
                }
                InputCritereItemDTO input = null;
                String lastInputValue = null;
                try {
                    lastInputValue = DataLayerManager.getImplementation().getLastInputValue(im.getId(), getXemeliosUser());
                } catch(Exception ex) {
                    throw new RuntimeException(ex);
                }
//                String inputId =
                if ("date".equals(im.getDatatype())) {
                    input = new DateCritereItemDTO("VALEUR:" + im.getId(), ParametreManager.getDisplayDateFormat(),lastInputValue);
                } else if ("numeric".equals(im.getDatatype())) {
                    input = new NumericCritereItemDTO("VALEUR:" + im.getId(),lastInputValue);
                } else {
                    input = new TextCritereItemDTO("VALEUR:" + im.getId(),lastInputValue);
                    ((TextCritereItemDTO) input).setUppercase(im.isUppercase());
                    ((TextCritereItemDTO) input).setHelpPath(im.getHelpPath());
                }
                toReturn.add(input);
            } else if (xm instanceof SelectModel) {
                SelectModel sm = (SelectModel) xm;
                String lastInputValue = null;
                try {
                    lastInputValue = DataLayerManager.getImplementation().getLastInputValue(sm.getId(), getXemeliosUser());
                } catch(Exception ex) {
                    throw new RuntimeException(ex);
                }
                if (sm.getLibelle() != null) {
                    LabelCritereItemDTO labelCritereItem = new LabelCritereItemDTO();
                    labelCritereItem.setLibelle(sm.getLibelle());
                    toReturn.add(labelCritereItem);
                }
                Collection<Pair> data = null;
                data = sm.getNewRecipient();
                if (sm.getOptions() != null) {
                    for (OptionModel om : sm.getOptions()) {
                        data.add(new Pair(om.getValue(), om.getLibelle()));
                    }
                }
                for (RecherchePaireModel rpm : sm.getRecherches()) {
                    if (rpm.getPath() != null) {
                        // on fait une requete dans le referentiel pour remplir la liste
                        try {
                            Collection<Pair> localData = null;
                            localData = rpm.getNewRecipient();

                            localData.addAll(DataLayerManager.getImplementation().queryRepository(getSearchSession(searchSessionId).getDocumentModel(), new Pair(collectiviteId, null), rpm, getXemeliosUser()));
                            data.addAll(localData);
                        } catch (Exception dbEx) {
                            throw new RuntimeException(dbEx);
                        }
                    }
                }
                for (SpecialKeyEntryModel skem : sm.getSpecialKeys()) {
                    if (!skem.isDepends()) {
                        // Si pas de dependance, on peut remplir maintenant :
                        try {
                            data.addAll(DataLayerManager.getImplementation().getSpecialKeys1(getSearchSession(searchSessionId).getDocumentModel(), new Pair(collectiviteId, null), new Pair(budgetId, null), skem.isDistinct(), getXemeliosUser()));
                        } catch (Exception dbEx) {
                            throw new RuntimeException(dbEx);
                        }
                    }
                }
                for(ResourceRefModel rrm:sm.getResources()) {
                    for(XmlMarshallable it:getSearchSession(searchSessionId).getDocumentModel().getResource(rrm.getRefId()).getData()) {
                        if(it instanceof OptionModel) {
                            OptionModel om=(OptionModel)it;
                            data.add(new Pair(om.getValue(), om.getLibelle()));
                        }
                    }
                }
                SelectOneCritereItemDTO selectOneCritereItem = new SelectOneCritereItemDTO("VALEUR:" + sm.getId(),lastInputValue);
                LabelValueDTO firstLabelValue = null;
                int idx = 0;
                for (Pair pair : data) {
                    LabelValueDTO labelValue = new LabelValueDTO(pair.key, pair.libelle);
                    if (idx == 0) {
                        firstLabelValue = labelValue;
                    }
                    idx++;
                    selectOneCritereItem.addItem(labelValue);
                    if (sm.getValue() != null && sm.getValue().equals(pair.key)) {
                        selectOneCritereItem.setSelectedItem(labelValue);
                    }
                }
                if (selectOneCritereItem.getSelectedItem() == null && firstLabelValue != null) {
                    selectOneCritereItem.setSelectedItem(firstLabelValue);
                }
                toReturn.add(selectOneCritereItem);
                selectOneCritereItemByKey1.put("##VALEUR:" + sm.getId() + "##", selectOneCritereItem);
            }
        }
        // Definition des dependances entre les champs :
        for (XmlMarshallable xm : critereModel.getInputs()) {
            if (xm instanceof SelectModel) {
                SelectModel sm = (SelectModel) xm;
                for (SpecialKeyEntryModel skem : sm.getSpecialKeys()) {
                    if (skem.isDepends()) {
                        SelectOneCritereItemDTO masterS = selectOneCritereItemByKey1.get(skem.getKey1().getData());
                        InputCritereItemDTO child = selectOneCritereItemByKey1.get("##VALEUR:" + sm.getId() + "##");
                        masterS.addCritereDependant(child);
                        child.addCritereMaitre(masterS);
                    }
                }
            }
        }
        // D?finition des valeurs des champs dependants :
        for (XmlMarshallable xm : critereModel.getInputs()) {
            if (xm instanceof SelectModel) {
                SelectModel sm = (SelectModel) xm;
                for (SpecialKeyEntryModel skem : sm.getSpecialKeys()) {
                    if (skem.isDepends()) {
                        SelectOneCritereItemDTO child = selectOneCritereItemByKey1.get("##VALEUR:" + sm.getId() + "##");
                        Map<String, String> inputValuesById = new HashMap<String, String>();
                        for (InputCritereItemDTO inputCritereItemDTO : child.getCriteresMaitres()) {
                            if (inputCritereItemDTO instanceof SelectOneCritereItemDTO) {
                                SelectOneCritereItemDTO masterS = (SelectOneCritereItemDTO) inputCritereItemDTO;
                                if (masterS.getSelectedItem() != null) {
                                    inputValuesById.put(inputCritereItemDTO.getInputId(), masterS.getSelectedItem().getValue());
                                }
                            }
                        }
                        SelectOneCritereItemDTO updatedSelectItems = getUpdatedSelectOneCritereItemDTO(searchSessionId, collectiviteId, budgetId, critereId, child.getInputId(), inputValuesById, pluginId);
                        if (updatedSelectItems != null) {
                            child.setItems(updatedSelectItems.getItems());
                            child.setSelectedItem(updatedSelectItems.getSelectedItem());
                        }
                    }
                }
            }
        }
        return toReturn;
    }

    public SearchResultDTO search(String searchSessionId, String collectiviteId, String budgetId, List<SearchParameterDTO> searchParams, String pluginId) {
        if (pluginId == null) {
            return simpleSearch(searchSessionId, collectiviteId, budgetId, searchParams);
        } else {
            return pluginSearch(searchSessionId, collectiviteId, budgetId, searchParams, pluginId);
        }
    }

    public SearchResultDTO pluginSearch(String searchSessionId, String collectiviteId, String budgetId, List<SearchParameterDTO> searchParams, String pluginId) {
        try {
            Pair collectivite = XemeliosHelper.getCollectivite(getXemeliosUser(), getSearchSession(searchSessionId).getDocumentModel(), collectiviteId);
            Pair budget = XemeliosHelper.getBudget(getXemeliosUser(), getSearchSession(searchSessionId).getDocumentModel(), collectiviteId, budgetId);
            PluginModel plugin = getPluginModel(searchSessionId, pluginId);
            Class clazz = Class.forName(plugin.getPluginClass());
            Constructor cc = clazz.getConstructor(XemeliosUser.class);
            SearchPlugin xp = (SearchPlugin) cc.newInstance(getXemeliosUser());
            xp.setPluginModel(plugin);
            xp.setCollectivite(collectivite);
            xp.setBudget(budget);
            xp.setParameters(getParameters(searchParams));
            xp.setElementModel(getSearchSession(searchSessionId).getElementModel());
            xp.setListeResultatModel(getSearchSession(searchSessionId).getLrm());
            DataResultSet dataResultSet = xp.getResultSet();
            // On conserve le dataResultSet pour pouvoir changer de page sans ré-exécuter la recherche :
            getSearchSession(searchSessionId).setDataResultSet(dataResultSet);
            // On conserve aussi la collectivite et le budget :
            getSearchSession(searchSessionId).setCollectivite(collectivite);
            getSearchSession(searchSessionId).setBudget(budget);
            return dataResultToSearchResult(dataResultSet, getSearchSession(searchSessionId).getLrm(), collectivite, budget,searchSessionId);
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Hashtable<String, String> getParameters(List<SearchParameterDTO> searchParams) {
        Hashtable<String, String> toReturn = new Hashtable<String, String>();
        for (SearchParameterDTO searchParam : searchParams) {
            for (Entry<String, String> entry : searchParam.getInputValues()) {
                toReturn.put(entry.getKey(), entry.getValue());
            }
        }
        return toReturn;
    }

    public SearchResultDTO simpleSearch(String searchSessionId, String collectiviteId, String budgetId, List<SearchParameterDTO> searchParams) {
        try {
            Pair collectivite = XemeliosHelper.getCollectivite(getXemeliosUser(), getSearchSession(searchSessionId).getDocumentModel(), collectiviteId);
            Pair budget = XemeliosHelper.getBudget(getXemeliosUser(), getSearchSession(searchSessionId).getDocumentModel(), collectiviteId, budgetId);
            StringBuffer xpathQuery = new StringBuffer();
            ElementModel elementModel = getSearchSession(searchSessionId).getElementModel();
            xpathQuery.append(elementModel.getPath().getPath());
            Vector<CritereModel> critereModels = new Vector<CritereModel>();
            XemeliosUser user = getXemeliosUser();
            for (SearchParameterDTO searchParam : searchParams) {
                processSearchParameter(elementModel, searchParam, critereModels, xpathQuery,user);
            }
            // TODO champs optionnels...
            DataResultSet dataResultSet = DataLayerManager.getImplementation().search(
                    elementModel,
                    collectivite,
                    budget,
                    xpathQuery.toString(),
                    getSearchSession(searchSessionId).getLrm(),
                    critereModels, getXemeliosUser());
            // On conserve le dataResultSet pour pouvoir changer de page sans re-executer la recherche :
            getSearchSession(searchSessionId).setDataResultSet(dataResultSet);
            // On conserve aussi la collectivite et le budget :
            getSearchSession(searchSessionId).setCollectivite(collectivite);
            getSearchSession(searchSessionId).setBudget(budget);
            return dataResultToSearchResult(dataResultSet, getSearchSession(searchSessionId).getLrm(), collectivite, budget,searchSessionId);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    public SearchResultDTO loadPage(String searchSessionId, int pageNumber) {
        try {
            DataResultSet dataResultSet = getSearchSession(searchSessionId).getDataResultSet();
            dataResultSet.setPage(pageNumber);
            return dataResultToSearchResult(dataResultSet, getSearchSession(searchSessionId).getLrm(), getSearchSession(searchSessionId).getCollectivite(), getSearchSession(searchSessionId).getBudget(),searchSessionId);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private SearchResultDTO dataResultToSearchResult(DataResultSet dataResultSet, ListeResultatModel lrm,Pair collectivite, Pair budget,String searchSessionId) throws IOException, ParseException, SAXException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        SAXWriter writer = new SAXWriter(baos);
        writer.setPrettyPrint(false);
        writer.startDocument();
        AttributesImpl emptyAttrs = new AttributesImpl();
        writer.startElement(XMLConstants.NULL_NS_URI, "page", "page", emptyAttrs);
        writer.startElement(XMLConstants.NULL_NS_URI, "additional-data", "additional-data", emptyAttrs);
        AttributesImpl attr = new AttributesImpl();
        attr.addAttribute(XMLConstants.NULL_NS_URI, "value", "value", "String", Boolean.toString(lrm.getParent().getXslt()!=null));
        writer.startElement(
                XMLConstants.NULL_NS_URI, 
                "navigation", 
                "navigation", attr);
        writer.endElement(XMLConstants.NULL_NS_URI, "navigation", "navigation");
        writer.startElement(XMLConstants.NULL_NS_URI, "searchSessionId", "searchSessionId", emptyAttrs);
        writer.characters(searchSessionId.toCharArray(), 0, searchSessionId.length());
        writer.endElement(XMLConstants.NULL_NS_URI, "searchSessionId", "searchSessionId");
        attr = new AttributesImpl();
        attr.addAttribute(XMLConstants.NULL_NS_URI, "id", "id", "String", lrm.getParent().getId());
        writer.startElement(XMLConstants.NULL_NS_URI, "element", "element", attr);
        writer.endElement(XMLConstants.NULL_NS_URI, "element", "element");
        attr = new AttributesImpl();
        attr.addAttribute(XMLConstants.NULL_NS_URI, "id", "id", "String", lrm.getParent().getParent().getId());
        writer.startElement(XMLConstants.NULL_NS_URI, "etat", "etat", attr);
        writer.endElement(XMLConstants.NULL_NS_URI, "etat", "etat");
        attr = new AttributesImpl();
        attr.addAttribute(XMLConstants.NULL_NS_URI, "id", "id", "String", lrm.getParent().getParent().getParent().getId());
        writer.startElement(XMLConstants.NULL_NS_URI, "document", "document", attr);
        writer.endElement(XMLConstants.NULL_NS_URI, "document", "document");
        attr = new AttributesImpl();
        attr.addAttribute(XMLConstants.NULL_NS_URI, "id", "id", "String", jsEscape(collectivite.key));
        attr.addAttribute(XMLConstants.NULL_NS_URI,"lib","lib","String",jsEscape(collectivite.libelle));
        writer.startElement(XMLConstants.NULL_NS_URI, "collectivite", "collectivite", attr);
        writer.endElement(XMLConstants.NULL_NS_URI, "collectivite", "collectivite");
        attr = new AttributesImpl();
        attr.addAttribute(XMLConstants.NULL_NS_URI, "id", "id", "String", jsEscape(budget.key));
        attr.addAttribute(XMLConstants.NULL_NS_URI,"lib","lib","String",jsEscape(budget.libelle));
        writer.startElement(XMLConstants.NULL_NS_URI, "budget", "budget", attr);
        writer.endElement(XMLConstants.NULL_NS_URI, "budget", "budget");
        writer.endElement(XMLConstants.NULL_NS_URI, "additional-data", "additional-data");
//        writer.startElement(XMLConstants.NULL_NS_URI, "aggregates", "aggregates", emptyAttrs);
//        String[] datatypes = {ChampModel.DATATYPE_AMOUNT, ChampModel.DATATYPE_DATE, ChampModel.DATATYPE_DECIMAL,ChampModel.DATATYPE_DECIMAL,ChampModel.DATATYPE_INTEGER};
//        for(String datatype:datatypes) {
//            AttributesImpl attrs = new AttributesImpl();
//            attrs.addAttribute(XMLConstants.NULL_NS_URI, "datatype", "datatype", "String" , datatype);
//            writer.startElement(XMLConstants.NULL_NS_URI, "operators", "operators", attrs);
//            try {
//                Pair[] operators = DataLayerManager.getImplementation().getAggregateOperators(datatype);
//                for(Pair p:operators) {
//                    AttributesImpl at = new AttributesImpl();
//                    at.addAttribute(XMLConstants.NULL_NS_URI, "key", "key", "String", p.key);
//                    at.addAttribute(XMLConstants.NULL_NS_URI, "lib", "lib", "String", p.libelle);
//                    writer.startElement(XMLConstants.NULL_NS_URI, "op", "op", at);
//                    writer.endElement(XMLConstants.NULL_NS_URI, "op", "op");
//                }
//            } catch(Exception ex) {
//                logger.error("while writting operators of "+datatype);
//            }
//            writer.endElement(XMLConstants.NULL_NS_URI, "operators", "operators");
//        }
//        writer.endElement(XMLConstants.NULL_NS_URI, "aggregates", "aggregates");
        writer.startElement(XMLConstants.NULL_NS_URI, "columns", "columns", emptyAttrs);
        for (ChampModel champModel : lrm.getListeChamps()) {
            if(!champModel.isAfficheDefaut()) continue;
            attr = new AttributesImpl();
            attr.addAttribute(XMLConstants.NULL_NS_URI, "id", "id", "String", champModel.getId());
            attr.addAttribute(XMLConstants.NULL_NS_URI,"libelle","libelle","String",champModel.getLibelle());
            attr.addAttribute(XMLConstants.NULL_NS_URI,"datatype","datatype","String",champModel.getDatatype());
            attr.addAttribute(XMLConstants.NULL_NS_URI,"display","display","String",champModel.isAfficheDefaut() ? "true" : "false");
            attr.addAttribute(XMLConstants.NULL_NS_URI, "aggregate", "aggregate", "Boolean", champModel.isSommePossible()?"true":"false");
            writer.startElement(XMLConstants.NULL_NS_URI, "column", "column", attr);
            writer.endElement(XMLConstants.NULL_NS_URI, "column", "column");
        }
        attr = new AttributesImpl();
        attr.addAttribute(XMLConstants.NULL_NS_URI, "id", "id", "String", "docId");
        attr.addAttribute(XMLConstants.NULL_NS_URI,"libelle","libelle","String","docId");
        attr.addAttribute(XMLConstants.NULL_NS_URI,"datatype","datatype","String","string");
        attr.addAttribute(XMLConstants.NULL_NS_URI,"display","display","String","false");
        writer.startElement(XMLConstants.NULL_NS_URI, "column", "column", attr);
        writer.endElement(XMLConstants.NULL_NS_URI, "column", "column");
        for (HiddenModel hidden : lrm.getHiddens()) {
            attr = new AttributesImpl();
            attr.addAttribute(XMLConstants.NULL_NS_URI, "id", "id", "String", hidden.getName());
            attr.addAttribute(XMLConstants.NULL_NS_URI,"display","display","String","false");
            writer.startElement(XMLConstants.NULL_NS_URI, "column", "column", attr);
            writer.endElement(XMLConstants.NULL_NS_URI, "column", "column");
        }
        writer.endElement(XMLConstants.NULL_NS_URI, "columns", "columns");
        writer.startElement(XMLConstants.NULL_NS_URI, "values", "values", emptyAttrs);
        while (dataResultSet.hasNext()) {
            DataHandler dataHandler = dataResultSet.next();
            if(dataHandler!=null) {
                writer.startElement(XMLConstants.NULL_NS_URI, "line", "line", emptyAttrs);
                int i = 0;
                for (ChampModel champModel : lrm.getListeChamps()) {
                    if(!champModel.isAfficheDefaut()) {
                        // nothing to do, but do not continue !
                    } else {
                        Object value = dataHandler.getValueAtColumn(i);
                        String sVal = value != null ? value.toString() : null;
                        if(value instanceof PjRefHandler) {
                            PjRefHandler pjs = (PjRefHandler)value;
                            writer.startElement(XMLConstants.NULL_NS_URI, "cell", "cell", emptyAttrs);
                                writer.startElement(XMLConstants.NULL_NS_URI, "pj-ref-list", "pj-ref-list", emptyAttrs);
                                for(PJRefInfo pj:pjs.getPjs()) {
                                    AttributesImpl attrs = new AttributesImpl();
                                    attrs.addAttribute(XMLConstants.NULL_NS_URI, "id", "id", "string", pj.getId());
                                    attrs.addAttribute(XMLConstants.NULL_NS_URI, "nom", "nom", "string", pj.getNom());
                                    attrs.addAttribute(XMLConstants.NULL_NS_URI, "support", "support", "string", pj.getSupport());
                                    writer.startElement(XMLConstants.NULL_NS_URI, "pj", "pj", attrs);
                                    writer.endElement(XMLConstants.NULL_NS_URI, "pj", "pj");
                                }
                                writer.endElement(XMLConstants.NULL_NS_URI, "pj-ref-list", "pj-ref-list");
                            writer.endElement(XMLConstants.NULL_NS_URI, "cell", "cell");
                        } else {
                            if (value instanceof Date) {
                                SimpleDateFormat sdf = new SimpleDateFormat(ParametreManager.getDisplayDateFormat());
                                sVal = sdf.format((Date) value);
                                // TODO: eventuellement, passer la date en numerique (genre millisecondes depuis...) pour pouvoir trier sur la valeur numérique
                            }
                            // warning: now writer.characters escapes chars
                            char[] ch = /*(value != null ? SAXWriter.escapeText(sVal) : "").toCharArray()*/ (value!=null ? sVal : "").toString().toCharArray();
                            writer.startElement(XMLConstants.NULL_NS_URI, "cell", "cell", emptyAttrs);
                            writer.characters(ch, 0, ch.length);
                            writer.endElement(XMLConstants.NULL_NS_URI, "cell", "cell");
                        }
                    }
                    i++;
                }
                char[] ch = dataHandler.getDocumentId().toCharArray();
                writer.startElement(XMLConstants.NULL_NS_URI, "cell", "cell", emptyAttrs);
                writer.characters(ch, 0, ch.length);
                writer.endElement(XMLConstants.NULL_NS_URI, "cell", "cell");
                for (HiddenModel hidden : lrm.getHiddens()) {
                    Object value = dataHandler.getValueAtColumn(i);
                    String sVal = value != null ? value.toString() : "";
                    char[] ch2 = SAXWriter.escapeText(sVal).toCharArray();
                    writer.startElement(XMLConstants.NULL_NS_URI, "cell", "cell", emptyAttrs);
                    writer.characters(ch2, 0, ch2.length);
                    writer.endElement(XMLConstants.NULL_NS_URI, "cell", "cell");
                    i++;
                }
                writer.endElement(XMLConstants.NULL_NS_URI, "line", "line");
            }
        }
        writer.endElement(XMLConstants.NULL_NS_URI, "values", "values");
        writer.endElement(XMLConstants.NULL_NS_URI, "page", "page");
        writer.endDocument();
        SearchResultDTO toReturn = new SearchResultDTO();
        toReturn.setXml(baos.toString(SAXWriter.DEFAULT_ENCODING));
        PaginationInfoDTO paginationInfoDTO = new PaginationInfoDTO();
        paginationInfoDTO.setCurrentPageNumber(dataResultSet.getCurrentPage());
        paginationInfoDTO.setPageCount(dataResultSet.getEstimatedPageCount());
        toReturn.setCountAll(dataResultSet.getEstimatedSize());
        toReturn.setPaginationInfo(paginationInfoDTO);
//        logger.debug(toReturn);
        return toReturn;
    }

    private static String jsEscape(String s) {
        //String ret = s.replaceAll("'", "\\'").replaceAll("\"", "\\\"");
        StringBuffer ret = new StringBuffer();
        for(char c:s.toCharArray()) {
            switch(c) {
                case '\'': { ret.append("\\'"); break; }
                case '"' : { ret.append("\\\""); break; }
                default: ret.append(c);
            }
        }
        logger.debug(ret.toString());
        return ret.toString();
    }

    private void processSearchParameter(ElementModel elementModel, SearchParameterDTO searchParam, Vector<CritereModel> critereModels, StringBuffer xpathQuery,XemeliosUser user) throws SAXException {
        CritereModel critereModel = elementModel.getCritere(searchParam.getCritereModelId()).clone();
        if (searchParam.getOperator() != null) {
            critereModel.addChild(new PropertyModel("AND_OR", searchParam.getOperator()), PropertyModel.QN);
            if ("||".equals(searchParam.getOperator())) {
                xpathQuery.append("|").append(elementModel.getPath().getPath());
            }
        }
        String data = critereModel.getPath().getPath();
        int pos = data.indexOf("##");
        while (pos >= 0) {
            int endPos = data.indexOf("##", pos + 2);
            String pattern = data.substring(pos, endPos + 2);
            String paramName = pattern.substring(2, pattern.length() - 2);
//            logger.debug("replacing " + paramName);
            String paramValue = searchParam.getValue(paramName);
            if (paramValue != null) {
                // est-ce que le champ est un input et est-il de type date
                for (XmlMarshallable champ : critereModel.getInputs()) {
                    if (champ instanceof InputModel) {
                        InputModel input = (InputModel) champ;
                        if ("VALEUR:".concat(input.getId()).equals(paramName)) {
                            // on enregistre la valeur dans les lastInput
                            try {
                                DataLayerManager.getImplementation().setLastInputValue(input.getId(),paramValue,user);
                            } catch(Exception ex) {
                                throw new RuntimeException(ex);
                            }
                            if ("date".equals(input.getDatatype())) {
                                // il faut convertir la date
                                SimpleDateFormat sdf = new SimpleDateFormat(ParametreManager.getDisplayDateFormat());
                                Date d;
                                try {
                                    d = sdf.parse(paramValue);
                                    sdf = new SimpleDateFormat(input.getXmlDateFormat());
                                    paramValue = sdf.format(d);
                                } catch (ParseException ex) {
                                    logger.info("invalid date format: " + paramValue);
                                    paramValue = "\"\"";
                                }
                            }
                            break;
                        } else if("OPERATEUR:".concat(input.getId()).equals(paramName)) {
                            try {
                                DataLayerManager.getImplementation().setLastInputValue(input.getId().concat(".OPERATEUR"),paramValue,user);
                            } catch(Exception ex) {
                                throw new RuntimeException(ex);
                            }

                        }
                    }
                }
                data = data.replace(pattern, paramValue);
            } else {
                data = data.replace(pattern, "\"\"");
            }
            pos = data.indexOf("##");
            critereModel.addChild(new PropertyModel(paramName, paramValue), PropertyModel.QN);
        }
        data = data.replaceAll("\\{", "[").replaceAll("\\}", "]");
        critereModels.add(critereModel);
        xpathQuery.append(data);
    }

    private CritereModel getCritereModel(String searchSessionId, String critereId, String pluginId) {
        if (pluginId == null) {
            return getSearchSession(searchSessionId).getElementModel().getCritere(critereId);
        } else {
            for (CritereModel cm : getPluginModel(searchSessionId, pluginId).getCriteres()) {
                if (cm.getId().equals(critereId)) {
                    return cm;
                }
            }
            // On a pas trouve dans les criteres, on cherche dans les criteres optionnels :
            for (CritereRefModel crm : getPluginModel(searchSessionId, pluginId).getCriteresOp()) {
                CritereModel cm = getSearchSession(searchSessionId).getElementModel().getCritere(crm.getIdRef());
                if (cm.getId().equals(critereId)) {
                    return cm;
                }
            }
        }
        return null;
    }

    private PluginModel getPluginModel(String searchSessionId, String pluginId) {
        try {
            String persistence = DataLayerManager.getImplementation().getLayerName();
            for (PluginModel pm : getSearchSession(searchSessionId).getElementModel().getPlugins()) {
                if ((pm.getPluginType().equals(PluginModel.TYPE_SEARCH) || pm.getPluginType().equals(PluginModel.TYPE_EXPORT)) && pm.isAvailableFor(persistence) && pluginId.equals(pm.getId())) {
                    return pm;
                }
            }
            return null;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public SearchInitParametersDTO getSearchInitParameters(String searchSessionId, String pluginId) {
        try {
            PluginModel pm = getPluginModel(searchSessionId, pluginId);
            if (pm != null) {
                List<CritereTypeDTO> requiredCriteresTypes = new ArrayList<CritereTypeDTO>();
                for (CritereModel cm : pm.getCriteres()) {
                    requiredCriteresTypes.add(new CritereTypeDTO(cm.getId(), cm.getLibelle()));
                }
                List<CritereTypeDTO> optionalCriteresTypes = new ArrayList<CritereTypeDTO>();
                for (CritereRefModel crm : pm.getCriteresOp()) {
                    CritereModel cm = getSearchSession(searchSessionId).getElementModel().getCritere(crm.getIdRef());
                    optionalCriteresTypes.add(new CritereTypeDTO(cm.getId(), cm.getLibelle()));
                }
                return new SearchInitParametersDTO(requiredCriteresTypes, optionalCriteresTypes, pluginId, pluginTypeStrToInt(pm.getPluginType()));
            }
            return null;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public SearchInitParametersDTO getSearchInitParametersForSavedSearch(String searchSessionId, String searchId) {
        try {
            SearchSession ss = getSearchSession(searchSessionId);
            XemeliosUser user = getXemeliosUser();
            ElementModel em = ss.getElementModel();
            RechercheModel rm = DataLayerManager.getImplementation().getSavedRequest(em, user, searchId);
            List<CritereTypeDTO> requiredCriteresTypes = new ArrayList<CritereTypeDTO>();
            for (CritereModel cm : rm.getCriteres()) {
                Properties props = new Properties();
                CritereTypeDTO critere = new CritereTypeDTO(cm.getId(), cm.getLibelle());
                for(PropertyModel pm:cm.getValeurs()) {
                    critere.addProperty(pm.getName(), pm.getValue());
                }
                requiredCriteresTypes.add(critere);
            }
            return new SearchInitParametersDTO(requiredCriteresTypes/*,getCriteres(searchSessionId)*/);
        } catch(Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    public List<StoredSearchDTO> getStoredSearchs(String searchSessionId) {
        try {
            String persistence = DataLayerManager.getImplementation().getLayerName();
            List<StoredSearchDTO> toReturn = new ArrayList<StoredSearchDTO>();
            for (PluginModel pm : getSearchSession(searchSessionId).getElementModel().getPlugins()) {
                if (pm.getPluginType().equals(PluginModel.TYPE_SEARCH) && pm.isAvailableFor(persistence)) {
                    toReturn.add(new StoredSearchDTO(pm.getId(), pm.getDisplayName(), StoredSearchDTO.TYPE_SEARCH, pm.getResource()));
                } else if (pm.getPluginType().equals(PluginModel.TYPE_EXPORT) && pm.isAvailableFor(persistence)) {
                    toReturn.add(new StoredSearchDTO(pm.getId(), pm.getDisplayName(), StoredSearchDTO.TYPE_EXPORT, pm.getResource()));
                }
            }
            for(RechercheModel rm:DataLayerManager.getImplementation().getSavedRequests(getSearchSession(searchSessionId).getElementModel(), getXemeliosUser(), false)) {
                toReturn.add(new StoredSearchDTO(rm.getId(),rm.getDisplayName(),StoredSearchDTO.TYPE_SAVED,rm.getResource()));
            }

            Collections.sort(toReturn, new Comparator<StoredSearchDTO>() {

                public int compare(StoredSearchDTO o1, StoredSearchDTO o2) {
                    return o1.getLibelle().compareToIgnoreCase(o2.getLibelle());
                }
            });
            return toReturn;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public List<StoredSearchDTO> getSavedPrivateSearchs(String searchSessionId) {
        try {
            String persistence = DataLayerManager.getImplementation().getLayerName();
            List<StoredSearchDTO> toReturn = new ArrayList<StoredSearchDTO>();
            for(RechercheModel rm:DataLayerManager.getImplementation().getSavedRequests(getSearchSession(searchSessionId).getElementModel(), getXemeliosUser(), false)) {
                toReturn.add(new StoredSearchDTO(rm.getId(),rm.getDisplayName(),StoredSearchDTO.TYPE_SAVED,rm.getResource()));
            }

            Collections.sort(toReturn, new Comparator<StoredSearchDTO>() {

                public int compare(StoredSearchDTO o1, StoredSearchDTO o2) {
                    return o1.getLibelle().compareToIgnoreCase(o2.getLibelle());
                }
            });
            return toReturn;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public boolean isPjAvailable(String collectivite, String pjNom) {
        try {
            return DataLayerManager.getImplementation().isPjAvailable(new Pair(collectivite,""), pjNom, getXemeliosUser());
        } catch(Exception ex) {
            logger.error("in isPjAvailable("+collectivite+","+pjNom+")",ex);
            return false;
        }
    }

    public String saveRequest(String searchSessionId, List<SearchParameterDTO> searchParams, String requestName) {
        String ret = "OK";
        Exception exceptionThrown = null;
        SearchSession searchSession = getSearchSession(searchSessionId);
        XemeliosUser user = getXemeliosUser();
        StringBuffer xpathQuery = new StringBuffer();
        Vector<CritereModel> critereModels = new Vector<CritereModel>();
        try {
            for(SearchParameterDTO searchParam:searchParams) {
                processSearchParameter(searchSession.getElementModel(), searchParam, critereModels, xpathQuery, user);
            }
        } catch(SAXException saxEx) {
            exceptionThrown = saxEx;
        }
        RechercheModel rm = new RechercheModel(RechercheModel.QN);
        rm.setName(requestName);
        for(CritereModel cm:critereModels) try { rm.addChild(cm, CritereModel.QN); } catch(Exception ex) { exceptionThrown = ex; }
        if(exceptionThrown==null) {
            try {
                DataLayerManager.getImplementation().saveRequest(searchSession.getElementModel(), rm, user);
            } catch (DataConfigurationException ex) {
                exceptionThrown = ex;
            }
        }
        if(exceptionThrown!=null) {
            logger.error("saveRequest(String,List<SearchParameterDTO>,String)",exceptionThrown);
            ret = "ERROR";
        }
        return ret;
    }
    public String saveRequest(String searchSessionId, List<SearchParameterDTO> searchParams, String requestName, String requestId) {
        String ret = "OK";
        Exception exceptionThrown = null;
        SearchSession searchSession = getSearchSession(searchSessionId);
        XemeliosUser user = getXemeliosUser();
        StringBuffer xpathQuery = new StringBuffer();
        Vector<CritereModel> critereModels = new Vector<CritereModel>();
        try {
            for(SearchParameterDTO searchParam:searchParams) {
                processSearchParameter(searchSession.getElementModel(), searchParam, critereModels, xpathQuery, user);
            }
        } catch(SAXException saxEx) {
            exceptionThrown = saxEx;
        }
        RechercheModel rm = new RechercheModel(RechercheModel.QN);
        rm.setName(requestName);
        rm.setId(requestId);
        for(CritereModel cm:critereModels) try { rm.addChild(cm, CritereModel.QN); } catch(Exception ex) { exceptionThrown = ex; }
        if(exceptionThrown==null) {
            try {
                DataLayerManager.getImplementation().saveRequest(searchSession.getElementModel(), rm, user);
            } catch (DataConfigurationException ex) {
                exceptionThrown = ex;
            }
        }
        if(exceptionThrown!=null) {
            logger.error("saveRequest(String,List<SearchParameterDTO>,String,String)",exceptionThrown);
            ret = "ERROR";
        }
        return ret;
    }
    public String processPluginExport(String searchSessionId, String collectiviteId, String budgetId, String pluginId, List<SearchParameterDTO> searchParams) {
        // TODO
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        String mimeType = null;
        try {
            Pair collectivite = XemeliosHelper.getCollectivite(getXemeliosUser(), getSearchSession(searchSessionId).getDocumentModel(), collectiviteId);
            Pair budget = XemeliosHelper.getBudget(getXemeliosUser(), getSearchSession(searchSessionId).getDocumentModel(), collectiviteId, budgetId);
            PluginModel plugin = getPluginModel(searchSessionId, pluginId);
            Class clazz = Class.forName(plugin.getPluginClass());
            Constructor cc = clazz.getConstructor(XemeliosUser.class);
            ExportPlugin xp = (ExportPlugin) cc.newInstance(getXemeliosUser());
            xp.setPluginModel(plugin);
            xp.setCollectivite(collectivite);
            xp.setBudget(budget);
            xp.setParameters(getParameters(searchParams));
            xp.setElementModel(getSearchSession(searchSessionId).getElementModel());
            xp.setOutputStream(baos);
            // To avoid NPE in plugin implementations
            xp.setProgressListener(new ExportPlugin.ProgressListener() {
                public void setProgressInfos(int arg0, String msg) {
                }
                public void pushProgress() {
                }
                public void setProgressVisible(boolean arg0) {
                }
            });
            xp.computeParameters();
            mimeType = ViewAttachmentAction.MIME_TYPES.get(xp.getDefaultFileExtension().toLowerCase());
            xp.doExport();
            baos.flush();
            baos.close();
        } catch(Exception ex) {
            logger.error("processPluginExport(String,List<SearchParameterDTO>)",ex);
        }
        getThreadLocalRequest().getSession().setAttribute("export-"+searchSessionId, new ExportResult(baos.toByteArray(),mimeType));
        return "getExportResult.do?searchSessionId="+searchSessionId;
    }

    private static int pluginTypeStrToInt(String pluginType) {
        if(PluginModel.TYPE_EXPORT.equals(pluginType)) return StoredSearchDTO.TYPE_EXPORT;
        else if(PluginModel.TYPE_SEARCH.equals(pluginType)) return StoredSearchDTO.TYPE_SEARCH;
        else return 0;
    }

    public ListResultatDTO getListResultatDTO(String searchSessionId) {
        SearchSession ss = getSearchSession(searchSessionId);
        ListeResultatModel lrm = ss.getLrm();
        ListResultatDTO ret = new ListResultatDTO();
        String _ordre = lrm.getTriDefaut();
        String[] ordre = _ordre.split(",");
        String[] sort = lrm.getOrdre().split(",");
        ArrayList<DisplayableFieldDTO> sortedFields = new ArrayList<DisplayableFieldDTO>();
        for(ChampModel cm:lrm.getListeChamps()) {
            if(!cm.isAffichable()) continue;
            DisplayableFieldDTO field = new DisplayableFieldDTO(cm.getId(), cm.getLibelle(), cm.isAfficheDefaut());
            int ordrePos = findInStringArray(ordre,field.getId());
            if(ordrePos>=0) {
                field.setSortPosition(ordrePos);
                field.setSortOrder("DESC".equals(sort[ordrePos])?DisplayableFieldDTO.SORT_ORDER_DESC:DisplayableFieldDTO.SORT_ORDER_ASC);
                sortedFields.add(field);
            }
            ret.addField(field);
        }
        Collections.sort(sortedFields, new Comparator<DisplayableFieldDTO>() {
            public int compare(DisplayableFieldDTO o1, DisplayableFieldDTO o2) {
                return o1.getSortPosition()-o2.getSortPosition();
            }
        });
        for(DisplayableFieldDTO field:sortedFields) ret.addSortField(field);
        return ret;
    }
    private int findInStringArray(String[] array,String what) {
        int ret = -1;
        for(int i=0;i<array.length;i++) {
            if(what.equals(array[i])) {
                ret = i;
                break;
            }
        }
        return ret;
    }
    public String setListResultatDTO(String searchSessionId, ListResultatDTO lr) {
//        logger.debug(lr);
        try {
            SearchSession ss = getSearchSession(searchSessionId);
            ListeResultatModel lrm = ss.getLrm();
            ArrayList<DisplayableFieldDTO> sortedFieldsLst = new ArrayList<DisplayableFieldDTO>();
            for(DisplayableFieldDTO field:lr.getDisplayableFields()) sortedFieldsLst.add(field);
            Collections.sort(sortedFieldsLst, new Comparator<DisplayableFieldDTO>() {
                public int compare(DisplayableFieldDTO o1, DisplayableFieldDTO o2) {
                    return o1.getSortPosition()-o2.getSortPosition();
                }
            });
            StringBuffer sortedFields = new StringBuffer();
            StringBuffer sortOrders = new StringBuffer();
            Vector<ChampModel> modified = new Vector<ChampModel>();
            for(int i=0;i<lr.getDisplayableFields().size();i++) {
                DisplayableFieldDTO field = lr.getDisplayableFields().get(i);
                ChampModel cm = lrm.getChamps().get(field.getId());
                if(cm==null) throw new Exception("Champ "+field.getId()+" non trouvé !");
                else cm = cm.clone();
                if(field.getSortOrder()!=DisplayableFieldDTO.SORT_ORDER_NO_SORT && field.getSortPosition()>=0 && !field.isDisplay()) {
                    throw new Exception(field.getId()+": on ne peut pas trier sur un champ non affiché.");
                }
                cm.setAfficheDefaut(field.isDisplay());
                // tris
                DisplayableFieldDTO field2 = sortedFieldsLst.get(i);
                if(field2.getSortPosition()>=0) {
                    sortedFields.append(field2.getId()).append(",");
                    sortOrders.append(field2.getSortOrder()==DisplayableFieldDTO.SORT_ORDER_DESC?"DESC":"ASC").append(",");
                }
                modified.add(cm);
            }
            if(sortedFields.length()>0) sortedFields.deleteCharAt(sortedFields.length()-1);
            if(sortOrders.length()>0) sortOrders.deleteCharAt(sortOrders.length()-1);
            lrm.setTriDefaut(sortedFields.toString());
            lrm.setOrdre(sortOrders.toString());
            lrm.changeAllDisplayableChamps(modified);
//            logger.debug(lrm);
        } catch(Exception ex) {
            return ex.getMessage();
        }
        return null;
    }

    public List<String> getDistinctValues(String searchSessionId, String collectivite, String budget, String path) {
        SearchSession searchSession = getSearchSession(searchSessionId);
        XemeliosUser user = getXemeliosUser();
        ElementModel element = searchSession.getElementModel();
        Collection<String> coll = new Vector<String>();
        try {
            coll = DataLayerManager.getImplementation().getDistinctValues(element, new Pair(collectivite,collectivite), new Pair(budget,budget), path, user);
        } catch(Exception ex) {
            logger.error("getDistinctValues("+searchSessionId+","+collectivite+","+budget+","+path+")",ex);
            coll.add(ex.getMessage());
        }
        ArrayList<String> list = new ArrayList<String>();
        list.addAll(coll);
        return list;
    }

    public DocumentsDTO getDocuments() {
        XemeliosUser user = getXemeliosUser();
        DocumentsModel docsModel = null;
        try {
            docsModel = Loader.getDocumentsInfos(null);
        } catch(Exception ex) {}
        DocumentsDTO ret = new DocumentsDTO();
        if(docsModel!=null) {
            for(DocumentModel dm:docsModel.getDocuments()) {
                DocumentDTO doc = new DocumentDTO(dm.getId(), dm.getTitre());
                for(EtatModel em:dm.getEtats()) {
                    if(em.isExportable()) {
                        EtatDTO etat = new EtatDTO(em.getId(), em.getTitre(), em.isExportable(), em.isMultiPage());
                        doc.addEtat(etat);
                    }
                }
                ret.addDocument(doc);
            }
        }
        return ret;
    }

    public List<ConfigExportDTO> getListeConfigExport(String docId, String etatId) {
        XemeliosUser user = getXemeliosUser();
        ArrayList<ConfigExportDTO> ret = new ArrayList<ConfigExportDTO>();
        try {
            Vector<ConfigModel> configs = DataLayerManager.getImplementation().getListeConfigsExport(docId, etatId, user);
            for(ConfigModel cfg:configs) {
                ConfigExportDTO dto = new ConfigExportDTO(cfg.getId(),cfg.getDocID(), cfg.getEtatID(),cfg.getOwner(),cfg.getDateCreation(),cfg.getCode(), cfg.getLibelle());
                ret.add(dto);
            }
        } catch(Exception ex) {
            logger.error("getListeConfigExport(String,String",ex);
        }
        return ret;
    }

    public ConfigExportDTO getExportModel(String docId, String etatId, String id) {
        XemeliosUser user = getXemeliosUser();
        ConfigExportDTO ret = null;
        try {
            ConfigModel cfg = DataLayerManager.getImplementation().getConfigExport(docId,etatId,id,user);
            ret = new ConfigExportDTO(cfg.getId(),cfg.getDocID(), cfg.getEtatID(),cfg.getOwner(),cfg.getDateCreation(),cfg.getCode(), cfg.getLibelle());
            ExportModel em = cfg.getExportModel();
            ExportModelDTO emd = new ExportModelDTO(cfg.getDocID(), cfg.getEtatID(), em.getId(), em.getLibelle(), em.getCode());
            ExportElementModel eem=em.getEnfants().get(0);
            ExportElementDTO element = new ExportElementDTO(eem.getId(), eem.getLibelle());
            for(ExportChampModel ecm:eem.getChamps()) {
                ExportChampDTO ecd = new ExportChampDTO(ecm.getId(), ecm.getLibelle(), ecm.isEditable(), ecm.isSelectionne(), ecm.isIdentifiant());
                element.addChamp(ecd);
            }
            // check if all champs are in export model
            DocumentsModel docsModel = null;
            try {
                docsModel = Loader.getDocumentsInfos(null);
            } catch(Exception ex) {}
            EtatModel etat = docsModel.getDocumentById(docId).getEtatById(etatId);
            ElementModel elMo = etat.getElementById(eem.getId());
            for(ChampModel cmo:elMo.getListeResultat().getListeChamps()) {
                if(element.getChampById(cmo.getId())==null) {
                    ExportChampDTO champ = new ExportChampDTO(cmo.getId(), cmo.getLibelle(), false, false, cmo.isIdentifiant());
                    element.addChamp(champ);
                }
            }
            for(ExportElementModel ssElement:eem.getEnfants()) {
                element.addEnfant(convertExportElementModelToExportElementDTO(ssElement,etat));
            }
            // TODO: add newly created children
            emd.setElement(element);
            ret.setExportModel(emd);
        } catch(Exception ex) {
            logger.error("getExportModel(String,String,String)",ex);
        }
        return ret;
    }
    public ConfigExportDTO createExportModel(String docId, String etatId) {
        XemeliosUser user = getXemeliosUser();
        ConfigExportDTO ret = null;
        try {
            ret = new ConfigExportDTO(null,docId, etatId,null,null,"", "");
            ExportModelDTO emd = new ExportModelDTO(docId, etatId, null, "", "");
            DocumentsModel docsModel = null;
            try {
                docsModel = Loader.getDocumentsInfos(null);
            } catch(Exception ex) {}
            EtatModel etat = docsModel.getDocumentById(docId).getEtatById(etatId);
            ElementModel elMo = etat.getImportableElement();
            ExportElementDTO element = new ExportElementDTO(elMo.getId(), elMo.getTitre());
            for(ChampModel cmo:elMo.getListeResultat().getListeChamps()) {
                if(cmo.isExportable()) {
                    ExportChampDTO champ = new ExportChampDTO(cmo.getId(), cmo.getLibelle(), false, false, cmo.isIdentifiant());
                    element.addChamp(champ);
                }
            }
            for(ElementModel ssElement:elMo.getSousElements()) {
                element.addEnfant(convertElementModelToExportElementDTO(ssElement));
            }
            emd.setElement(element);
            ret.setExportModel(emd);
        } catch(Exception ex) {
            logger.error("createExportModel(String,String)",ex);
        }
        return ret;
    }

    private static ExportElementDTO convertExportElementModelToExportElementDTO(ExportElementModel eem,EtatModel etat) {
        ExportElementDTO ret = new ExportElementDTO(eem.getId(),eem.getLibelle());
        for(ExportChampModel ecm:eem.getChamps()) {
            ret.addChamp(new ExportChampDTO(ecm.getId(), ecm.getLibelle(), ecm.isEditable(), ecm.isSelectionne(), ecm.isIdentifiant()));
        }
        ElementModel elMo = etat.getElementById(eem.getId());
            for(ChampModel cmo:elMo.getListeResultat().getListeChamps()) {
                if(cmo.isExportable() && ret.getChampById(cmo.getId())==null) {
                    ExportChampDTO champ = new ExportChampDTO(cmo.getId(), cmo.getLibelle(), false, false, cmo.isIdentifiant());
                    ret.addChamp(champ);
                }
            }
        for(ExportElementModel eem2:eem.getEnfants()) {
            ret.addEnfant(convertExportElementModelToExportElementDTO(eem2,etat));
        }
        return ret;
    }
    private ExportElementDTO convertElementModelToExportElementDTO(ElementModel el) {
        ExportElementDTO ret = new ExportElementDTO(el.getId(),el.getTitre());
        for(ChampModel champ:el.getListeResultat().getListeChamps()) {
            if(champ.isExportable()) {
                ret.addChamp(new ExportChampDTO(champ.getId(), champ.getLibelle(), false, false, champ.isIdentifiant()));
            }
        }
        for(ElementModel ssEl:el.getSousElements()) {
            ret.addEnfant(convertElementModelToExportElementDTO(ssEl));
        }
        return ret;
    }

    public String saveExportModel(ConfigExportDTO config) {
        String ret = "OK";
        XemeliosUser user = getXemeliosUser();
        try {
            ConfigModel cfg = createConfigModel(config);
            if(cfg.getId()!=null) {
                DataLayerManager.getImplementation().updateConfigExport(cfg, user);
            } else {
                DataLayerManager.getImplementation().insertConfigExport(cfg, user);
            }
        } catch(Exception ex) {
            logger.error("saveExportModel(ConfigExportDTO)",ex);
            ret="ERROR";
        }
        return ret;
    }
    private ConfigModel createConfigModel(ConfigExportDTO config) {
        ConfigModel cfg = new ConfigModel(ConfigModel.QN);
        cfg.setCode(config.getCode());
        cfg.setLibelle(config.getLibelle());
        cfg.setDocID(config.getDocID());
        cfg.setEtatID(config.getEtatID());
        if(config.getDateCreation()!=null) {
            cfg.setDateCreation(config.getDateCreation());
        }
        if(config.getId()!=null) {
            cfg.setId(config.getId());
        }
        ExportModel em = new ExportModel(ExportModel.QN);
        em.setId(cfg.getCode());
        em.setCode(cfg.getCode());
        em.setLibelle(cfg.getLibelle());
        em.addEnfant(createExportElementModel(config.getExportModel()));
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            OutputStreamWriter os = new OutputStreamWriter(baos,"ISO-8859-1");
            XmlOutputter xo = new XmlOutputter(os,"ISO-8859-1");
            em.marshall(xo);
            os.flush();
        } catch(Exception ex) {
            logger.error("marshalling em",ex);
        }
        cfg.setConfigLines(baos.toByteArray());
        return cfg;
    }
    private ExportElementModel createExportElementModel(ExportModelDTO m) {
        return createExportElementModel(m.getElement());
    }
    private ExportElementModel createExportElementModel(ExportElementDTO m) {
        m.ensureKeysSelected();
        ExportElementModel eem = new ExportElementModel(ExportElementModel.QN);
        eem.setId(m.getId());
        eem.setLibelle(m.getLibelle());
        for(ExportChampDTO champ:m.getChamps()) {
            ExportChampModel ecm = new ExportChampModel(ExportChampModel.QN);
            ecm.setId(champ.getId());
            ecm.setLibelle(champ.getLibelle());
            ecm.setEditable(champ.isEditable());
            ecm.setIdentifiant(champ.isIdentifiant());
            ecm.setSelectionne(champ.isSelectionne());
            eem.addChamp(ecm);
        }
        for(ExportElementDTO ee:m.getEnfants()) {
            eem.addEnfant(createExportElementModel(ee));
        }
        return eem;
    }

    public String deleteExportModel(String docId, String etatId, String id) {
        String ret = "OK";
        ConfigModel cm = new ConfigModel(ConfigModel.QN);
        cm.setDocID(docId);
        cm.setEtatID(etatId);
        cm.setId(id);
        XemeliosUser user = getXemeliosUser();
        try {
            DataLayerManager.getImplementation().deleteConfigExport(cm, user);
        } catch(Exception ex) {
            logger.error("deleteExportModel(String,String,String)",ex);
            ret = "ERROR - "+ex.getMessage();
        }
        return ret;
    }
    public SearchSessionDTO getSearchSessionInformations(String searchSessionId) {
        logger.debug("getSearchSessionInformations("+searchSessionId+")");
        return getSearchSession(searchSessionId).getSearchSessionDTO();
    }
    public String createExportRequest(String searchSessionId, String exportConfigId, String fileName, String exportName) {
        String ret = "ERROR";
        SearchSession searchSession = getSearchSession(searchSessionId);
        XemeliosUser user = getXemeliosUser();
        ElementModel element = searchSession.getElementModel();
        try {
            ExportJob ej = new ExportJob();
            ej.setDataResultSet(searchSession.getDataResultSet());
            ej.setDocumentId(searchSession.getDocumentId());
            ej.setEtatId(searchSession.getEtatId());
            ej.setElementId(searchSession.getElementId());
            ej.setExportConfigId(Long.parseLong(exportConfigId));
            ej.setExportName(exportName);
            ej.setFileName(fileName);
            ej.setSize(searchSession.getDataResultSet().getEstimatedSize());
            DataLayerManager.getImplementation().createExportJob(ej,user);
            ret = "OK";
        } catch(Exception ex) {
            logger.error("createExportRequest(String,String,String,String)",ex);
        }
        return ret;
    }

    public String getDocumentType(String searchSessionId) {
        return getSearchSession(searchSessionId).getDocumentId();
    }
}
