PageRenderTime 67ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/src/main/java/org/nrg/xnat/services/cache/DefaultGroupsAndPermissionsCache.java

https://bitbucket.org/radiologics/xnat-web-old-v2
Java | 2002 lines | 1631 code | 227 blank | 144 comment | 214 complexity | cd0aae515f9ce3d9be056923c204fa8b MD5 | raw file
  1. /*
  2. * web: org.nrg.xnat.services.cache.DefaultUserProjectCache
  3. * XNAT http://www.xnat.org
  4. * Copyright (c) 2017, Washington University School of Medicine
  5. * All Rights Reserved
  6. *
  7. * Released under the Simplified BSD.
  8. */
  9. package org.nrg.xnat.services.cache;
  10. import com.google.common.base.Function;
  11. import com.google.common.base.Joiner;
  12. import com.google.common.base.Predicate;
  13. import com.google.common.base.Predicates;
  14. import com.google.common.collect.*;
  15. import lombok.extern.slf4j.Slf4j;
  16. import net.sf.ehcache.CacheException;
  17. import net.sf.ehcache.Ehcache;
  18. import net.sf.ehcache.Element;
  19. import org.apache.commons.collections.CollectionUtils;
  20. import org.apache.commons.lang3.StringUtils;
  21. import org.apache.commons.lang3.time.DurationFormatUtils;
  22. import org.nrg.framework.exceptions.NrgServiceRuntimeException;
  23. import org.nrg.framework.orm.DatabaseHelper;
  24. import org.nrg.framework.utilities.LapStopWatch;
  25. import org.nrg.xdat.XDAT;
  26. import org.nrg.xdat.display.ElementDisplay;
  27. import org.nrg.xdat.om.*;
  28. import org.nrg.xdat.schema.SchemaElement;
  29. import org.nrg.xdat.security.SecurityManager;
  30. import org.nrg.xdat.security.*;
  31. import org.nrg.xdat.security.helpers.Permissions;
  32. import org.nrg.xdat.security.helpers.Users;
  33. import org.nrg.xdat.security.user.exceptions.UserInitException;
  34. import org.nrg.xdat.security.user.exceptions.UserNotFoundException;
  35. import org.nrg.xdat.services.Initializing;
  36. import org.nrg.xdat.services.cache.GroupsAndPermissionsCache;
  37. import org.nrg.xdat.servlet.XDATServlet;
  38. import org.nrg.xft.db.PoolDBUtils;
  39. import org.nrg.xft.event.XftItemEventI;
  40. import org.nrg.xft.event.methods.XftItemEventCriteria;
  41. import org.nrg.xft.exception.ElementNotFoundException;
  42. import org.nrg.xft.exception.FieldNotFoundException;
  43. import org.nrg.xft.exception.ItemNotFoundException;
  44. import org.nrg.xft.exception.XFTInitException;
  45. import org.nrg.xft.schema.XFTManager;
  46. import org.nrg.xft.security.UserI;
  47. import org.nrg.xnat.services.cache.jms.InitializeGroupRequest;
  48. import org.slf4j.event.Level;
  49. import org.springframework.beans.factory.annotation.Autowired;
  50. import org.springframework.cache.CacheManager;
  51. import org.springframework.dao.DataAccessException;
  52. import org.springframework.jdbc.core.JdbcTemplate;
  53. import org.springframework.jdbc.core.ResultSetExtractor;
  54. import org.springframework.jdbc.core.RowCallbackHandler;
  55. import org.springframework.jdbc.core.namedparam.EmptySqlParameterSource;
  56. import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
  57. import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
  58. import org.springframework.jms.core.JmsTemplate;
  59. import org.springframework.scheduling.annotation.Async;
  60. import org.springframework.scheduling.annotation.AsyncResult;
  61. import org.springframework.stereotype.Service;
  62. import javax.annotation.Nonnull;
  63. import javax.annotation.Nullable;
  64. import java.sql.ResultSet;
  65. import java.sql.SQLException;
  66. import java.text.DateFormat;
  67. import java.text.NumberFormat;
  68. import java.util.*;
  69. import java.util.concurrent.ConcurrentHashMap;
  70. import java.util.concurrent.Future;
  71. import java.util.concurrent.atomic.AtomicBoolean;
  72. import java.util.regex.Matcher;
  73. import java.util.regex.Pattern;
  74. import static org.nrg.framework.exceptions.NrgServiceError.ConfigurationError;
  75. import static org.nrg.xapi.rest.users.DataAccessApi.*;
  76. import static org.nrg.xdat.security.PermissionCriteria.dumpCriteriaList;
  77. import static org.nrg.xdat.security.helpers.Groups.*;
  78. import static org.nrg.xft.event.XftItemEventI.*;
  79. @SuppressWarnings("Duplicates")
  80. @Service
  81. @Slf4j
  82. public class DefaultGroupsAndPermissionsCache extends AbstractXftItemAndCacheEventHandlerMethod implements GroupsAndPermissionsCache, Initializing, GroupsAndPermissionsCache.Provider {
  83. @Autowired
  84. public DefaultGroupsAndPermissionsCache(final CacheManager cacheManager, final NamedParameterJdbcTemplate template, final JmsTemplate jmsTemplate) throws SQLException {
  85. super(cacheManager,
  86. XftItemEventCriteria.builder().xsiType(XnatProjectdata.SCHEMA_ELEMENT_NAME).actions(CREATE, UPDATE, DELETE).build(),
  87. XftItemEventCriteria.builder().xsiType(XnatSubjectdata.SCHEMA_ELEMENT_NAME).xsiType(XnatExperimentdata.SCHEMA_ELEMENT_NAME).actions(CREATE, XftItemEventI.DELETE, SHARE).build(),
  88. XftItemEventCriteria.getXsiTypeCriteria(XdatUsergroup.SCHEMA_ELEMENT_NAME),
  89. XftItemEventCriteria.getXsiTypeCriteria(XdatElementSecurity.SCHEMA_ELEMENT_NAME));
  90. _template = template;
  91. _jmsTemplate = jmsTemplate;
  92. _helper = new DatabaseHelper((JdbcTemplate) _template.getJdbcOperations());
  93. _totalCounts = new HashMap<>();
  94. _missingElements = new HashMap<>();
  95. _userChecks = new ConcurrentHashMap<>();
  96. _initialized = new AtomicBoolean(false);
  97. if (_helper.tableExists("xnat_projectdata")) {
  98. resetTotalCounts();
  99. }
  100. }
  101. /**
  102. * {@inheritDoc}
  103. */
  104. @Override
  105. public void notifyElementRemoved(final Ehcache cache, final Element element) throws CacheException {
  106. handleCacheRemoveEvent(cache, element, "removed");
  107. }
  108. /**
  109. * {@inheritDoc}
  110. */
  111. @Override
  112. public void notifyElementExpired(final Ehcache cache, final Element element) {
  113. handleCacheRemoveEvent(cache, element, "expired");
  114. }
  115. /**
  116. * {@inheritDoc}
  117. */
  118. @Override
  119. public void notifyElementEvicted(final Ehcache cache, final Element element) {
  120. handleCacheRemoveEvent(cache, element, "evicted");
  121. }
  122. /**
  123. * {@inheritDoc}
  124. */
  125. @Override
  126. public void notifyRemoveAll(final Ehcache cache) {
  127. handleCacheRemoveEvent(cache, null, "removed");
  128. }
  129. /**
  130. * {@inheritDoc}
  131. */
  132. @Override
  133. public String getCacheName() {
  134. return CACHE_NAME;
  135. }
  136. /**
  137. * Gets the specified project if the user has any access to it. Returns null otherwise.
  138. *
  139. * @param groupId The ID or alias of the project to retrieve.
  140. *
  141. * @return The project object if the user can access it, null otherwise.
  142. */
  143. @Override
  144. public UserGroupI get(final String groupId) {
  145. if (StringUtils.isBlank(groupId)) {
  146. throw new IllegalArgumentException("Can not request a group with a blank group ID");
  147. }
  148. // Check that the group is cached and, if so, return it.
  149. log.trace("Retrieving group through cache ID {}", groupId);
  150. final UserGroupI cachedGroup = getCachedGroup(groupId);
  151. if (cachedGroup != null) {
  152. log.debug("Found cached group entry for cache ID '{}'", groupId);
  153. return cachedGroup;
  154. }
  155. try {
  156. log.trace("Initializing group entry for cache ID '{}'", groupId);
  157. return initGroup(groupId);
  158. } catch (ItemNotFoundException e) {
  159. log.info("Can't find a group with the group ID '{}', returning null", groupId);
  160. return null;
  161. }
  162. }
  163. /**
  164. * {@inheritDoc}
  165. */
  166. @Override
  167. public Map<String, Long> getReadableCounts(final UserI user) {
  168. if (user == null) {
  169. return Collections.emptyMap();
  170. }
  171. final String username = user.getUsername();
  172. final String cacheId = getCacheIdForUserElements(username, READABLE);
  173. // Check whether the element types are cached and, if so, return that.
  174. log.trace("Retrieving readable counts for user {} through cache ID {}", username, cacheId);
  175. final Map<String, Long> cachedReadableCounts = getCachedMap(cacheId);
  176. if (cachedReadableCounts != null) {
  177. log.debug("Found cached readable counts entry for user '{}' with cache ID '{}' containing {} entries", username, cacheId, cachedReadableCounts.size());
  178. return cachedReadableCounts;
  179. }
  180. return initReadableCountsForUser(cacheId, user);
  181. }
  182. /**
  183. * {@inheritDoc}
  184. */
  185. @Override
  186. public Map<String, ElementDisplay> getBrowseableElementDisplays(final UserI user) {
  187. if (user == null) {
  188. return Collections.emptyMap();
  189. }
  190. final Map<String, ElementDisplay> guestBrowseableElementDisplays = getGuestBrowseableElementDisplays();
  191. if (user.isGuest()) {
  192. log.debug("Got a request for browseable element displays for the guest user, returning {} entries", guestBrowseableElementDisplays.size());
  193. return guestBrowseableElementDisplays;
  194. }
  195. final String username = user.getUsername();
  196. final String cacheId = getCacheIdForUserElements(username, BROWSEABLE);
  197. // Check whether the element types are cached and, if so, return that.
  198. log.trace("Retrieving browseable element displays for user {} through cache ID {}", username, cacheId);
  199. final Map<String, ElementDisplay> cachedUserEntry = getCachedMap(cacheId);
  200. if (cachedUserEntry != null) {
  201. @SuppressWarnings("unchecked") final Map<String, ElementDisplay> browseables = buildImmutableMap(cachedUserEntry, guestBrowseableElementDisplays);
  202. log.debug("Found a cached entry for user '{}' browseable element displays under cache ID '{}' with {} entries", username, cacheId, browseables.size());
  203. return browseables;
  204. }
  205. log.trace("Initializing browseable element displays for user '{}' with cache ID '{}'", username, cacheId);
  206. @SuppressWarnings("unchecked") final Map<String, ElementDisplay> browseables = buildImmutableMap(initBrowseableElementDisplaysForUser(cacheId, user), guestBrowseableElementDisplays);
  207. log.debug("Initialized browseable element displays for user '{}' with cache ID '{}' with {} entries (including guest browseable element displays)", username, cacheId, browseables.size());
  208. return browseables;
  209. }
  210. /**
  211. * {@inheritDoc}
  212. */
  213. @Override
  214. public List<ElementDisplay> getSearchableElementDisplays(final UserI user) {
  215. if (user == null) {
  216. return Collections.emptyList();
  217. }
  218. final String username = user.getUsername();
  219. final String cacheId = getCacheIdForUserElements(username, SEARCHABLE);
  220. log.debug("Retrieving searchable element displays for user {} through cache ID {}", username, cacheId);
  221. final Map<String, Long> counts = getReadableCounts(user);
  222. try {
  223. return Lists.newArrayList(Iterables.filter(getActionElementDisplays(username, SecurityManager.READ), new Predicate<ElementDisplay>() {
  224. @Override
  225. public boolean apply(@Nullable final ElementDisplay elementDisplay) {
  226. if (elementDisplay == null) {
  227. return false;
  228. }
  229. final String elementName = elementDisplay.getElementName();
  230. try {
  231. return ElementSecurity.IsSearchable(elementName) && counts.containsKey(elementName) && counts.get(elementName) > 0;
  232. } catch (Exception e) {
  233. return false;
  234. }
  235. }
  236. }));
  237. } catch (Exception e) {
  238. log.error("An unknown error occurred", e);
  239. }
  240. return Collections.emptyList();
  241. }
  242. /**
  243. * {@inheritDoc}
  244. */
  245. @Override
  246. public List<ElementDisplay> getActionElementDisplays(final UserI user, final String action) {
  247. return getActionElementDisplays(user.getUsername(), action);
  248. }
  249. @Override
  250. public List<ElementDisplay> getActionElementDisplays(final String username, final String action) {
  251. if (!ACTIONS.contains(action)) {
  252. throw new NrgServiceRuntimeException(ConfigurationError, "The action '" + action + "' is invalid, must be one of: " + StringUtils.join(ACTIONS, ", "));
  253. }
  254. final List<ElementDisplay> elementDisplays = getActionElementDisplays(username).get(action);
  255. if (log.isTraceEnabled()) {
  256. log.trace("Found {} element displays for user {} action {}: {}", elementDisplays.size(), username, action, formatElementDisplays(elementDisplays));
  257. }
  258. return elementDisplays;
  259. }
  260. /**
  261. * {@inheritDoc}
  262. */
  263. @Override
  264. public List<PermissionCriteriaI> getPermissionCriteria(final UserI user, final String dataType) {
  265. return getPermissionCriteria(user.getUsername(), dataType);
  266. }
  267. /**
  268. * {@inheritDoc}
  269. */
  270. @Override
  271. public List<PermissionCriteriaI> getPermissionCriteria(final String username, final String dataType) {
  272. try {
  273. PoolDBUtils.CheckSpecialSQLChars(dataType);
  274. } catch (Exception e) {
  275. return null;
  276. }
  277. try {
  278. final List<PermissionCriteriaI> criteria = new ArrayList<>();
  279. final Map<String, ElementAccessManager> managers = getElementAccessManagers(username);
  280. if (managers.isEmpty()) {
  281. log.info("Couldn't find element access managers for user {} trying to retrieve permissions for data type {}", username, dataType);
  282. } else {
  283. final ElementAccessManager manager = managers.get(dataType);
  284. if (manager == null) {
  285. log.info("Couldn't find element access manager for data type {} for user {} while trying to retrieve permissions ", dataType, username);
  286. } else {
  287. criteria.addAll(manager.getCriteria());
  288. if (criteria.isEmpty()) {
  289. log.debug("Couldn't find any permission criteria for data type {} for user {} while trying to retrieve permissions ", dataType, username);
  290. }
  291. }
  292. }
  293. final Map<String, UserGroupI> userGroups = getMutableGroupsForUser(username);
  294. if (log.isDebugEnabled()) {
  295. log.debug("Found {} user groups for the user {}", userGroups.size(), username, userGroups.isEmpty() ? "" : ": " + Joiner.on(", ").join(userGroups.keySet()));
  296. }
  297. final Set<String> groups = userGroups.keySet();
  298. if (CollectionUtils.containsAny(groups, ALL_DATA_GROUPS)) {
  299. if (groups.contains(ALL_DATA_ADMIN_GROUP)) {
  300. _template.query(QUERY_GET_ALL_MEMBER_GROUPS, new RowCallbackHandler() {
  301. @Override
  302. public void processRow(final ResultSet resultSet) throws SQLException {
  303. final String projectId = resultSet.getString("project_id");
  304. final String groupId = resultSet.getString("group_id");
  305. // If the user is a collaborator on a project, we're going to upgrade them to member,
  306. // so remove that collaborator nonsense, this is the big time.
  307. userGroups.remove(projectId + "_collaborator");
  308. // If the user is already a member of owner of a project, then don't bother: they already have
  309. // sufficient access to the project.
  310. if (!userGroups.containsKey(projectId + "_owner") && !userGroups.containsKey(projectId + "_member")) {
  311. userGroups.put(groupId, get(groupId));
  312. }
  313. }
  314. });
  315. } else if (userGroups.containsKey(ALL_DATA_ACCESS_GROUP)) {
  316. _template.query(QUERY_GET_ALL_COLLAB_GROUPS, new RowCallbackHandler() {
  317. @Override
  318. public void processRow(final ResultSet resultSet) throws SQLException {
  319. final String projectId = resultSet.getString("project_id");
  320. final String groupId = resultSet.getString("group_id");
  321. // If the user has no group membership, then add as a collaborator.
  322. if (!CollectionUtils.containsAny(groups, Arrays.asList(groupId, projectId + "_member", projectId + "_owner"))) {
  323. userGroups.put(groupId, get(groupId));
  324. }
  325. }
  326. });
  327. }
  328. }
  329. for (final UserGroupI group : userGroups.values()) {
  330. final List<PermissionCriteriaI> permissions = group.getPermissionsByDataType(dataType);
  331. if (permissions != null) {
  332. if (log.isTraceEnabled()) {
  333. log.trace("Searched for permission criteria for user {} on type {} in group {}: {}", username, dataType, group.getId(), dumpCriteriaList(permissions));
  334. } else {
  335. log.debug("Searched for permission criteria for user {} on type {} in group {}: {} permissions found", username, dataType, group.getId(), permissions.size());
  336. }
  337. criteria.addAll(permissions);
  338. } else {
  339. log.warn("Tried to retrieve permissions for data type {} for user {} in group {}, but this returned null.", dataType, username, group.getId());
  340. }
  341. }
  342. if (!isGuest(username)) {
  343. try {
  344. final List<PermissionCriteriaI> permissions = getPermissionCriteria(getGuest().getUsername(), dataType);
  345. if (permissions != null) {
  346. criteria.addAll(permissions);
  347. } else {
  348. log.warn("Tried to retrieve permissions for data type {} for the guest user, but this returned null.", dataType);
  349. }
  350. } catch (Exception e) {
  351. log.error("An error occurred trying to retrieve the guest user", e);
  352. }
  353. }
  354. if (log.isTraceEnabled()) {
  355. log.trace("Retrieved permission criteria for user {} on the data type {}: {}", username, dataType, dumpCriteriaList(criteria));
  356. } else {
  357. log.debug("Retrieved permission criteria for user {} on the data type {}: {} criteria found", username, dataType, criteria.size());
  358. }
  359. return ImmutableList.copyOf(criteria);
  360. } catch (UserNotFoundException e) {
  361. log.error("Couldn't find the indicated user");
  362. return Collections.emptyList();
  363. }
  364. }
  365. @Override
  366. public Map<String, Long> getTotalCounts() {
  367. if (_totalCounts.isEmpty()) {
  368. resetTotalCounts();
  369. }
  370. return ImmutableMap.copyOf(_totalCounts);
  371. }
  372. @Nonnull
  373. @Override
  374. public List<String> getProjectsForUser(final String username, final String access) {
  375. log.info("Getting projects with {} access for user {}", access, username);
  376. final String cacheId = getCacheIdForUserProjectAccess(username, access);
  377. final List<String> cachedUserProjects = getCachedList(cacheId);
  378. if (cachedUserProjects != null) {
  379. log.debug("Found a cache entry for user '{}' '{}' access with ID '{}' and {} elements", username, access, cacheId, cachedUserProjects.size());
  380. return cachedUserProjects;
  381. }
  382. return updateUserProjectAccess(username, access, cacheId);
  383. }
  384. /**
  385. * {@inheritDoc}
  386. */
  387. @Nonnull
  388. @Override
  389. public List<UserGroupI> getGroupsForTag(final String tag) {
  390. // Get the group IDs associated with the tag.
  391. log.info("Getting groups for tag {}", tag);
  392. final List<String> groupIds = getTagGroups(tag);
  393. return getUserGroupList(groupIds);
  394. }
  395. @Nonnull
  396. @Override
  397. public Map<String, UserGroupI> getGroupsForUser(final String username) throws UserNotFoundException {
  398. return ImmutableMap.copyOf(getMutableGroupsForUser(username));
  399. }
  400. /**
  401. * {@inheritDoc}
  402. */
  403. @Override
  404. public void refreshGroupsForUser(final String username) throws UserNotFoundException {
  405. initUserGroupIds(getCacheIdForUserGroups(username), username);
  406. }
  407. /**
  408. * {@inheritDoc}
  409. */
  410. @Override
  411. @Nullable
  412. public UserGroupI getGroupForUserAndTag(final String username, final String tag) throws UserNotFoundException {
  413. final String groupId = _template.query(QUERY_GET_GROUP_FOR_USER_AND_TAG, checkUser(username).addValue("tag", tag), new ResultSetExtractor<String>() {
  414. @Override
  415. public String extractData(final ResultSet results) throws DataAccessException, SQLException {
  416. return results.next() ? results.getString("id") : null;
  417. }
  418. });
  419. return StringUtils.isNotBlank(groupId) ? get(groupId) : null;
  420. }
  421. /**
  422. * {@inheritDoc}
  423. */
  424. @Override
  425. public List<String> getUserIdsForGroup(final String groupId) {
  426. final UserGroupI userGroup = get(groupId);
  427. if (userGroup == null) {
  428. return Collections.emptyList();
  429. }
  430. return ImmutableList.copyOf(userGroup.getUsernames());
  431. }
  432. @Override
  433. public void refreshGroup(final String groupId) throws ItemNotFoundException {
  434. final UserGroupI group = initGroup(groupId);
  435. for (final String username : group.getUsernames()) {
  436. try {
  437. if (!getGroupIdsForUser(username).contains(groupId)) {
  438. refreshGroupsForUser(username);
  439. }
  440. } catch (UserNotFoundException ignored) {
  441. //
  442. }
  443. }
  444. }
  445. @Override
  446. public Date getUserLastUpdateTime(final UserI user) {
  447. return getUserLastUpdateTime(user.getUsername());
  448. }
  449. @Override
  450. public Date getUserLastUpdateTime(final String username) {
  451. try {
  452. @SuppressWarnings("unchecked") final List<String> cacheIds = new ArrayList<>(buildImmutableSet(getGroupIdsForUser(username), getCacheIdsForUsername(username)));
  453. if (cacheIds.isEmpty()) {
  454. return new Date();
  455. }
  456. if (log.isDebugEnabled()) {
  457. log.debug("Found {} cache entries related to user {}: {}", cacheIds.size(), username, StringUtils.join(cacheIds, ", "));
  458. }
  459. final long lastUpdateTime = Collections.max(Lists.transform(cacheIds, new Function<String, Long>() {
  460. @Override
  461. public Long apply(@Nullable final String cacheId) {
  462. final Date lastUpdateTime = getCacheEntryLastUpdateTime(cacheId);
  463. log.trace("User {} cache entry '{}' last updated: {}", username, cacheId, lastUpdateTime == null ? "null" : lastUpdateTime.getTime());
  464. return lastUpdateTime == null ? 0L : lastUpdateTime.getTime();
  465. }
  466. }));
  467. log.debug("Found latest cache entry last updated time for user {}: {}", username, lastUpdateTime);
  468. return new Date(lastUpdateTime);
  469. } catch (UserNotFoundException ignored) {
  470. log.warn("Someone requested the cache entry last updated time for user {} but that user wasn't found", username);
  471. return new Date();
  472. }
  473. }
  474. /**
  475. * Finds all user element cache IDs for the specified user and evicts them from the cache.
  476. *
  477. * @param username The username to be cleared.
  478. */
  479. @Override
  480. public void clearUserCache(final String username) {
  481. final List<String> cacheIds = getCacheIdsForUserElements(username);
  482. if (log.isDebugEnabled()) {
  483. log.debug("Clearing caches for user '{}': {}", username, StringUtils.join(cacheIds, ", "));
  484. }
  485. evict(cacheIds);
  486. }
  487. @Override
  488. public boolean canInitialize() {
  489. try {
  490. if (_listener == null) {
  491. return false;
  492. }
  493. final boolean doesUserGroupTableExists = _helper.tableExists("xdat_usergroup");
  494. final boolean isXftManagerComplete = XFTManager.isComplete();
  495. final boolean isDatabasePopulateOrUpdateCompleted = XDATServlet.isDatabasePopulateOrUpdateCompleted();
  496. log.info("User group table {}, XFTManager initialization completed {}, database populate or updated completed {}", doesUserGroupTableExists, isXftManagerComplete, isDatabasePopulateOrUpdateCompleted);
  497. return doesUserGroupTableExists && isXftManagerComplete && isDatabasePopulateOrUpdateCompleted;
  498. } catch (SQLException e) {
  499. log.info("Got an SQL exception checking for xdat_usergroup table", e);
  500. return false;
  501. }
  502. }
  503. @Async
  504. @Override
  505. public Future<Boolean> initialize() {
  506. final LapStopWatch stopWatch = LapStopWatch.createStarted(log, Level.INFO);
  507. // This clears out any group initialization requests that may be left in the database from earlier starts.
  508. _template.update("DELETE FROM activemq_msgs WHERE container LIKE '%initializeGroupRequest'", EmptySqlParameterSource.INSTANCE);
  509. final int tags = initializeTags();
  510. stopWatch.lap("Processed {} tags", tags);
  511. final List<String> groupIds = _template.queryForList(QUERY_ALL_GROUPS, EmptySqlParameterSource.INSTANCE, String.class);
  512. _listener.setGroupIds(groupIds);
  513. stopWatch.lap("Initialized listener of type {} with {} tags", _listener.getClass().getName(), tags);
  514. try {
  515. final UserI adminUser = Users.getAdminUser();
  516. assert adminUser != null;
  517. stopWatch.lap("Found {} group IDs to run through, initializing cache with these as user {}", groupIds.size(), adminUser.getUsername());
  518. for (final String groupId : groupIds) {
  519. stopWatch.lap(Level.DEBUG, "Creating queue entry for group {}", groupId);
  520. XDAT.sendJmsRequest(_jmsTemplate, new InitializeGroupRequest(groupId));
  521. }
  522. } finally {
  523. if (stopWatch.isStarted()) {
  524. stopWatch.stop();
  525. }
  526. log.info("Total time to queue {} groups was {} ms", groupIds.size(), NUMBER_FORMAT.format(stopWatch.getTime()));
  527. if (log.isInfoEnabled()) {
  528. log.info(stopWatch.toTable());
  529. }
  530. }
  531. resetGuestBrowseableElementDisplays();
  532. _initialized.set(true);
  533. return new AsyncResult<>(true);
  534. }
  535. @Override
  536. public boolean isInitialized() {
  537. return _initialized.get();
  538. }
  539. @Override
  540. public Map<String, String> getInitializationStatus() {
  541. final Map<String, String> status = new HashMap<>();
  542. if (_listener == null) {
  543. status.put("message", "No listener registered, so no status to report.");
  544. return status;
  545. }
  546. final Set<String> processed = _listener.getProcessed();
  547. final int processedCount = processed.size();
  548. final Set<String> unprocessed = _listener.getUnprocessed();
  549. final Date start = _listener.getStart();
  550. status.put("start", DATE_FORMAT.format(start));
  551. status.put("processedCount", Integer.toString(processedCount));
  552. status.put("processed", StringUtils.join(processed, ", "));
  553. if (unprocessed.isEmpty()) {
  554. final Date completed = _listener.getCompleted();
  555. final String duration = DurationFormatUtils.formatPeriodISO(start.getTime(), completed.getTime());
  556. status.put("completed", DATE_FORMAT.format(completed));
  557. status.put("duration", duration);
  558. status.put("message", "Cache initialization is complete. Processed " + processedCount + " groups in " + duration);
  559. return status;
  560. }
  561. final Date now = new Date();
  562. final String duration = DurationFormatUtils.formatPeriodISO(start.getTime(), now.getTime());
  563. final int unprocessedCount = unprocessed.size();
  564. status.put("unprocessedCount", Integer.toString(unprocessedCount));
  565. status.put("unprocessed", StringUtils.join(unprocessed, ", "));
  566. status.put("current", DATE_FORMAT.format(now));
  567. status.put("duration", duration);
  568. status.put("message", "Cache initialization is on-going, with " + processedCount + " groups processed and " + unprocessedCount + " groups remaining, time elapsed so far is " + duration);
  569. return status;
  570. }
  571. @Override
  572. public void registerListener(final Listener listener) {
  573. _listener = listener;
  574. }
  575. @Override
  576. public Listener getListener() {
  577. return _listener;
  578. }
  579. @Override
  580. protected boolean handleEventImpl(final XftItemEventI event) {
  581. switch (event.getXsiType()) {
  582. case XnatProjectdata.SCHEMA_ELEMENT_NAME:
  583. return handleProjectEvents(event);
  584. case XnatSubjectdata.SCHEMA_ELEMENT_NAME:
  585. return handleSubjectEvents(event);
  586. case XdatUsergroup.SCHEMA_ELEMENT_NAME:
  587. return handleGroupRelatedEvents(event);
  588. case XdatElementSecurity.SCHEMA_ELEMENT_NAME:
  589. return handleElementSecurityEvents(event);
  590. default:
  591. // This is always some type of experiment.
  592. return handleExperimentEvents(event);
  593. }
  594. }
  595. private boolean handleProjectEvents(final XftItemEventI event) {
  596. final String xsiType = event.getXsiType();
  597. final String id = event.getId();
  598. final String action = event.getAction();
  599. final Map<String, ?> properties = event.getProperties();
  600. try {
  601. switch (action) {
  602. case CREATE:
  603. log.debug("New project created with ID {}, caching new instance", xsiType, id);
  604. for (final String owner : getProjectOwners(id)) {
  605. updateUserProjectAccess(owner);
  606. if (!Iterables.any(getActionElementDisplays(owner).get(SecurityManager.CREATE), CONTAINS_MR_SESSION)) {
  607. initActionElementDisplays(owner, true);
  608. }
  609. }
  610. final boolean created = !initGroups(getGroups(xsiType, id)).isEmpty();
  611. final String access = Permissions.getProjectAccess(_template, id);
  612. if (StringUtils.isNotBlank(access)) {
  613. switch (access) {
  614. case "private":
  615. clearAllDataUserProjectAccess();
  616. break;
  617. case "public":
  618. if (!Iterables.any(getActionElementDisplays(GUEST_USERNAME).get(SecurityManager.READ), CONTAINS_MR_SESSION)) {
  619. initActionElementDisplays(GUEST_USERNAME, true);
  620. }
  621. case "protected":
  622. updateProjectRelatedCaches(xsiType, id, false);
  623. break;
  624. }
  625. }
  626. return created;
  627. case UPDATE:
  628. log.debug("The {} object {} was updated, caching updated instance", xsiType, id);
  629. if (properties.containsKey("accessibility")) {
  630. final String accessibility = (String) properties.get("accessibility");
  631. switch (accessibility) {
  632. case "private":
  633. return updateProjectRelatedCaches(xsiType, id, true);
  634. case "public":
  635. if (!Iterables.any(getActionElementDisplays(GUEST_USERNAME).get(SecurityManager.READ), CONTAINS_MR_SESSION)) {
  636. initActionElementDisplays(GUEST_USERNAME, true);
  637. }
  638. case "protected":
  639. return updateProjectRelatedCaches(xsiType, id, true);
  640. default:
  641. log.warn("The project {}'s accessibility setting was updated to an invalid value: {}. Must be one of private, protected, or public.", id, accessibility);
  642. }
  643. }
  644. break;
  645. case XftItemEventI.DELETE:
  646. log.debug("The {} {} was deleted, removing related instances from cache", xsiType, id);
  647. final String cacheId = getCacheIdForProject(id);
  648. evict(cacheId);
  649. for (final String accessCacheId : Iterables.filter(getCacheIdsForUserElements(), Predicates.contains(REGEX_USER_PROJECT_ACCESS_CACHE_ID))) {
  650. final List<String> projectIds = getCachedList(accessCacheId);
  651. if (projectIds != null && projectIds.contains(id)) {
  652. final List<String> updated = new ArrayList<>(projectIds);
  653. updated.remove(id);
  654. forceCacheObject(accessCacheId, updated);
  655. }
  656. }
  657. resetGuestBrowseableElementDisplays();
  658. initReadableCountsForUsers(this.<String>getCachedSet(cacheId));
  659. resetTotalCounts();
  660. return true;
  661. default:
  662. log.warn("I was informed that the '{}' action happened to the project with ID '{}'. I don't know what to do with this action.", action, xsiType, id);
  663. break;
  664. }
  665. } catch (ItemNotFoundException e) {
  666. log.warn("While handling action {}, I couldn't find a group for type {} ID {}.", action, xsiType, id);
  667. }
  668. return false;
  669. }
  670. private void clearAllDataUserProjectAccess() {
  671. for (final String groupId : ALL_DATA_GROUPS) {
  672. final UserGroupI group = get(groupId);
  673. if (group != null) {
  674. for (final String user : group.getUsernames()) {
  675. for (final String projectAction : ACTIONS) {
  676. evict(getCacheIdForUserProjectAccess(user, projectAction));
  677. }
  678. }
  679. }
  680. }
  681. }
  682. private boolean updateProjectRelatedCaches(final String xsiType, final String id, final boolean affectsOtherDataTypes) throws ItemNotFoundException {
  683. final boolean cachedRelatedGroups = !initGroups(getGroups(xsiType, id)).isEmpty();
  684. evict(GUEST_CACHE_ID);
  685. evict(GUEST_ACTION_READ);
  686. resetGuestBrowseableElementDisplays();
  687. initActionElementDisplays(GUEST_USERNAME, true);
  688. final Set<String> readableCountCacheIds = new HashSet<>(getCacheIdsForUserReadableCounts());
  689. if (affectsOtherDataTypes) {
  690. for (final String cacheId : readableCountCacheIds) {
  691. evict(cacheId);
  692. }
  693. resetTotalCounts();
  694. } else {
  695. // Update existing user element displays
  696. final List<String> cacheIds = getCacheIdsForActions();
  697. cacheIds.addAll(getCacheIdsForUserElements());
  698. clearAllUserProjectAccess();
  699. initReadableCountsForUsers(Sets.newHashSet(Iterables.filter(Lists.transform(cacheIds, FUNCTION_CACHE_IDS_TO_USERNAMES), Predicates.notNull())));
  700. resetProjectCount();
  701. }
  702. return cachedRelatedGroups;
  703. }
  704. private boolean handleSubjectEvents(final XftItemEventI event) {
  705. final String action = event.getAction();
  706. log.debug("Handling subject {} event for {} {}", XftItemEventI.ACTIONS.get(action), event.getXsiType(), event.getId());
  707. final Set<String> projectIds = new HashSet<>();
  708. switch (action) {
  709. case CREATE:
  710. projectIds.add(_template.queryForObject(QUERY_GET_SUBJECT_PROJECT, new MapSqlParameterSource("subjectId", event.getId()), String.class));
  711. resetTotalCounts();
  712. break;
  713. case SHARE:
  714. projectIds.add((String) event.getProperties().get("target"));
  715. break;
  716. case MOVE:
  717. projectIds.add((String) event.getProperties().get("origin"));
  718. projectIds.add((String) event.getProperties().get("target"));
  719. break;
  720. case XftItemEventI.DELETE:
  721. projectIds.add((String) event.getProperties().get("target"));
  722. handleGroupRelatedEvents(event);
  723. resetTotalCounts();
  724. break;
  725. default:
  726. log.warn("I was informed that the '{}' action happened to subject '{}'. I don't know what to do with this action.", action, event.getId());
  727. }
  728. if (projectIds.isEmpty()) {
  729. return false;
  730. }
  731. final Set<String> users = getProjectUsers(projectIds);
  732. for (final String username : users) {
  733. initReadableCountsForUser(username);
  734. }
  735. return true;
  736. }
  737. private boolean handleGroupRelatedEvents(final XftItemEventI event) {
  738. final String xsiType = event.getXsiType();
  739. final String id = event.getId();
  740. final String action = event.getAction();
  741. final Map<String, ?> properties = event.getProperties();
  742. final Set<String> usernames = new HashSet<>();
  743. try {
  744. final List<UserGroupI> groups = getGroups(xsiType, id);
  745. switch (action) {
  746. case CREATE:
  747. log.debug("New {} created with ID {}, caching new instance", xsiType, id);
  748. for (final UserGroupI group : groups) {
  749. usernames.addAll(group.getUsernames());
  750. }
  751. log.debug("Handling create group event with ID '{}' for users: {}", id);
  752. return !initGroups(groups).isEmpty();
  753. case UPDATE:
  754. log.debug("The {} object {} was updated, caching updated instance", xsiType, id);
  755. for (final UserGroupI group : groups) {
  756. usernames.addAll(group.getUsernames());
  757. evict(group.getId());
  758. }
  759. if (properties.containsKey(OPERATION) && StringUtils.equals((String) properties.get(OPERATION), OPERATION_REMOVE_USERS)) {
  760. //noinspection unchecked
  761. usernames.addAll((Collection<? extends String>) properties.get(USERS));
  762. }
  763. log.debug("Handling update group event with ID '{}' for users: {}", id);
  764. return !initGroups(groups).isEmpty();
  765. case XftItemEventI.DELETE:
  766. if (StringUtils.equals(XnatProjectdata.SCHEMA_ELEMENT_NAME, xsiType)) {
  767. final List<String> groupIds = getTagGroups(id);
  768. if (CollectionUtils.isNotEmpty(groupIds)) {
  769. log.info("Found {} groups cached for deleted project {}", groupIds.size(), id);
  770. for (final String groupId : groupIds) {
  771. evictGroup(groupId, usernames);
  772. }
  773. }
  774. } else {
  775. evictGroup(id, usernames);
  776. }
  777. break;
  778. default:
  779. log.warn("I was informed that the '{}' action happened to the {} object with ID '{}'. I don't know what to do with this action.", action, xsiType, id);
  780. }
  781. } catch (ItemNotFoundException e) {
  782. log.warn("While handling action {}, I couldn't find a group for type {} ID {}.", action, xsiType, id);
  783. } finally {
  784. for (final String username : usernames) {
  785. try {
  786. final String cacheId = getCacheIdForUserGroups(username);
  787. initUserGroupIds(cacheId, username);
  788. initReadableCountsForUser(username);
  789. } catch (UserNotFoundException e) {
  790. log.warn("While handling action {} for type {} ID {}, I couldn't find a user with username {}.", action, xsiType, id, username);
  791. }
  792. }
  793. }
  794. return false;
  795. }
  796. private boolean handleElementSecurityEvents(final XftItemEventI event) {
  797. log.debug("Handling {} event for '{}' IDs {}. Updating guest browseable element displays...", event.getAction(), event.getXsiType(), event.getIds());
  798. final Map<String, ElementDisplay> displays = resetGuestBrowseableElementDisplays();
  799. if (log.isTraceEnabled()) {
  800. log.trace("Got back {} browseable element displays for guest user after refresh: {}", displays.size(), StringUtils.join(displays.keySet(), ", "));
  801. }
  802. log.debug("Evicting all action and user element cache IDs");
  803. for (final String cacheId : Iterables.concat(getCacheIdsForActions(), getCacheIdsForUserElements())) {
  804. log.trace("Evicting cache entry with ID '{}'", cacheId);
  805. evict(cacheId);
  806. }
  807. for (final String dataType : event.getIds()) {
  808. final List<String> groupIds = getGroupIdsForDataType(dataType);
  809. log.debug("Found {} groups that reference the '{}' data type, updating cache entries for: {}", groupIds.size(), dataType, StringUtils.join(groupIds, ", "));
  810. for (final String groupId : groupIds) {
  811. log.trace("Evicting group '{}' due to change in element securities for data type {}", groupId, dataType);
  812. evict(groupId);
  813. }
  814. }
  815. return true;
  816. }
  817. private boolean handleExperimentEvents(final XftItemEventI event) {
  818. final String action = event.getAction();
  819. final String xsiType = event.getXsiType();
  820. log.debug("Handling experiment {} event for {} {}", XftItemEventI.ACTIONS.get(action), xsiType, event.getId());
  821. final String target, origin;
  822. switch (action) {
  823. case CREATE:
  824. target = _template.queryForObject(QUERY_GET_EXPERIMENT_PROJECT, new MapSqlParameterSource("experimentId", event.getId()), String.class);
  825. origin = null;
  826. break;
  827. case SHARE:
  828. target = (String) event.getProperties().get("target");
  829. origin = null;
  830. break;
  831. case MOVE:
  832. origin = (String) event.getProperties().get("origin");
  833. target = (String) event.getProperties().get("target");
  834. break;
  835. case XftItemEventI.DELETE:
  836. target = (String) event.getProperties().get("target");
  837. origin = null;
  838. break;
  839. default:
  840. log.warn("I was informed that the '{}' action happened to experiment '{}' with ID '{}'. I don't know what to do with this action.", action, xsiType, event.getId());
  841. return false;
  842. }
  843. final Map<String, ElementDisplay> displays = getGuestBrowseableElementDisplays();
  844. log.debug("Found {} elements for guest user: {}", displays.size(), StringUtils.join(displays.keySet(), ", "));
  845. // If the data type of the experiment isn't in the guest list AND the target project is public,
  846. // OR if the origin project is both specified and public (meaning the data type might be REMOVED
  847. // from the guest browseable element displays), then we update the guest browseable element displays.
  848. final boolean hasEventXsiType = displays.containsKey(xsiType);
  849. final boolean isTargetProjectPublic = Permissions.isProjectPublic(_template, target);
  850. final boolean hasOriginProject = StringUtils.isNotBlank(origin);
  851. final boolean isMovedFromPublicToNon = !isTargetProjectPublic && hasOriginProject && Permissions.isProjectPublic(_template, origin);
  852. // We need to add the XSI type if guest doesn't already have it and the target project is public.
  853. final boolean needsPublicXsiTypeAdded = !hasEventXsiType && isTargetProjectPublic;
  854. // We need to check if the XSI type should be removed if guest has XSI type and item was moved from public to non-public.
  855. final boolean needsXsiTypeChecked = hasEventXsiType && isMovedFromPublicToNon;
  856. if (needsPublicXsiTypeAdded || needsXsiTypeChecked) {
  857. if (needsPublicXsiTypeAdded) {
  858. log.debug("Updating guest browseable element displays: guest doesn't have the event XSI type '{}' and the target project {} is public.", xsiType, target);
  859. } else {
  860. log.debug("Updating guest browseable element displays: guest has the event XSI type '{}' and item was moved from public project {} to non-public project {}.", xsiType, origin, target);
  861. }
  862. resetGuestBrowseableElementDisplays();
  863. } else {
  864. log.debug("Not updating guest browseable element displays: guest {} '{}' and {}",
  865. hasEventXsiType ? "already has the event XSI type " : "doesn't have the event XSI type",
  866. xsiType,
  867. isTargetProjectPublic ? "target project is public" : "target project is not public");
  868. }
  869. initReadableCountsForUsers(hasOriginProject ? getProjectUsers(target) : getProjectUsers(target, origin));
  870. if (StringUtils.equalsAny(action, CREATE, XftItemEventI.DELETE)) {
  871. resetTotalCounts();
  872. }
  873. return true;
  874. }
  875. private void handleCacheRemoveEvent(final Ehcache cache, final Element element, final String event) {
  876. if (isGroupsAndPermissionsCacheEvent(cache)) {
  877. if (element == null) {
  878. log.debug("Got a {} event for cache {}, no specific element affected", event, cache.getName());
  879. return;
  880. }
  881. final Object objectValue = element.getObjectValue();
  882. log.debug("Got a {} event for cache {} on ID {} with value of type {}", event, cache.getName(), element.getObjectKey(), objectValue != null ? objectValue.getClass().getName() : "<null>");
  883. }
  884. }
  885. @SuppressWarnings({"UnusedReturnValue", "SameParameterValue"})
  886. private synchronized List<String> updateUserProjectAccess(final String username) {
  887. final List<String> projectIds = new ArrayList<>();
  888. for (final String access : Arrays.asList(SecurityManager.READ, SecurityManager.EDIT, SecurityManager.DELETE)) {
  889. projectIds.addAll(updateUserProjectAccess(username, access));
  890. }
  891. return projectIds;
  892. }
  893. @SuppressWarnings({"UnusedReturnValue", "SameParameterValue"})
  894. private synchronized List<String> updateUserProjectAccess(final String username, final String access) {
  895. return updateUserProjectAccess(username, access, getCacheIdForUserProjectAccess(username, access));
  896. }
  897. private synchronized List<String> updateUserProjectAccess(final String username, final String access, final String cacheId) {
  898. final List<String> projectIds;
  899. switch (access) {
  900. case SecurityManager.READ:
  901. projectIds = getUserReadableProjects(username);
  902. break;
  903. case SecurityManager.EDIT:
  904. projectIds = getUserEditableProjects(username);
  905. break;
  906. case SecurityManager.DELETE:
  907. projectIds = getUserOwnedProjects(username);
  908. break;
  909. default:
  910. throw new RuntimeException("Unknown access level '" + access + "'. Must be one of " + SecurityManager.READ + ", " + SecurityManager.EDIT + ", or " + SecurityManager.DELETE + ".");
  911. }
  912. cacheObject(cacheId, projectIds);
  913. return ImmutableList.copyOf(projectIds);
  914. }
  915. private List<String> getProjectOwners(final String projectId) {
  916. return _template.queryForList(QUERY_PROJECT_OWNERS, new MapSqlParameterSource("projectId", projectId), String.class);
  917. }
  918. private ListMultimap<String, ElementDisplay> getActionElementDisplays(final String username) {
  919. final String cacheId = getCacheIdForActionElements(username);
  920. // Check whether the action elements are cached and, if so, return that.
  921. final ListMultimap<String, ElementDisplay> cachedActions = getCachedListMultimap(cacheId);
  922. if (cachedActions != null) {
  923. log.debug("Found a cache entry for user '{}' action elements by ID '{}'", username, cacheId);
  924. return cachedActions;
  925. }
  926. return initActionElementDisplays(username);
  927. }
  928. private Long getUserReadableWorkflowCount(final UserI user) {
  929. return _template.queryForObject(QUERY_USER_READABLE_WORKFLOW_COUNT, new MapSqlParameterSource("username", user.getUsername()), Long.class);
  930. }
  931. @Nonnull
  932. private Map<String, UserGroupI> getMutableGroupsForUser(final String username) throws UserNotFoundException {
  933. final List<String> groupIds = getGroupIdsForUser(username);
  934. final Map<String, UserGroupI> groups = new HashMap<>();
  935. for (final String groupId : groupIds) {
  936. final UserGroupI group = get(groupId);
  937. if (group != null) {
  938. log.trace("Adding group {} to groups for user {}", groupId, username);
  939. groups.put(groupId, group);
  940. } else {
  941. log.info("User '{}' is associated with the group ID '{}', but I couldn't find that actual group", username, groupId);
  942. }
  943. }
  944. return groups;
  945. }
  946. @Nonnull
  947. private Map<String, ElementAccessManager> getElementAccessManagers(final String username) {
  948. if (StringUtils.isBlank(username)) {
  949. return Collections.emptyMap();
  950. }
  951. final String cacheId = getCacheIdForUserElementAccessManagers(username);
  952. final Map<String, ElementAccessManager> cachedElementAccessManagers = getCachedMap(cacheId);
  953. if (cachedElementAccessManagers != null) {
  954. log.debug("Found a cache entry for user '{}' element access managers by ID '{}' with {} entries", username, cacheId, cachedElementAccessManagers.size());
  955. return cachedElementAccessManagers;
  956. }
  957. return initElementAccessManagersForUser(cacheId, username);
  958. }
  959. private Map<String, ElementDisplay> getGuestBrowseableElementDisplays() {
  960. final Map<String, ElementDisplay> guestBrowseableElementDisplays = getCachedMap(GUEST_CACHE_ID);
  961. if (guestBrowseableElementDisplays != null) {
  962. return guestBrowseableElementDisplays;
  963. }
  964. return resetBrowseableElementDisplays(_guest);
  965. }
  966. private void clearAllUserProjectAccess() {
  967. final List<String> cacheIds = getCacheIdsForUserElements();
  968. for (final String cacheId : cacheIds) {
  969. evict(cacheId);
  970. }
  971. }
  972. /**
  973. * This method retrieves the group with the specified group ID and puts it in the cache. This method
  974. * does <i>not</i> check to see if the group is already in the cache! This is primarily for use during
  975. * cache initialization and shouldn't be used for routine access.
  976. *
  977. * @param groupId The ID or alias of the group to retrieve.
  978. *
  979. * @return The group object for the specified ID if it exists, null otherwise.
  980. */
  981. @CacheLock(true)
  982. private UserGroupI initGroup(@CacheId final String groupId) throws ItemNotFoundException {
  983. return initGroupImpl(groupId, new UserGroup(groupId, _template));
  984. }
  985. @CacheLock(true)
  986. private UserGroupI initGroup(@CacheId final String groupId, final UserGroupI group) {
  987. return initGroupImpl(groupId, group);
  988. }
  989. private UserGroupI initGroupImpl(final String groupId, final UserGroupI group) {
  990. log.info("Initializing cache entry for group '{}'", groupId);
  991. cacheObject(groupId, group);
  992. log.debug("Retrieved and cached the group for the ID {}", groupId);
  993. return group;
  994. }
  995. @CacheLock(true)
  996. private Map<String, Long> initReadableCountsForUser(final String cacheId, final UserI user) {
  997. log.info("Initializing readable counts for user '{}' with cache entry '{}'", user.getUsername(), cacheId);
  998. final String username = user.getUsername();
  999. try {
  1000. if (!user.isGuest()) {
  1001. initProjectMember(cacheId, username);
  1002. }
  1003. final Map<String, Long> readableCounts = new HashMap<>();
  1004. final List<String> readableProjects = getUserReadableProjects(username);
  1005. readableCounts.put(XnatProjectdata.SCHEMA_ELEMENT_NAME, (long) readableProjects.size());
  1006. readableCounts.put(WrkWorkflowdata.SCHEMA_ELEMENT_NAME, getUserReadableWorkflowCount(user));
  1007. readableCounts.putAll(getUserReadableSubjectsAndExperiments(readableProjects));
  1008. if (log.isDebugEnabled()) {
  1009. log.debug("Caching the following readable element counts for user '{}' with cache ID '{}': '{}'", username, cacheId, getDisplayForReadableCounts(readableCounts));
  1010. }
  1011. cacheObject(cacheId, readableCounts);
  1012. return ImmutableMap.copyOf(readableCounts);
  1013. } catch (DataAccessException e) {
  1014. log.error("An error occurred in the SQL for retrieving readable counts for the user {}", username, e);
  1015. return Collections.emptyMap();
  1016. }
  1017. }
  1018. @CacheLock(true)
  1019. private void initProjectMember(final String cacheId, final String username) {
  1020. log.debug("Initializing cached project entries for user '{}' with cache ID '{}', initializing entry", username, cacheId);
  1021. final List<String> projects = getUserProjects(username);
  1022. for (final String project : projects) {
  1023. final String projectCacheId = getCacheIdForProject(project);
  1024. final Set<String> cachedSet = getCachedSet(projectCacheId);
  1025. final Set<String> projectUsers = new HashSet<>();
  1026. projectUsers.add(username);
  1027. if (cachedSet != null) {
  1028. projectUsers.addAll(cachedSet);
  1029. }
  1030. forceCacheObject(projectCacheId, projectUsers);
  1031. }
  1032. }
  1033. @CacheLock(true)
  1034. private synchronized List<String> initTag(final String cacheId, final String tag) {
  1035. // If there's a blank tag...
  1036. if (StringUtils.isBlank(tag)) {
  1037. throw new IllegalArgumentException("Can not request a blank tag, that's not a thing.");
  1038. }
  1039. log.info("Initializing cache entry for tag '{}' with cache ID '{}'", tag, cacheId);
  1040. // Then retrieve and cache the groups if found or cache DOES_NOT_EXIST if the tag isn't found.
  1041. final List<String> groups = getGroupIdsForProject(tag);
  1042. // If this is empty, then the tag doesn't exist and we'll just put DOES_NOT_EXIST there.
  1043. if (groups.isEmpty()) {
  1044. log.info("Someone tried to get groups for the tag {}, but there are no groups with that tag.", tag);
  1045. return Collections.emptyList();
  1046. } else {
  1047. log.debug("Cached tag {} for {} groups: {}", tag, groups.size(), StringUtils.join(groups, ", "));
  1048. cacheObject(cacheId, groups);
  1049. return groups;
  1050. }
  1051. }
  1052. @CacheLock(true)
  1053. private Map<String, ElementAccessManager> initElementAccessManagersForUser(final String cacheId, final String username) {
  1054. log.info("Initializing element access managers cache entry for user '{}' with cache ID '{}'", username, cacheId);
  1055. final Map<String, ElementAccessManager> managers = ElementAccessManager.initialize(_template, QUERY_USER_PERMISSIONS, new MapSqlParameterSource("username", username));
  1056. log.trace("Found {} element access managers for user '{}'", managers.size());
  1057. cacheObject(cacheId, managers);
  1058. return managers;
  1059. }
  1060. @CacheLock(true)
  1061. private synchronized Map<String, ElementDisplay> initBrowseableElementDisplaysForUser(final String cacheId, final UserI user) {
  1062. final String username = user.getUsername();
  1063. log.info("Initializing browseable element displays cache entry for user '{}' with cache ID '{}'", username, cacheId);
  1064. final Map<String, Long> counts = getReadableCounts(user);
  1065. log.debug("Found {} readable counts for user {}: {}", counts.size(), username, counts);
  1066. try {
  1067. final Map<String, ElementDisplay> browseableElements = new HashMap<>();
  1068. final List<ElementDisplay> elementDisplays = getActionElementDisplays(user.getUsername(), SecurityManager.READ);
  1069. if (log.isTraceEnabled()) {
  1070. log.trace("Found {} readable action element displays for user {}: {}", elementDisplays.size(), username, formatElementDisplays(elementDisplays));
  1071. }
  1072. for (final ElementDisplay elementDisplay : elementDisplays) {
  1073. final String elementName = elementDisplay.getElementName();
  1074. final boolean isBrowseableElement = ElementSecurity.IsBrowseableElement(elementName);
  1075. final boolean countsContainsKey = counts.containsKey(elementName);
  1076. final boolean hasOneOrMoreElement = countsContainsKey && counts.get(elementName) > 0;
  1077. if (isBrowseableElement && countsContainsKey && hasOneOrMoreElement) {
  1078. log.debug("Adding element display {} to cache entry {} for user {}", elementName, cacheId, username);
  1079. browseableElements.put(elementName, elementDisplay);
  1080. } else if (log.isTraceEnabled()) {
  1081. log.trace("Did not add element display {} for user {}: {}, {}", elementName, username, isBrowseableElement ? "browseable" : "not browseable", countsContainsKey ? "counts contains key" : "counts does not contain key", hasOneOrMoreElement ? "counts has one or more elements of this type" : "counts does not have any elements of this type");
  1082. }
  1083. }
  1084. log.info("Adding {} element displays to cache entry {}", browseableElements.size(), cacheId);
  1085. cacheObject(cacheId, browseableElements);
  1086. return browseableElements;
  1087. } catch (ElementNotFoundException e) {
  1088. if (!_missingElements.containsKey(e.ELEMENT)) {
  1089. log.warn("Element '{}' not found. This may be a data type that was installed previously but can't be located now. This warning will only be displayed once. Set logging level to DEBUG to see a message each time this occurs for each element, along with a count of the number of times the element was referenced.", e.ELEMENT);
  1090. _missingElements.put(e.ELEMENT, 1L);
  1091. } else {
  1092. final long count = _missingElements.get(e.ELEMENT) + 1;
  1093. _missingElements.put(e.ELEMENT, count);
  1094. log.debug("Element '{}' not found. This element has been referenced {} times.", e.ELEMENT, count);
  1095. }
  1096. } catch (XFTInitException e) {
  1097. log.error("There was an error initializing or accessing XFT", e);
  1098. } catch (Exception e) {
  1099. log.error("An unknown error occurred", e);
  1100. }
  1101. log.info("No browseable element displays found for user {}", username);
  1102. return Collections.emptyMap();
  1103. }
  1104. @Nonnull
  1105. @CacheLock(true)
  1106. private List<String> initUserGroupIds(final String cacheId, final String username) throws UserNotFoundException {
  1107. log.info("Initializing user group IDs cache entry for user '{}' with cache ID '{}'", username, cacheId);
  1108. final List<String> groupIds = _template.queryForList(QUERY_GET_GROUPS_FOR_USER, checkUser(username), String.class);
  1109. log.debug("Found {} user group IDs cache entry for user '{}'", groupIds.size(), username);
  1110. cacheObject(cacheId, groupIds);
  1111. return ImmutableList.copyOf(groupIds);
  1112. }
  1113. private ListMultimap<String, ElementDisplay> initActionElementDisplays(final String username) {
  1114. return initActionElementDisplays(username, false);
  1115. }
  1116. private synchronized ListMultimap<String, ElementDisplay> initActionElementDisplays(final String username, final boolean evict) {
  1117. final String cacheId = getCacheIdForActionElements(username);
  1118. log.info("Initializing action element displays cache entry {} for user '{}', evict is {}", cacheId, username, evict);
  1119. // If they want to evict the cache entry, then do that and proceed. They explicitly don't want any cached entry to be returned.
  1120. if (evict) {
  1121. evict(cacheId);
  1122. } else {
  1123. final ListMultimap<String, ElementDisplay> cachedObject = getCachedListMultimap(cacheId);
  1124. if (cachedObject != null) {
  1125. log.debug("Found an existing cached entry for cache ID {}, which probably means the cache was initialized while this call was locked out by method sync", cacheId);
  1126. return cachedObject;
  1127. }
  1128. }
  1129. final ListMultimap<String, ElementDisplay> elementDisplays = ArrayListMultimap.create();
  1130. try {
  1131. final List<ElementSecurity> securities = ElementSecurity.GetSecureElements();
  1132. if (log.isDebugEnabled()) {
  1133. log.debug("Evaluating {} element security objects: {}", securities.size(), StringUtils.join(Lists.transform(securities, FUNCTION_ELEMENT_SECURITY_TO_STRING), ", "));
  1134. }
  1135. for (final ElementSecurity elementSecurity : securities) {
  1136. try {
  1137. final SchemaElement schemaElement = elementSecurity.getSchemaElement();
  1138. if (schemaElement != null) {
  1139. final String fullXMLName = schemaElement.getFullXMLName();
  1140. log.debug("Evaluating schema element {}", fullXMLName);
  1141. if (schemaElement.hasDisplay()) {
  1142. log.debug("Schema element {} has a display", fullXMLName);
  1143. for (final String action : ACTIONS) {
  1144. log.debug("Check user {} permission for action {} on schema element {}", username, action, fullXMLName);
  1145. if (Permissions.canAny(username, elementSecurity.getElementName(), action)) {
  1146. log.debug("User {} can {} schema element {}", username, action, fullXMLName);
  1147. final ElementDisplay elementDisplay = schemaElement.getDisplay();
  1148. if (elementDisplay != null) {
  1149. log.debug("Adding element display {} to action {} for user {}", elementDisplay.getElementName(), action, username);
  1150. elementDisplays.put(action, elementDisplay);
  1151. }
  1152. } else {
  1153. log.debug("User {} can not {} schema element {}", username, action, fullXMLName);
  1154. }
  1155. }
  1156. } else {
  1157. log.debug("Schema element {} does not have a display, rejecting", fullXMLName);
  1158. }
  1159. } else {
  1160. log.warn("Element '{}' not found. This may be a data type that was installed previously but can't be located now.", elementSecurity.getElementName());
  1161. }
  1162. } catch (ElementNotFoundException e) {
  1163. log.warn("Element '{}' not found. This may be a data type that was installed previously but can't be located now.", e.ELEMENT);
  1164. } catch (Exception e) {
  1165. log.error("An exception occurred trying to retrieve a secure element schema", e);
  1166. }
  1167. }
  1168. } catch (Exception e) {
  1169. log.error("An error occurred trying to retrieve the list of secure elements. Proceeding but things probably won't go well from here on out.", e);
  1170. }
  1171. try {
  1172. for (final ElementSecurity elementSecurity : ElementSecurity.GetInSecureElements()) {
  1173. try {
  1174. final SchemaElement schemaElement = elementSecurity.getSchemaElement();
  1175. if (schemaElement.hasDisplay()) {
  1176. final ElementDisplay elementDisplay = schemaElement.getDisplay();
  1177. log.debug("Adding all actions for insecure schema element {} to user {} permissions", elementDisplay.getElementName(), username);
  1178. for (final String action : ACTIONS) {
  1179. elementDisplays.put(action, elementDisplay);
  1180. }
  1181. }
  1182. } catch (ElementNotFoundException e) {
  1183. log.warn("Element '{}' not found. This may be a data type that was installed previously but can't be located now.", e.ELEMENT);
  1184. } catch (Exception e) {
  1185. log.error("An exception occurred trying to retrieve an insecure element schema", e);
  1186. }
  1187. }
  1188. } catch (Exception e) {
  1189. log.error("An error occurred trying to retrieve the list of insecure elements. Proceeding but things probably won't go well from here on out.", e);
  1190. }
  1191. if (log.isTraceEnabled()) {
  1192. final List<ElementDisplay> readElements = elementDisplays.get(SecurityManager.READ);
  1193. final List<ElementDisplay> editElements = elementDisplays.get(SecurityManager.EDIT);
  1194. final List<ElementDisplay> createElements = elementDisplays.get(SecurityManager.CREATE);
  1195. log.trace("Cached {} elements with READ access, {} elements with EDIT access, and {} elements with CREATE access for user {} with cache ID {}:\n * READ: {}\n * EDIT: {}\n * CREATE: {}", readElements.size(), editElements.size(), createElements.size(), username, cacheId, formatElementDisplays(readElements), formatElementDisplays(editElements), formatElementDisplays(createElements));
  1196. } else {
  1197. log.info("Cached {} elements with READ access, {} elements with EDIT access, and {} elements with CREATE access for user {} with cache ID {}", elementDisplays.get(SecurityManager.READ).size(), elementDisplays.get(SecurityManager.EDIT).size(), elementDisplays.get(SecurityManager.CREATE).size(), username, cacheId);
  1198. }
  1199. cacheObject(cacheId, elementDisplays);
  1200. return elementDisplays;
  1201. }
  1202. private void initReadableCountsForUsers(@Nullable final Collection<String> usernames) {
  1203. if (CollectionUtils.isNotEmpty(usernames)) {
  1204. log.info("Initializing readable counts cache entry for {} users: {}", usernames.size(), usernames);
  1205. for (final String username : usernames) {
  1206. initReadableCountsForUser(username);
  1207. }
  1208. } else {
  1209. log.info("Request to initialize readable counts cache entry for users but the list of users is null or empty");
  1210. }
  1211. }
  1212. private void initReadableCountsForUser(final String username) {
  1213. final String cacheId = getCacheIdForUserElements(username, READABLE);
  1214. log.debug("Retrieving readable counts for user {} through cache ID {}", username, cacheId);
  1215. // Check whether the user has readable counts and browseable elements cached. We only need to refresh
  1216. // for those who have them cached.
  1217. final Map<String, Long> cachedReadableCounts = getCachedMap(cacheId);
  1218. if (cachedReadableCounts != null) {
  1219. try {
  1220. log.debug("Found a cache entry for user '{}' readable counts by ID '{}', updating cache entry", username, cacheId);
  1221. final XDATUser user = new XDATUser(username);
  1222. initReadableCountsForUser(cacheId, user);
  1223. initBrowseableElementDisplaysForUser(getCacheIdForUserElements(username, BROWSEABLE), user);
  1224. } catch (UserNotFoundException e) {
  1225. log.warn("Got a user not found exception for username '{}', which is weird because this user has a cache entry.", username, e);
  1226. } catch (UserInitException e) {
  1227. log.error("An error occurred trying to retrieve the user '{}'", username, e);
  1228. } catch (Exception e) {
  1229. log.error("An unexpected error occurred trying to retrieve the readable counts for user '{}'", username, e);
  1230. }
  1231. }
  1232. }
  1233. private synchronized List<UserGroupI> initGroups(final List<UserGroupI> groups) {
  1234. log.debug("Caching {} groups", groups.size());
  1235. return Lists.transform(groups, new InitGroupFunction(this));
  1236. }
  1237. private void resetTotalCounts() {
  1238. resetProjectCount();
  1239. final Long subjectCount = _template.queryForObject("SELECT COUNT(*) FROM xnat_subjectData", EmptySqlParameterSource.INSTANCE, Long.class);
  1240. _totalCounts.put(XnatSubjectdata.SCHEMA_ELEMENT_NAME, subjectCount);
  1241. final List<Map<String, Object>> elementCounts = _template.queryForList("SELECT element_name, COUNT(ID) AS count FROM xnat_experimentData expt LEFT JOIN xdat_meta_element xme ON expt.extension=xme.xdat_meta_element_id GROUP BY element_name", EmptySqlParameterSource.INSTANCE);
  1242. for (final Map<String, Object> elementCount : elementCounts) {
  1243. _totalCounts.put((String) elementCount.get("element_name"), (Long) elementCount.get("count"));
  1244. }
  1245. }
  1246. private void resetProjectCount() {
  1247. _totalCounts.clear();
  1248. final Long projectCount = _template.queryForObject("SELECT COUNT(*) FROM xnat_projectData", EmptySqlParameterSource.INSTANCE, Long.class);
  1249. _totalCounts.put(XnatProjectdata.SCHEMA_ELEMENT_NAME, projectCount);
  1250. }
  1251. private Map<String, ElementDisplay> resetGuestBrowseableElementDisplays() {
  1252. return resetBrowseableElementDisplays(getGuest());
  1253. }
  1254. private Map<String, ElementDisplay> resetBrowseableElementDisplays(final XDATUser user) {
  1255. log.debug("Updating guest browseable element displays, clearing local cache, updating element access managers, and updating browseable element displays");
  1256. user.clearLocalCache();
  1257. final String username = user.getUsername();
  1258. initElementAccessManagersForUser(getCacheIdForUserElementAccessManagers(username), username);
  1259. return ImmutableMap.copyOf(initBrowseableElementDisplaysForUser(getCacheIdForUserElements(username, BROWSEABLE), user));
  1260. }
  1261. private Map<String, Long> getUserReadableSubjectsAndExperiments(final List<String> readableProjectIds) {
  1262. if (readableProjectIds.isEmpty()) {
  1263. return Collections.emptyMap();
  1264. }
  1265. final MapSqlParameterSource parameters = new MapSqlParameterSource("projectIds", readableProjectIds);
  1266. final Map<String, Long> readableExperimentCounts = _template.query(QUERY_USER_READABLE_EXPERIMENT_COUNT, parameters, ELEMENT_COUNT_EXTRACTOR);
  1267. final Long readableSubjectCount = _template.queryForObject(QUERY_USER_READABLE_SUBJECT_COUNT, parameters, Long.class);
  1268. readableExperimentCounts.put(XnatSubjectdata.SCHEMA_ELEMENT_NAME, readableSubjectCount);
  1269. return readableExperimentCounts;
  1270. }
  1271. private List<UserGroupI> getGroups(final String type, final String id) throws ItemNotFoundException {
  1272. switch (type) {
  1273. case XnatProjectdata.SCHEMA_ELEMENT_NAME:
  1274. return getGroupsForTag(id);
  1275. case XdatUsergroup.SCHEMA_ELEMENT_NAME:
  1276. return Collections.<UserGroupI>singletonList(new UserGroup(id, _template));
  1277. case XdatElementSecurity.SCHEMA_ELEMENT_NAME:
  1278. final List<String> groupIds = getGroupIdsForDataType(id);
  1279. return initGroups(Lists.transform(groupIds, new Function<String, UserGroupI>() {
  1280. @Override
  1281. public UserGroupI apply(final String groupId) {
  1282. try {
  1283. return new UserGroup(groupId, _template);
  1284. } catch (ItemNotFoundException e) {
  1285. log.warn("Somehow didn't find a usergroup that should exist: {}", groupId, e);
  1286. return null;
  1287. }
  1288. }
  1289. }));
  1290. }
  1291. return Collections.emptyList();
  1292. }
  1293. private List<String> getGroupIdsForDataType(final String dataType) {
  1294. return _template.queryForList(QUERY_GET_GROUPS_FOR_DATATYPE, new MapSqlParameterSource("dataType", dataType), String.class);
  1295. }
  1296. private List<String> getGroupIdsForProject(final String projectId) {
  1297. return _template.queryForList(QUERY_GET_GROUPS_FOR_TAG, new MapSqlParameterSource("tag", projectId), String.class);
  1298. }
  1299. /**
  1300. * Retrieves a list of projects where the specified user is a member. This differs from {@link #getUserReadableProjects(String)} in that it
  1301. * includes neither public projects nor projects to which the user has read access due to all data access privileges.
  1302. *
  1303. * @param username The username to retrieve projects for.
  1304. *
  1305. * @return A list of projects for which the specified user is a member.
  1306. */
  1307. private List<String> getUserProjects(final String username) {
  1308. return _template.queryForList(QUERY_GET_PROJECTS_FOR_USER, new MapSqlParameterSource("username", username), String.class);
  1309. }
  1310. /**
  1311. * Retrieves a list of projects where the specified user has read access. This differs from {@link #getUserProjects(String)} in that it
  1312. * includes protected and public projects and projects to which the user has read access due to all data access privileges.
  1313. *
  1314. * @param username The username to retrieve projects for.
  1315. *
  1316. * @return A list of projects to which the specified user has read access.
  1317. */
  1318. private List<String> getUserReadableProjects(final String username) {
  1319. return Lists.newArrayList(Iterables.concat(getProjectsByAccessQuery(username, QUERY_READABLE_PROJECTS, false), Permissions.getAllPublicProjects(_template)));
  1320. }
  1321. private List<String> getUserEditableProjects(final String username) {
  1322. return getProjectsByAccessQuery(username, QUERY_EDITABLE_PROJECTS, true);
  1323. }
  1324. private List<String> getUserOwnedProjects(final String username) {
  1325. return getProjectsByAccessQuery(username, QUERY_OWNED_PROJECTS, true);
  1326. }
  1327. private List<String> getProjectsByAccessQuery(final String username, final String query, final boolean requireAdminAccess) {
  1328. final MapSqlParameterSource parameters = new MapSqlParameterSource("username", username);
  1329. return hasAllDataAdmin(username) || (hasAllDataAccess(username) && !requireAdminAccess)
  1330. ? _template.queryForList(QUERY_ALL_DATA_ACCESS_PROJECTS, parameters, String.class)
  1331. : _template.queryForList(query, parameters, String.class);
  1332. }
  1333. /**
  1334. * Cheap test to see if the user is in a group that has data read access on all projects.
  1335. *
  1336. * @param username The username to be checked.
  1337. *
  1338. * @return Returns true if the user has all-data-access, false otherwise.
  1339. */
  1340. private boolean hasAllDataAccess(final String username) {
  1341. return _template.queryForObject(QUERY_HAS_ALL_DATA_ACCESS, new MapSqlParameterSource("username", username), Boolean.class);
  1342. }
  1343. /**
  1344. * Cheap test to see if the user is in a group that has data admin access on all projects.
  1345. *
  1346. * @param username The username to be checked.
  1347. *
  1348. * @return Returns true if the user has all-data-admin, false otherwise.
  1349. */
  1350. private boolean hasAllDataAdmin(final String username) {
  1351. return _template.queryForObject(QUERY_HAS_ALL_DATA_ADMIN, new MapSqlParameterSource("username", username), Boolean.class);
  1352. }
  1353. private int initializeTags() {
  1354. final List<String> tags = _template.queryForList(QUERY_ALL_TAGS, EmptySqlParameterSource.INSTANCE, String.class);
  1355. for (final String tag : tags) {
  1356. getTagGroups(tag);
  1357. }
  1358. final int size = tags.size();
  1359. log.info("Completed initialization of {} tags", size);
  1360. return size;
  1361. }
  1362. private Set<String> getProjectUsers(final String... projectIds) {
  1363. return getProjectUsers(Arrays.asList(projectIds));
  1364. }
  1365. private Set<String> getProjectUsers(final Collection<String> projectIds) {
  1366. return projectIds.isEmpty() ? Collections.<String>emptySet() : new HashSet<>(_template.queryForList(QUERY_GET_USERS_FOR_PROJECTS, new MapSqlParameterSource("projectIds", projectIds), String.class));
  1367. }
  1368. private List<String> getTagGroups(final String tag) {
  1369. final String cacheId = getCacheIdForTag(tag);
  1370. final List<String> groupIds = getCachedList(cacheId);
  1371. if (groupIds != null) {
  1372. // If it's cached, we can just return the list.
  1373. log.info("Found {} groups cached for tag '{}'", groupIds.size(), tag);
  1374. return groupIds;
  1375. }
  1376. return initTag(getCacheIdForTag(tag), tag);
  1377. }
  1378. private List<String> getGroupIdsForUser(final String username) throws UserNotFoundException {
  1379. final String cacheId = getCacheIdForUserGroups(username);
  1380. final List<String> cachedList = getCachedList(cacheId);
  1381. if (cachedList != null) {
  1382. if (log.isTraceEnabled()) {
  1383. log.trace("Found cached groups list for user '{}' with cache ID '{}': {}", username, cacheId, StringUtils.join(cachedList, ", "));
  1384. } else {
  1385. log.info("Found cached groups list for user '{}' with cache ID '{}' with {} entries", username, cacheId, cachedList.size());
  1386. }
  1387. return cachedList;
  1388. }
  1389. return initUserGroupIds(cacheId, username);
  1390. }
  1391. private void evictGroup(final String groupId, final Set<String> usernames) {
  1392. final UserGroupI group = get(groupId);
  1393. if (group != null) {
  1394. final List<String> groupUsernames = group.getUsernames();
  1395. log.debug("Found group for ID '{}' with {} associated users", groupId, groupUsernames.size());
  1396. usernames.addAll(groupUsernames);
  1397. evict(groupId);
  1398. } else {
  1399. log.info("Requested to evict group with ID '{}', but I couldn't find that actual group", groupId);
  1400. }
  1401. }
  1402. /**
  1403. * Checks whether the users exists. If not, this throws the {@link UserNotFoundException}. Otherwise it returns
  1404. * a parameter source containing the username that can be used in subsequent queries.
  1405. *
  1406. * @param username The user to test.
  1407. *
  1408. * @return A parameter source containing the username parameter.
  1409. *
  1410. * @throws UserNotFoundException If the user doesn't exist.
  1411. */
  1412. private MapSqlParameterSource checkUser(final String username) throws UserNotFoundException {
  1413. final MapSqlParameterSource parameters = new MapSqlParameterSource("username", username);
  1414. // If the user isn't in the check map OR the user is in the check map but is set as not existing...
  1415. if (!_userChecks.containsKey(username) || !_userChecks.get(username)) {
  1416. // See if the user exists now. The non-existent user existing should be updated with the add user event,
  1417. // but we don't have a clearly defined handler for that yet.
  1418. _userChecks.put(username, _template.queryForObject(QUERY_CHECK_USER_EXISTS, parameters, Boolean.class));
  1419. }
  1420. if (!_userChecks.get(username)) {
  1421. throw new UserNotFoundException(username);
  1422. }
  1423. return parameters;
  1424. }
  1425. private XDATUser getGuest() {
  1426. if (_guest == null) {
  1427. log.debug("No guest user initialized, trying to retrieve now.");
  1428. try {
  1429. final UserI guest = Users.getGuest();
  1430. if (guest instanceof XDATUser) {
  1431. _guest = (XDATUser) guest;
  1432. } else {
  1433. _guest = new XDATUser(guest.getUsername());
  1434. }
  1435. } catch (UserNotFoundException e) {
  1436. log.error("Got a user name not found exception for the guest user which is very strange.", e);
  1437. } catch (UserInitException e) {
  1438. log.error("Got a user init exception for the guest user which is very unfortunate.", e);
  1439. }
  1440. }
  1441. return _guest;
  1442. }
  1443. private boolean isGuest(final String username) {
  1444. return _guest != null ? StringUtils.equalsIgnoreCase(_guest.getUsername(), username) : StringUtils.equalsIgnoreCase(GUEST_USERNAME, username);
  1445. }
  1446. private List<UserGroupI> getUserGroupList(final List groupIds) {
  1447. if (groupIds == null || groupIds.isEmpty()) {
  1448. return new ArrayList<>();
  1449. }
  1450. return ImmutableList.copyOf(Iterables.filter(Iterables.transform(Iterables.filter(groupIds, String.class), new Function<String, UserGroupI>() {
  1451. @Override
  1452. public UserGroupI apply(final String groupId) {
  1453. return get(groupId);
  1454. }
  1455. }), Predicates.notNull()));
  1456. }
  1457. private UserGroupI getCachedGroup(final String cacheId) {
  1458. return getCachedObject(cacheId, UserGroupI.class);
  1459. }
  1460. private List<String> getCacheIdsForUserReadableCounts() {
  1461. return Lists.newArrayList(Iterables.filter(Iterables.filter(getEhCache().getKeys(), String.class), Predicates.contains(REGEX_USER_READABLE_COUNTS)));
  1462. }
  1463. private List<String> getCacheIdsForActions() {
  1464. return getCacheIdsForPrefix(ACTIONS_PREFIX);
  1465. }
  1466. private List<String> getCacheIdsForUserElements() {
  1467. return getCacheIdsForPrefix(USER_ELEMENT_PREFIX);
  1468. }
  1469. private List<String> getCacheIdsForUserElements(final String username) {
  1470. return getCacheIdsForPrefix(USER_ELEMENT_PREFIX, username);
  1471. }
  1472. private List<String> getCacheIdsForPrefix(final String... prefixes) {
  1473. return Lists.newArrayList(Iterables.filter(Iterables.filter(getEhCache().getKeys(), String.class), Predicates.containsPattern("^" + StringUtils.join(prefixes, ":") + ":.*$")));
  1474. }
  1475. private List<String> getCacheIdsForUsername(final String username) {
  1476. return Lists.newArrayList(Iterables.filter(Iterables.filter(getEhCache().getKeys(), String.class), new Predicate<String>() {
  1477. @Override
  1478. public boolean apply(@Nullable final String cacheId) {
  1479. return StringUtils.equals(username, getUsernameFromCacheId(cacheId));
  1480. }
  1481. }));
  1482. }
  1483. private static String getUsernameFromCacheId(final @Nullable String cacheId) {
  1484. if (StringUtils.isBlank(cacheId)) {
  1485. return null;
  1486. }
  1487. final Matcher matcher = REGEX_EXTRACT_USER_FROM_CACHE_ID.matcher(cacheId);
  1488. if (!matcher.matches()) {
  1489. return null;
  1490. }
  1491. return matcher.group("username");
  1492. }
  1493. private static String getCacheIdForTag(final String tag) {
  1494. return StringUtils.startsWith(tag, TAG_PREFIX) ? tag : createCacheIdFromElements(TAG_PREFIX, tag);
  1495. }
  1496. private static String getCacheIdForProject(final String projectId) {
  1497. return StringUtils.startsWith(projectId, PROJECT_PREFIX) ? projectId : createCacheIdFromElements(PROJECT_PREFIX, projectId);
  1498. }
  1499. private static String getDisplayForReadableCounts(final Map<String, Long> readableCounts) {
  1500. final StringBuilder buffer = new StringBuilder();
  1501. buffer.append(readableCounts.get(XnatProjectdata.SCHEMA_ELEMENT_NAME)).append(" projects, ");
  1502. buffer.append(readableCounts.get(WrkWorkflowdata.SCHEMA_ELEMENT_NAME)).append(" workflows, ");
  1503. buffer.append(readableCounts.get(XnatSubjectdata.SCHEMA_ELEMENT_NAME)).append(" subjects");
  1504. if (readableCounts.size() > 3) {
  1505. for (final String type : readableCounts.keySet()) {
  1506. if (!StringUtils.equalsAny(type, XnatProjectdata.SCHEMA_ELEMENT_NAME, WrkWorkflowdata.SCHEMA_ELEMENT_NAME, XnatSubjectdata.SCHEMA_ELEMENT_NAME)) {
  1507. buffer.append(", ").append(readableCounts.get(type)).append(" ").append(type);
  1508. }
  1509. }
  1510. }
  1511. return buffer.toString();
  1512. }
  1513. private static String getCacheIdForUserElementAccessManagers(final String username) {
  1514. return createCacheIdFromElements(USER_ELEMENT_PREFIX, username, ELEMENT_ACCESS_MANAGERS_PREFIX);
  1515. }
  1516. private static String getCacheIdForUserGroups(final String username) {
  1517. return createCacheIdFromElements(USER_ELEMENT_PREFIX, username, GROUPS_ELEMENT_PREFIX);
  1518. }
  1519. private static String getCacheIdForUserProjectAccess(final String username, final String access) {
  1520. return createCacheIdFromElements(USER_ELEMENT_PREFIX, username, XnatProjectdata.SCHEMA_ELEMENT_NAME, access);
  1521. }
  1522. private static String getCacheIdForUserElements(final String username, final String elementType) {
  1523. return createCacheIdFromElements(USER_ELEMENT_PREFIX, username, elementType);
  1524. }
  1525. private static String getCacheIdForActionElements(final String username) {
  1526. return createCacheIdFromElements(ACTIONS_PREFIX, username);
  1527. }
  1528. private static boolean isGroupsAndPermissionsCacheEvent(final Ehcache cache) {
  1529. return StringUtils.equals(CACHE_NAME, cache.getName());
  1530. }
  1531. private static String formatElementDisplays(final List<ElementDisplay> elementDisplays) {
  1532. return StringUtils.join(Lists.transform(elementDisplays, FUNCTION_ELEMENT_DISPLAY_TO_STRING), ", ");
  1533. }
  1534. private static class InitGroupFunction implements Function<UserGroupI, UserGroupI> {
  1535. InitGroupFunction(final DefaultGroupsAndPermissionsCache cache) {
  1536. _cache = cache;
  1537. }
  1538. @Override
  1539. public UserGroupI apply(final UserGroupI incoming) {
  1540. log.error("I'm applying the thing for the group {}", incoming.getId());
  1541. return _cache.initGroup(incoming.getId(), incoming);
  1542. }
  1543. private final DefaultGroupsAndPermissionsCache _cache;
  1544. }
  1545. private static final ResultSetExtractor<Map<String, Long>> ELEMENT_COUNT_EXTRACTOR = new ResultSetExtractor<Map<String, Long>>() {
  1546. @Override
  1547. public Map<String, Long> extractData(final ResultSet results) throws SQLException, DataAccessException {
  1548. final Map<String, Long> elementCounts = new HashMap<>();
  1549. while (results.next()) {
  1550. final String elementName = results.getString("element_name");
  1551. final long elementCount = results.getLong("element_count");
  1552. log.debug("Found element '{}' with {} instances", elementName, elementCount);
  1553. elementCounts.put(elementName, elementCount);
  1554. }
  1555. return elementCounts;
  1556. }
  1557. };
  1558. private static final DateFormat DATE_FORMAT = DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault());
  1559. private static final NumberFormat NUMBER_FORMAT = NumberFormat.getNumberInstance(Locale.getDefault());
  1560. private static final String QUERY_GET_GROUPS_FOR_USER = "SELECT groupid " +
  1561. "FROM xdat_user_groupid xug " +
  1562. " LEFT JOIN xdat_user xu ON groups_groupid_xdat_user_xdat_user_id = xdat_user_id " +
  1563. "WHERE xu.login = :username " +
  1564. "ORDER BY groupid";
  1565. private static final String QUERY_GET_GROUP_FOR_USER_AND_TAG = "SELECT id " +
  1566. "FROM xdat_usergroup xug " +
  1567. " LEFT JOIN xdat_user_groupid xugid ON xug.id = xugid.groupid " +
  1568. " LEFT JOIN xdat_user xu ON xugid.groups_groupid_xdat_user_xdat_user_id = xu.xdat_user_id " +
  1569. "WHERE xu.login = :username AND tag = :tag " +
  1570. "ORDER BY groupid";
  1571. private static final String QUERY_GET_GROUPS_FOR_DATATYPE = "SELECT DISTINCT usergroup.id AS group_name " +
  1572. "FROM xdat_usergroup usergroup " +
  1573. " LEFT JOIN xdat_element_access xea ON usergroup.xdat_usergroup_id = xea.xdat_usergroup_xdat_usergroup_id " +
  1574. "WHERE " +
  1575. " xea.element_name = :dataType " +
  1576. "ORDER BY group_name";
  1577. private static final String QUERY_ALL_GROUPS = "SELECT id FROM xdat_usergroup";
  1578. private static final String QUERY_ALL_TAGS = "SELECT DISTINCT tag FROM xdat_usergroup WHERE tag IS NOT NULL AND tag <> ''";
  1579. private static final String QUERY_GET_GROUPS_FOR_TAG = "SELECT id FROM xdat_usergroup WHERE tag = :tag";
  1580. private static final String QUERY_GET_ALL_MEMBER_GROUPS = "SELECT " +
  1581. " tag AS project_id, " +
  1582. " id AS group_id " +
  1583. "FROM " +
  1584. " xdat_usergroup " +
  1585. "WHERE " +
  1586. " tag IS NOT NULL AND " +
  1587. " id LIKE '%_member' " +
  1588. "ORDER BY project_id, group_id";
  1589. private static final String QUERY_GET_ALL_COLLAB_GROUPS = "SELECT " +
  1590. " tag AS project_id, " +
  1591. " id AS group_id " +
  1592. "FROM " +
  1593. " xdat_usergroup " +
  1594. "WHERE " +
  1595. " tag IS NOT NULL AND " +
  1596. " id LIKE '%_collaborator' " +
  1597. "ORDER BY project_id, group_id";
  1598. private static final String QUERY_CHECK_USER_EXISTS = "SELECT EXISTS(SELECT TRUE FROM xdat_user WHERE login = :username) AS exists";
  1599. private static final String QUERY_GET_EXPERIMENT_PROJECT = "SELECT project FROM xnat_experimentdata WHERE id = :experimentId";
  1600. private static final String QUERY_GET_SUBJECT_PROJECT = "SELECT project FROM xnat_subjectdata WHERE id = :subjectId OR label = :subjectId";
  1601. private static final String QUERY_GET_USERS_FOR_PROJECTS = "SELECT DISTINCT login " +
  1602. "FROM xdat_user u " +
  1603. " LEFT JOIN xdat_user_groupid gid ON u.xdat_user_id = gid.groups_groupid_xdat_user_xdat_user_id " +
  1604. " LEFT JOIN xdat_usergroup g ON gid.groupid = g.id " +
  1605. " LEFT JOIN xdat_element_access xea ON g.xdat_usergroup_id = xea.xdat_usergroup_xdat_usergroup_id " +
  1606. " LEFT JOIN xdat_field_mapping_set xfms ON xea.xdat_element_access_id = xfms.permissions_allow_set_xdat_elem_xdat_element_access_id " +
  1607. " LEFT JOIN xdat_field_mapping xfm ON xfms.xdat_field_mapping_set_id = xfm.xdat_field_mapping_set_xdat_field_mapping_set_id " +
  1608. "WHERE tag IN (:projectIds) OR (tag IS NULL AND field_value = '*') " +
  1609. "ORDER BY login";
  1610. private static final String QUERY_GET_PROJECTS_FOR_USER = "SELECT DISTINCT " +
  1611. " g.tag AS project " +
  1612. "FROM xdat_usergroup g " +
  1613. " LEFT JOIN xdat_user_groupid gid ON g.id = gid.groupid " +
  1614. " LEFT JOIN xdat_user u ON gid.groups_groupid_xdat_user_xdat_user_id = u.xdat_user_id " +
  1615. "WHERE g.tag IS NOT NULL AND " +
  1616. " u.login = :username";
  1617. private static final String QUERY_PROJECT_OWNERS = "SELECT DISTINCT u.login AS owner " +
  1618. "FROM xdat_user u " +
  1619. " LEFT JOIN xdat_user_groupid map ON u.xdat_user_id = map.groups_groupid_xdat_user_xdat_user_id " +
  1620. " LEFT JOIN xdat_usergroup g ON map.groupid = g.id " +
  1621. " LEFT JOIN xdat_element_access xea ON (g.xdat_usergroup_id = xea.xdat_usergroup_xdat_usergroup_id OR u.xdat_user_id = xea.xdat_user_xdat_user_id) " +
  1622. " LEFT JOIN xdat_field_mapping_set xfms ON xea.xdat_element_access_id = xfms.permissions_allow_set_xdat_elem_xdat_element_access_id " +
  1623. " LEFT JOIN xdat_field_mapping xfm ON xfms.xdat_field_mapping_set_id = xfm.xdat_field_mapping_set_xdat_field_mapping_set_id " +
  1624. "WHERE " +
  1625. " xfm.field_value != '*' AND " +
  1626. " xea.element_name = 'xnat:projectData' AND " +
  1627. " xfm.delete_element = 1 AND " +
  1628. " g.id LIKE '%_owner' AND " +
  1629. " g.tag = :projectId " +
  1630. "ORDER BY owner";
  1631. private static final String QUERY_USER_PERMISSIONS = "SELECT " +
  1632. " xea.element_name AS element_name, " +
  1633. " xeamd.status AS active_status, " +
  1634. " xfms.method AS method, " +
  1635. " xfm.field AS field, " +
  1636. " xfm.field_value AS field_value, " +
  1637. " xfm.comparison_type AS comparison_type, " +
  1638. " xfm.read_element AS read_element, " +
  1639. " xfm.edit_element AS edit_element, " +
  1640. " xfm.create_element AS create_element, " +
  1641. " xfm.delete_element AS delete_element, " +
  1642. " xfm.active_element AS active_element " +
  1643. "FROM xdat_user u " +
  1644. " LEFT JOIN xdat_element_access xea ON u.xdat_user_id = xea.xdat_user_xdat_user_id " +
  1645. " LEFT JOIN xdat_element_access_meta_data xeamd ON xea.element_access_info = xeamd.meta_data_id " +
  1646. " LEFT JOIN xdat_field_mapping_set xfms ON xea.xdat_element_access_id = xfms.permissions_allow_set_xdat_elem_xdat_element_access_id " +
  1647. " LEFT JOIN xdat_field_mapping xfm ON xfms.xdat_field_mapping_set_id = xfm.xdat_field_mapping_set_xdat_field_mapping_set_id " +
  1648. "WHERE " +
  1649. " u.login = :username";
  1650. private static final String QUERY_ACCESSIBLE_DATA_PROJECTS = "SELECT " +
  1651. " project " +
  1652. "FROM " +
  1653. " (SELECT DISTINCT f.field_value AS project " +
  1654. " FROM " +
  1655. " xdat_user u " +
  1656. " LEFT JOIN xdat_user_groupid map ON u.xdat_user_id = map.groups_groupid_xdat_user_xdat_user_id " +
  1657. " LEFT JOIN xdat_usergroup g ON map.groupid = g.id " +
  1658. " LEFT JOIN xdat_element_access a ON (g.xdat_usergroup_id = a.xdat_usergroup_xdat_usergroup_id OR u.xdat_user_id = a.xdat_user_xdat_user_id) " +
  1659. " LEFT JOIN xdat_field_mapping_set s ON a.xdat_element_access_id = s.permissions_allow_set_xdat_elem_xdat_element_access_id " +
  1660. " LEFT JOIN xdat_field_mapping f ON s.xdat_field_mapping_set_id = f.xdat_field_mapping_set_xdat_field_mapping_set_id " +
  1661. " WHERE " +
  1662. " f.field_value != '*' AND " +
  1663. " a.element_name = 'xnat:subjectData' AND " +
  1664. " f.%s = 1 AND " +
  1665. " u.login IN ('guest', :username)) projects";
  1666. private static final String QUERY_OWNED_PROJECTS = String.format(QUERY_ACCESSIBLE_DATA_PROJECTS, "delete_element");
  1667. private static final String QUERY_EDITABLE_PROJECTS = String.format(QUERY_ACCESSIBLE_DATA_PROJECTS, "edit_element");
  1668. private static final String QUERY_READABLE_PROJECTS = String.format(QUERY_ACCESSIBLE_DATA_PROJECTS, "read_element");
  1669. private static final String QUERY_HAS_ALL_DATA_PRIVILEGES = "SELECT " +
  1670. " EXISTS(SELECT TRUE " +
  1671. " FROM " +
  1672. " xdat_user u " +
  1673. " LEFT JOIN xdat_user_groupid map ON u.xdat_user_id = map.groups_groupid_xdat_user_xdat_user_id " +
  1674. " LEFT JOIN xdat_usergroup ug ON map.groupid = ug.id " +
  1675. " LEFT JOIN xdat_element_access ea ON (ug.xdat_usergroup_id = ea.xdat_usergroup_xdat_usergroup_id OR u.xdat_user_id = ea.xdat_user_xdat_user_id) " +
  1676. " LEFT JOIN xdat_field_mapping_set fms ON ea.xdat_element_access_id = fms.permissions_allow_set_xdat_elem_xdat_element_access_id " +
  1677. " LEFT JOIN xdat_field_mapping fm ON fms.xdat_field_mapping_set_id = fm.xdat_field_mapping_set_xdat_field_mapping_set_id " +
  1678. " WHERE " +
  1679. " fm.field_value = '*' AND " +
  1680. " ea.element_name = 'xnat:projectData' AND " +
  1681. " fm.%s = 1 AND " +
  1682. " u.login = :username)";
  1683. private static final String QUERY_HAS_ALL_DATA_ACCESS = String.format(QUERY_HAS_ALL_DATA_PRIVILEGES, "read_element");
  1684. private static final String QUERY_HAS_ALL_DATA_ADMIN = String.format(QUERY_HAS_ALL_DATA_PRIVILEGES, "edit_element");
  1685. private static final String QUERY_ALL_DATA_ACCESS_PROJECTS = "SELECT id AS project FROM xnat_projectdata ORDER BY project";
  1686. private static final String QUERY_USER_READABLE_WORKFLOW_COUNT = "SELECT COUNT(*) FROM wrk_workflowData wrk_workflowData";
  1687. private static final String QUERY_USER_READABLE_SUBJECT_COUNT = "SELECT " +
  1688. " COUNT(*) " +
  1689. "FROM " +
  1690. " (SELECT SEARCH.* " +
  1691. " FROM " +
  1692. " (SELECT DISTINCT ON (subjectId) * " +
  1693. " FROM " +
  1694. " (SELECT xnat_subjectData.id AS subjectId, xnat_subjectData.project AS subjectProject, sharedSubjects.project AS sharedProject " +
  1695. " FROM " +
  1696. " xnat_subjectData xnat_subjectData " +
  1697. " LEFT JOIN xnat_projectParticipant sharedSubjects ON xnat_subjectData.id = sharedSubjects.subject_id) SECURITY " +
  1698. " WHERE " +
  1699. " subjectProject IN (:projectIds) OR " +
  1700. " sharedProject IN (:projectIds)) SECURITY " +
  1701. " LEFT JOIN xnat_subjectData SEARCH ON SECURITY.subjectId = SEARCH.id) xnat_subjectData";
  1702. private static final String QUERY_USER_READABLE_EXPERIMENT_COUNT = "SELECT " +
  1703. " element_name, " +
  1704. " COUNT(*) AS element_count " +
  1705. "FROM " +
  1706. " (SELECT xnat_experimentData.id AS id " +
  1707. " FROM " +
  1708. " (SELECT SEARCH.* " +
  1709. " FROM " +
  1710. " (SELECT DISTINCT ON (id) * " +
  1711. " FROM " +
  1712. " (SELECT xnat_experimentData.id AS id, xnat_experimentData.project AS experimentProject, sharedExperiments.project AS experimentSharedProject " +
  1713. " FROM " +
  1714. " xnat_experimentData xnat_experimentData " +
  1715. " LEFT JOIN xnat_experimentData_share sharedExperiments ON xnat_experimentData.id = sharedExperiments.sharing_share_xnat_experimentda_id) SECURITY " +
  1716. " WHERE " +
  1717. " experimentProject IN (:projectIds) OR " +
  1718. " experimentSharedProject IN (:projectIds)) SECURITY " +
  1719. " LEFT JOIN xnat_experimentData SEARCH ON SECURITY.id = SEARCH.id) xnat_experimentData) SEARCH " +
  1720. " LEFT JOIN xnat_experimentData expt ON search.id = expt.id " +
  1721. " LEFT JOIN xdat_meta_element xme ON expt.extension = xme.xdat_meta_element_id " +
  1722. "GROUP BY " +
  1723. " element_name";
  1724. private static final String GUEST_USERNAME = "guest";
  1725. private static final String ACTIONS_PREFIX = "actions";
  1726. private static final String TAG_PREFIX = "tag";
  1727. private static final String PROJECT_PREFIX = "project";
  1728. private static final String USER_ELEMENT_PREFIX = "user";
  1729. private static final String ELEMENT_ACCESS_MANAGERS_PREFIX = "eam";
  1730. private static final String GROUPS_ELEMENT_PREFIX = "groups";
  1731. private static final String GUEST_ACTION_READ = getCacheIdForActionElements(GUEST_USERNAME);
  1732. private static final Pattern REGEX_EXTRACT_USER_FROM_CACHE_ID = Pattern.compile("^(?<prefix>" + ACTIONS_PREFIX + "|" + USER_ELEMENT_PREFIX + "):(?<username>[^:]+)(?<remainder>:.*)?$");
  1733. private static final Pattern REGEX_USER_READABLE_COUNTS = Pattern.compile("^(?<prefix>" + USER_ELEMENT_PREFIX + "):(?<username>[^:]+):" + READABLE + "$");
  1734. private static final Pattern REGEX_USER_PROJECT_ACCESS_CACHE_ID = Pattern.compile("^" + USER_ELEMENT_PREFIX + ":(?<username>[A-z0-9_]+):" + XnatProjectdata.SCHEMA_ELEMENT_NAME + ":(?<access>[A-z]+)$");
  1735. private static final String GUEST_CACHE_ID = getCacheIdForUserElements(GUEST_USERNAME, BROWSEABLE);
  1736. private static final Function<ElementDisplay, String> FUNCTION_ELEMENT_DISPLAY_TO_STRING = new Function<ElementDisplay, String>() {
  1737. @Override
  1738. public String apply(final ElementDisplay elementDisplay) {
  1739. return elementDisplay.getElementName();
  1740. }
  1741. };
  1742. private static final Function<ElementSecurity, String> FUNCTION_ELEMENT_SECURITY_TO_STRING = new Function<ElementSecurity, String>() {
  1743. @Override
  1744. public String apply(final ElementSecurity security) {
  1745. try {
  1746. return security.getElementName();
  1747. } catch (XFTInitException | ElementNotFoundException | FieldNotFoundException e) {
  1748. log.error("Got an error trying to get an element security object name", e);
  1749. return "";
  1750. }
  1751. }
  1752. };
  1753. private static final Function<String, String> FUNCTION_CACHE_IDS_TO_USERNAMES = new Function<String, String>() {
  1754. @Nullable
  1755. @Override
  1756. public String apply(@Nullable final String cacheId) {
  1757. return getUsernameFromCacheId(cacheId);
  1758. }
  1759. };
  1760. private static final Predicate<ElementDisplay> CONTAINS_MR_SESSION = new Predicate<ElementDisplay>() {
  1761. @Override
  1762. public boolean apply(final ElementDisplay display) {
  1763. return StringUtils.equalsIgnoreCase(XnatMrsessiondata.SCHEMA_ELEMENT_NAME, display.getElementName());
  1764. }
  1765. };
  1766. private final NamedParameterJdbcTemplate _template;
  1767. private final JmsTemplate _jmsTemplate;
  1768. private final DatabaseHelper _helper;
  1769. private final Map<String, Long> _totalCounts;
  1770. private final Map<String, Long> _missingElements;
  1771. private final Map<String, Boolean> _userChecks;
  1772. private final AtomicBoolean _initialized;
  1773. private Listener _listener;
  1774. private XDATUser _guest;
  1775. }