PageRenderTime 25ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/plugin/src/main/java/com/atlassian/confluence/extra/userlister/UserLister.java

https://bitbucket.org/atlassian/confluence-user-lister-plugin
Java | 290 lines | 229 code | 54 blank | 7 comment | 23 complexity | dcd4756381637cb92574a78630651fb2 MD5 | raw file
  1. package com.atlassian.confluence.extra.userlister;
  2. import com.atlassian.confluence.content.render.xhtml.ConversionContext;
  3. import com.atlassian.confluence.content.render.xhtml.DefaultConversionContext;
  4. import com.atlassian.confluence.extra.userlister.model.UserList;
  5. import com.atlassian.confluence.languages.LocaleManager;
  6. import com.atlassian.confluence.macro.Macro;
  7. import com.atlassian.confluence.macro.MacroExecutionException;
  8. import com.atlassian.confluence.plugin.descriptor.web.conditions.PeopleDirectoryEnabledCondition;
  9. import com.atlassian.confluence.plugin.services.VelocityHelperService;
  10. import com.atlassian.confluence.security.PermissionManager;
  11. import com.atlassian.confluence.user.AuthenticatedUserThreadLocal;
  12. import com.atlassian.confluence.user.UserAccessor;
  13. import com.atlassian.confluence.util.i18n.I18NBean;
  14. import com.atlassian.confluence.util.i18n.I18NBeanFactory;
  15. import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
  16. import com.atlassian.renderer.RenderContext;
  17. import com.atlassian.renderer.TokenType;
  18. import com.atlassian.renderer.v2.RenderMode;
  19. import com.atlassian.renderer.v2.RenderUtils;
  20. import com.atlassian.renderer.v2.macro.BaseMacro;
  21. import com.atlassian.renderer.v2.macro.MacroException;
  22. import com.atlassian.sal.api.rdbms.TransactionalExecutorFactory;
  23. import com.atlassian.user.Group;
  24. import com.atlassian.user.search.page.Pager;
  25. import com.atlassian.user.search.page.PagerUtils;
  26. import org.apache.commons.lang3.ArrayUtils;
  27. import org.apache.commons.lang3.StringUtils;
  28. import org.slf4j.Logger;
  29. import org.slf4j.LoggerFactory;
  30. import org.springframework.beans.factory.annotation.Autowired;
  31. import java.util.ArrayList;
  32. import java.util.Arrays;
  33. import java.util.Collection;
  34. import java.util.Collections;
  35. import java.util.List;
  36. import java.util.Map;
  37. import java.util.Set;
  38. import java.util.TreeSet;
  39. import java.util.stream.Collectors;
  40. import static java.util.stream.Collectors.toList;
  41. import static org.apache.commons.lang3.StringUtils.join;
  42. import static org.apache.commons.lang3.StringUtils.split;
  43. public class UserLister extends BaseMacro implements Macro {
  44. static final String USER_LISTER_LIMIT_PROPERTY = "confluence.extra.userlister.limit";
  45. private static final Logger logger = LoggerFactory.getLogger(UserLister.class);
  46. private static final int DEFAULT_USER_LISTER_LIMIT = 10000;
  47. private final UserAccessor userAccessor;
  48. private final UserListManager userListManager;
  49. private final LocaleManager localeManager;
  50. private final I18NBeanFactory i18NBeanFactory;
  51. private final VelocityHelperService velocityHelperService;
  52. private final PermissionManager permissionManager;
  53. private final TransactionalExecutorFactory transactionalExecutorFactory;
  54. private enum ListMode {
  55. ALL,
  56. ONLINE_ONLY,
  57. OFFLINE_ONLY
  58. }
  59. @Autowired
  60. public UserLister(
  61. final @ComponentImport UserAccessor userAccessor,
  62. final UserListManager userListManager,
  63. final @ComponentImport LocaleManager localeManager,
  64. final @ComponentImport I18NBeanFactory i18NBeanFactory,
  65. final @ComponentImport VelocityHelperService velocityHelperService,
  66. final @ComponentImport PermissionManager permissionManager,
  67. final @ComponentImport TransactionalExecutorFactory transactionalExecutorFactory) {
  68. this.userAccessor = userAccessor;
  69. this.userListManager = userListManager;
  70. this.localeManager = localeManager;
  71. this.i18NBeanFactory = i18NBeanFactory;
  72. this.velocityHelperService = velocityHelperService;
  73. this.permissionManager = permissionManager;
  74. this.transactionalExecutorFactory = transactionalExecutorFactory;
  75. }
  76. @Override
  77. public TokenType getTokenType(Map parameters, String body, RenderContext context) {
  78. return TokenType.BLOCK;
  79. }
  80. public boolean hasBody() {
  81. return false;
  82. }
  83. public RenderMode getBodyRenderMode() {
  84. return RenderMode.NO_RENDER;
  85. }
  86. private I18NBean getI18NBean() {
  87. return i18NBeanFactory.getI18NBean(localeManager.getLocale(AuthenticatedUserThreadLocal.get()));
  88. }
  89. private String getText(String key) {
  90. return getI18NBean().getText(key);
  91. }
  92. private String getText(String key, List params) {
  93. return getI18NBean().getText(key, params);
  94. }
  95. public String execute(Map parameters, String body, RenderContext renderContext) throws MacroException {
  96. try {
  97. return execute(parameters, body, new DefaultConversionContext(renderContext));
  98. } catch (MacroExecutionException e) {
  99. throw new MacroException(e);
  100. }
  101. }
  102. private String createCSVList(List<String> emptyGroups) {
  103. return getText("userlister.noresultsfoundforgroups", Collections.singletonList(join(emptyGroups, ',')));
  104. }
  105. private Set<String> getGroups(final String groupNames) {
  106. String[] groupNameArray = split(groupNames, ',');
  107. final Set<String> groups = Arrays.stream(groupNameArray)
  108. .map(String::trim)
  109. .filter(StringUtils::isNotBlank)
  110. .collect(Collectors.toSet());
  111. /* We need to check if the user specified * in the group(s) macro parameter.
  112. * If he/she did, we need to expand it to all groups in Confluence
  113. */
  114. if (groups.contains(UserList.ALL_GROUP_NAME)) {
  115. userAccessor.getGroups().forEach(group -> groups.add(group.getName()));
  116. /* Then remove the wildcard */
  117. groups.remove(UserList.ALL_GROUP_NAME);
  118. }
  119. return groups;
  120. }
  121. private Set<String> getAllowedGroups(Set<String> groups) {
  122. Set<String> allowedGroups = new TreeSet<>(groups);
  123. allowedGroups.removeAll(userListManager.getGroupBlackList());
  124. return allowedGroups;
  125. }
  126. private Set<String> getDeniedGroups(Set<String> groups) {
  127. Set<String> deniedGroups = new TreeSet<>(groups);
  128. deniedGroups.retainAll(userListManager.getGroupBlackList());
  129. return deniedGroups;
  130. }
  131. public String execute(Map<String, String> parameters, String body, ConversionContext conversionContext) throws MacroExecutionException {
  132. if (isPeopleDirectoryDisabled()) {
  133. return RenderUtils.blockError(
  134. getText("userlister.notpermitted.viewuserprofile"),
  135. ""
  136. );
  137. }
  138. //1. parse parameters from macro
  139. final String groupNames = StringUtils.defaultString(parameters.get("groups"), parameters.get("group"));
  140. final boolean returnOnlineUsers = Boolean.parseBoolean(StringUtils.trim(parameters.get("online")));
  141. final Boolean showWarning = Boolean.valueOf(StringUtils.defaultString(StringUtils.trim(parameters.get("showWarning")), "true"));
  142. final Set blackListedGroups = userListManager.getGroupBlackList();
  143. if (StringUtils.isBlank(groupNames)) {
  144. return getText("userlister.no.groups.specified");
  145. }
  146. if (ArrayUtils.contains(split(groupNames, ','), UserList.ALL_GROUP_NAME)
  147. && blackListedGroups.contains(UserList.ALL_GROUP_NAME)) {
  148. return getText("userlister.group.name.list.contains.asterisk");
  149. }
  150. final Set<String> groups = getGroups(groupNames);
  151. final Set<String> allowedGroups = getAllowedGroups(groups);
  152. final Set<String> deniedGroups = getDeniedGroups(groups);
  153. final Set<String> loggedInUsernames = userListManager.getLoggedInUsers();
  154. final List<UserList> groupList = new ArrayList<>();
  155. final List<String> emptyGroups = new ArrayList<>();
  156. final ListMode listMode = !parameters.containsKey("online")
  157. ? ListMode.ALL
  158. : (returnOnlineUsers ? ListMode.ONLINE_ONLY : ListMode.OFFLINE_ONLY);
  159. final int userLimit = Integer.getInteger(USER_LISTER_LIMIT_PROPERTY, DEFAULT_USER_LISTER_LIMIT);
  160. //run the batch query in a new readonly transaction to avoid unnecessary automatic flush
  161. String macroText = transactionalExecutorFactory.createExecutor(true, true).execute(connection -> {
  162. int userCount = 0;
  163. List<UserList> usersToBeLoaded = new ArrayList<>();
  164. for (String currentGroup : allowedGroups) {
  165. List<String> usernames = getUserNames(currentGroup, loggedInUsernames, listMode);
  166. userCount += usernames.size();
  167. if (userCount >= userLimit) {
  168. logger.warn(String.format("There are too many users in the specified groups. The limit is %s.", userLimit));
  169. return getText("userlister.too.many.users", Collections.singletonList(userLimit));
  170. }
  171. usersToBeLoaded.add(new UserList(currentGroup, userAccessor, usernames, loggedInUsernames));
  172. }
  173. for (UserList userList : usersToBeLoaded) {
  174. if (!userList.getUsers().isEmpty()) {
  175. groupList.add(userList);
  176. } else {
  177. emptyGroups.add(userList.getGroup());
  178. }
  179. }
  180. return null;
  181. }
  182. );
  183. if (StringUtils.isNotBlank(macroText)) {
  184. return macroText;
  185. }
  186. // now create a simple velocity context and render a template for the output
  187. Map<String, Object> contextMap = velocityHelperService.createDefaultVelocityContext();
  188. if (!deniedGroups.isEmpty())
  189. contextMap.put("deniedGroups", deniedGroups);
  190. contextMap.put("showWarning", showWarning);
  191. contextMap.put("userlists", groupList);
  192. if (listMode != ListMode.ALL) {
  193. contextMap.put("online", returnOnlineUsers);
  194. } else {
  195. contextMap.put("allUserStatuses", true);
  196. }
  197. if (emptyGroups.size() > 0) {
  198. contextMap.put("emptyGroups", createCSVList(emptyGroups));
  199. }
  200. try {
  201. return velocityHelperService.getRenderedTemplate("templates/extra/userlister/userlistermacro.vm", contextMap);
  202. } catch (Exception e) {
  203. logger.error("Error while trying to display UserList!", e);
  204. return getText("userlister.unable.to.render.result", Collections.singletonList(e.toString()));
  205. }
  206. }
  207. private List<String> getUserNames(String groupName, Collection loggedInUsers, ListMode listMode) {
  208. final List<String> usernames = getUserNamesByGroup(groupName);
  209. if (listMode == ListMode.ALL) {
  210. return usernames;
  211. }
  212. return usernames.stream().filter(username -> {
  213. final boolean online = loggedInUsers.contains(username);
  214. return (listMode == ListMode.ONLINE_ONLY) == online;
  215. }).collect(toList());
  216. }
  217. private List<String> getUserNamesByGroup(String groupName) {
  218. Pager<String> usernames = null;
  219. final Group group = userAccessor.getGroup(groupName);
  220. if (group != null) {
  221. usernames = userAccessor.getMemberNames(group);
  222. }
  223. return usernames == null ? Collections.emptyList() : PagerUtils.toList(usernames);
  224. }
  225. private boolean isPeopleDirectoryDisabled() {
  226. PeopleDirectoryEnabledCondition peopleDirectoryEnabledCondition = new PeopleDirectoryEnabledCondition();
  227. peopleDirectoryEnabledCondition.setPermissionManager(permissionManager);
  228. return peopleDirectoryEnabledCondition.isPeopleDirectoryDisabled(AuthenticatedUserThreadLocal.get());
  229. }
  230. public BodyType getBodyType() {
  231. return BodyType.NONE;
  232. }
  233. public OutputType getOutputType() {
  234. return OutputType.BLOCK;
  235. }
  236. }