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