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