/** * Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.masterdb.user; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.EnumMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.TreeSet; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.threeten.bp.Instant; import org.threeten.bp.ZoneId; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; import com.google.common.collect.ImmutableList; import com.opengamma.DataDuplicationException; import com.opengamma.DataVersionException; import com.opengamma.core.user.DateStyle; import com.opengamma.core.user.TimeStyle; import com.opengamma.core.user.UserAccount; import com.opengamma.core.user.UserAccountStatus; import com.opengamma.core.user.impl.SimpleUserAccount; import com.opengamma.core.user.impl.SimpleUserProfile; import com.opengamma.elsql.ElSqlBundle; import com.opengamma.id.ExternalId; import com.opengamma.id.ExternalIdBundle; import com.opengamma.id.ObjectId; import com.opengamma.id.UniqueId; import com.opengamma.master.user.HistoryEvent; import com.opengamma.master.user.HistoryEventType; import com.opengamma.master.user.ManageableRole; import com.opengamma.master.user.ManageableUser; import com.opengamma.master.user.UserEventHistoryRequest; import com.opengamma.master.user.UserEventHistoryResult; import com.opengamma.master.user.UserMaster; import com.opengamma.master.user.UserSearchRequest; import com.opengamma.master.user.UserSearchResult; import com.opengamma.master.user.UserSearchSortOrder; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.db.DbConnector; import com.opengamma.util.db.DbMapSqlParameterSource; import com.opengamma.util.paging.Paging; import com.opengamma.util.paging.PagingRequest; import com.opengamma.util.tuple.Pair; import com.opengamma.util.tuple.Pairs; /** * A user master implementation using a database for persistence. * <p> * This is a full implementation of the user master using an SQL database. * Full details of the API are in {@link UserMaster}. * <p> * The SQL is stored externally in {@code DbUserMaster.elsql}. * Alternate databases or specific SQL requirements can be handled using database * specific overrides, such as {@code DbUserMaster-MySpecialDB.elsql}. * <p> * This class is mutable but must be treated as immutable after configuration. */ public class DbUserMaster extends AbstractDbUserMaster<ManageableUser> implements UserMaster { /** Event sequence name. */ private static final String USR_USER_EVENT_SEQ = "usr_user_event_seq"; /** Logger. */ private static final Logger s_logger = LoggerFactory.getLogger(DbUserMaster.class); /** * The default scheme for unique identifiers. */ public static final String IDENTIFIER_SCHEME_DEFAULT = "DbUsr"; /** * SQL order by. */ protected static final EnumMap<UserSearchSortOrder, String> ORDER_BY_MAP = new EnumMap<UserSearchSortOrder, String>(UserSearchSortOrder.class); static { ORDER_BY_MAP.put(UserSearchSortOrder.OBJECT_ID_ASC, "oid ASC"); ORDER_BY_MAP.put(UserSearchSortOrder.OBJECT_ID_DESC, "oid DESC"); ORDER_BY_MAP.put(UserSearchSortOrder.NAME_ASC, "user_name ASC"); ORDER_BY_MAP.put(UserSearchSortOrder.NAME_DESC, "user_name DESC"); } private final DbRoleMaster _roleMaster; // ----------------------------------------------------------------- // TIMERS FOR METRICS GATHERING // By default these do nothing. Registration will replace them // so that they actually do something. // ----------------------------------------------------------------- private Timer _searchTimer = new Timer(); /** * Creates an instance. * * @param dbConnector the database connector, not null */ public DbUserMaster(final DbConnector dbConnector) { this(dbConnector, new DbRoleMaster(dbConnector)); } /** * Creates an instance controlling the role master. * * @param dbConnector the database connector, not null * @param roleMaster the role master, not null */ public DbUserMaster(final DbConnector dbConnector, DbRoleMaster roleMaster) { super(dbConnector, IDENTIFIER_SCHEME_DEFAULT); _roleMaster = ArgumentChecker.notNull(roleMaster, "roleMaster"); setElSqlBundle(ElSqlBundle.of(dbConnector.getDialect().getElSqlConfig(), DbUserMaster.class)); } @Override public void registerMetrics(MetricRegistry summaryRegistry, MetricRegistry detailedRegistry, String namePrefix) { super.registerMetrics(summaryRegistry, detailedRegistry, namePrefix); _searchTimer = summaryRegistry.timer(namePrefix + ".search"); } //------------------------------------------------------------------------- @Override public boolean nameExists(String userName) { ArgumentChecker.notNull(userName, "userName"); return doNameExists(userName); } @Override public ManageableUser getByName(String userName) { ArgumentChecker.notNull(userName, "userName"); s_logger.debug("getByName {}", userName); ObjectId oid = lookupName(userName, OnDeleted.EXCEPTION); return doGetById(oid, new UserExtractor()); } @Override public ManageableUser getById(ObjectId objectId) { ArgumentChecker.notNull(objectId, "objectId"); s_logger.debug("getById {}", objectId); checkScheme(objectId); return doGetById(objectId, new UserExtractor()); } //------------------------------------------------------------------------- @Override public UniqueId add(final ManageableUser user) { UniqueId added = doAdd(user); setupRole(user); return added; } private void setupRole(final ManageableUser user) { for (int retry = 0; retry < 5; retry++) { try { ManageableRole role; if (user.getUserName().equals("admin")) { if (roleMaster().nameExists("admin")) { role = roleMaster().getByName("admin"); if (role.getAssociatedUsers().contains("admin") == false) { role.getAssociatedUsers().add("admin"); } } else { role = new ManageableRole("admin"); role.setDescription("Administrators"); role.getAssociatedUsers().add("admin"); role.getAssociatedPermissions().add("*"); } } else { if (roleMaster().nameExists("registered")) { role = roleMaster().getByName("registered"); if (role.getAssociatedUsers().contains(caseInsensitive(user.getUserName())) == false) { role.getAssociatedUsers().add(caseInsensitive(user.getUserName())); } } else { role = new ManageableRole("registered"); role.setDescription("Registered users"); role.getAssociatedUsers().add(caseInsensitive(user.getUserName())); } } roleMaster().save(role); return; } catch (DataVersionException | DataDuplicationException ex) { // retry, handling contended user setup senarios } catch (RuntimeException ex) { // ignore and do not assign a role return; } } } /** * Processes the user add, within a retrying transaction. * * @param user the user to add, not null * @return the information, not null */ @Override Pair<UniqueId, Instant> doAddInTransaction(ManageableUser user) { // check if user exists if (doNameExists(user.getUserName())) { throw new DataDuplicationException("User already exists: " + user.getUserName()); } // insert new row final Instant now = now(); final long docOid = nextId("usr_user_seq"); final UniqueId uniqueId = createUniqueId(docOid, docOid); insertMain(docOid, user); insertNameLookup(user.getUserName(), uniqueId.getObjectId()); insertAlternateIds(docOid, user); insertPermissions(docOid, user); insertExtensions(docOid, user); HistoryEvent event = HistoryEvent.of(HistoryEventType.ADDED, uniqueId, "system", now, ImmutableList.<String>of()); insertEvent(event, USR_USER_EVENT_SEQ); return Pairs.of(uniqueId, now); } //------------------------------------------------------------------------- @Override public UniqueId update(final ManageableUser user) { return doUpdate(user); } /** * Processes the update, within a retrying transaction. * * @param user the updated user, not null * @return the updated document, not null */ @Override Pair<UniqueId, Instant> doUpdateInTransaction(ManageableUser user) { ObjectId objectId = user.getObjectId(); String oldVersion = user.getUniqueId().getVersion(); ManageableUser current = getById(objectId); int newVersion = Integer.parseInt(oldVersion) + 1; UniqueId newUniqueId = objectId.atVersion(Integer.toString(newVersion)); // validate if (current.equals(user)) { return Pairs.of(newUniqueId, null); // no change } if (current.getUniqueId().getVersion().equals(oldVersion) == false) { throw new DataVersionException("Invalid version, User has already been updated: " + objectId); } if (caseInsensitive(user.getUserName()).equals(caseInsensitive(current.getUserName())) == false) { // check if user exists if (doNameExists(user.getUserName())) { throw new DataDuplicationException("User cannot be renamed, new name already exists: " + user.getUserName()); } insertNameLookup(user.getUserName(), current.getObjectId()); } // update long docOid = extractOid(objectId); updateMain(docOid, newVersion, user); if (current.getAlternateIds().equals(user.getAlternateIds()) == false) { deleteAlternateIds(docOid); insertAlternateIds(docOid, user); } if (current.getAssociatedPermissions().equals(user.getAssociatedPermissions()) == false) { deletePermissions(docOid); insertPermissions(docOid, user); } if (current.getProfile().getExtensions().equals(user.getProfile().getExtensions()) == false) { deleteExtensions(docOid); insertExtensions(docOid, user); } final Instant now = now(); List<String> changes = calculateChanges(current, user); HistoryEvent event = HistoryEvent.of(HistoryEventType.CHANGED, newUniqueId, "system", now, changes); insertEvent(event, USR_USER_EVENT_SEQ); return Pairs.of(newUniqueId, now); } private List<String> calculateChanges(ManageableUser current, ManageableUser updated) { List<String> changes = new ArrayList<>(); // changes createChange(changes, current, updated, ManageableUser.meta().userName()); if (Objects.equals(current.getPasswordHash(), updated.getPasswordHash()) == false) { changes.add("Changed password"); } createChange(changes, current, updated, ManageableUser.meta().status()); createChange(changes, current, updated, ManageableUser.meta().emailAddress()); SimpleUserProfile currentProfile = SimpleUserProfile.from(current.getProfile()); SimpleUserProfile updatedProfile = SimpleUserProfile.from(updated.getProfile()); createChange(changes, currentProfile, updatedProfile, SimpleUserProfile.meta().displayName()); createChange(changes, currentProfile, updatedProfile, SimpleUserProfile.meta().locale()); createChange(changes, currentProfile, updatedProfile, SimpleUserProfile.meta().zone()); createChange(changes, currentProfile, updatedProfile, SimpleUserProfile.meta().dateStyle()); createChange(changes, currentProfile, updatedProfile, SimpleUserProfile.meta().timeStyle()); // added permission Set<String> addedPermissions = new TreeSet<>(updated.getAssociatedPermissions()); addedPermissions.removeAll(current.getAssociatedPermissions()); for (String permission : addedPermissions) { changes.add(StringUtils.left("Added permission: " + permission, 255)); } // removed permission Set<String> removedPermissions = new TreeSet<>(current.getAssociatedPermissions()); removedPermissions.removeAll(updated.getAssociatedPermissions()); for (String permission : removedPermissions) { changes.add(StringUtils.left("Removed permission: " + permission, 255)); } // added alternate id Set<ExternalId> addedIds = new TreeSet<>(updated.getAlternateIds().getExternalIds()); addedIds.removeAll(current.getAlternateIds().getExternalIds()); for (ExternalId alternateId : addedIds) { changes.add(StringUtils.left("Added alternateId: " + alternateId, 255)); } // removed alternate id Set<ExternalId> removedIds = new TreeSet<>(current.getAlternateIds().getExternalIds()); removedIds.removeAll(updated.getAlternateIds().getExternalIds()); for (ExternalId alternateId : removedIds) { changes.add(StringUtils.left("Removed alternateId: " + alternateId, 255)); } // added extension Set<Entry<String, String>> addedExtensions = new HashSet<>(updated.getProfile().getExtensions().entrySet()); addedExtensions.removeAll(current.getProfile().getExtensions().entrySet()); for (Entry<String, String> extension : addedExtensions) { changes.add(StringUtils.left("Added extension: " + extension, 255)); } // removed extension Set<Entry<String, String>> removedExtensions = new HashSet<>(current.getProfile().getExtensions().entrySet()); removedExtensions.removeAll(updated.getProfile().getExtensions().entrySet()); for (Entry<String, String> extension : removedExtensions) { changes.add(StringUtils.left("Removed extension: " + extension, 255)); } return changes; } //------------------------------------------------------------------------- @Override public UniqueId save(ManageableUser user) { ArgumentChecker.notNull(user, "user"); s_logger.debug("save {}", user.getUserName()); if (user.getUniqueId() != null) { return update(user); } else { return add(user); } } //------------------------------------------------------------------------- @Override public void removeByName(String userName) { doRemoveByName(userName); } @Override public void removeById(final ObjectId objectId) { doRemoveById(objectId); } /** * Processes the document update, within a retrying transaction. * * @param objectId the object identifier to remove, not null * @return the updated document, not null */ @Override Instant doRemoveInTransaction(final ObjectId objectId) { ManageableUser current = getById(objectId); int newVersion = Integer.parseInt(current.getUniqueId().getVersion()) + 1; UniqueId newUniqueId = objectId.atVersion(Integer.toString(newVersion)); long docOid = extractOid(objectId); deleteAlternateIds(docOid); deletePermissions(docOid); deleteExtensions(docOid); deleteMain(docOid); updateNameLookupToDeleted(docOid); Instant now = now(); HistoryEvent event = HistoryEvent.of(HistoryEventType.REMOVED, newUniqueId, "system", now, ImmutableList.<String>of()); insertEvent(event, USR_USER_EVENT_SEQ); return now; } //------------------------------------------------------------------------- @Override public UserSearchResult search(UserSearchRequest request) { ArgumentChecker.notNull(request, "request"); ArgumentChecker.notNull(request.getPagingRequest(), "request.pagingRequest"); s_logger.debug("search {}", request); if ((request.getObjectIds() != null && request.getObjectIds().isEmpty())) { Paging paging = Paging.of(request.getPagingRequest(), 0); return new UserSearchResult(paging, new ArrayList<ManageableUser>()); } try (Timer.Context context = _searchTimer.time()) { return doSearch(request); } } private UserSearchResult doSearch(UserSearchRequest request) { PagingRequest pagingRequest = request.getPagingRequest(); // setup args final DbMapSqlParameterSource args = createParameterSource() .addValueNullIgnored("user_name_ci", caseInsensitive(getDialect().sqlWildcardAdjustValue(request.getUserName()))) .addValueNullIgnored("email_address_ci", caseInsensitive(getDialect().sqlWildcardAdjustValue(request.getEmailAddress()))) .addValueNullIgnored("display_name_ci", caseInsensitive(getDialect().sqlWildcardAdjustValue(request.getDisplayName()))) .addValueNullIgnored("alternate_id_scheme", getDialect().sqlWildcardAdjustValue(request.getAlternateIdScheme())) .addValueNullIgnored("alternate_id_value", getDialect().sqlWildcardAdjustValue(request.getAlternateIdValue())) .addValueNullIgnored("permission_str", request.getAssociatedPermission()); if (request.getObjectIds() != null) { StringBuilder buf = new StringBuilder(request.getObjectIds().size() * 10); for (ObjectId objectId : request.getObjectIds()) { checkScheme(objectId); buf.append(extractOid(objectId)).append(", "); } buf.setLength(buf.length() - 2); args.addValue("sql_search_object_ids", buf.toString()); } args.addValue("sort_order", ORDER_BY_MAP.get(request.getSortOrder())); args.addValue("paging_offset", pagingRequest.getFirstItem()); args.addValue("paging_fetch", pagingRequest.getPagingSize()); // search String[] sql = {getElSqlBundle().getSql("Search", args), getElSqlBundle().getSql("SearchCount", args)}; final NamedParameterJdbcOperations namedJdbc = getJdbcTemplate(); Paging paging; List<ManageableUser> results = new ArrayList<>(); if (pagingRequest.equals(PagingRequest.ALL)) { paging = Paging.of(pagingRequest, results); results.addAll(namedJdbc.query(sql[0], args, new UserExtractor())); } else { s_logger.debug("executing sql {}", sql[1]); final int count = namedJdbc.queryForObject(sql[1], args, Integer.class); paging = Paging.of(pagingRequest, count); if (count > 0 && pagingRequest.equals(PagingRequest.NONE) == false) { s_logger.debug("executing sql {}", sql[0]); results.addAll(namedJdbc.query(sql[0], args, new UserExtractor())); } } return new UserSearchResult(paging, results); } //------------------------------------------------------------------------- @Override public UserEventHistoryResult eventHistory(UserEventHistoryRequest request) { ArgumentChecker.notNull(request, "request"); s_logger.debug("eventHistory {}", request); ObjectId objectId = request.getObjectId(); if (objectId == null) { objectId = lookupName(request.getUserName(), OnDeleted.RETURN_ID); } checkScheme(objectId); return new UserEventHistoryResult(doEventHistory(objectId)); } //------------------------------------------------------------------------- @Override public UserAccount getAccount(String userName) { ArgumentChecker.notNull(userName, "userName"); ManageableUser user = getByName(userName); SimpleUserAccount account = new SimpleUserAccount(user.getUserName()); account.setPasswordHash(user.getPasswordHash()); account.setStatus(user.getStatus()); account.setAlternateIds(user.getAlternateIds()); account.setEmailAddress(user.getEmailAddress()); account.setProfile(user.getProfile()); return roleMaster().resolveAccount(account); } @Override public DbRoleMaster roleMaster() { return _roleMaster; } //------------------------------------------------------------------------- private void insertMain(long docOid, ManageableUser user) { final DbMapSqlParameterSource docArgs = mainArgs(docOid, 0, user); final String sqlDoc = getElSqlBundle().getSql("InsertMain", docArgs); getJdbcTemplate().update(sqlDoc, docArgs); } private void insertAlternateIds(long docOid, ManageableUser user) { final List<DbMapSqlParameterSource> assocList = new ArrayList<DbMapSqlParameterSource>(); final List<DbMapSqlParameterSource> idKeyList = new ArrayList<DbMapSqlParameterSource>(); final String sqlSelectIdKey = getElSqlBundle().getSql("SelectIdKey"); for (ExternalId id : user.getAlternateIds()) { final DbMapSqlParameterSource assocArgs = createParameterSource() .addValue("doc_id", docOid) .addValue("key_scheme", id.getScheme().getName()) .addValue("key_value", id.getValue()); assocList.add(assocArgs); if (getJdbcTemplate().queryForList(sqlSelectIdKey, assocArgs).isEmpty()) { // select avoids creating unecessary id, but id may still not be used final long idKeyId = nextId("usr_user_idkey_seq"); final DbMapSqlParameterSource idkeyArgs = createParameterSource() .addValue("idkey_id", idKeyId) .addValue("key_scheme", id.getScheme().getName()) .addValue("key_value", id.getValue()); idKeyList.add(idkeyArgs); } } final String sqlIdKey = getElSqlBundle().getSql("InsertIdKey"); final String sqlDoc2IdKey = getElSqlBundle().getSql("InsertDoc2IdKey"); getJdbcTemplate().batchUpdate(sqlIdKey, idKeyList.toArray(new DbMapSqlParameterSource[idKeyList.size()])); getJdbcTemplate().batchUpdate(sqlDoc2IdKey, assocList.toArray(new DbMapSqlParameterSource[assocList.size()])); } private void insertPermissions(long docOid, ManageableUser user) { final List<DbMapSqlParameterSource> argsList = new ArrayList<DbMapSqlParameterSource>(); for (String permission : user.getAssociatedPermissions()) { argsList.add(createParameterSource() .addValue("id", nextId("usr_user_perm_seq")) .addValue("doc_id", docOid) .addValue("permission_str", permission)); } final String sql = getElSqlBundle().getSql("InsertAssocPermission"); getJdbcTemplate().batchUpdate(sql, argsList.toArray(new DbMapSqlParameterSource[argsList.size()])); } private void insertExtensions(long docOid, ManageableUser user) { final List<DbMapSqlParameterSource> argsList = new ArrayList<DbMapSqlParameterSource>(); for (Entry<String, String> entry : user.getProfile().getExtensions().entrySet()) { argsList.add(createParameterSource() .addValue("id", nextId("usr_user_extn_seq")) .addValue("doc_id", docOid) .addValue("extn_key", entry.getKey()) .addValue("extn_value", entry.getValue())); } final String sql = getElSqlBundle().getSql("InsertExtension"); getJdbcTemplate().batchUpdate(sql, argsList.toArray(new DbMapSqlParameterSource[argsList.size()])); } //------------------------------------------------------------------------- private void updateMain(long docOid, int version, ManageableUser user) { final DbMapSqlParameterSource docArgs = mainArgs(docOid, version, user); final String sqlDoc = getElSqlBundle().getSql("UpdateMain", docArgs); getJdbcTemplate().update(sqlDoc, docArgs); } private DbMapSqlParameterSource mainArgs(long docOid, int version, ManageableUser user) { final DbMapSqlParameterSource docArgs = createParameterSource() .addValue("doc_id", docOid) .addValue("version", version) .addValue("user_name", user.getUserName()) .addValue("user_name_ci", caseInsensitive(user.getUserName())) .addValue("password_hash", user.getPasswordHash()) .addValue("status", user.getStatus().name().substring(0, 1)) .addValue("email_address", user.getEmailAddress()) .addValue("email_address_ci", caseInsensitive(user.getEmailAddress())) .addValue("display_name", user.getProfile().getDisplayName()) .addValue("display_name_ci", caseInsensitive(user.getProfile().getDisplayName())) .addValue("locale_tag", user.getProfile().getLocale().toLanguageTag()) .addValue("time_zone", user.getProfile().getZone().getId()) .addValue("date_fmt_style", user.getProfile().getDateStyle().name()) .addValue("time_fmt_style", user.getProfile().getTimeStyle().name()); return docArgs; } //------------------------------------------------------------------------- private void deleteMain(long docOid) { final DbMapSqlParameterSource args = createParameterSource() .addValue("doc_id", docOid); final String sql = getElSqlBundle().getSql("DeleteMain", args); getJdbcTemplate().update(sql, args); } private void deleteAlternateIds(long docOid) { final DbMapSqlParameterSource args = createParameterSource() .addValue("doc_id", docOid); final String sql = getElSqlBundle().getSql("DeleteAlternateIds", args); getJdbcTemplate().update(sql, args); } private void deletePermissions(long docOid) { final DbMapSqlParameterSource args = createParameterSource() .addValue("doc_id", docOid); final String sql = getElSqlBundle().getSql("DeleteAssocPermissions", args); getJdbcTemplate().update(sql, args); } private void deleteExtensions(long docOid) { final DbMapSqlParameterSource args = createParameterSource() .addValue("doc_id", docOid); final String sql = getElSqlBundle().getSql("DeleteExtensions", args); getJdbcTemplate().update(sql, args); } //------------------------------------------------------------------------- /** * Mapper from SQL rows to a ManageableUser. */ final class UserExtractor implements ResultSetExtractor<List<ManageableUser>> { private long _previousDocId = -1L; private ManageableUser _currUser; private Set<ExternalId> _currExternalIds = new HashSet<>(); private Set<String> _currPermissions = new LinkedHashSet<>(); private Map<String, String> _currExtensions = new LinkedHashMap<>(); private List<ManageableUser> _users = new ArrayList<>(); @Override public List<ManageableUser> extractData(final ResultSet rs) throws SQLException, DataAccessException { while (rs.next()) { final long docId = rs.getLong("DOC_ID"); // DOC_ID tells us when we're on a new document. if (docId != _previousDocId) { if (_previousDocId >= 0) { _currUser.setAlternateIds(ExternalIdBundle.of(_currExternalIds)); _currUser.setAssociatedPermissions(_currPermissions); _currUser.getProfile().setExtensions(_currExtensions); } _previousDocId = docId; buildUser(rs, docId); _currExternalIds.clear(); _currPermissions.clear(); _currExtensions.clear(); } String idKey = rs.getString("KEY_SCHEME"); String idValue = rs.getString("KEY_VALUE"); if (idKey != null && idValue != null) { _currExternalIds.add(ExternalId.of(idKey, idValue)); } String permissionPattern = rs.getString("PERMISSION_STR"); if (permissionPattern != null) { _currPermissions.add(permissionPattern); } String extKey = rs.getString("EXTN_KEY"); String extValue = rs.getString("EXTN_VALUE"); if (extKey != null && extValue != null) { _currExtensions.put(extKey, extValue); } } // patch up last document read if (_previousDocId >= 0) { _currUser.setAlternateIds(ExternalIdBundle.of(_currExternalIds)); _currUser.setAssociatedPermissions(_currPermissions); _currUser.getProfile().setExtensions(_currExtensions); } return _users; } private void buildUser(final ResultSet rs, final long docId) throws SQLException { int version = rs.getInt("VERSION"); UniqueId uniqueId = UniqueId.of(getUniqueIdScheme(), Long.toString(docId), Integer.toString(version)); ManageableUser user = new ManageableUser(rs.getString("USER_NAME")); user.setUniqueId(uniqueId); user.setPasswordHash(rs.getString("PASSWORD_HASH")); user.setStatus(extractEnum(rs.getString("STATUS"), UserAccountStatus.values())); user.setEmailAddress(rs.getString("EMAIL_ADDRESS")); user.getProfile().setDisplayName(rs.getString("DISPLAY_NAME")); user.getProfile().setLocale(Locale.forLanguageTag(rs.getString("LOCALE_TAG"))); user.getProfile().setZone(ZoneId.of(rs.getString("TIME_ZONE"))); user.getProfile().setDateStyle(DateStyle.valueOf(rs.getString("DATE_FMT_STYLE"))); user.getProfile().setTimeStyle(TimeStyle.valueOf(rs.getString("TIME_FMT_STYLE"))); _currUser = user; _users.add(user); } } }