001/*
002 * (C) Copyright 2006-2007 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Nuxeo - initial API and implementation
018 *
019 * $Id$
020 */
021
022package org.nuxeo.ecm.platform.ui.web.directory;
023
024import java.io.Serializable;
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.LinkedHashMap;
028import java.util.LinkedList;
029import java.util.List;
030import java.util.Locale;
031import java.util.Map;
032import java.util.Set;
033
034import javax.faces.context.FacesContext;
035
036import org.apache.commons.logging.Log;
037import org.apache.commons.logging.LogFactory;
038import org.nuxeo.common.utils.i18n.I18NUtils;
039import org.nuxeo.ecm.core.api.DocumentModel;
040import org.nuxeo.ecm.core.api.DocumentModelList;
041import org.nuxeo.ecm.core.api.NuxeoException;
042import org.nuxeo.ecm.directory.DirectoryException;
043import org.nuxeo.ecm.directory.Session;
044import org.nuxeo.ecm.directory.api.DirectoryService;
045import org.nuxeo.runtime.api.Framework;
046
047/**
048 * @author <a href="mailto:glefter@nuxeo.com">George Lefter</a>
049 */
050public final class DirectoryHelper {
051
052    private static final Log log = LogFactory.getLog(DirectoryHelper.class);
053
054    /** Directory with a parent column. */
055    public static final String XVOCABULARY_TYPE = "xvocabulary";
056
057    public static final String[] VOCABULARY_TYPES = { "vocabulary", XVOCABULARY_TYPE };
058
059    private static final String[] displayOptions = { "id", "label", "idAndLabel" };
060
061    private static DirectoryHelper instance;
062
063    private static DirectoryService service;
064
065    private DirectoryHelper() {
066    }
067
068    public static DirectoryHelper instance() {
069        if (null == instance) {
070            instance = new DirectoryHelper();
071        }
072        return instance;
073    }
074
075    public boolean hasParentColumn(String directoryName) {
076        try {
077            return XVOCABULARY_TYPE.equals(getService().getDirectorySchema(directoryName));
078        } catch (DirectoryException e) {
079            // GR: our callers can't throw anything. Better to catch here, then
080            log.error("Could not retrieve schema name for directory: " + directoryName, e);
081            return false;
082        }
083    }
084
085    public DirectorySelectItem getSelectItem(String directoryName, Map<String, Serializable> filter) {
086        List<DirectorySelectItem> items = getSelectItems(directoryName, filter);
087        if (items.size() > 1) {
088            log.warn("More than one entry found in directory " + directoryName + " and filter:");
089            for (Map.Entry<String, Serializable> e : filter.entrySet()) {
090                log.warn(String.format("%s=%s", e.getKey(), e.getValue()));
091            }
092        } else if (items.isEmpty()) {
093            return null;
094        }
095        return items.get(0);
096    }
097
098    public List<DirectorySelectItem> getSelectItems(String directoryName, Map<String, Serializable> filter) {
099        return getSelectItems(directoryName, filter, Boolean.FALSE);
100    }
101
102    public List<DirectorySelectItem> getSelectItems(String directoryName, Map<String, Serializable> filter,
103            Boolean localize) {
104        List<DirectorySelectItem> list = new LinkedList<DirectorySelectItem>();
105
106        Set<String> emptySet = Collections.emptySet();
107
108        Map<String, String> orderBy = new LinkedHashMap<String, String>();
109
110        FacesContext context = FacesContext.getCurrentInstance();
111
112        // an extended schema also has parent field
113        try (Session session = getService().open(directoryName)) {
114            String schema = getService().getDirectorySchema(directoryName);
115            if (session == null) {
116                throw new NuxeoException("could not open session on directory: " + directoryName);
117            }
118
119            // adding sorting support
120            // XXX It seems that this ordering is obsolete as
121            // DirectoryAwareComponent made it's own (NXP-7349)
122            if (schema.equals(VOCABULARY_TYPES[0]) || schema.equals(VOCABULARY_TYPES[1])) {
123                orderBy.put("ordering", "asc");
124                orderBy.put("id", "asc");
125            }
126
127            DocumentModelList docModelList = session.query(filter, emptySet, orderBy);
128
129            for (DocumentModel docModel : docModelList) {
130                String id = (String) docModel.getProperty(schema, "id");
131                String label = (String) docModel.getProperty(schema, "label");
132                long ordering = (Long) docModel.getProperty(schema, "ordering");
133
134                if (Boolean.TRUE.equals(localize)) {
135                    label = translate(context, label);
136                }
137                DirectorySelectItem item = new DirectorySelectItem(id, label, ordering);
138                list.add(item);
139            }
140
141        }
142
143        return list;
144    }
145
146    public static DirectoryService getDirectoryService() {
147        return instance().getService();
148    }
149
150    public static List<DirectorySelectItem> getSelectItems(VocabularyEntryList directoryValues,
151            Map<String, Serializable> filter) {
152        return getSelectItems(directoryValues, filter, Boolean.FALSE);
153    }
154
155    public static List<DirectorySelectItem> getSelectItems(VocabularyEntryList directoryValues,
156            Map<String, Serializable> filter, Boolean localize) {
157        List<DirectorySelectItem> list = new ArrayList<DirectorySelectItem>();
158
159        // in obsolete filter we have either null (include obsoletes)
160        // or 0 (don't include obsoletes)
161        boolean obsolete = filter.get("obsolete") == null;
162        String parentFilter = (String) filter.get("parent");
163
164        FacesContext context = FacesContext.getCurrentInstance();
165        for (VocabularyEntry entry : directoryValues.getEntries()) {
166            if (!obsolete && Boolean.TRUE.equals(entry.getObsolete())) {
167                continue;
168            }
169            String parent = entry.getParent();
170            if (parentFilter == null) {
171                if (parent != null) {
172                    continue;
173                }
174            } else if (!parentFilter.equals(parent)) {
175                continue;
176            }
177
178            String idValue = (String) filter.get("id");
179            if (idValue != null && !idValue.equals(entry.getId())) {
180                continue;
181            }
182            String id = entry.getId();
183            String label = entry.getLabel();
184            if (Boolean.TRUE.equals(localize)) {
185                label = translate(context, label);
186            }
187            DirectorySelectItem item = new DirectorySelectItem(id, label);
188            list.add(item);
189        }
190        return list;
191    }
192
193    public static String getOptionValue(String optionId, String optionLabel, String display, boolean displayIdAndLabel,
194            String displayIdAndLabelSeparator) {
195        StringBuilder displayValue = new StringBuilder();
196        if (display != null && !"".equals(display)) {
197            if (display.equals(displayOptions[0])) {
198                displayValue.append(optionId);
199            } else if (display.equals(displayOptions[1])) {
200                displayValue.append(optionLabel);
201            } else if (display.equals(displayOptions[2])) {
202                displayValue.append(optionId).append(displayIdAndLabelSeparator).append(optionLabel);
203            } else {
204                displayValue.append(optionLabel);
205            }
206        } else if (displayIdAndLabel) {
207            displayValue.append(optionId).append(displayIdAndLabelSeparator).append(optionLabel);
208        } else {
209            displayValue.append(optionLabel);
210        }
211        return displayValue.toString();
212    }
213
214    private static DocumentModel getEntryThrows(String directoryName, String entryId) {
215        DirectoryService dirService = getDirectoryService();
216        try (Session session = dirService.open(directoryName)) {
217            return session.getEntry(entryId);
218        }
219    }
220
221    /**
222     * Returns the entry with given id from specified directory.
223     * <p>
224     * Method to use from components, since JSF base class that we extend don't allow to throw proper exceptions.
225     *
226     * @param directoryName
227     * @param entryId
228     * @return the entry, or null in case of exception in callees.
229     */
230    public static DocumentModel getEntry(String directoryName, String entryId) {
231        try {
232            return getEntryThrows(directoryName, entryId);
233        } catch (DirectoryException e) {
234            log.error(String.format("Error retrieving the entry (dirname=%s, entryId=%s)", directoryName, entryId), e);
235            return null;
236        }
237    }
238
239    protected static String translate(FacesContext context, String label) {
240        String bundleName = context.getApplication().getMessageBundle();
241        Locale locale = context.getViewRoot().getLocale();
242        label = I18NUtils.getMessageString(bundleName, label, null, locale);
243        return label;
244    }
245
246    protected DirectoryService getService() {
247        if (service == null) {
248            service = Framework.getService(DirectoryService.class);
249        }
250        return service;
251    }
252
253}