001/*
002 * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
016 *
017 * $Id:  $
018 */
019
020package org.nuxeo.ecm.webapp.security;
021
022import static org.jboss.seam.ScopeType.PAGE;
023
024import java.io.Serializable;
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030
031import javax.faces.convert.Converter;
032
033import org.apache.commons.lang.StringUtils;
034import org.apache.commons.logging.Log;
035import org.apache.commons.logging.LogFactory;
036import org.jboss.seam.annotations.In;
037import org.jboss.seam.annotations.Name;
038import org.jboss.seam.annotations.Scope;
039import org.jboss.seam.annotations.web.RequestParameter;
040import org.jboss.seam.faces.FacesMessages;
041import org.jboss.seam.international.StatusMessage;
042import org.nuxeo.ecm.core.api.DocumentModel;
043import org.nuxeo.ecm.core.api.DocumentModelComparator;
044import org.nuxeo.ecm.core.api.DocumentModelList;
045import org.nuxeo.ecm.core.api.NuxeoGroup;
046import org.nuxeo.ecm.core.api.NuxeoPrincipal;
047import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl;
048import org.nuxeo.ecm.directory.SizeLimitExceededException;
049import org.nuxeo.ecm.platform.ui.web.component.list.UIEditableList;
050import org.nuxeo.ecm.platform.usermanager.UserManager;
051import org.nuxeo.ecm.webapp.helpers.ResourcesAccessor;
052
053/**
054 * Methods to get user/groups suggestions from searches.
055 *
056 * @author Anahide Tchertchian
057 */
058@Name("userSuggestionActions")
059@Scope(PAGE)
060public class UserSuggestionActionsBean implements Serializable {
061
062    private static final long serialVersionUID = 1L;
063
064    private static final Log log = LogFactory.getLog(UserSuggestionActionsBean.class);
065
066    public static final String USER_TYPE = "USER_TYPE";
067
068    public static final String GROUP_TYPE = "GROUP_TYPE";
069
070    public static final String TYPE_KEY_NAME = "type";
071
072    public static final String PREFIXED_ID_KEY_NAME = "prefixed_id";
073
074    public static final String ID_KEY_NAME = "id";
075
076    public static final String ENTRY_KEY_NAME = "entry";
077
078    @In(create = true)
079    protected transient UserManager userManager;
080
081    @In(create = true, required = false)
082    protected transient FacesMessages facesMessages;
083
084    @In(create = true)
085    protected transient ResourcesAccessor resourcesAccessor;
086
087    @RequestParameter
088    protected String userSuggestionSearchType;
089
090    protected String cachedUserSuggestionSearchType;
091
092    @RequestParameter
093    protected Integer userSuggestionMaxSearchResults;
094
095    @RequestParameter
096    protected Boolean hideVirtualGroups;
097
098    protected Integer cachedUserSuggestionMaxSearchResults;
099
100    protected Object cachedInput;
101
102    protected Object cachedSuggestions;
103
104    @RequestParameter
105    protected String userSuggestionMessageId;
106
107    /**
108     * Id of the editable list component where selection ids are put.
109     * <p>
110     * Component must be an instance of {@link UIEditableList}
111     */
112    @RequestParameter
113    protected String suggestionSelectionListId;
114
115    protected void addSearchOverflowMessage() {
116        if (userSuggestionMessageId != null) {
117            facesMessages.addToControl(userSuggestionMessageId, StatusMessage.Severity.ERROR,
118                    resourcesAccessor.getMessages().get("label.security.searchOverFlow"));
119        } else {
120            log.error("Search overflow");
121        }
122    }
123
124    public List<DocumentModel> getGroupsSuggestions(Object input) {
125        try {
126            Map<String, DocumentModel> uniqueGroups = new HashMap<String, DocumentModel>();
127
128            String pattern = (String) input;
129            for (String field : userManager.getGroupSearchFields()) {
130                // XXX: this doesn't fetch group members (references)
131                Map<String, Serializable> filter = new HashMap<String, Serializable>();
132
133                if (pattern != null && pattern != "") {
134                    filter.put(field, pattern);
135                }
136                if (Boolean.TRUE.equals(hideVirtualGroups)) {
137                    filter.put("__virtualGroup", false);
138                }
139
140                for (DocumentModel group : userManager.searchGroups(filter, filter.keySet())) {
141                    uniqueGroups.put(group.getId(), group);
142                }
143            }
144
145            DocumentModelList groups = new DocumentModelListImpl();
146            groups.addAll(uniqueGroups.values());
147            Collections.sort(groups, new DocumentModelComparator(userManager.getGroupSchemaName(), getGroupsOrderBy()));
148            return groups;
149        } catch (SizeLimitExceededException e) {
150            addSearchOverflowMessage();
151            return Collections.emptyList();
152        }
153    }
154
155    protected Map<String, String> getGroupsOrderBy() {
156        Map<String, String> order = new HashMap<String, String>();
157        order.put(userManager.getGroupLabelField(), DocumentModelComparator.ORDER_ASC);
158        return order;
159    }
160
161    public List<DocumentModel> getUserSuggestions(Object input) {
162        try {
163            String searchPattern = (String) input;
164            return userManager.searchUsers(searchPattern);
165        } catch (SizeLimitExceededException e) {
166            addSearchOverflowMessage();
167            return Collections.emptyList();
168        }
169    }
170
171    protected boolean equals(Object item1, Object item2) {
172        if (item1 == null && item2 == null) {
173            return true;
174        } else if (item1 == null) {
175            return false;
176        } else {
177            return item1.equals(item2);
178        }
179    }
180
181    public Object getSuggestions(Object input) {
182        if (equals(cachedUserSuggestionSearchType, userSuggestionSearchType)
183                && equals(cachedUserSuggestionMaxSearchResults, userSuggestionMaxSearchResults)
184                && equals(cachedInput, input)) {
185            return cachedSuggestions;
186        }
187
188        List<DocumentModel> users = Collections.emptyList();
189        if (USER_TYPE.equals(userSuggestionSearchType) || StringUtils.isEmpty(userSuggestionSearchType)) {
190            users = getUserSuggestions(input);
191        }
192
193        List<DocumentModel> groups = Collections.emptyList();
194        if (GROUP_TYPE.equals(userSuggestionSearchType) || StringUtils.isEmpty(userSuggestionSearchType)) {
195            groups = getGroupsSuggestions(input);
196        }
197
198        int userSize = users.size();
199        int groupSize = groups.size();
200        int totalSize = userSize + groupSize;
201
202        if (userSuggestionMaxSearchResults != null && userSuggestionMaxSearchResults > 0) {
203            if (userSize > userSuggestionMaxSearchResults || groupSize > userSuggestionMaxSearchResults
204                    || totalSize > userSuggestionMaxSearchResults) {
205                addSearchOverflowMessage();
206                return null;
207            }
208        }
209
210        List<Map<String, Object>> result = new ArrayList<Map<String, Object>>(totalSize);
211
212        for (DocumentModel user : users) {
213            Map<String, Object> entry = new HashMap<String, Object>();
214            entry.put(TYPE_KEY_NAME, USER_TYPE);
215            entry.put(ENTRY_KEY_NAME, user);
216            String userId = user.getId();
217            entry.put(ID_KEY_NAME, userId);
218            entry.put(PREFIXED_ID_KEY_NAME, NuxeoPrincipal.PREFIX + userId);
219            result.add(entry);
220        }
221
222        for (DocumentModel group : groups) {
223            Map<String, Object> entry = new HashMap<String, Object>();
224            entry.put(TYPE_KEY_NAME, GROUP_TYPE);
225            entry.put(ENTRY_KEY_NAME, group);
226            String groupId = group.getId();
227            entry.put(ID_KEY_NAME, groupId);
228            entry.put(PREFIXED_ID_KEY_NAME, NuxeoGroup.PREFIX + groupId);
229            result.add(entry);
230        }
231
232        cachedInput = input;
233        cachedUserSuggestionSearchType = userSuggestionSearchType;
234        cachedUserSuggestionMaxSearchResults = userSuggestionMaxSearchResults;
235        cachedSuggestions = result;
236
237        return result;
238    }
239
240    // XXX: needs optimisation
241    public Map<String, Object> getPrefixedUserInfo(String id) {
242        Map<String, Object> res = new HashMap<String, Object>();
243        res.put(PREFIXED_ID_KEY_NAME, id);
244        if (!StringUtils.isBlank(id)) {
245            if (id.startsWith(NuxeoPrincipal.PREFIX)) {
246                res.put(TYPE_KEY_NAME, USER_TYPE);
247                String username = id.substring(NuxeoPrincipal.PREFIX.length());
248                res.put(ID_KEY_NAME, username);
249                res.put(ENTRY_KEY_NAME, userManager.getUserModel(username));
250            } else if (id.startsWith(NuxeoGroup.PREFIX)) {
251                res.put(TYPE_KEY_NAME, GROUP_TYPE);
252                String groupname = id.substring(NuxeoGroup.PREFIX.length());
253                res.put(ID_KEY_NAME, groupname);
254                res.put(ENTRY_KEY_NAME, userManager.getGroupModel(groupname));
255            } else {
256                res.putAll(getUserInfo(id));
257            }
258        }
259        return res;
260    }
261
262    // XXX: needs optimisation
263    public Map<String, Object> getUserInfo(String id) {
264        Map<String, Object> res = new HashMap<String, Object>();
265        res.put(ID_KEY_NAME, id);
266        if (userManager.getGroup(id) != null) {
267            // group
268            res.put(PREFIXED_ID_KEY_NAME, NuxeoGroup.PREFIX + id);
269            res.put(TYPE_KEY_NAME, GROUP_TYPE);
270            res.put(ENTRY_KEY_NAME, userManager.getGroupModel(id));
271        } else if (!StringUtils.isBlank(id)) {
272            // user
273            res.put(PREFIXED_ID_KEY_NAME, NuxeoPrincipal.PREFIX + id);
274            res.put(TYPE_KEY_NAME, USER_TYPE);
275            res.put(ENTRY_KEY_NAME, userManager.getUserModel(id));
276        }
277        return res;
278    }
279
280    public Converter getUserConverter() {
281        return new UserDisplayConverter();
282    }
283
284}